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

CLAUDE: CUAS Layer-1 — synth bg-avg fast path + Time-ROI window + MF-S head

- IntersceneMatchParameters: add curt_synth_bg_avg (synth quick-run: average N=2^LEV real
  frames per output frame and force a single level — restores the fast synthetic+background path).
- CuasDetectRT: that fast synth path (decimate-average to a short sequence + single level),
  honoring "Time ROI to" for the averaged-sequence length.
- CuasDnnInfer: MF-S head reads channel 0 as the raw matched-filter path-sum (clamp>=0, no sigmoid).

Validated: weighted model tracks the real UAS sub-pixel at LEV2/3; linear conditioning
(LReLU off) is dropout-free and matches the no-LReLU training. Checkpoint before Layer 2.
Co-authored-by: 's avatarClaude Opus 4.8 (1M context) <noreply@anthropic.com>
parent c0fa0743
...@@ -284,7 +284,7 @@ public class CuasDetectRT { ...@@ -284,7 +284,7 @@ public class CuasDetectRT {
double[] accS = new double[npix], accVx = new double[npix], accVy = new double[npix]; double[] accS = new double[npix], accVx = new double[npix], accVy = new double[npix];
for (int p = 0; p < npix; p++) { for (int p = 0; p < npix; p++) {
double s = S[p]; if (!(s > 0)) continue; double s = S[p]; if (!(s > 0)) continue;
double w = s * s; // s^2: suppress the many weak-s (noise) votes double w = s; // MF-S (option a): S is the matched-filter path-sum, already the right vote weight (full at head, ~0 on noise). By Claude on 06/18/2026
double tx = (p % rw) - Vx[p] * Nm1, ty = (p / rw) - Vy[p] * Nm1; double tx = (p % rw) - Vx[p] * Nm1, ty = (p / rw) - Vy[p] * Nm1;
int x0 = (int) Math.floor(tx), y0 = (int) Math.floor(ty); int x0 = (int) Math.floor(tx), y0 = (int) Math.floor(ty);
double fx = tx - x0, fy = ty - y0; double fx = tx - x0, fy = ty - y0;
...@@ -299,6 +299,68 @@ public class CuasDetectRT { ...@@ -299,6 +299,68 @@ public class CuasDetectRT {
return new double[][] { accS, accVx, accVy }; return new double[][] { accS, accVx, accVy };
} }
/** Distribution-vote: each pixel's velocity-grid cell votes at its tail (P - V_cell*(N-1)),
* weighted by the cell value (softmax*s). Keeps the FULL per-pixel velocity distribution (no
* collapse to one V); competition resolves across pixels at the tails. field=[npix][1][nvel],
* cell order vy-outer/vx-inner (matches training). Returns [3][npix]=(accS,accVx,accVy). By Claude on 06/19/2026 */
static double[][] voteScatterGrid(double[][][] field, int vdim, int velRadius, double velDec, int rw, int rh, int Nm1) {
int npix = rw * rh;
double[] accS = new double[npix], accVx = new double[npix], accVy = new double[npix];
for (int p = 0; p < npix; p++) {
double[] fv = field[p][0];
int px = p % rw, py = p / rw;
for (int v = 0; v < fv.length; v++) {
double w = fv[v]; if (!(w > 0)) continue; // inferROI leaves sub-thresh pixels all-0 -> skipped
double vx = ((v % vdim) - velRadius) / velDec, vy = ((v / vdim) - velRadius) / velDec;
double tx = px - vx * Nm1, ty = py - vy * Nm1;
int x0 = (int) Math.floor(tx), y0 = (int) Math.floor(ty);
double fx = tx - x0, fy = ty - y0;
for (int dx = 0; dx < 2; dx++) for (int dy = 0; dy < 2; dy++) {
int xi = x0 + dx, yi = y0 + dy;
if (xi < 0 || xi >= rw || yi < 0 || yi >= rh) continue;
double bw = w * (dx == 0 ? 1 - fx : fx) * (dy == 0 ? 1 - fy : fy);
int idx = yi * rw + xi;
accS[idx] += bw; accVx[idx] += bw * vx; accVy[idx] += bw * vy;
}
}
}
return new double[][] { accS, accVx, accVy };
}
/** Bilinear sample of a full-frame float buffer at (x,y); 0 outside (off-frame = absent). By Claude on 06/19/2026 */
static double bilinearFrame(float[] f, int w, int h, double x, double y) {
int x0 = (int) Math.floor(x), y0 = (int) Math.floor(y);
if (x0 < 0 || x0 >= w - 1 || y0 < 0 || y0 >= h - 1) return 0.0;
double fx = x - x0, fy = y - y0;
return (1 - fy) * ((1 - fx) * f[y0 * w + x0] + fx * f[y0 * w + x0 + 1])
+ fy * ((1 - fx) * f[(y0 + 1) * w + x0] + fx * f[(y0 + 1) * w + x0 + 1]);
}
/** Head (T0) map straight from the CLEAN vote accumulator - the inverse of the vote scatter, but
* driven by the accumulated (robust) consensus velocity, not the soft per-pixel refine. Each tail
* bin with accS >= frac*max projects its vote strength to head = tail + (accVx/accS)*(N-1), so
* detections land where -VXYS lights up (current target position). Bypasses the refine, whose peaks
* are softer than its background; the summed vote is the better source. By Claude on 06/18/2026 */
static double[] headFromVote(double[] accS, double[] accVx, double[] accVy, int rw, int rh, int Nm1, double frac) {
int npix = rw * rh;
double mx = 1e-9; for (double v : accS) if (v > mx) mx = v;
double thr = frac * mx;
double[] head = new double[npix];
for (int p = 0; p < npix; p++) {
double s = accS[p]; if (!(s >= thr)) continue; // only the strong vote bins (real tails); ratios below are meaningless in the noise floor
double vcx = accVx[p] / s, vcy = accVy[p] / s; // accumulated consensus velocity at this tail
double hx = (p % rw) + vcx * Nm1, hy = (p / rw) + vcy * Nm1;
int x0 = (int) Math.floor(hx), y0 = (int) Math.floor(hy);
double fx = hx - x0, fy = hy - y0;
for (int dx = 0; dx < 2; dx++) for (int dy = 0; dy < 2; dy++) {
int xi = x0 + dx, yi = y0 + dy;
if (xi < 0 || xi >= rw || yi < 0 || yi >= rh) continue;
head[yi * rw + xi] += s * (dx == 0 ? 1 - fx : fx) * (dy == 0 ? 1 - fy : fy);
}
}
return head;
}
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
if ((c5_levels == null) || (c5_levels.length == 0)) return true; if ((c5_levels == null) || (c5_levels.length == 0)) return true;
for (int l : c5_levels) if (l == nlev) return true; for (int l : c5_levels) if (l == nlev) return true;
...@@ -716,6 +778,31 @@ public class CuasDetectRT { ...@@ -716,6 +778,31 @@ public class CuasDetectRT {
QuadCLTCPU.saveImagePlusInDirectory(imp_5d,getModelDirectory()); QuadCLTCPU.saveImagePlusInDirectory(imp_5d,getModelDirectory());
} }
double [][] dpixels = getDPpixels(); double [][] dpixels = getDPpixels();
// Synthetic quick-run fast path (curt_synth_bg_avg > 1): decimate-average the REAL background by N // By Claude on 06/20/2026
// (the LEV-L noise floor, N=2^L) into a sequence (length = the "Time ROI" window; open/-1 = all frames) and force a single level - restoring the
// old synthetic+background mode speed. SUBAVG then runs over this short sequence; the SYNTHETIC targets are
// injected later (after SUBAVG), so they never enter the average. Velocity cells then encode LEV1/2/3.
final boolean synth_fast = clt_parameters.imp.curt_synth_src && (clt_parameters.imp.curt_synth_bg_avg > 1);
if (synth_fast && (dpixels != null) && (dpixels.length >= clt_parameters.imp.curt_synth_bg_avg)) {
final int navg = clt_parameters.imp.curt_synth_bg_avg;
int ngrp = dpixels.length / navg; // each group = navg input frames -> one averaged (LEV) frame
String [] ts_grp = new String [ngrp];
for (int k = 0; k < ngrp; k++) ts_grp[k] = time_stamps[k*navg + navg - 1]; // last ts of each navg-group (the LEV timestamp)
int [] gwin = timeWindow(ts_grp); // honor curt_time_from/to on the averaged (decimated) timestamps so the range can be extended // By Claude on 06/20/2026
int g0 = gwin[0], g1 = gwin[1]; // open Time ROI (-1) -> [0,ngrp) = ALL frames; "to" set -> up to that timestamp // By Claude on 06/20/2026
int kout = g1 - g0;
int npx_a = dpixels[0].length;
double [][] davg = new double [kout][npx_a];
String [] tsavg = new String [kout];
for (int k = 0; k < kout; k++) {
int g = g0 + k;
for (int j = 0; j < navg; j++) { double [] fr = dpixels[g*navg + j]; for (int p = 0; p < npx_a; p++) davg[k][p] += fr[p]; }
for (int p = 0; p < npx_a; p++) davg[k][p] /= navg;
tsavg[k] = ts_grp[g]; // 8x-decimated timestamp (last of the navg-group)
}
this.dpixels = davg; this.time_stamps = tsavg; dpixels = davg;
System.out.println(now()+" detectTargets(): synth fast path - bg-averaged x"+navg+" -> "+kout+"/"+ngrp+" frames (Time ROI groups ["+g0+","+g1+")), single level"); // By Claude on 06/20/2026
}
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 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; int npx = dpixels[0].length;
double [] mean = new double [npx]; double [] mean = new double [npx];
...@@ -799,13 +886,13 @@ public class CuasDetectRT { ...@@ -799,13 +886,13 @@ public class CuasDetectRT {
} }
// building pyramid // building pyramid
int pyramid_levels = clt_parameters.imp.curt_pyramid; int pyramid_levels = synth_fast ? 1 : clt_parameters.imp.curt_pyramid; // synth fast path forces a single level // By Claude on 06/20/2026
// Single level selector for ALL heavy per-level paths (3d3/conv5d, C5P-direct, DNN): // By Claude on 06/13/2026 // Single level selector for ALL heavy per-level paths (3d3/conv5d, C5P-direct, DNN): // By Claude on 06/13/2026
// curt_c5_levels = {} -> all built levels; {k} -> run the heavy stages only at level k // curt_c5_levels = {} -> all built levels; {k} -> run the heavy stages only at level k
// (the cheap temporalAverageLReLU/convolve3D3LReLU pyramid chain still builds up to k). // (the cheap temporalAverageLReLU/convolve3D3LReLU pyramid chain still builds up to k).
// Synthetic input now uses the same pyramid + level selection as real data (the old // Synthetic input now uses the same pyramid + level selection as real data (the old
// pyramid_levels=1 force and the curt_synth_bg_avg N= decimation were retired). // pyramid_levels=1 force and the curt_synth_bg_avg N= decimation were retired).
final int [] c5_levels = clt_parameters.imp.curt_c5_levels; final int [] c5_levels = synth_fast ? new int[0] : clt_parameters.imp.curt_c5_levels; // synth fast path -> all built (= level 0 only) // By Claude on 06/20/2026
double [][][] dpixels_pyramid = new double [pyramid_levels][][]; double [][][] dpixels_pyramid = new double [pyramid_levels][][];
String [][] ts_pyramid = new String [pyramid_levels][]; String [][] ts_pyramid = new String [pyramid_levels][];
...@@ -1287,8 +1374,21 @@ public class CuasDetectRT { ...@@ -1287,8 +1374,21 @@ public class CuasDetectRT {
window[h] = fw; window[h] = fw;
} }
double [][] o6 = dnn.inferROIReg(window, width, height, curt_save_select); // [pix]{S,Vx,Vy,sigma,dx,dy} double [][] o6 = dnn.inferROIReg(window, width, height, curt_save_select); // [pix]{S,Vx,Vy,sigma,dx,dy}
final double t8frac = clt_parameters.imp.curt_dnn_t8frac; // T-8 ghost filter (debug); 0 = off // By Claude on 06/19/2026
for (int p = 0; p < rnpr; p++) { for (int p = 0; p < rnpr; p++) {
double sv = o6[p][0]; double sv = o6[p][0];
// T-8 ghost filter: drop pixels whose oldest 2 frames (T-8,T-7) dominate the path-sum
// (a lone-apex sample admits any direction -> ghost fan). Real targets spread ~uniformly. // By Claude on 06/19/2026
if ((t8frac > 0) && (sv > 0)) {
int cx = curt_save_select.x + (p % curt_save_select.width);
int cy = curt_save_select.y + (p / curt_save_select.width);
double vx = o6[p][1], vy = o6[p][2], tot = 0, old2 = 0;
for (int i = 0; i < N_dnn; i++) { // window[i]: i=0 newest (T-0) .. i=N-1 oldest (T-8)
double val = bilinearFrame(window[i], width, height, cx - vx*i, cy - vy*i);
tot += val; if (i >= N_dnn - 2) old2 += val; // oldest 2 = T-8,T-7
}
if ((tot > 1e-6) && (old2/tot > t8frac)) sv = 0; // apex-dominated -> ghost, drop
}
svxys[0][j][p] = sv; svxys[1][j][p] = o6[p][1]; svxys[2][j][p] = o6[p][2]; svxys[3][j][p] = o6[p][3]; svxys[0][j][p] = sv; svxys[1][j][p] = o6[p][1]; svxys[2][j][p] = o6[p][2]; svxys[3][j][p] = o6[p][3];
soffr[0][j][p] = (sv >= dnn_thresh) ? o6[p][4] : Double.NaN; soffr[0][j][p] = (sv >= dnn_thresh) ? o6[p][4] : Double.NaN;
soffr[1][j][p] = (sv >= dnn_thresh) ? o6[p][5] : Double.NaN; soffr[1][j][p] = (sv >= dnn_thresh) ? o6[p][5] : Double.NaN;
...@@ -1303,13 +1403,17 @@ public class CuasDetectRT { ...@@ -1303,13 +1403,17 @@ public class CuasDetectRT {
soffr, curt_save_select.width, title_reg+"-OFFSET", ts_reg, new String[]{"dx","dy","s"}, false), clt_parameters.imp), getModelDirectory()); soffr, curt_save_select.width, title_reg+"-OFFSET", ts_reg, new String[]{"dx","dy","s"}, false), clt_parameters.imp), getModelDirectory());
if (!clt_parameters.imp.curt_stage2_model.isEmpty()) { // v2 Stage-2: vote + learned refine views // By Claude on 06/18/2026 if (!clt_parameters.imp.curt_stage2_model.isEmpty()) { // v2 Stage-2: vote + learned refine views // By Claude on 06/18/2026
int rw2 = curt_save_select.width, rh2 = curt_save_select.height, Nm1v = N_dnn - 1; int rw2 = curt_save_select.width, rh2 = curt_save_select.height, Nm1v = N_dnn - 1;
double [][][] votem = new double [1][num][rnpr]; // vote heatmap (accS) double [][][] votem = new double [1][num][rnpr]; // vote heatmap (accS), tail-indexed
double [][][] refd = new double [3][num][rnpr]; // refined {S,Vx,Vy} double [][][] refd = new double [3][num][rnpr]; // refined {S,Vx,Vy}, tail-indexed
double [][][] heads = new double [1][num][rnpr]; // detection re-indexed to head (T0) // By Claude on 06/18/2026
try { try {
CuasStage2Infer s2 = new CuasStage2Infer(clt_parameters.imp.curt_stage2_model); CuasStage2Infer s2 = new CuasStage2Infer(clt_parameters.imp.curt_stage2_model);
for (int j = 0; j < num; j++) { for (int j = 0; j < num; j++) {
double [][] acc = voteScatter(svxys[0][j], svxys[1][j], svxys[2][j], rw2, rh2, Nm1v); double [][] acc = voteScatter(svxys[0][j], svxys[1][j], svxys[2][j], rw2, rh2, Nm1v);
votem[0][j] = acc[0]; votem[0][j] = acc[0].clone(); // raw vote heatmap (display)
heads[0][j] = headFromVote(acc[0], acc[1], acc[2], rw2, rh2, Nm1v, 0.25); // head from clean vote consensus (frac=0.25*max) // By Claude on 06/18/2026
double mx = 1e-9; for (double v : acc[0]) if (v > mx) mx = v; // per-scene normalize to MATCH refine training (python: acc /= accS.max()) // By Claude on 06/18/2026
for (int c = 0; c < 3; c++) for (int p = 0; p < acc[c].length; p++) acc[c][p] /= mx;
double [][] r = s2.refine(acc, rw2, rh2); double [][] r = s2.refine(acc, rw2, rh2);
refd[0][j] = r[0]; refd[1][j] = r[1]; refd[2][j] = r[2]; refd[0][j] = r[0]; refd[1][j] = r[1]; refd[2][j] = r[2];
} }
...@@ -1318,7 +1422,9 @@ public class CuasDetectRT { ...@@ -1318,7 +1422,9 @@ public class CuasDetectRT {
votem, rw2, title_reg+"-VOTE", ts_reg, new String[]{"vote"}, false), clt_parameters.imp), getModelDirectory()); votem, rw2, title_reg+"-VOTE", ts_reg, new String[]{"vote"}, false), clt_parameters.imp), getModelDirectory());
QuadCLTCPU.saveImagePlusInDirectory(tagCuasImp(ShowDoubleFloatArrays.showArraysHyperstack( QuadCLTCPU.saveImagePlusInDirectory(tagCuasImp(ShowDoubleFloatArrays.showArraysHyperstack(
refd, rw2, title_reg+"-REFINED", ts_reg, new String[]{"S","Vx","Vy"}, false), clt_parameters.imp), getModelDirectory()); refd, rw2, title_reg+"-REFINED", ts_reg, new String[]{"S","Vx","Vy"}, false), clt_parameters.imp), getModelDirectory());
System.out.println(now()+" v2 Stage-2: saved -VOTE + -REFINED ("+num+" scenes)"); QuadCLTCPU.saveImagePlusInDirectory(tagCuasImp(ShowDoubleFloatArrays.showArraysHyperstack(
heads, rw2, title_reg+"-HEADS", ts_reg, new String[]{"head"}, false), clt_parameters.imp), getModelDirectory());
System.out.println(now()+" v2 Stage-2: saved -VOTE + -REFINED + -HEADS ("+num+" scenes)");
} catch (Exception e) { System.out.println("v2 Stage-2 failed: "+e); } } catch (Exception e) { System.out.println("v2 Stage-2 failed: "+e); }
} }
continue; // reg level done - skip the grid path below continue; // reg level done - skip the grid path below
...@@ -1344,6 +1450,27 @@ public class CuasDetectRT { ...@@ -1344,6 +1450,27 @@ public class CuasDetectRT {
ts_dnn[j] = ts_pyramid[nlev][newest] + " f"+newest; // timestamp + abs frame index for matching // By Claude on 06/14/2026 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 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 } // By Claude on 06/13/2026
// Distribution-vote (forward path): each pixel's velocity-grid cell votes at its tail // By Claude on 06/19/2026
// P - V_cell*(N-1), weighted by softmax*s; competition resolves at the tails, NOT collapsed
// per-pixel. Uses the RAW distribution (before the old ghostbuster). -GVOTE = accumulator,
// -GHEADS = re-indexed to T0 via the consensus velocity.
{
int rwg = curt_save_select.width, rhg = curt_save_select.height, Nm1g = N_dnn - 1;
int vdimg = 2 * vr_dnn + 1; double velDecg = clt_parameters.imp.curt_vel_decimate;
double [][][] gvote = new double [1][num][rwg * rhg];
double [][][] gheads = new double [1][num][rwg * rhg];
for (int j = 0; j < num; j++) {
double [][] acc = voteScatterGrid(dnn_roi[j], vdimg, vr_dnn, velDecg, rwg, rhg, Nm1g);
gvote[0][j] = acc[0];
gheads[0][j] = headFromVote(acc[0], acc[1], acc[2], rwg, rhg, Nm1g, 0.25);
}
String title_g = title_conv5d+"-DNN"+((nlev > 0)?("-LEV"+nlev):"")+"-ROI"+curt_save_select.x+"_"+curt_save_select.y+"_"+curt_save_select.width+"_"+curt_save_select.height;
QuadCLTCPU.saveImagePlusInDirectory(tagCuasImp(ShowDoubleFloatArrays.showArraysHyperstack(
gvote, rwg, title_g+"-GVOTE", ts_dnn, new String[]{"vote"}, false), clt_parameters.imp), getModelDirectory());
QuadCLTCPU.saveImagePlusInDirectory(tagCuasImp(ShowDoubleFloatArrays.showArraysHyperstack(
gheads, rwg, title_g+"-GHEADS", ts_dnn, new String[]{"head"}, false), clt_parameters.imp), getModelDirectory());
System.out.println(now()+" grid distribution-vote: saved -GVOTE + -GHEADS ("+num+" scenes)");
}
// Ghostbuster: drop untrained corner velocities (cell-R > vmax*vel_decimate) before save // By Claude on 06/15/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) // + 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 if (clt_parameters.imp.curt_dnn_vmax > 0) { // By Claude on 06/15/2026
......
...@@ -157,8 +157,11 @@ public class CuasDnnInfer implements AutoCloseable { ...@@ -157,8 +157,11 @@ public class CuasDnnInfer implements AutoCloseable {
} }
/** Continuous-velocity (reg) inference over an ROI. For each pixel returns /** Continuous-velocity (reg) inference over an ROI. For each pixel returns
* {S, Vx, Vy, sigma, dx, dy} from the 6-ch reg head (det, Vx, Vy, logvar, dx, dy): * {S, Vx, Vy, sigma, dx, dy} from the 6-ch reg head (det/S, Vx, Vy, logvar, dx, dy):
* S=sigmoid(det), Vx/Vy already bounded by the model, sigma=exp(logvar/2). By Claude on 06/17/2026 */ * Vx/Vy already bounded by the model, sigma=exp(logvar/2).
* MF-S models (option a, Andrey 2026-06-18): channel 0 is the RAW matched-filter path-sum S
* (clamp>=0), NOT a det logit -> read raw, no sigmoid (sigmoid would re-saturate the unbounded
* response). S is then the informative vote weight directly (CuasDetectRT.voteScatter). By Claude on 06/18/2026 */
public double[][] inferROIReg(float[][] frames, int width, int height, Rectangle roi) throws Exception { public double[][] inferROIReg(float[][] frames, int width, int height, Rectangle roi) throws Exception {
final int N = frames.length; final int N = frames.length;
final int P = patch, half = P / 2; final int P = patch, half = P / 2;
...@@ -190,7 +193,7 @@ public class CuasDnnInfer implements AutoCloseable { ...@@ -190,7 +193,7 @@ public class CuasDnnInfer implements AutoCloseable {
for (int q = 0; q < bs; q++) { for (int q = 0; q < bs; q++) {
float[][][] op = out[q]; float[][][] op = out[q];
double[] o = out6[base + q]; double[] o = out6[base + q];
o[0] = sigmoid(op[0][0][0]); // S o[0] = Math.max(0.0, op[0][0][0]); // S = raw MF path-sum (clamp>=0); MF-S model, no sigmoid. By Claude on 06/18/2026
o[1] = op[1][0][0]; o[2] = op[2][0][0]; // Vx, Vy (model-bounded) o[1] = op[1][0][0]; o[2] = op[2][0][0]; // Vx, Vy (model-bounded)
o[3] = Math.exp(0.5 * op[3][0][0]); // sigma = exp(logvar/2) o[3] = Math.exp(0.5 * op[3][0][0]); // sigma = exp(logvar/2)
o[4] = op[4][0][0]; o[5] = op[5][0][0]; // dx, dy o[4] = op[4][0][0]; o[5] = op[5][0][0]; // dx, dy
......
...@@ -1184,11 +1184,13 @@ min_str_neib_fpn 0.35 ...@@ -1184,11 +1184,13 @@ min_str_neib_fpn 0.35
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 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 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 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 double curt_dnn_t8frac = 0.0; // T-8 ghost filter (DEBUG, reg head): drop a DNN pixel (s=0) whose OLDEST 2 frames (T-8,T-7) carry > this fraction of its trajectory path-sum. A lone apex (t-8) sample admits any direction -> a fan of ghosts; a real target spreads ~uniformly (oldest-2 ~0.22). 0.5 is a safe cut; 0 disables. Principled replacement for curt_dnn_vmax. // By Claude on 06/19/2026
public int curt_dnn_patch = 24; // DNN receptive-field / training patch size (px): inferROI extracts this PxP patch per ROI pixel (half=P/2 = the output pixel). MUST match the loaded model (24 = base/PM models, 32 = larger-attention model) // By Claude on 06/16/2026 public int curt_dnn_patch = 24; // DNN receptive-field / training patch size (px): inferROI extracts this PxP patch per ROI pixel (half=P/2 = the output pixel). MUST match the loaded model (24 = base/PM models, 32 = larger-attention model) // By Claude on 06/16/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 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 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 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 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 int curt_synth_bg_avg = 1; // synth quick-run: average this many real frames per output frame (=2^LEV noise floor, e.g. 8=LEV3) + force single level; 1=off // By Claude on 06/20/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_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 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 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
...@@ -3544,6 +3546,8 @@ min_str_neib_fpn 0.35 ...@@ -3544,6 +3546,8 @@ min_str_neib_fpn 0.35
"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 "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 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."); "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 T-8 ghost filter (frac)", this.curt_dnn_t8frac, 3,8,"", // By Claude on 06/19/2026
"DEBUG: drop DNN pixels whose oldest 2 frames (T-8,T-7) exceed this fraction of the trajectory path-sum (apex-dominated = ghost). Real targets ~0.2, lone-apex ghosts ~1.0; 0.5 is a safe cut; 0 disables.");
gd.addNumericField("DNN patch size (RF)", this.curt_dnn_patch, 0,3,"pix", // By Claude on 06/16/2026 gd.addNumericField("DNN patch size (RF)", this.curt_dnn_patch, 0,3,"pix", // By Claude on 06/16/2026
"Receptive-field / training patch the model was built at (24 = base/PM, 32 = larger-attention). MUST match the loaded model."); "Receptive-field / training patch the model was built at (24 = base/PM, 32 = larger-attention). MUST match the loaded model.");
gd.addNumericField("DNN temporal stride", this.curt_dnn_stride, 0,3,"slices", // By Claude on 06/14/2026 gd.addNumericField("DNN temporal stride", this.curt_dnn_stride, 0,3,"slices", // By Claude on 06/14/2026
...@@ -3552,6 +3556,8 @@ min_str_neib_fpn 0.35 ...@@ -3552,6 +3556,8 @@ min_str_neib_fpn 0.35
"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 "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 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 "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.addNumericField("Synthetic bg frame averaging", this.curt_synth_bg_avg, 0,3,"frames", // By Claude on 06/20/2026
"Synth quick-run: average this many consecutive REAL frames per output frame (LEV noise floor = 2^LEV: 2=LEV1, 4=LEV2, 8=LEV3) and force a single level. 1 = off (full pyramid)."); // By Claude on 06/20/2026
gd.addCheckbox ("Subtract input temporal average", this.curt_subtract_avg, // By Claude on 06/14/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 "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 gd.addCheckbox ("Synthetic over real background", this.curt_synth_bg, // By Claude on 06/12/2026
...@@ -5104,10 +5110,12 @@ min_str_neib_fpn 0.35 ...@@ -5104,10 +5110,12 @@ min_str_neib_fpn 0.35
this.curt_dnn_recur_splat = gd.getNextBoolean(); // By Claude on 06/14/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_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_vmax = gd.getNextNumber(); // By Claude on 06/15/2026
this.curt_dnn_t8frac = gd.getNextNumber(); // By Claude on 06/19/2026
this.curt_dnn_patch = (int) gd.getNextNumber(); // By Claude on 06/16/2026 this.curt_dnn_patch = (int) gd.getNextNumber(); // By Claude on 06/16/2026
this.curt_dnn_stride = (int) 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_src = gd.getNextBoolean(); // By Claude on 06/11/2026
this.curt_synth_scale = gd.getNextNumber(); // By Claude on 06/12/2026 this.curt_synth_scale = gd.getNextNumber(); // By Claude on 06/12/2026
this.curt_synth_bg_avg = (int) gd.getNextNumber(); // By Claude on 06/20/2026
this.curt_subtract_avg = gd.getNextBoolean(); // By Claude on 06/14/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_synth_bg = gd.getNextBoolean(); // By Claude on 06/12/2026
this.curt_min_frac = gd.getNextNumber(); // By Claude on 06/12/2026 this.curt_min_frac = gd.getNextNumber(); // By Claude on 06/12/2026
...@@ -6481,9 +6489,11 @@ min_str_neib_fpn 0.35 ...@@ -6481,9 +6489,11 @@ min_str_neib_fpn 0.35
properties.setProperty(prefix+"curt_dnn_recur_scale", this.curt_dnn_recur_scale+""); // double // 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_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_dnn_vmax", this.curt_dnn_vmax+""); // double // By Claude on 06/15/2026
properties.setProperty(prefix+"curt_dnn_t8frac", this.curt_dnn_t8frac+""); // double // By Claude on 06/19/2026
properties.setProperty(prefix+"curt_dnn_patch", this.curt_dnn_patch+""); // int // By Claude on 06/16/2026 properties.setProperty(prefix+"curt_dnn_patch", this.curt_dnn_patch+""); // int // By Claude on 06/16/2026
properties.setProperty(prefix+"curt_synth_src", this.curt_synth_src+""); // boolean // By Claude on 06/11/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_synth_scale", this.curt_synth_scale+""); // double // By Claude on 06/12/2026
properties.setProperty(prefix+"curt_synth_bg_avg", this.curt_synth_bg_avg+""); // int // By Claude on 06/20/2026
properties.setProperty(prefix+"curt_subtract_avg", this.curt_subtract_avg+""); // boolean // By Claude on 06/14/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_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 properties.setProperty(prefix+"curt_min_frac", this.curt_min_frac+""); // double // By Claude on 06/12/2026
...@@ -6892,10 +6902,12 @@ min_str_neib_fpn 0.35 ...@@ -6892,10 +6902,12 @@ min_str_neib_fpn 0.35
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_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_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_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_dnn_t8frac")!=null) this.curt_dnn_t8frac=Double.parseDouble(properties.getProperty(prefix+"curt_dnn_t8frac")); // By Claude on 06/19/2026
if (properties.getProperty(prefix+"curt_dnn_patch")!=null) this.curt_dnn_patch=Integer.parseInt(properties.getProperty(prefix+"curt_dnn_patch")); // By Claude on 06/16/2026 if (properties.getProperty(prefix+"curt_dnn_patch")!=null) this.curt_dnn_patch=Integer.parseInt(properties.getProperty(prefix+"curt_dnn_patch")); // By Claude on 06/16/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_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_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_synth_bg_avg")!=null) this.curt_synth_bg_avg=Integer.parseInt(properties.getProperty(prefix+"curt_synth_bg_avg")); // By Claude on 06/20/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_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_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 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
...@@ -9185,9 +9197,11 @@ min_str_neib_fpn 0.35 ...@@ -9185,9 +9197,11 @@ min_str_neib_fpn 0.35
imp.curt_dnn_recur_scale = this.curt_dnn_recur_scale; // 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_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_dnn_vmax = this.curt_dnn_vmax; // By Claude on 06/15/2026
imp.curt_dnn_t8frac = this.curt_dnn_t8frac; // By Claude on 06/19/2026
imp.curt_dnn_patch = this.curt_dnn_patch; // By Claude on 06/16/2026 imp.curt_dnn_patch = this.curt_dnn_patch; // By Claude on 06/16/2026
imp.curt_synth_src = this.curt_synth_src; // By Claude on 06/11/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_synth_scale = this.curt_synth_scale; // By Claude on 06/12/2026
imp.curt_synth_bg_avg = this.curt_synth_bg_avg; // By Claude on 06/20/2026
imp.curt_subtract_avg = this.curt_subtract_avg; // By Claude on 06/14/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_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 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