Commit a6a8ad3d authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: lean pose DEBUG observability: -POSE-RT-CORR2D save + all-tiles bypass

Per Andrey's debug approach (same as used on the oracle): render the actual
scene-vs-virtual-center correlations in the pixel domain and save them as a
scene sequence. Observability only - NO processing changes, the roll-bias
symptom is preserved.

- curt.pose_corr_save (checkbox "Pose test save 2D correlations"): save
  -POSE-RT-CORR2D.tiff - z=scenes (aligned with -POSE-RT-HYPER incl. NaN
  slices for failed/coasted scenes), tile grid of 16x16-pix cells, last LMA
  cycle per scene, ImageDtt.corr_partial_dbg convention as CuasMotion CORR2D.
  Lean engine only (the oracle does not expose its correlation tiles).
- curt.pose_full (checkbox "Pose test ALL tiles (ignore calibration)"):
  temporarily drop the 150-tile filter and use all strength-selected (~1074)
  tiles; -POSE-RT-TILE-CALIB is neither read NOR written so a debug run
  never pollutes the persistent tile calibration.
- leanMeasure/leanFitScene: optional corr_pd_out holder (last-cycle PD tiles).

Context: lean run v013-LEAN-01 confirmed the v*tau signature on az/tilt
(implied tau = 8.4/8.0 ms = mb_tau) but showed a constant +0.52 mrad roll
bias; peaks ~50% wider than oracle on common tiles (suspect: consolidation
averages /16 while oracle sums pairs -> effective fat-zero 16x larger).
These debug outputs are for inspecting exactly that before changing the
processing.

Verified: mvn -DskipTests clean package OK.
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent 630a91b7
......@@ -252,6 +252,8 @@ public class CuasPoseRT {
* peak masking (input is FPN-subtracted by conditioning), no moving-object filter,
* min_confidence not applied (tiles are pre-calibrated).
* By Claude on 07/05/2026, from Andrey's design.
* @param corr_pd_out null or length-1 holder - filled with the pixel-domain
* correlation tiles ([tile][15*15], dense grid index) for debug rendering
* @return [3][num_pix][] {centers pXpYD, vector_XYS {dx,dy,strength}, eigen
* {e0x,e0y,l0,l1}} - the coord_motion contract of interCorrPair - or null
*/
......@@ -266,6 +268,7 @@ public class CuasPoseRT {
final double [] scene_xyz,
final double [] scene_atr,
final int margin,
final double [][][] corr_pd_out, // By Claude on 07/04/2026
final int debugLevel) {
final GpuQuad gpuQuad = center_CLT.getGPUQuad();
final int num_pix = pXpYD_center.length;
......@@ -329,6 +332,7 @@ public class CuasPoseRT {
System.out.println("leanMeasure(): convertTDtoPD returned null for scene "+scene.getImageName());
return null;
}
if (corr_pd_out != null) corr_pd_out[0] = corr_pd; // debug rendering (-POSE-RT-CORR2D) // By Claude on 07/04/2026
// 6. peak + eigen per tile (the GPU argmax+eigen kernel's Java oracle)
final int corr_width = 2 * GPUTileProcessorDttSize() - 1;
final double [][] centers = new double [num_pix][];
......@@ -388,6 +392,7 @@ public class CuasPoseRT {
final int margin,
final double [] lma_rms, // out
final double [][][] coord_motion_rslt, // out [3][][]
final double [][][] corr_pd_out, // null or len-1: last-cycle PD correlations // By Claude on 07/04/2026
final int debugLevel) {
final IntersceneLma intersceneLma = new IntersceneLma(
clt_parameters.ilp.ilma_thread_invariant,
......@@ -399,7 +404,7 @@ public class CuasPoseRT {
cm = leanMeasure(
clt_parameters, image_dtt, center_CLT, scene,
center_disparity, pXpYD_center, selection,
scene_xyzatr0[0], scene_xyzatr0[1], margin, debugLevel);
scene_xyzatr0[0], scene_xyzatr0[1], margin, corr_pd_out, debugLevel);
if (cm == null) return null;
final double [][] eigen_masked = clt_parameters.imp.eig_xy_lma ? null : cm[2];
intersceneLma.prepareLMA(
......@@ -530,11 +535,17 @@ public class CuasPoseRT {
// rank-N budget) and AND with the strength mask - a filtered run. Otherwise run FULL
// (all strength-selected tiles) and generate the calibration at the end.
boolean full_selection = true; // the tile calibration is (re)saved only from a full-selection run
ImagePlus imp_max = clt_parameters.curt.pose_recalc ? null :
// pose_full (DEBUG): all strength-selected tiles, calibration neither read nor written
// (a measurement-debug run must not pollute the persistent tile calibration). By Claude on 07/04/2026.
ImagePlus imp_max = (clt_parameters.curt.pose_recalc || clt_parameters.curt.pose_full) ? null :
center_CLT.readImagePlusFromModelDirectory(TILE_CALIB_SUFFIX);
if ((imp_max == null) && !clt_parameters.curt.pose_recalc) { // legacy name fallback
if ((imp_max == null) && !clt_parameters.curt.pose_recalc && !clt_parameters.curt.pose_full) { // legacy name fallback
imp_max = center_CLT.readImagePlusFromModelDirectory(TILE_CALIB_SUFFIX_OLD);
}
if (clt_parameters.curt.pose_full) {
System.out.println("CuasPoseRT.testPoseSequence(): pose_full ON (DEBUG) - using ALL "+num_reliable+
" strength-selected tiles, tile calibration NOT read/written");
}
if (imp_max != null) {
final float [] fmax = (float []) imp_max.getProcessor().getPixels();
final boolean [] filt = deriveSelection(
......@@ -634,6 +645,20 @@ public class CuasPoseRT {
"TD-average(16) x virtual-center single conj-multiply. NOTE: no MB compensation "+
"in lean v1 - compare against the NOMB baseline");
}
// DEBUG (pose_corr_save): render per-scene pixel-domain correlations vs the virtual
// center (-POSE-RT-CORR2D, z=scenes, last LMA cycle, 16x16 cells per tile) - the same
// visual debugging used on the oracle. Lean only: the oracle engine does not expose
// its correlation tiles. By Claude on 07/04/2026, per Andrey's debug approach.
final boolean corr_save = clt_parameters.curt.pose_corr_save && clt_parameters.curt.pose_lean;
if (clt_parameters.curt.pose_corr_save && !clt_parameters.curt.pose_lean) {
System.out.println("CuasPoseRT.testPoseSequence(): pose_corr_save is only implemented "+
"for the LEAN engine (pose_lean) - ignored");
}
final int corr_size = 2 * com.elphel.imagej.gpu.GPUTileProcessor.DTT_SIZE - 1; // 15
final ImageStack stack_corr = corr_save ? new ImageStack(tilesX*(corr_size+1), tilesY*(corr_size+1)) : null;
final double [][][] corr_pd_holder = corr_save ? new double [1][][] : null;
final float [] corr_nan_slice = corr_save ? new float [tilesX*(corr_size+1)*tilesY*(corr_size+1)] : null;
if (corr_nan_slice != null) Arrays.fill(corr_nan_slice, Float.NaN);
final double [][][] fitted = new double [quadCLTs.length][][];
final int [] fail_reason = new int [1];
final StringBuffer sb = new StringBuffer();
......@@ -682,6 +707,7 @@ public class CuasPoseRT {
for (float [] c : nan_comps) Arrays.fill(c, Float.NaN);
hyper_scenes.add(nan_comps);
hyper_ts.add(ts_name);
if (stack_corr != null) stack_corr.addSlice(ts_name, corr_nan_slice.clone()); // keep z aligned // By Claude on 07/04/2026
sb.append(nscene+", "+ts_name);
for (int j = 0; j < 12; j++) sb.append(", NaN");
sb.append(", NaN, 0.0, 0, -1\n");
......@@ -710,6 +736,7 @@ public class CuasPoseRT {
}
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
double [][] pose;
if (clt_parameters.curt.pose_lean) {
pose = leanFitScene(
......@@ -726,6 +753,7 @@ public class CuasPoseRT {
margin, // int margin,
lma_rms, // double [] lma_rms (out),
coord_motion_rslt, // double [][][] coord_motion_rslt (out),
corr_pd_holder, // double [][][] corr_pd_out (last-cycle PD correlations) // By Claude on 07/04/2026
debugLevel - 2); // int debug_level
if (pose == null) fail_reason[0] = 1; // measurement/LMA
} else {
......@@ -799,6 +827,22 @@ public class CuasPoseRT {
hyper_scenes.add(comps);
hyper_ts.add(ts_name);
}
// DEBUG: render this scene's PD correlations into the -POSE-RT-CORR2D stack // By Claude on 07/04/2026
if (stack_corr != null) {
if (corr_pd_holder[0] != null) {
final double [][] dbg_corr = ImageDtt.corr_partial_dbg(
new double [][][] {corr_pd_holder[0]}, // [layer][tile][15*15]
tilesX, // tilesX
corr_size, // corr_size (15)
clt_parameters.corr_border_contrast, // border_contrast
debugLevel); // globalDebugLevel
final float [] fslice = new float [dbg_corr[0].length];
for (int i = 0; i < fslice.length; i++) fslice[i] = (float) dbg_corr[0][i];
stack_corr.addSlice(ts_name, fslice);
} else {
stack_corr.addSlice(ts_name, corr_nan_slice.clone());
}
}
// CSV row + comparison stats
final double [] st = (stored[nscene] != null) ? stored[nscene][1] : null;
sb.append(nscene+", "+ts_name);
......@@ -861,7 +905,7 @@ public class CuasPoseRT {
// not selected/measured. Saved only from a FULL-selection run so a filtered run
// never shrinks the calibration coverage. The derived boolean selection is also
// saved (-POSE-RT-RELIABLE-FILT) for visualization.
if (full_selection && !hyper_scenes.isEmpty()) {
if (full_selection && !clt_parameters.curt.pose_full && !hyper_scenes.isEmpty()) { // pose_full: never write the calibration // By Claude on 07/04/2026
final float [] fmax = new float [num_pix];
Arrays.fill(fmax, Float.NaN);
for (int i = 0; i < num_pix; i++) if (reliable_ref[i]) {
......@@ -890,6 +934,12 @@ public class CuasPoseRT {
center_CLT.saveImagePlusInModelDirectory(null, new ImagePlus(
center_CLT.getImageName()+"-POSE-RT-RELIABLE-FILT", new FloatProcessor(tilesX, tilesY, fkeep)));
}
// -POSE-RT-CORR2D (DEBUG): per-scene pixel-domain correlations vs the virtual center // By Claude on 07/04/2026
if ((stack_corr != null) && (stack_corr.getSize() > 0)) {
final ImagePlus imp_corr = new ImagePlus(center_CLT.getImageName()+"-POSE-RT-CORR2D", stack_corr);
imp_corr.getProcessor().resetMinAndMax();
center_CLT.saveImagePlusInModelDirectory(null, imp_corr); // title as filename
}
// -POSE-RT-HYPER: z = scenes (top slider), t = components (bottom slider) -
// same layout as renderSceneSequence make_hyper (component is the outer slice order)
if (!hyper_scenes.isEmpty()) {
......
......@@ -24,6 +24,8 @@ public class CuasRtParameters {
public boolean pose_recalc = false; // force a FULL-selection pose run that regenerates the -POSE-RT-TILE-CALIB calibration even if it exists. Default (false): automatic FPN-style reuse - if -POSE-RT-TILE-CALIB exists, derive the selection from it (NMAD gate + rank-N) and use it; if not, run full and generate it. // By Claude on 07/04/2026
public boolean pose_raw = false; // phase A2 ingest: per scene read RAW /jp4/, condition with the current calibration (CuasConditioning: rowcol + photometric + FPN) and FORCE-upload straight to the GPU, bypassing the QuadCLT prepared image_data (which carries the old broken Photogrammetric Calibration). Judge by own dstored quality, not agreement with phase A. // By Claude on 07/05/2026
public boolean pose_lean = false; // phase B measurement engine: TD-average the 16 sensors (CuasTD, CPU bridge) then ONE conj-multiply vs the virtual-center TD -> FZ-normalize -> PD -> argmax+eigen (getMaxXYCmEig) -> 3-angle LMA. All existing GPU kernels. v1: NO motion-blur compensation - compare vs the NOMB baseline. // By Claude on 07/05/2026
public boolean pose_full = false; // DEBUG: use ALL strength-selected tiles (~1074) - the -POSE-RT-TILE-CALIB calibration is neither read nor written (temporary bypass of the 150-tile filter for measurement debugging). // By Claude on 07/04/2026
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 double psf_radius = 1.0; // sensor PSF radius for LoG pre-filter
public double n_sigma = 4.0; // cutoff LoG kernel array, number of sigmas
public int pyramid = 7; // temporal pyramid levels
......@@ -97,6 +99,10 @@ public class CuasRtParameters {
"Per scene: read RAW /jp4/, condition with the CURRENT calibration (rowcol+photometric+FPN, CuasConditioning) and force-upload straight to the GPU, bypassing the prepared image_data (old broken Photogrammetric Calibration).");
gd.addCheckbox ("Pose test lean correlation (B)", this.pose_lean, // By Claude on 07/05/2026
"Phase B measurement: TD-average the 16 sensors, then ONE conj-multiply vs the virtual-center TD -> FZ-normalize -> PD -> argmax+eigen -> 3-angle LMA. v1 has NO motion-blur compensation (compare vs the NOMB baseline).");
gd.addCheckbox ("Pose test ALL tiles (ignore calibration)", this.pose_full, // By Claude on 07/04/2026
"DEBUG: use all strength-selected tiles (~1074); the tile-selection calibration is neither read nor written.");
gd.addCheckbox ("Pose test save 2D correlations", this.pose_corr_save, // By Claude on 07/04/2026
"DEBUG: save per-scene pixel-domain 2D correlations vs the virtual center (-POSE-RT-CORR2D, z=scenes, last LMA cycle). Lean engine only.");
gd.addMessage("=== LoG prefilter ===");
gd.addNumericField("Optical PSF radius", this.psf_radius, 6,8,"pix",
......@@ -228,6 +234,8 @@ public class CuasRtParameters {
this.pose_recalc = gd.getNextBoolean(); // By Claude on 07/04/2026
this.pose_raw = gd.getNextBoolean(); // By Claude on 07/05/2026
this.pose_lean = gd.getNextBoolean(); // By Claude on 07/05/2026
this.pose_full = gd.getNextBoolean(); // By Claude on 07/04/2026
this.pose_corr_save = gd.getNextBoolean(); // By Claude on 07/04/2026
this.psf_radius = gd.getNextNumber();
this.n_sigma = gd.getNextNumber();
......@@ -303,6 +311,8 @@ public class CuasRtParameters {
properties.setProperty(prefix+"pose_recalc", this.pose_recalc+""); // boolean // By Claude on 07/04/2026
properties.setProperty(prefix+"pose_raw", this.pose_raw+""); // boolean // By Claude on 07/05/2026
properties.setProperty(prefix+"pose_lean", this.pose_lean+""); // boolean // By Claude on 07/05/2026
properties.setProperty(prefix+"pose_full", this.pose_full+""); // boolean // By Claude on 07/04/2026
properties.setProperty(prefix+"pose_corr_save", this.pose_corr_save+""); // boolean // By Claude on 07/04/2026
properties.setProperty(prefix+"psf_radius", this.psf_radius+""); // double
properties.setProperty(prefix+"n_sigma", this.n_sigma+""); // double
......@@ -378,6 +388,8 @@ public class CuasRtParameters {
if (properties.getProperty(prefix+"pose_recalc")!=null) this.pose_recalc=Boolean.parseBoolean(properties.getProperty(prefix+"pose_recalc")); // By Claude on 07/04/2026
if (properties.getProperty(prefix+"pose_raw")!=null) this.pose_raw=Boolean.parseBoolean(properties.getProperty(prefix+"pose_raw")); // By Claude on 07/05/2026
if (properties.getProperty(prefix+"pose_lean")!=null) this.pose_lean=Boolean.parseBoolean(properties.getProperty(prefix+"pose_lean")); // By Claude on 07/05/2026
if (properties.getProperty(prefix+"pose_full")!=null) this.pose_full=Boolean.parseBoolean(properties.getProperty(prefix+"pose_full")); // By Claude on 07/04/2026
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+"psf_radius")!=null) this.psf_radius=Double.parseDouble(properties.getProperty(prefix+"psf_radius"));
if (properties.getProperty(prefix+"n_sigma")!=null) this.n_sigma=Double.parseDouble(properties.getProperty(prefix+"n_sigma"));
......@@ -456,6 +468,8 @@ public class CuasRtParameters {
cp.pose_recalc = this.pose_recalc;
cp.pose_raw = this.pose_raw;
cp.pose_lean = this.pose_lean;
cp.pose_full = this.pose_full; // By Claude on 07/04/2026
cp.pose_corr_save = this.pose_corr_save; // By Claude on 07/04/2026
cp.psf_radius = this.psf_radius;
cp.n_sigma = this.n_sigma;
cp.pyramid = this.pyramid;
......
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