Commit 923f3785 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: fix uniform-MB representative point + per-cycle dATR convergence trace

- uniformMotionBlur: representative = defined reference tile nearest the frame
  center passing getMotionBlur's own gates (mirrored locally; the synthetic
  averaged frame-center point failed SILENTLY on every scene in the 07/12 run).
  Degenerate fallback now prints the tried point + rates (self-explaining).
- leanFitScene: per-scene 'CuasPoseRT cycles: ... dATR/cycle = ...' trace line
  (fixed AND legacy modes) - the convergence-trajectory data for the open
  'why does azimuth need so many cycles' investigation (3 cycles: az RMS
  0.5209; 6 cycles: 0.1805; legacy baseline: 0.1355 px).
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent 6731866f
......@@ -453,11 +453,13 @@ public class CuasPoseRT {
* 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.
* OpticalFlow.getMotionBlur at ONE representative point - the DEFINED reference tile
* nearest the frame center (a real tile provably valid in the per-tile path; a
* synthetic frame-center point with averaged disparity silently failed getMotionBlur's
* gates in the 07/12 run) - 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).
......@@ -472,13 +474,31 @@ public class CuasPoseRT {
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++;
// representative = the defined tile nearest the frame center that passes
// getMotionBlur's own gates (in-frame; disparity within its local
// min_disparity=-0.5 .. max_disparity=100.0) - mirrored here because a
// gate-rejected tile fails SILENTLY (NaN result). By Claude on 07/12/2026.
final double cx = 0.5 * wh[0], cy = 0.5 * wh[1];
double best_r2 = Double.MAX_VALUE;
double [] best = null;
for (double [] p : pXpYD_center) {
if ((p != null) &&
!Double.isNaN(p[0]) && (p[0] >= 0) && (p[0] < wh[0]) &&
!Double.isNaN(p[1]) && (p[1] >= 0) && (p[1] < wh[1]) &&
!Double.isNaN(p[2]) && (p[2] >= -0.5) && (p[2] < 100.0)) {
final double r2 = (p[0]-cx)*(p[0]-cx) + (p[1]-cy)*(p[1]-cy);
if (r2 < best_r2) {
best_r2 = r2;
best = p;
}
}
}
if (best == null) {
System.out.println("uniformMotionBlur(): no defined in-gate reference tile at all ("+
pXpYD_center.length+" tiles) - degenerate");
return null;
}
final double [][] one_point = {{0.5 * wh[0], 0.5 * wh[1], (num_d > 0)? (sum_d / num_d) : 0.0}};
final double [][] one_point = {{best[0], best[1], best[2]}};
final double [][] mb1 = OpticalFlow.getMotionBlur(
center_CLT, // QuadCLT ref_scene
scene, // QuadCLT scene
......@@ -490,6 +510,16 @@ public class CuasPoseRT {
0, // int shrink_gaps - nothing to fill
debugLevel); // int debug_level
if ((mb1 == null) || Double.isNaN(mb1[0][0]) || Double.isNaN(mb1[1][0])) {
// self-explaining fallback: the point PASSED the mirrored gates, so the NaN
// came from getMotionBlur's remaining silent path (getDPxSceneDParameters /
// projection math). Report everything that was tried. By Claude on 07/12/2026.
System.out.println(String.format(
"uniformMotionBlur(): degenerate at representative point pXpYD=(%.2f, %.2f, %.4f)"+
" (r=%.1f px from frame center, passed gates), atr_dt=(%.3g, %.3g, %.3g),"+
" xyz_dt=(%.3g, %.3g, %.3g) - silent failure inside getMotionBlur",
one_point[0][0], one_point[0][1], one_point[0][2], Math.sqrt(best_r2),
camera_atr_dt[0], camera_atr_dt[1], camera_atr_dt[2],
camera_xyz_dt[0], camera_xyz_dt[1], camera_xyz_dt[2]));
return null;
}
final double [][] mb_vectors = new double [2][pXpYD_center.length];
......@@ -541,6 +571,8 @@ public class CuasPoseRT {
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
final double [] cycle_datr = new double [max_cycles]; // per-cycle ATR-change trace (convergence trajectory) // By Claude on 07/12/2026
int ncyc = 0; // cycles actually executed // By Claude on 07/12/2026
int nlma = 0;
for (; nlma < max_cycles; nlma++) {
cm = leanMeasure(
......@@ -586,6 +618,8 @@ public class CuasPoseRT {
final double [] diffs_atr = intersceneLma.getV3Diff(ErsCorrection.DP_DSAZ);
final double [] diffs_xyz = intersceneLma.getV3Diff(ErsCorrection.DP_DSX);
last_diff_atr = diffs_atr[0]; // By Claude on 07/12/2026
cycle_datr[nlma] = diffs_atr[0]; // By Claude on 07/12/2026
ncyc = nlma + 1; // 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)) {
......@@ -601,6 +635,17 @@ public class CuasPoseRT {
fixed_cycles+" fixed cycles: last ATR change "+last_diff_atr+
" >= exit_change_atr "+clt_parameters.imp.exit_change_atr);
}
// Per-cycle convergence trajectory (the "why does it take that many cycles"
// investigation, Andrey 07/12/2026): geometric decay = healthy damped iteration,
// a stall/plateau = something re-anchoring each cycle (pull term, lambda warm-up,
// prediction). One compact line per scene, both fixed and legacy modes.
// By Claude on 07/12/2026.
if (debugLevel > -4) {
final StringBuilder tsb = new StringBuilder();
for (int i = 0; i < ncyc; i++) tsb.append(String.format(" %.3g", cycle_datr[i]));
System.out.println("CuasPoseRT cycles: scene "+scene.getImageName()+
" ("+ncyc+" of max "+max_cycles+") dATR/cycle ="+tsb);
}
if (lma_rms != null) {
final double [] last_rms = intersceneLma.getLastRms();
lma_rms[0] = last_rms[0];
......
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