Commit 079d4237 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: Add Layer 2 recurrent accumulation buffer (CuasRecurrentBuffer)

Implements the recurrent 4D (px, py, vx, vy) accumulation buffer per the
2026-06-09 design (cuas_rt_gpu-internal CONTINUE.md, Step 4):

- Update rule: buf = leak*(1-w_eff)*shift(buf, v) + w_eff*conditioned(meas),
  applied every curt_recur_period (default 4) time units so the per-update
  pixel shift is integer-clean (1 px per minimum velocity step).
- shift(): each 4D point moves by its own velocity; predictions leaving the
  ROI are dropped.
- conditioned(): adaptive 4D spreading of filterConv5dROI survivors -
  dimensions spanned by adjacent survivors are "resolved" and not expanded;
  total spread weight per survivor is 1.
- w_eff: fixed curt_recur_w, or per-pixel adaptive w*tanh(strength/wthresh)
  when curt_recur_wthresh > 0 (dark frames then decay by curt_recur_leak
  alone - blind prediction).
- convolve3d() still runs at every time step with unchanged output files;
  the buffer consumes every curt_recur_period-th ROI output (level 0 only).
- New params (curt_recur_en/period/w/leak/wthresh/spread) plumbed through
  IntersceneMatchParameters (dialog, get/set, properties, copy).
- Buffer state snapshots saved as -RECUR-RECT and -RECUR-HYPER-RECT images.

Single-threaded over the ROI for now; optimization deferred by design.
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent 11658a73
...@@ -21,6 +21,7 @@ public class CuasDetectRT { ...@@ -21,6 +21,7 @@ public class CuasDetectRT {
public static final String SUFFIX_CONV2D = "-CONV2D"; public static final String SUFFIX_CONV2D = "-CONV2D";
public static final String SUFFIX_CONV3D3 = "-CONV3D3"; public static final String SUFFIX_CONV3D3 = "-CONV3D3";
public static final String SUFFIX_CONV5D = "-CONV5D"; public static final String SUFFIX_CONV5D = "-CONV5D";
public static final String SUFFIX_RECUR = "-RECUR"; // By Claude on 06/09/2026
public static final String [] CONV3D3_MODES = {"plain", "winner", "power"}; public static final String [] CONV3D3_MODES = {"plain", "winner", "power"};
public static final int CONV3D3_MODE_PLAIN = 0; public static final int CONV3D3_MODE_PLAIN = 0;
public static final int CONV3D3_MODE_WINNER = 1; public static final int CONV3D3_MODE_WINNER = 1;
...@@ -171,6 +172,12 @@ public class CuasDetectRT { ...@@ -171,6 +172,12 @@ public class CuasDetectRT {
double curt_vel_athresh = clt_parameters.imp.curt_vel_athresh; double curt_vel_athresh = clt_parameters.imp.curt_vel_athresh;
double curt_vel_rthresh = clt_parameters.imp.curt_vel_rthresh; double curt_vel_rthresh = clt_parameters.imp.curt_vel_rthresh;
boolean curt_recur_en = clt_parameters.imp.curt_recur_en; // By Claude on 06/09/2026
int curt_recur_period = clt_parameters.imp.curt_recur_period; // By Claude on 06/09/2026
double curt_recur_w = clt_parameters.imp.curt_recur_w; // By Claude on 06/09/2026
double curt_recur_leak = clt_parameters.imp.curt_recur_leak; // By Claude on 06/09/2026
double curt_recur_wthresh =clt_parameters.imp.curt_recur_wthresh; // By Claude on 06/09/2026
double curt_recur_spread = clt_parameters.imp.curt_recur_spread; // By Claude on 06/09/2026
double[] curt_temp_weights = clt_parameters.imp.curt_temp_weights.clone(); double[] curt_temp_weights = clt_parameters.imp.curt_temp_weights.clone();
...@@ -314,6 +321,26 @@ public class CuasDetectRT { ...@@ -314,6 +321,26 @@ public class CuasDetectRT {
dpixels_5d_pyramid[0] = new double[num_5d_lev0][][][]; dpixels_5d_pyramid[0] = new double[num_5d_lev0][][][];
dpixels_5d_roi_pyramid[0] = new double[num_5d_lev0][][][]; dpixels_5d_roi_pyramid[0] = new double[num_5d_lev0][][][];
String[] ts_5d_lev0 = new String[num_5d_lev0]; String[] ts_5d_lev0 = new String[num_5d_lev0];
// Layer 2 recurrent accumulation buffer (level 0, ROI only). convolve3d() still runs // By Claude on 06/09/2026
// at every time step; the buffer consumes every curt_recur_period-th output. // By Claude on 06/09/2026
boolean run_recur = curt_recur_en && (curt_save_select != null); // By Claude on 06/09/2026
CuasRecurrentBuffer recurrentBuffer = null; // By Claude on 06/09/2026
double [][][][] recur_states = null; // By Claude on 06/09/2026
String [] recur_ts = null; // By Claude on 06/09/2026
int num_recur = 0; // By Claude on 06/09/2026
if (run_recur) { // By Claude on 06/09/2026
recurrentBuffer = new CuasRecurrentBuffer( // By Claude on 06/09/2026
curt_save_select.width, // int roi_width, // By Claude on 06/09/2026
curt_save_select.height, // int roi_height, // By Claude on 06/09/2026
curt_pix_decimate * curt_pix_decimate, // int num_sub, // By Claude on 06/09/2026
curt_vel_radius, // int velocity_radius, // By Claude on 06/09/2026
curt_vel_decimate, // int velocity_decimate, // By Claude on 06/09/2026
curt_recur_period, // int period, // By Claude on 06/09/2026
curt_recur_spread); // double spread) // By Claude on 06/09/2026
int max_recur = num_5d_lev0 / curt_recur_period + 1; // By Claude on 06/09/2026
recur_states = new double[max_recur][][][]; // By Claude on 06/09/2026
recur_ts = new String[max_recur]; // By Claude on 06/09/2026
} // By Claude on 06/09/2026
System.out.println("detectTargets(): will run convolve3d()"); System.out.println("detectTargets(): will run convolve3d()");
for (int n5d = 0; n5d < num_5d_lev0; n5d++) { for (int n5d = 0; n5d < num_5d_lev0; n5d++) {
System.out.print ("n5d="+n5d+" "); System.out.print ("n5d="+n5d+" ");
...@@ -328,7 +355,7 @@ public class CuasDetectRT { ...@@ -328,7 +355,7 @@ public class CuasDetectRT {
min_frac_last); // final double min_frac_last){ min_frac_last); // final double min_frac_last){
} }
if (curt_save_c5rect && (curt_save_select != null)) { if ((curt_save_c5rect || run_recur) && (curt_save_select != null)) { // By Claude on 06/09/2026 (added run_recur)
dpixels_5d_roi_pyramid[0][n5d] = cuasRTUtils.convolve3d( dpixels_5d_roi_pyramid[0][n5d] = cuasRTUtils.convolve3d(
window, window,
curt_save_select, curt_save_select,
...@@ -341,6 +368,15 @@ public class CuasDetectRT { ...@@ -341,6 +368,15 @@ public class CuasDetectRT {
curt_vel_athresh, curt_vel_athresh,
curt_vel_rthresh); curt_vel_rthresh);
} }
if (run_recur && ((n5d % curt_recur_period) == 0)) { // By Claude on 06/09/2026
recurrentBuffer.update( // By Claude on 06/09/2026
dpixels_5d_roi_pyramid[0][n5d], // double [][][] measurement, // By Claude on 06/09/2026
curt_recur_w, // double w, // By Claude on 06/09/2026
curt_recur_leak, // double leak, // By Claude on 06/09/2026
curt_recur_wthresh); // double wthresh) // By Claude on 06/09/2026
recur_states[num_recur] = recurrentBuffer.getBufferCopy(); // By Claude on 06/09/2026
recur_ts[num_recur++] = ts_pyramid[0][n5d + num_hist_5d - 1]; // By Claude on 06/09/2026
} // By Claude on 06/09/2026
} }
ts_5d_lev0[n5d] = ts_pyramid[0][n5d + num_hist_5d - 1]; ts_5d_lev0[n5d] = ts_pyramid[0][n5d + num_hist_5d - 1];
} }
...@@ -365,6 +401,27 @@ public class CuasDetectRT { ...@@ -365,6 +401,27 @@ public class CuasDetectRT {
title_conv5d+"-HYPER-RECT"); title_conv5d+"-HYPER-RECT");
QuadCLTCPU.saveImagePlusInDirectory(imp_5d_hyper, getModelDirectory()); QuadCLTCPU.saveImagePlusInDirectory(imp_5d_hyper, getModelDirectory());
} }
if (run_recur && (num_recur > 0)) { // By Claude on 06/09/2026
double [][][][] recur_show = new double[num_recur][][][]; // By Claude on 06/09/2026
String [] recur_ts_show = new String[num_recur]; // By Claude on 06/09/2026
System.arraycopy(recur_states, 0, recur_show, 0, num_recur); // By Claude on 06/09/2026
System.arraycopy(recur_ts, 0, recur_ts_show, 0, num_recur); // By Claude on 06/09/2026
String title_recur = title_conv5d+ // By Claude on 06/09/2026
"-RP"+curt_recur_period+"-RW"+d2s(curt_recur_w)+"-RL"+d2s(curt_recur_leak)+ // By Claude on 06/09/2026
"-RWT"+d2s(curt_recur_wthresh)+"-RS"+d2s(curt_recur_spread)+SUFFIX_RECUR; // By Claude on 06/09/2026
ImagePlus imp_recur_rect = cuasRTUtils.showConvKernel5d( // By Claude on 06/09/2026
recur_show, // By Claude on 06/09/2026
curt_save_select, // By Claude on 06/09/2026
recur_ts_show, // By Claude on 06/09/2026
title_recur+"-RECT"); // By Claude on 06/09/2026
QuadCLTCPU.saveImagePlusInDirectory(imp_recur_rect, getModelDirectory()); // By Claude on 06/09/2026
ImagePlus imp_recur_hyper = cuasRTUtils.showConvKernel5dHyperRect( // By Claude on 06/09/2026
recur_show, // By Claude on 06/09/2026
curt_save_select, // By Claude on 06/09/2026
recur_ts_show, // By Claude on 06/09/2026
title_recur+"-HYPER-RECT"); // By Claude on 06/09/2026
QuadCLTCPU.saveImagePlusInDirectory(imp_recur_hyper, getModelDirectory()); // By Claude on 06/09/2026
} // By Claude on 06/09/2026
for (int nlev = 0; nlev < pyramid_levels; nlev++) { for (int nlev = 0; nlev < pyramid_levels; nlev++) {
if (save_LoG_pixels) { if (save_LoG_pixels) {
String title_conv2d_lev = getBaseName()+SUFFIX_CONV2D+"-PSF"+d2s(cuasRTUtils.getPsfRadius())+ String title_conv2d_lev = getBaseName()+SUFFIX_CONV2D+"-PSF"+d2s(cuasRTUtils.getPsfRadius())+
......
...@@ -1149,6 +1149,13 @@ min_str_neib_fpn 0.35 ...@@ -1149,6 +1149,13 @@ min_str_neib_fpn 0.35
public double curt_vel_athresh = 1.0; // absolute threshold: discard this pixel-velocity if it has immediate neighbor (of 80=81-1) stronger by this value public double curt_vel_athresh = 1.0; // absolute threshold: discard this pixel-velocity if it has immediate neighbor (of 80=81-1) stronger by this value
public double curt_vel_rthresh = 0.1; // relative threshold: discard this pixel-velocity if it has immediate neighbor stronger by this value times stronger value public double curt_vel_rthresh = 0.1; // relative threshold: discard this pixel-velocity if it has immediate neighbor stronger by this value times stronger value
public double[] curt_temp_weights = {1,1,1,1,1}; // historic weights for 5D convolution ([0] is the latest sample) public double[] curt_temp_weights = {1,1,1,1,1}; // historic weights for 5D convolution ([0] is the latest sample)
// === Layer 2 recurrent accumulation buffer === // By Claude on 06/09/2026
public boolean curt_recur_en = true; // enable recurrent accumulation buffer (Layer 2) // By Claude on 06/09/2026
public int curt_recur_period = 4; // time units between buffer updates (= curt_vel_decimate for integer-pixel shifts) // By Claude on 06/09/2026
public double curt_recur_w = 0.25; // measurement weight w: buf = leak*(1-w)*shift(buf) + w*conditioned(meas) // By Claude on 06/09/2026
public double curt_recur_leak = 1.0; // extra decay of the shifted prediction per update (gap decay = leak*(1-w) for fixed w) // By Claude on 06/09/2026
public double curt_recur_wthresh = 0.0; // if >0: adaptive per-pixel weight w_eff=w*tanh(strength/wthresh); 0 - fixed w // By Claude on 06/09/2026
public double curt_recur_spread = 0.5; // orthogonal 4D neighbor weight in adaptive spreading (diagonals get products) // By Claude on 06/09/2026
// debug/saving images // debug/saving images
public boolean curt_save_c5full = false; // save fine velocities [direction][scene][subpixels] public boolean curt_save_c5full = false; // save fine velocities [direction][scene][subpixels]
public boolean curt_save_c5rect = true; // save fine velocities for selected rectangle only [scene][flattened image] public boolean curt_save_c5rect = true; // save fine velocities for selected rectangle only [scene][flattened image]
...@@ -3425,6 +3432,20 @@ min_str_neib_fpn 0.35 ...@@ -3425,6 +3432,20 @@ min_str_neib_fpn 0.35
gd.addStringField ("Historic weight for 5D convolution", IntersceneMatchParameters.doublesToString(curt_temp_weights), 80, gd.addStringField ("Historic weight for 5D convolution", IntersceneMatchParameters.doublesToString(curt_temp_weights), 80,
"Historic weights for 5D convolution ([0] is the latest sample). Will be normalized"); "Historic weights for 5D convolution ([0] is the latest sample). Will be normalized");
gd.addMessage("=== Layer 2 recurrent accumulation buffer ==="); // By Claude on 06/09/2026
gd.addCheckbox ("Enable recurrent buffer", this.curt_recur_en, // By Claude on 06/09/2026
"Maintain a recurrent 4D (pixel, velocity) accumulation buffer over the selected rectangle."); // By Claude on 06/09/2026
gd.addNumericField("Update period", this.curt_recur_period, 0,3,"time units", // By Claude on 06/09/2026
"Time units between buffer updates. Equal to velocity decimation for integer-pixel shifts (1 px per minimum velocity)."); // By Claude on 06/09/2026
gd.addNumericField("Measurement weight", this.curt_recur_w, 6,8,"", // By Claude on 06/09/2026
"Update rule: buf = leak*(1-w)*shift(buf) + w*conditioned(measurement). Gap decay = leak*(1-w) with fixed w."); // By Claude on 06/09/2026
gd.addNumericField("Prediction leak", this.curt_recur_leak, 6,8,"", // By Claude on 06/09/2026
"Extra decay of the velocity-shifted prediction per update. Keep leak^n_gap >= 0.2-0.3 for expected dark gaps."); // By Claude on 06/09/2026
gd.addNumericField("Adaptive weight threshold", this.curt_recur_wthresh, 6,8,"", // By Claude on 06/09/2026
"If >0: per-pixel adaptive weight w_eff = w*tanh(strength/wthresh), dark frames give w_eff~0 (pure prediction). 0 - fixed w."); // By Claude on 06/09/2026
gd.addNumericField("4D spreading weight", this.curt_recur_spread, 6,8,"", // By Claude on 06/09/2026
"Weight of orthogonal 4D neighbors when spreading each surviving measurement point (diagonals get products of this)."); // By Claude on 06/09/2026
gd.addMessage("=== Debug, saving images ==="); gd.addMessage("=== Debug, saving images ===");
gd.addCheckbox ("Save full velocities hyperstack", this.curt_save_c5full, gd.addCheckbox ("Save full velocities hyperstack", this.curt_save_c5full,
"Save fine velocities [direction][scene][subpixels]."); "Save fine velocities [direction][scene][subpixels].");
...@@ -4930,6 +4951,13 @@ min_str_neib_fpn 0.35 ...@@ -4930,6 +4951,13 @@ min_str_neib_fpn 0.35
this.curt_temp_weights = IntersceneMatchParameters. StringToDoubles(gd.getNextString(), 0); this.curt_temp_weights = IntersceneMatchParameters. StringToDoubles(gd.getNextString(), 0);
this.curt_recur_en = gd.getNextBoolean(); // By Claude on 06/09/2026
this.curt_recur_period = (int) gd.getNextNumber(); // By Claude on 06/09/2026
this.curt_recur_w = gd.getNextNumber(); // By Claude on 06/09/2026
this.curt_recur_leak = gd.getNextNumber(); // By Claude on 06/09/2026
this.curt_recur_wthresh = gd.getNextNumber(); // By Claude on 06/09/2026
this.curt_recur_spread = gd.getNextNumber(); // By Claude on 06/09/2026
this.curt_save_c5full = gd.getNextBoolean(); this.curt_save_c5full = gd.getNextBoolean();
this.curt_save_c5rect = gd.getNextBoolean(); this.curt_save_c5rect = gd.getNextBoolean();
this.curt_save_select = stringToRectangle(gd.getNextString());// Rectangle this.curt_save_select = stringToRectangle(gd.getNextString());// Rectangle
...@@ -6260,6 +6288,13 @@ min_str_neib_fpn 0.35 ...@@ -6260,6 +6288,13 @@ min_str_neib_fpn 0.35
properties.setProperty(prefix+"curt_vel_rthresh", this.curt_vel_rthresh+""); // double properties.setProperty(prefix+"curt_vel_rthresh", this.curt_vel_rthresh+""); // double
properties.setProperty(prefix+"curt_temp_weights", IntersceneMatchParameters.doublesToString(this.curt_temp_weights)); properties.setProperty(prefix+"curt_temp_weights", IntersceneMatchParameters.doublesToString(this.curt_temp_weights));
properties.setProperty(prefix+"curt_recur_en", this.curt_recur_en+""); // boolean // By Claude on 06/09/2026
properties.setProperty(prefix+"curt_recur_period", this.curt_recur_period+""); // int // By Claude on 06/09/2026
properties.setProperty(prefix+"curt_recur_w", this.curt_recur_w+""); // double // By Claude on 06/09/2026
properties.setProperty(prefix+"curt_recur_leak", this.curt_recur_leak+""); // double // By Claude on 06/09/2026
properties.setProperty(prefix+"curt_recur_wthresh", this.curt_recur_wthresh+""); // double // By Claude on 06/09/2026
properties.setProperty(prefix+"curt_recur_spread", this.curt_recur_spread+""); // double // By Claude on 06/09/2026
properties.setProperty(prefix+"curt_save_c5full", this.curt_save_c5full+""); // boolean properties.setProperty(prefix+"curt_save_c5full", this.curt_save_c5full+""); // boolean
properties.setProperty(prefix+"curt_save_c5rect", this.curt_save_c5rect+""); // boolean properties.setProperty(prefix+"curt_save_c5rect", this.curt_save_c5rect+""); // boolean
properties.setProperty(prefix+"curt_save_select", rectangleToString(curt_save_select)+""); // Rectangle properties.setProperty(prefix+"curt_save_select", rectangleToString(curt_save_select)+""); // Rectangle
...@@ -7577,6 +7612,13 @@ min_str_neib_fpn 0.35 ...@@ -7577,6 +7612,13 @@ min_str_neib_fpn 0.35
if (properties.getProperty(prefix+"curt_temp_weights")!=null) this.curt_temp_weights= IntersceneMatchParameters.StringToDoubles(properties.getProperty(prefix+"curt_temp_weights"),0); // use 0 if (properties.getProperty(prefix+"curt_temp_weights")!=null) this.curt_temp_weights= IntersceneMatchParameters.StringToDoubles(properties.getProperty(prefix+"curt_temp_weights"),0); // use 0
if (properties.getProperty(prefix+"curt_recur_en")!=null) this.curt_recur_en=Boolean.parseBoolean(properties.getProperty(prefix+"curt_recur_en")); // By Claude on 06/09/2026
if (properties.getProperty(prefix+"curt_recur_period")!=null) this.curt_recur_period=Integer.parseInt(properties.getProperty(prefix+"curt_recur_period")); // By Claude on 06/09/2026
if (properties.getProperty(prefix+"curt_recur_w")!=null) this.curt_recur_w=Double.parseDouble(properties.getProperty(prefix+"curt_recur_w")); // By Claude on 06/09/2026
if (properties.getProperty(prefix+"curt_recur_leak")!=null) this.curt_recur_leak=Double.parseDouble(properties.getProperty(prefix+"curt_recur_leak")); // By Claude on 06/09/2026
if (properties.getProperty(prefix+"curt_recur_wthresh")!=null) this.curt_recur_wthresh=Double.parseDouble(properties.getProperty(prefix+"curt_recur_wthresh")); // By Claude on 06/09/2026
if (properties.getProperty(prefix+"curt_recur_spread")!=null) this.curt_recur_spread=Double.parseDouble(properties.getProperty(prefix+"curt_recur_spread")); // By Claude on 06/09/2026
if (properties.getProperty(prefix+"curt_save_c5full")!=null) this.curt_save_c5full=Boolean.parseBoolean(properties.getProperty(prefix+"curt_save_c5full")); if (properties.getProperty(prefix+"curt_save_c5full")!=null) this.curt_save_c5full=Boolean.parseBoolean(properties.getProperty(prefix+"curt_save_c5full"));
if (properties.getProperty(prefix+"curt_save_c5rect")!=null) this.curt_save_c5rect=Boolean.parseBoolean(properties.getProperty(prefix+"curt_save_c5rect")); if (properties.getProperty(prefix+"curt_save_c5rect")!=null) this.curt_save_c5rect=Boolean.parseBoolean(properties.getProperty(prefix+"curt_save_c5rect"));
if (properties.getProperty(prefix+"curt_save_select")!=null) this.curt_save_select=stringToRectangle((String) properties.getProperty(prefix+"curt_save_select")); if (properties.getProperty(prefix+"curt_save_select")!=null) this.curt_save_select=stringToRectangle((String) properties.getProperty(prefix+"curt_save_select"));
...@@ -8870,6 +8912,13 @@ min_str_neib_fpn 0.35 ...@@ -8870,6 +8912,13 @@ min_str_neib_fpn 0.35
imp.curt_vel_rthresh = this.curt_vel_rthresh; imp.curt_vel_rthresh = this.curt_vel_rthresh;
imp.curt_temp_weights = this.curt_temp_weights.clone(); imp.curt_temp_weights = this.curt_temp_weights.clone();
imp.curt_recur_en = this.curt_recur_en; // By Claude on 06/09/2026
imp.curt_recur_period = this.curt_recur_period; // By Claude on 06/09/2026
imp.curt_recur_w = this.curt_recur_w; // By Claude on 06/09/2026
imp.curt_recur_leak = this.curt_recur_leak; // By Claude on 06/09/2026
imp.curt_recur_wthresh = this.curt_recur_wthresh; // By Claude on 06/09/2026
imp.curt_recur_spread = this.curt_recur_spread; // By Claude on 06/09/2026
imp.curt_save_c5full = this.curt_save_c5full; imp.curt_save_c5full = this.curt_save_c5full;
imp.curt_save_c5rect = this.curt_save_c5rect; imp.curt_save_c5rect = this.curt_save_c5rect;
imp.curt_save_select = new Rectangle(this.curt_save_select); imp.curt_save_select = new Rectangle(this.curt_save_select);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment