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 { ...@@ -98,6 +98,7 @@ public class CuasDetectRT {
} }
String fpixels_name = Path.of(fpixels_file).getFileName().toString(); 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 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 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 // 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 // (peak 1) clean targets / a velocity-reference grid: load + scale it, keep it
...@@ -249,6 +250,30 @@ public class CuasDetectRT { ...@@ -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]); 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 /** 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. */ * 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 private static boolean c5LevelSelected(int [] c5_levels, int nlev) { // By Claude on 06/13/2026
...@@ -619,6 +644,14 @@ public class CuasDetectRT { ...@@ -619,6 +644,14 @@ public class CuasDetectRT {
QuadCLTCPU.saveImagePlusInDirectory(imp_5d,getModelDirectory()); QuadCLTCPU.saveImagePlusInDirectory(imp_5d,getModelDirectory());
} }
double [][] dpixels = getDPpixels(); 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 [][] dpixels_log = new double [dpixels.length][dpixels[0].length];
double [] kernel2d = cuasRTUtils.getKernel2d(); double [] kernel2d = cuasRTUtils.getKernel2d();
double alpha0 = 1.0 - clt_parameters.imp.curt_rleak0; double alpha0 = 1.0 - clt_parameters.imp.curt_rleak0;
...@@ -1202,6 +1235,28 @@ public class CuasDetectRT { ...@@ -1202,6 +1235,28 @@ public class CuasDetectRT {
QuadCLTCPU.saveImagePlusInDirectory(ShowDoubleFloatArrays.showArraysHyperstack( QuadCLTCPU.saveImagePlusInDirectory(ShowDoubleFloatArrays.showArraysHyperstack(
off_hyper, rwid, title_dnn+"-OFFSET", ts_dnn_w, new String[]{"dx","dy","s"}, false), getModelDirectory()); 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 } // By Claude on 06/13/2026
dnn.close(); // By Claude on 06/13/2026 dnn.close(); // By Claude on 06/13/2026
} catch (Exception e) { // By Claude on 06/13/2026 } catch (Exception e) { // By Claude on 06/13/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