Commit 6731866f authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: lean v2 pose policies - uniform MB vector + fixed LMA cycles

Phase A items 1-2 of the pose GPU-migration plan (Andrey's design 07/12/2026):
- curt.pose_mb_uniform (default ON): ONE motion-blur vector per scene shared by
  all tiles - constant-rpm CUAS flow is uniform up to the roll component (small)
  and wobble (2nd degree). Same Jacobian-times-rates as per-tile getMotionBlur,
  evaluated at the frame center and broadcast; rates = finite differences of
  predicted poses, unchanged. OFF = legacy per-tile path (A/B knob).
- curt.pose_cycles (default 3): EXACTLY N measure<->solve cycles, no convergence
  early exit (static GPU launch shape); convergence delta kept as QC printout
  (flags scenes missing the legacy exit_change_atr). 0 = legacy convergence exit.

Validation = pose_test run vs 07/12 JNA baseline (497/497, az/tilt/roll RMS
0.1355/0.0765/0.1005 px) - policy A/B by pose quality, not bit-exactness.
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent 0937dbcc
......@@ -447,6 +447,57 @@ public class CuasPoseRT {
return com.elphel.imagej.gpu.GPUTileProcessor.DTT_SIZE;
}
/**
* Lean v2 CUAS motion blur (Andrey's ruling 07/12/2026): ONE uniform blur vector for
* the whole scene, shared by all tiles. Under constant-rpm rotation the image flow is
* uniform across the frame up to the roll flow component (roll rate << azimuth/tilt
* rates) and the wobble contribution (second-degree) - both ignored by ruling.
* Evaluates the SAME Jacobian-times-rates product as the per-tile
* OpticalFlow.getMotionBlur at ONE representative point (frame center at the mean
* disparity of the defined reference tiles) - identical sign/scale conventions to the
* per-tile oracle - then broadcasts. Rates source unchanged (finite differences of
* predicted poses). The GPU migration consumes this as two scalars per scene: the MB
* TpTask pair differs from the main task set by a single shared pixel shift.
* By Claude on 07/12/2026, from Andrey's design.
* @return [2][pXpYD_center.length] uniform blur vectors (px/s), or null when the
* representative point is degenerate (caller falls back to the per-tile path).
*/
public static double [][] uniformMotionBlur(
final QuadCLT center_CLT,
final QuadCLT scene,
final double [][] pXpYD_center,
final double [] camera_xyz,
final double [] camera_atr,
final double [] camera_xyz_dt,
final double [] camera_atr_dt,
final int debugLevel) {
final int [] wh = center_CLT.getGeometryCorrection().getSensorWH();
double sum_d = 0.0;
int num_d = 0;
for (double [] p : pXpYD_center) if ((p != null) && !Double.isNaN(p[2])) {
sum_d += p[2];
num_d++;
}
final double [][] one_point = {{0.5 * wh[0], 0.5 * wh[1], (num_d > 0)? (sum_d / num_d) : 0.0}};
final double [][] mb1 = OpticalFlow.getMotionBlur(
center_CLT, // QuadCLT ref_scene
scene, // QuadCLT scene
one_point, // double [][] ref_pXpYD - the single representative point
camera_xyz, // double [] camera_xyz
camera_atr, // double [] camera_atr
camera_xyz_dt, // double [] camera_xyz_dt
camera_atr_dt, // double [] camera_atr_dt
0, // int shrink_gaps - nothing to fill
debugLevel); // int debug_level
if ((mb1 == null) || Double.isNaN(mb1[0][0]) || Double.isNaN(mb1[1][0])) {
return null;
}
final double [][] mb_vectors = new double [2][pXpYD_center.length];
Arrays.fill(mb_vectors[0], mb1[0][0]);
Arrays.fill(mb_vectors[1], mb1[1][0]);
return mb_vectors;
}
/**
* Phase B lean per-scene fit: the measure<->solve cycle with leanMeasure() as the
* measurement engine and the same IntersceneLma solver/exit rules as the oracle
......@@ -481,8 +532,17 @@ public class CuasPoseRT {
0.0); // no disparity weight (2D only)
double [][] scene_xyzatr0 = new double [][] {predicted[0].clone(), predicted[1].clone()};
double [][][] cm = null;
// lean v2 (Andrey's ruling 07/12/2026): pose_cycles > 0 = run EXACTLY that many
// measure<->solve cycles, no convergence-based early exit (CUAS scenes are
// near-identical - the needed count is a constant of the setup; a fixed count
// gives the GPU pipeline a static launch shape). The convergence delta is still
// computed below as a QC diagnostic. 0 = legacy convergence exit (flying-camera/
// varying-scene applications). By Claude on 07/12/2026.
final int fixed_cycles = clt_parameters.curt.pose_cycles;
final int max_cycles = (fixed_cycles > 0) ? fixed_cycles : clt_parameters.imp.max_cycles;
double last_diff_atr = Double.NaN; // By Claude on 07/12/2026
int nlma = 0;
for (; nlma < clt_parameters.imp.max_cycles; nlma++) {
for (; nlma < max_cycles; nlma++) {
cm = leanMeasure(
clt_parameters, image_dtt, center_CLT, scene,
center_disparity, pXpYD_center, selection,
......@@ -525,11 +585,22 @@ public class CuasPoseRT {
scene_xyzatr0 = intersceneLma.getSceneXYZATR(false);
final double [] diffs_atr = intersceneLma.getV3Diff(ErsCorrection.DP_DSAZ);
final double [] diffs_xyz = intersceneLma.getV3Diff(ErsCorrection.DP_DSX);
if ((diffs_atr[0] < clt_parameters.imp.exit_change_atr) &&
last_diff_atr = diffs_atr[0]; // By Claude on 07/12/2026
if ((fixed_cycles <= 0) && // lean v2: fixed count = no early exit // By Claude on 07/12/2026
(diffs_atr[0] < clt_parameters.imp.exit_change_atr) &&
(diffs_xyz[0] < clt_parameters.imp.exit_change_xyz)) {
break;
}
}
// QC diagnostic (fixed-cycle mode): flag scenes whose last ATR change would have
// missed the legacy convergence threshold - data for choosing between 2 and 3
// cycles, and a canary for scene classes that ever need more. Never steers the
// loop. By Claude on 07/12/2026, from Andrey's design.
if ((fixed_cycles > 0) && !(last_diff_atr < clt_parameters.imp.exit_change_atr) && (debugLevel > -4)) {
System.out.println("CuasPoseRT QC: scene "+scene.getImageName()+" pose NOT settled after "+
fixed_cycles+" fixed cycles: last ATR change "+last_diff_atr+
" >= exit_change_atr "+clt_parameters.imp.exit_change_atr);
}
if (lma_rms != null) {
final double [] last_rms = intersceneLma.getLastRms();
lma_rms[0] = last_rms[0];
......@@ -910,6 +981,28 @@ public class CuasPoseRT {
// removes the LWIR bolometer exponential tail). mb_en OFF -> single-run path (A/B).
double [][] mb_vectors_scene = null;
if (clt_parameters.imp.mb_en) {
if (clt_parameters.curt.pose_mb_uniform) {
// lean v2: single shared vector for all tiles (CUAS constant-rpm flow;
// roll component and wobble ignored by ruling). By Claude on 07/12/2026.
mb_vectors_scene = uniformMotionBlur(
center_CLT, // QuadCLT center_CLT,
quadCLTs[nscene], // QuadCLT scene,
pXpYD_center, // double [][] pXpYD_center,
predicted[0], // double [] camera_xyz,
predicted[1], // double [] camera_atr,
dxyzatr_dt[0], // double [] camera_xyz_dt,
dxyzatr_dt[1], // double [] camera_atr_dt,
debugLevel - 2); // int debugLevel
if (mb_vectors_scene == null) {
System.out.println("CuasPoseRT: scene "+ts_name+
" uniform MB degenerate - falling back to per-tile getMotionBlur");
} else if (debugLevel > -3) {
System.out.println(String.format(
"CuasPoseRT: scene %s uniform MB vector = (%.4f, %.4f) px/s",
ts_name, mb_vectors_scene[0][0], mb_vectors_scene[1][0]));
}
}
if (mb_vectors_scene == null) { // legacy per-tile path (pose_mb_uniform OFF or degenerate fallback) // By Claude on 07/12/2026
mb_vectors_scene = OpticalFlow.getMotionBlur(
center_CLT, // QuadCLT ref_scene,
quadCLTs[nscene], // QuadCLT scene,
......@@ -921,6 +1014,7 @@ public class CuasPoseRT {
0, // int shrink_gaps,
debugLevel - 2); // int debug_level
}
}
fail_reason[0] = 0;
final double [][][] coord_motion_rslt = new double [3][][]; // [centers, vector_XYS, eigen] of the last LMA cycle
if (corr_pd_holder != null) corr_pd_holder[0] = null; // no stale slices on failure // By Claude on 07/04/2026
......
......@@ -28,6 +28,8 @@ public class CuasRtParameters {
public boolean pose_corr_save = false; // DEBUG: save the per-scene 2D correlations vs the virtual center in the pixel domain (-POSE-RT-CORR2D: z=scenes, tile grid of 16x16 cells, last LMA cycle) - lean engine only. // By Claude on 07/04/2026
public boolean pose_img_save = false; // DEBUG: save the composite scenes actually correlated - imclt render of the consolidated 16-sensor TD average at the converged pose (-POSE-RT-COMPOSITE: z=scenes; NaN outside task tiles) + the virtual-center reference render (-POSE-RT-CENTER-REF). Lean engine only. // By Claude on 07/04/2026
public boolean pose_stored = false; // DEBUG: decouple rendering/measurement from the LMA - use the STORED (oracle vintage) pose for every scene, no adjustment; one leanMeasure per scene feeds -POSE-RT-HYPER/-CORR2D/-COMPOSITE. The composite must then match the oracle CUAS-MERGED-CUAS-DBG renders tile-for-tile. Lean engine only. // By Claude on 07/04/2026
public boolean pose_mb_uniform = true; // lean v2 (Andrey's ruling 07/12/2026): ONE uniform motion-blur vector per scene, shared by ALL tiles - under constant-rpm rotation the image flow is uniform across the frame up to the roll flow component (roll rate << azimuth/tilt) and the wobble contribution (second-degree), both ignored. Same Jacobian-times-rates product as the per-tile OpticalFlow.getMotionBlur (identical sign/scale conventions), evaluated at ONE representative point (frame center, mean reference disparity) and broadcast; rates source unchanged (finite differences of predicted poses). OFF = legacy per-tile getMotionBlur (the A/B knob). // By Claude on 07/12/2026
public int pose_cycles = 3; // lean v2 (Andrey's ruling 07/12/2026): run EXACTLY this many LMA measure<->solve cycles per scene, no convergence-based early exit - CUAS scenes are near-identical perturbations of the same view, so the needed count is a constant of the setup (and a fixed count gives the GPU pipeline a static launch shape). The convergence delta is still computed: a QC line flags scenes whose last ATR change misses the legacy imp.exit_change_atr threshold (diagnostic only). 0 = legacy convergence exit (imp.max_cycles cap + exit_change thresholds - for varying-scene/flying-camera applications). // By Claude on 07/12/2026
public boolean rend_test = false; // RT full-render product (CuasRender.testRenderSequence): per scene raw /jp4/ -> conditionSceneToGpu -> virtual-grid render at BORROWED stored pose+ERS rates -> -CUAS-RT-RENDER hyperstack [s00..s15, merged][scenes], comparable to -CUAS-INDIVIDUAL/MERGED-CUAS-DBG. NON-exclusive: runs AFTER the pose stage when both are ON (the pose stage is part of the RT cycle - Andrey 07/06/2026). // By Claude on 07/05/2026
public boolean log_test = false; // LoG A/B with the RT render (CuasRender): also save -CUAS-RT-RENDER-LOG-ORACLE (product convolved with the EXISTING pre-DNN pixel LoG, LINEAR alpha=1) and -CUAS-RT-RENDER-LOG-FOLDED (same render, LoG folded into the GPU aberration kernels via CuasLogFold - no pixel convolution; originals restored). The aberration+LoG kernel-fold validation (RT-seed step 1b). // By Claude on 07/06/2026
public boolean log_ident = false; // LoG ISOLATION test with the RT render (CuasRender): render with IDENTITY kernels (-CUAS-RT-RENDER-ID, aberrations skipped), pixel-LoG it (-ID-LOG-ORACLE), and render with PURE-LoG kernels (-LOG-ONLY). If -LOG-ONLY vs -ID-LOG-ORACLE residual persists, the fold/L0 is wrong; if it collapses to the wrap floor, the aberration-kernel interaction was responsible (Andrey 07/06/2026). // By Claude on 07/06/2026
......@@ -132,6 +134,10 @@ public class CuasRtParameters {
"DEBUG: save the rendered composite (TD-averaged, grid-transformed) scenes correlated against the virtual center (-POSE-RT-COMPOSITE, z=scenes) + the center reference render (-POSE-RT-CENTER-REF). Lean engine only.");
gd.addCheckbox ("Pose test stored poses (no LMA)", this.pose_stored, // By Claude on 07/04/2026
"DEBUG: measure/render every scene at its STORED pose, skip the LMA entirely - decouples the task/render/correlation chain from the solver. Lean engine only.");
gd.addCheckbox ("Pose lean uniform MB vector (v2)", this.pose_mb_uniform, // By Claude on 07/12/2026
"Lean v2 (CUAS): ONE motion-blur vector per scene shared by all tiles - constant-rpm flow is uniform up to the (small) roll component; wobble is second-degree. Same Jacobian-times-rates as the per-tile oracle, evaluated at the frame center. OFF = legacy per-tile getMotionBlur (A/B).");
gd.addNumericField("Pose lean fixed LMA cycles", this.pose_cycles, 0,7,"", // By Claude on 07/12/2026
"Lean v2 (CUAS): run EXACTLY this many measure<->solve cycles per scene (near-identical scenes need a constant count; fixed count = static GPU launch shape). A QC line flags scenes missing the legacy exit threshold. 0 = legacy convergence-based exit (imp.max_cycles + exit_change).");
gd.addCheckbox ("CUAS RT render (full product)", this.rend_test, // By Claude on 07/05/2026
"Full RT-chain render: raw jp4 -> conditioning -> virtual-grid render at borrowed stored poses/ERS; saves -CUAS-RT-RENDER [s00..s15+merged][scenes], comparable to the -CUAS-*-DBG oracle products. NON-exclusive: runs AFTER the pose stage when both are ON.");
gd.addCheckbox ("RT render LoG A/B (folded kernels)", this.log_test, // By Claude on 07/06/2026
......@@ -310,6 +316,8 @@ public class CuasRtParameters {
this.pose_corr_save = gd.getNextBoolean(); // By Claude on 07/04/2026
this.pose_img_save = gd.getNextBoolean(); // By Claude on 07/04/2026
this.pose_stored = gd.getNextBoolean(); // By Claude on 07/04/2026
this.pose_mb_uniform = gd.getNextBoolean(); // By Claude on 07/12/2026
this.pose_cycles = (int) gd.getNextNumber(); // By Claude on 07/12/2026
this.rend_test = gd.getNextBoolean(); // By Claude on 07/05/2026
this.log_test = gd.getNextBoolean(); // By Claude on 07/06/2026
this.log_ident = gd.getNextBoolean(); // By Claude on 07/06/2026
......@@ -408,6 +416,8 @@ public class CuasRtParameters {
properties.setProperty(prefix+"pose_corr_save", this.pose_corr_save+""); // boolean // By Claude on 07/04/2026
properties.setProperty(prefix+"pose_img_save", this.pose_img_save+""); // boolean // By Claude on 07/04/2026
properties.setProperty(prefix+"pose_stored", this.pose_stored+""); // boolean // By Claude on 07/04/2026
properties.setProperty(prefix+"pose_mb_uniform", this.pose_mb_uniform+"");// boolean // By Claude on 07/12/2026
properties.setProperty(prefix+"pose_cycles", this.pose_cycles+""); // int // By Claude on 07/12/2026
properties.setProperty(prefix+"rend_test", this.rend_test+""); // boolean // By Claude on 07/05/2026
properties.setProperty(prefix+"log_test", this.log_test+""); // boolean // By Claude on 07/06/2026
properties.setProperty(prefix+"log_ident", this.log_ident+""); // boolean // By Claude on 07/06/2026
......@@ -506,6 +516,8 @@ public class CuasRtParameters {
if (properties.getProperty(prefix+"pose_corr_save")!=null) this.pose_corr_save=Boolean.parseBoolean(properties.getProperty(prefix+"pose_corr_save")); // By Claude on 07/04/2026
if (properties.getProperty(prefix+"pose_img_save")!=null) this.pose_img_save=Boolean.parseBoolean(properties.getProperty(prefix+"pose_img_save")); // By Claude on 07/04/2026
if (properties.getProperty(prefix+"pose_stored")!=null) this.pose_stored=Boolean.parseBoolean(properties.getProperty(prefix+"pose_stored")); // By Claude on 07/04/2026
if (properties.getProperty(prefix+"pose_mb_uniform")!=null) this.pose_mb_uniform=Boolean.parseBoolean(properties.getProperty(prefix+"pose_mb_uniform")); // By Claude on 07/12/2026
if (properties.getProperty(prefix+"pose_cycles")!=null) this.pose_cycles=Integer.parseInt(properties.getProperty(prefix+"pose_cycles")); // By Claude on 07/12/2026
if (properties.getProperty(prefix+"rend_test")!=null) this.rend_test=Boolean.parseBoolean(properties.getProperty(prefix+"rend_test")); // By Claude on 07/05/2026
if (properties.getProperty(prefix+"log_test")!=null) this.log_test=Boolean.parseBoolean(properties.getProperty(prefix+"log_test")); // By Claude on 07/06/2026
if (properties.getProperty(prefix+"log_ident")!=null) this.log_ident=Boolean.parseBoolean(properties.getProperty(prefix+"log_ident")); // By Claude on 07/06/2026
......@@ -607,6 +619,8 @@ public class CuasRtParameters {
cp.pose_corr_save = this.pose_corr_save; // By Claude on 07/04/2026
cp.pose_img_save = this.pose_img_save; // By Claude on 07/04/2026
cp.pose_stored = this.pose_stored; // By Claude on 07/04/2026
cp.pose_mb_uniform = this.pose_mb_uniform; // By Claude on 07/12/2026
cp.pose_cycles = this.pose_cycles; // By Claude on 07/12/2026
cp.rend_test = this.rend_test; // By Claude on 07/05/2026
cp.log_test = this.log_test; // By Claude on 07/06/2026
cp.log_ident = this.log_ident; // By Claude on 07/06/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