Commit 0bd16311 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: DNN ghostbuster - discard untrained corner velocities (R>vmax)

The DNN velocity grid (radius vel_radius) extends past the trained velocity
disk, so untrained corner cells emit spurious sidelobes (ghosts) of non-trivial
strength (s up to ~0.09) that would confuse the recurrent layer. New
dnnGhostbust() zeros velocity cells with cell-radius > curt_dnn_vmax*vel_decimate,
and if a pixel's peak lands in the untrained corner it discards the whole
detection (field=0, s=0). Applied to the DNN field + offset before save and the
recurrent feed. New param curt_dnn_vmax (px/frame, default 1.4 = PM models'
training vmax_px; set to match the loaded model; <=0 disables).
Co-Authored-By: 's avatarClaude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 65664688
......@@ -282,6 +282,35 @@ public class CuasDetectRT {
return false;
}
/** "Ghostbuster": the DNN velocity grid (radius vel_radius cells) extends past the trained # By Claude on 06/15/2026
* velocity disk, so untrained corner cells emit spurious sidelobes (ghosts) of non-trivial
* strength that would confuse the recurrent layer. For each ROI pixel: zero velocity cells with
* cell-radius > rmax_cells (= trained vmax_px * vel_decimate); if the pixel's PEAK lands in that
* untrained region the whole detection is a ghost -> zero its field AND set s=0 (off[pix][2]).
* field = [npix][1][nvel] (softmax*s), off = [npix][{dx,dy,s}] (may be null). rmax_cells<=0 disables. */
private static void dnnGhostbust(double [][][] field, double [][] off, int vel_radius, double rmax_cells) {
if ((field == null) || (rmax_cells <= 0)) return;
final int vdim = 2 * vel_radius + 1;
final double r2 = rmax_cells * rmax_cells;
for (int pix = 0; pix < field.length; pix++) {
double [] fv = (field[pix] != null) ? field[pix][0] : null;
if (fv == null) continue;
int amax = -1; double amx = Double.NEGATIVE_INFINITY;
for (int v = 0; v < fv.length; v++) if (fv[v] > amx) { amx = fv[v]; amax = v; }
if (amax < 0) continue;
int avx = amax % vdim - vel_radius, avy = amax / vdim - vel_radius;
if ((avx * avx + avy * avy) > r2) { // peak is in the untrained corner -> ghost
java.util.Arrays.fill(fv, 0.0);
if ((off != null) && (off[pix] != null)) off[pix][2] = 0.0; // s = 0 (don't feed the recurrent)
} else { // real peak: keep it, drop the corner sidelobes
for (int v = 0; v < fv.length; v++) {
int vx = v % vdim - vel_radius, vy = v / vdim - vel_radius;
if ((vx * vx + vy * vy) > r2) fv[v] = 0.0;
}
}
}
}
public int getWidth() {
return width;
......@@ -1213,6 +1242,12 @@ public class CuasDetectRT {
ts_dnn[j] = ts_pyramid[nlev][newest] + " f"+newest; // timestamp + abs frame index for matching // By Claude on 06/14/2026
System.out.println(now()+" DNN scene "+(j+1)+"/"+num+" (frame "+newest+", "+ts_pyramid[nlev][newest]+") done"); // By Claude on 06/14/2026
} // By Claude on 06/13/2026
// Ghostbuster: drop untrained corner velocities (cell-R > vmax*vel_decimate) before save // By Claude on 06/15/2026
// + recurrent feed, so the spurious corner sidelobes don't confuse the recurrent (s=0 for ghost-peak pixels)
if (clt_parameters.imp.curt_dnn_vmax > 0) { // By Claude on 06/15/2026
double rmax_cells = clt_parameters.imp.curt_dnn_vmax * clt_parameters.imp.curt_vel_decimate;
for (int j = 0; j < num; j++) dnnGhostbust(dnn_roi[j], dnn_off[j], vr_dnn, rmax_cells);
}
String title_dnn = title_conv5d+"-DNN"+((nlev > 0)?("-LEV"+nlev):""); // By Claude on 06/13/2026
int [] win_dnn = timeWindow(ts_dnn); // timing ROI // By Claude on 06/14/2026
double [][][][] dnn_w = win4(dnn_roi, win_dnn); String [] ts_dnn_w = winS(ts_dnn, win_dnn); // By Claude on 06/14/2026
......
......@@ -1181,6 +1181,7 @@ 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 double curt_dnn_vmax = 1.4; // "ghostbuster": trained DNN velocity limit (px/frame); zero velocity-grid cells beyond this radius (cell-R > vmax*vel_decimate) and discard pixels whose peak lands there (s=0) - the untrained corner cells emit spurious sidelobes that would confuse the recurrent. Set to match the loaded model's training vmax_px (PM models=1.4, base=1.0); <=0 disables // By Claude on 06/15/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
......@@ -3534,6 +3535,8 @@ min_str_neib_fpn 0.35
"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 ghostbuster vmax", this.curt_dnn_vmax, 4,8,"pix/frame", // By Claude on 06/15/2026
"Zero DNN velocities beyond this radius (untrained corner cells) and discard pixels peaking there (s=0); match the model's training vmax_px (PM=1.4, base=1.0); <=0 disables.");
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
......@@ -5089,6 +5092,7 @@ min_str_neib_fpn 0.35
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_vmax = gd.getNextNumber(); // By Claude on 06/15/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
......@@ -6462,6 +6466,7 @@ min_str_neib_fpn 0.35
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_dnn_vmax", this.curt_dnn_vmax+""); // double // By Claude on 06/15/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
......@@ -6869,6 +6874,7 @@ min_str_neib_fpn 0.35
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_dnn_vmax")!=null) this.curt_dnn_vmax=Double.parseDouble(properties.getProperty(prefix+"curt_dnn_vmax")); // By Claude on 06/15/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
......@@ -9158,6 +9164,7 @@ min_str_neib_fpn 0.35
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_dnn_vmax = this.curt_dnn_vmax; // By Claude on 06/15/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
......
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