Commit 1f04cb6a authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: DNN de-streak (subtract-avg), compute-window, and recurrent feed

- curt_subtract_avg (+ -SUBAVG filename tag): subtract the input per-pixel temporal
  mean before LoG. Removes the static treeline edge (and so its 8px tile-grid
  horizontal streak and the first-LReLU amplification of it); the moving target is
  not in the average, so it survives. Uses the whole sequence (not realtime; realtime
  would use a prior-run average).
- DNN compute-window: the timing ROI (curt_time_from/to) now gates the DNN inference
  loop (only in-window scenes are inferred), not just the saved output - fast iteration
  on a target's few seconds.
- DNN -> recurrent layer: feed the DNN field to runRecurrentLevel (per selected level,
  curt_recur_*). curt_dnn_recur_splat toggles feed-as-is vs bilinear splat of each
  pixel's velocity vector to its fractional (px+dx,py+dy) so neighbours reinforce in one
  sub-pixel bin (-SPLAT mark). curt_dnn_recur_scale (default 10) lifts the [0,1] field
  (peaks ~0.1) to the recurrent's tuned rs_min~1.0 scale. splatField() helper added.
Co-authored-by: 's avatarClaude Opus 4.8 (1M context) <noreply@anthropic.com>
parent ee86f0fc
......@@ -98,6 +98,7 @@ public class CuasDetectRT {
}
String fpixels_name = Path.of(fpixels_file).getFileName().toString();
base_name = fpixels_name.substring(0,fpixels_name.length() - fpixels_suffix.length()); // By Claude on 06/11/2026
if (clt_parameters.imp.curt_subtract_avg) base_name += "-SUBAVG"; // distinct output filenames for average-subtracted runs (don't overwrite) // By Claude on 06/14/2026
if (clt_parameters.imp.curt_synth_src) { // load the synthetic reference grid separately (NOT mixed here) // By Claude on 06/14/2026
// Real (loaded above) is the pyramid base. The synthetic file holds NORMALIZED
// (peak 1) clean targets / a velocity-reference grid: load + scale it, keep it
......@@ -249,6 +250,30 @@ public class CuasDetectRT {
return ((w[0] == 0) && (w[1] == a.length)) ? a : java.util.Arrays.copyOfRange(a, w[0], w[1]);
}
/** Re-bin each ROI pixel's velocity vector to its fractional offset position (px+dx, py+dy) via // By Claude on 06/14/2026
* bilinear splat, so neighbours that both claim one sub-pixel target reinforce in the same bin.
* field[roi_pix][nsub][nvel]; off[roi_pix][{dx,dy,s}] (may be null). Returns a new field. */
private static double [][][] splatField(double [][][] field, double [][] off, Rectangle roi) {
int rw = roi.width, rh = roi.height, npix = field.length;
int nsub = field[0].length, nvel = field[0][0].length;
double [][][] out = new double [npix][nsub][nvel];
for (int p = 0; p < npix; p++) {
if ((off == null) || (off[p] == null)) continue;
double fx = (p % rw) + off[p][0], fy = (p / rw) + off[p][1];
int x0 = (int) Math.floor(fx), y0 = (int) Math.floor(fy);
double wx = fx - x0, wy = fy - y0;
for (int gy = 0; gy < 2; gy++) for (int gx = 0; gx < 2; gx++) {
int tx = x0 + gx, ty = y0 + gy;
if ((tx < 0) || (tx >= rw) || (ty < 0) || (ty >= rh)) continue;
double wgt = (gx == 0 ? 1.0 - wx : wx) * (gy == 0 ? 1.0 - wy : wy);
if (wgt <= 0) continue;
double [][] dst = out[ty * rw + tx], src = field[p];
for (int su = 0; su < nsub; su++) for (int v = 0; v < nvel; v++) dst[su][v] += wgt * src[su][v];
}
}
return out;
}
/** Level selector for the heavy per-level paths (3d3/conv5d, C5P-direct, DNN): // By Claude on 06/13/2026
* empty/null curt_c5_levels = all built levels, otherwise only the listed ones. */
private static boolean c5LevelSelected(int [] c5_levels, int nlev) { // By Claude on 06/13/2026
......@@ -619,6 +644,14 @@ public class CuasDetectRT {
QuadCLTCPU.saveImagePlusInDirectory(imp_5d,getModelDirectory());
}
double [][] dpixels = getDPpixels();
if (clt_parameters.imp.curt_subtract_avg && (dpixels != null) && (dpixels.length > 0)) { // de-streak: subtract the static background before LoG so the sharp treeline edge (and its tile-grid ringing) is gone; the moving target is not in the average // By Claude on 06/14/2026
int npx = dpixels[0].length;
double [] mean = new double [npx];
for (double [] fr : dpixels) for (int p = 0; p < npx; p++) mean[p] += fr[p];
for (int p = 0; p < npx; p++) mean[p] /= dpixels.length;
for (double [] fr : dpixels) for (int p = 0; p < npx; p++) fr[p] -= mean[p];
System.out.println(now()+" detectTargets(): subtracted temporal average over "+dpixels.length+" input frames (curt_subtract_avg - uses whole sequence; realtime would use a prior-run average)"); // By Claude on 06/14/2026
}
double [][] dpixels_log = new double [dpixels.length][dpixels[0].length];
double [] kernel2d = cuasRTUtils.getKernel2d();
double alpha0 = 1.0 - clt_parameters.imp.curt_rleak0;
......@@ -1202,6 +1235,28 @@ public class CuasDetectRT {
QuadCLTCPU.saveImagePlusInDirectory(ShowDoubleFloatArrays.showArraysHyperstack(
off_hyper, rwid, title_dnn+"-OFFSET", ts_dnn_w, new String[]{"dx","dy","s"}, false), getModelDirectory());
}
// Feed the DNN field to the recurrent layer (Layer 2): as-is, or offset-splatted (px+dx,py+dy). // By Claude on 06/14/2026
// Tune the recurrent blend with curt_recur_w (0.25/0.75) etc.; the field is [0,1]-scaled so rs_min may need lowering.
if (run_recur && (num > 0)) {
boolean splat = clt_parameters.imp.curt_dnn_recur_splat;
double rscale = clt_parameters.imp.curt_dnn_recur_scale; // DNN field ~0.1 -> recurrent rs_min ~1.0 // By Claude on 06/14/2026
double [][][][] dnn_feed = new double [num][][][];
for (int j = 0; j < num; j++) {
double [][][] f = splat ? splatField(dnn_roi[j], dnn_off[j], curt_save_select) : dnn_roi[j];
if (rscale != 1.0) {
double [][][] g = new double [f.length][][];
for (int p = 0; p < f.length; p++) {
g[p] = new double [f[p].length][];
for (int su = 0; su < f[p].length; su++) { g[p][su] = f[p][su].clone(); for (int v = 0; v < g[p][su].length; v++) g[p][su][v] *= rscale; }
}
f = g;
}
dnn_feed[j] = f;
}
String rtitle = title_dnn + (splat ? "-SPLAT" : "");
System.out.println(now()+" detectTargets(): DNN recurrent feed LEV"+nlev+(splat?" (offset-splat)":" (as-is)")+", scale "+rscale); // By Claude on 06/14/2026
runRecurrentLevel(cuasRTUtils, dnn_feed, ts_dnn, curt_save_select, rtitle, false);
}
} // By Claude on 06/13/2026
dnn.close(); // By Claude on 06/13/2026
} catch (Exception e) { // By Claude on 06/13/2026
......
......@@ -1181,9 +1181,12 @@ min_str_neib_fpn 0.35
public String curt_dnn_model = ""; // C5P DNN front-end model (ONNX): empty = disabled; local path, scp user@host:path, or http(s) URL fetched to cache; overrides bundled resource (mirrors tile_processor_gpu) // By Claude on 06/13/2026
public double curt_dnn_thresh = 0.0; // DNN display threshold: zero the (Vx,Vy,s) field where detection s < this (0 - show all); cosmetic FP-suppression - real-clutter FPs need a retrain with real-bg negatives, not a threshold // By Claude on 06/13/2026
public int curt_dnn_stride = 1; // DNN temporal output stride (per pyramid-level slice): 1 = every step (testing, watch each temporal unit); 4 = production (50% overlap, matches the convolver's integer-pixel-shift cadence = curt_recur_period) // By Claude on 06/14/2026
public boolean curt_dnn_recur_splat = false; // when feeding the DNN field to the recurrent layer: false = feed per-pixel field as-is; true = splat each pixel's velocity vector to its fractional offset (px+dx,py+dy) so neighbours reinforce in one sub-pixel bin // By Claude on 06/14/2026
public double curt_dnn_recur_scale = 10.0; // multiply the DNN field (softmax*s, peaks ~0.1) by this before the recurrent feed, to reach the recurrent's tuned scale (rs_min=1.0); ~10 -> peak ~1.0. Alternative to lowering curt_recur_rs_min // By Claude on 06/14/2026
public boolean curt_synth_src = true; // default set for the synthetic B-measurement experiment (set false for real-data runs); reads *-CUAS-SYNTHETIC-CUAS.tiff, output titles get -SYNTH // By Claude on 06/12/2026
public double curt_synth_scale = 5.0; // synthetic target peak, counts (synthetic file is peak-1 normalized; scaled at load) // By Claude on 06/12/2026
public boolean curt_synth_bg = true; // add the real *-CUAS-MERGED-CUAS.tiff scene under the synthetic targets (label-matched frames); false - clean targets only // By Claude on 06/12/2026
public boolean curt_subtract_avg = false; // subtract the input temporal average (over the whole sequence) before LoG - removes static background (treeline edge -> kills its tile-grid streak); moving targets survive. NOTE: uses the whole sequence (not realtime); realtime would use a prior-run average // By Claude on 06/14/2026
public double curt_min_frac = 0.0; // default 0 for linear synthetic/B runs (production value ~0.1); zeroes conv5d outputs where the newest frame contributes less than this fraction (NONLINEAR dark-frame gate) // By Claude on 06/12/2026
// debug/saving images
public boolean curt_save_c5full = false; // save fine velocities [direction][scene][subpixels]
......@@ -3527,12 +3530,18 @@ min_str_neib_fpn 0.35
"Trained DNN front-end model location. Empty = disabled (use matched-filter/posterior path). Local path, scp user@host:/path (fetched to cache), or http(s) URL. Overrides any bundled resource - same default-vs-override scheme as the GPU kernel sources."); // By Claude on 06/13/2026
gd.addNumericField("DNN confidence threshold", this.curt_dnn_thresh, 6,8,"", // By Claude on 06/13/2026
"Zero the DNN (Vx,Vy,s) field where detection confidence s < this (0 = show all). Display/FP-suppression only - real-clutter false positives need a retrain with real-background negatives, not a threshold."); // By Claude on 06/13/2026
gd.addCheckbox ("DNN recurrent feed: offset-splat", this.curt_dnn_recur_splat, // By Claude on 06/14/2026
"Feeding the DNN field to the recurrent layer: unchecked = feed per-pixel field as-is; checked = splat each pixel to its fractional (px+dx,py+dy) so neighbours reinforce in one sub-pixel bin. Output gets a -SPLAT mark."); // By Claude on 06/14/2026
gd.addNumericField("DNN recurrent feed scale", this.curt_dnn_recur_scale, 4,8,"", // By Claude on 06/14/2026
"Multiply the DNN field (peaks ~0.1) by this before the recurrent feed so it reaches the recurrent's tuned scale (rs_min=1.0); ~10 -> peak ~1.0. Set 1 and instead lower curt_recur_rs_min if you prefer."); // By Claude on 06/14/2026
gd.addNumericField("DNN temporal stride", this.curt_dnn_stride, 0,3,"slices", // By Claude on 06/14/2026
"DNN output stride along the pyramid-level time axis: 1 = a fresh inference at every slice (testing, watch each temporal step); 4 = production (50% overlap of the window, matches the convolver's integer-pixel-shift cadence)."); // By Claude on 06/14/2026
gd.addCheckbox ("Use synthetic input", this.curt_synth_src, // By Claude on 06/11/2026
"Read *-CUAS-SYNTHETIC-CUAS.tiff (generated test targets) instead of *-CUAS-MERGED-CUAS.tiff from the same model directory; all output titles get a -SYNTH mark."); // By Claude on 06/11/2026
gd.addNumericField("Synthetic target peak", this.curt_synth_scale, 6,8,"counts", // By Claude on 06/12/2026
"Scale applied to the (peak-1 normalized) synthetic targets at load = target peak in counts. Reference: birds ~3, background sigma ~1.7, strong targets >30."); // By Claude on 06/12/2026
gd.addCheckbox ("Subtract input temporal average", this.curt_subtract_avg, // By Claude on 06/14/2026
"Subtract the per-pixel temporal average of the input (whole sequence) before LoG. Removes the static treeline edge and its tile-grid streak; moving targets are not in the average so they survive. Uses the whole sequence (not realtime - realtime would use a prior-run average)."); // By Claude on 06/14/2026
gd.addCheckbox ("Synthetic over real background", this.curt_synth_bg, // By Claude on 06/12/2026
"Add the real *-CUAS-MERGED-CUAS.tiff scene under the scaled synthetic targets (frames matched by timestamp label). Unchecked - clean targets on zero background (linear B measurement)."); // By Claude on 06/12/2026
gd.addNumericField("Min newest-frame fraction", this.curt_min_frac, 6,8,"", // By Claude on 06/12/2026
......@@ -5078,9 +5087,12 @@ min_str_neib_fpn 0.35
this.curt_c5_white_vel = gd.getNextNumber(); // By Claude on 06/13/2026
this.curt_dnn_model = gd.getNextString().trim(); // By Claude on 06/13/2026
this.curt_dnn_thresh = gd.getNextNumber(); // By Claude on 06/13/2026
this.curt_dnn_recur_splat = gd.getNextBoolean(); // By Claude on 06/14/2026
this.curt_dnn_recur_scale = gd.getNextNumber(); // By Claude on 06/14/2026
this.curt_dnn_stride = (int) gd.getNextNumber(); // By Claude on 06/14/2026
this.curt_synth_src = gd.getNextBoolean(); // By Claude on 06/11/2026
this.curt_synth_scale = gd.getNextNumber(); // By Claude on 06/12/2026
this.curt_subtract_avg = gd.getNextBoolean(); // By Claude on 06/14/2026
this.curt_synth_bg = gd.getNextBoolean(); // By Claude on 06/12/2026
this.curt_min_frac = gd.getNextNumber(); // By Claude on 06/12/2026
......@@ -6447,9 +6459,12 @@ min_str_neib_fpn 0.35
properties.setProperty(prefix+"curt_c5_white_vel", this.curt_c5_white_vel+""); // double // By Claude on 06/13/2026
properties.setProperty(prefix+"curt_dnn_model", this.curt_dnn_model); // String // By Claude on 06/13/2026
properties.setProperty(prefix+"curt_dnn_thresh", this.curt_dnn_thresh+""); // double // By Claude on 06/13/2026
properties.setProperty(prefix+"curt_dnn_recur_splat", this.curt_dnn_recur_splat+""); // boolean // By Claude on 06/14/2026
properties.setProperty(prefix+"curt_dnn_recur_scale", this.curt_dnn_recur_scale+""); // double // By Claude on 06/14/2026
properties.setProperty(prefix+"curt_dnn_stride", this.curt_dnn_stride+""); // int // By Claude on 06/14/2026
properties.setProperty(prefix+"curt_synth_src", this.curt_synth_src+""); // boolean // By Claude on 06/11/2026
properties.setProperty(prefix+"curt_synth_scale", this.curt_synth_scale+""); // double // By Claude on 06/12/2026
properties.setProperty(prefix+"curt_subtract_avg", this.curt_subtract_avg+""); // boolean // By Claude on 06/14/2026
properties.setProperty(prefix+"curt_synth_bg", this.curt_synth_bg+""); // boolean // By Claude on 06/12/2026
properties.setProperty(prefix+"curt_min_frac", this.curt_min_frac+""); // double // By Claude on 06/12/2026
......@@ -6851,10 +6866,13 @@ min_str_neib_fpn 0.35
if (properties.getProperty(prefix+"curt_c5_white_vel")!=null) this.curt_c5_white_vel=Double.parseDouble(properties.getProperty(prefix+"curt_c5_white_vel")); // By Claude on 06/13/2026
if (properties.getProperty(prefix+"curt_dnn_model")!=null) this.curt_dnn_model=(String) properties.getProperty(prefix+"curt_dnn_model"); // By Claude on 06/13/2026
if (properties.getProperty(prefix+"curt_dnn_thresh")!=null) this.curt_dnn_thresh=Double.parseDouble(properties.getProperty(prefix+"curt_dnn_thresh")); // By Claude on 06/13/2026
if (properties.getProperty(prefix+"curt_dnn_recur_splat")!=null) this.curt_dnn_recur_splat=Boolean.parseBoolean(properties.getProperty(prefix+"curt_dnn_recur_splat")); // By Claude on 06/14/2026
if (properties.getProperty(prefix+"curt_dnn_recur_scale")!=null) this.curt_dnn_recur_scale=Double.parseDouble(properties.getProperty(prefix+"curt_dnn_recur_scale")); // By Claude on 06/14/2026
if (properties.getProperty(prefix+"curt_dnn_stride")!=null) this.curt_dnn_stride=Integer.parseInt(properties.getProperty(prefix+"curt_dnn_stride")); // By Claude on 06/14/2026
if (properties.getProperty(prefix+"curt_synth_src")!=null) this.curt_synth_src=Boolean.parseBoolean(properties.getProperty(prefix+"curt_synth_src")); // By Claude on 06/11/2026
if (properties.getProperty(prefix+"curt_synth_scale")!=null) this.curt_synth_scale=Double.parseDouble(properties.getProperty(prefix+"curt_synth_scale")); // By Claude on 06/12/2026
if (properties.getProperty(prefix+"curt_subtract_avg")!=null) this.curt_subtract_avg=Boolean.parseBoolean(properties.getProperty(prefix+"curt_subtract_avg")); // By Claude on 06/14/2026
if (properties.getProperty(prefix+"curt_synth_bg")!=null) this.curt_synth_bg=Boolean.parseBoolean(properties.getProperty(prefix+"curt_synth_bg")); // By Claude on 06/12/2026
if (properties.getProperty(prefix+"curt_min_frac")!=null) this.curt_min_frac=Double.parseDouble(properties.getProperty(prefix+"curt_min_frac")); // By Claude on 06/12/2026
......@@ -9137,9 +9155,12 @@ min_str_neib_fpn 0.35
imp.curt_c5_white_vel = this.curt_c5_white_vel; // By Claude on 06/13/2026
imp.curt_dnn_model = this.curt_dnn_model; // By Claude on 06/13/2026
imp.curt_dnn_thresh = this.curt_dnn_thresh; // By Claude on 06/13/2026
imp.curt_dnn_recur_splat = this.curt_dnn_recur_splat; // By Claude on 06/14/2026
imp.curt_dnn_recur_scale = this.curt_dnn_recur_scale; // By Claude on 06/14/2026
imp.curt_dnn_stride = this.curt_dnn_stride; // By Claude on 06/14/2026
imp.curt_synth_src = this.curt_synth_src; // By Claude on 06/11/2026
imp.curt_synth_scale = this.curt_synth_scale; // By Claude on 06/12/2026
imp.curt_subtract_avg = this.curt_subtract_avg; // By Claude on 06/14/2026
imp.curt_synth_bg = this.curt_synth_bg; // By Claude on 06/12/2026
imp.curt_min_frac = this.curt_min_frac; // By Claude on 06/12/2026
......
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