Commit 53ba6631 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: 3-B rung B2 - MB descriptor via serial one-point getMotionBlurPoint

Replace the per-scene one-point getMotionBlur call (~5-6 ms/scene, B0-attributed
to IntersceneLma prepareLMA scaffolding + two ~THREADS_MAX thread-array spawns)
with OpticalFlow.getMotionBlurPoint: the identical double chain (setupERS x2,
getInterRotDeriveMatrices, getDPxSceneDParameters, same-order rate dot product)
run serially at the representative point - bit-for-bit the same result at ~us
cost. uniformMotionBlur broadcasts it unchanged; degenerate return still falls
back to legacy getMotionBlur (per-tile), so non-uniform behavior is untouched.

One-shot oracle at pose_lma_debug>=1 (replaces the closed B0 MB warm-repeat):
times the fast path, evaluates legacy getMotionBlur at the SAME representative
point, prints max|d| (expected 0 exactly). IntersceneLma.INFINITY_DISPARITY
becomes the single source for the 0.01 infinity threshold.

Gate (design doc B2): oracle max|d| = 0, real-scene poses within the B1 bounds,
MB setup ~6 -> <0.5 ms/scene. Needs Andrey's run to close.
Co-authored-by: 's avatarClaude <claude@elphel.com>
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent f32c1f18
......@@ -973,7 +973,7 @@ public class CuasPoseRT {
// for that one call only. By Claude on 07/16/2026.
private static boolean b0_grid_reported = false;
private static boolean b0_task_reported = false;
private static boolean b0_mb_reported = false;
private static boolean b2_mb_reported = false; // B2 MB-descriptor one-shot oracle (was b0_mb_reported) // By Claude on 07/16/2026
// 3-B rung B1 (design 2026-07-16): resident GPU task build. By Claude on 07/16/2026.
private static boolean task_build_path_reported = false; // once-per-program "path active" note
private static boolean b1_task_oracle_reported = false; // one-shot GPU vs Java-double vs float-serial compare
......@@ -1362,6 +1362,11 @@ public class CuasPoseRT {
* 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.
* 3-B rung B2 (07/16/2026): the one-point evaluation now goes through the serial
* OpticalFlow.getMotionBlurPoint (identical double math, no IntersceneLma/thread
* scaffolding - the whole ~5.5 ms/scene B0-attributed MB-setup overhead); the
* legacy getMotionBlur stays as the pose_lma_debug one-shot oracle (caller) and
* as the per-tile fallback on degenerate return. By Claude on 07/16/2026.
* @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).
*/
......@@ -1374,61 +1379,73 @@ public class CuasPoseRT {
final double [] camera_xyz_dt,
final double [] camera_atr_dt,
final int debugLevel) {
final int [] wh = center_CLT.getGeometryCorrection().getSensorWH();
// 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;
}
}
}
final double [] best = mbRepresentativePoint(center_CLT, pXpYD_center);
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 = {{best[0], best[1], best[2]}};
final double [][] mb1 = OpticalFlow.getMotionBlur(
final double [] mb1 = OpticalFlow.getMotionBlurPoint(
center_CLT, // QuadCLT ref_scene
scene, // QuadCLT scene
one_point, // double [][] ref_pXpYD - the single representative point
best, // 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])) {
// 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.
if (mb1 == null) {
// self-explaining fallback: the point PASSED the mirrored gates, so the null
// came from the projection math (getDPxSceneDParameters). Report everything
// that was tried. By Claude on 07/12/2026.
final int [] wh = center_CLT.getGeometryCorrection().getSensorWH();
final double rr = Math.hypot(best[0] - 0.5 * wh[0], best[1] - 0.5 * wh[1]);
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),
" xyz_dt=(%.3g, %.3g, %.3g) - silent failure inside getMotionBlurPoint",
best[0], best[1], best[2], rr,
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];
Arrays.fill(mb_vectors[0], mb1[0][0]);
Arrays.fill(mb_vectors[1], mb1[1][0]);
Arrays.fill(mb_vectors[0], mb1[0]);
Arrays.fill(mb_vectors[1], mb1[1]);
return mb_vectors;
}
/**
* The uniform-MB representative point: 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). Extracted from
* uniformMotionBlur so the B2 one-shot oracle evaluates the legacy
* getMotionBlur at the SAME point. By Claude on 07/16/2026 (scan from 07/12).
* @return the winning pXpYD row (by reference), or null if none qualifies
*/
private static double [] mbRepresentativePoint(
final QuadCLT center_CLT,
final double [][] pXpYD_center) {
final int [] wh = center_CLT.getGeometryCorrection().getSensorWH();
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;
}
}
}
return best;
}
/**
* 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
......@@ -2238,22 +2255,37 @@ public class CuasPoseRT {
"CuasPoseRT: scene %s uniform MB vector = (%.4f, %.4f) px/s",
ts_name, mb_vectors_scene[0][0], mb_vectors_scene[1][0]));
}
// B0 MB-setup attribution (one-shot): warm repeat of the whole
// uniformMotionBlur (representative scan + one-point getMotionBlur +
// 2 full-grid fills) - a large warm time means ERS/thread overhead
// inside getMotionBlur, not the fills. By Claude on 07/16/2026.
if ((clt_parameters.curt.pose_lma_debug >= 1) && !b0_mb_reported &&
// B2 MB-descriptor oracle (one-shot): evaluate the LEGACY one-point
// getMotionBlur (IntersceneLma prepareLMA path) at the SAME
// representative point and compare against the fast serial
// getMotionBlurPoint the production path just used. Identical double
// math - the gate expectation is max|d| == 0 exactly; also times both
// (design gate: MB setup ~6 -> <0.5 ms/scene). By Claude on 07/16/2026.
if ((clt_parameters.curt.pose_lma_debug >= 1) && !b2_mb_reported &&
(mb_vectors_scene != null)) {
b0_mb_reported = true;
final long b0_t0 = System.nanoTime();
b2_mb_reported = true;
final long b2_t0 = System.nanoTime();
uniformMotionBlur(center_CLT, quadCLTs[nscene], pXpYD_center,
predicted[0], predicted[1], dxyzatr_dt[0], dxyzatr_dt[1],
debugLevel - 2);
final long b0_t1 = System.nanoTime();
debugLevel - 2); // warm repeat of the FAST path (B2 timing gate)
final long b2_t1 = System.nanoTime();
final double [] rep_point = mbRepresentativePoint(center_CLT, pXpYD_center);
final double [][] one_point = {{rep_point[0], rep_point[1], rep_point[2]}};
final double [][] mb_legacy = OpticalFlow.getMotionBlur(
center_CLT, quadCLTs[nscene], one_point,
predicted[0], predicted[1], dxyzatr_dt[0], dxyzatr_dt[1],
0, debugLevel - 2);
final long b2_t2 = System.nanoTime();
final double d_max = ((mb_legacy == null) ||
Double.isNaN(mb_legacy[0][0]) || Double.isNaN(mb_legacy[1][0])) ?
Double.NaN :
Math.max(Math.abs(mb_vectors_scene[0][0] - mb_legacy[0][0]),
Math.abs(mb_vectors_scene[1][0] - mb_legacy[1][0]));
System.out.println(String.format(
"CuasPoseRT B0 MB-setup (one-shot, warm repeat): %.3f ms"+
" (grid fill alone is ~%d doubles - trivial)",
(b0_t1-b0_t0)*1e-6, 2*pXpYD_center.length));
"CuasPoseRT B2 MB descriptor oracle (one-shot): fast=%.3f ms,"+
" legacy getMotionBlur=%.3f ms; max|d|=%.3g px/s (gate: 0 exactly -"+
" same double chain; any diff = a real divergence)",
(b2_t1-b2_t0)*1e-6, (b2_t2-b2_t1)*1e-6, d_max));
}
}
if (mb_vectors_scene == null) { // legacy per-tile path (pose_mb_uniform OFF or degenerate fallback) // By Claude on 07/12/2026
......
......@@ -63,7 +63,8 @@ public class IntersceneLma {
private double [] parameters_pull = null; // for regularization - error is proportional to difference between
// current vector and parameters_pull
private double [][] macrotile_centers = null; // (will be used to pull for regularization)
private double infinity_disparity = 0.01; // treat lower as infinity
public static final double INFINITY_DISPARITY = 0.01; // single source for the MB one-point path // By Claude on 07/16/2026
private double infinity_disparity = INFINITY_DISPARITY; // treat lower as infinity
private int num_samples = 0;
///////////////////////////////////////////////////////////
// thread_invariant is needed for LMA, otherwise even with the same parameter vector RMS may be slightly different
......
......@@ -17746,16 +17746,100 @@ java.lang.NullPointerException
return new double [][] {camera_xyz0, camera_atr0};
}
/**
* 3-B rung B2 (design 2026-07-16): motion-blur vector at ONE point, serially.
* Numerically the SAME chain getMotionBlur() drives for each tile - setupERS x2,
* getInterRotDeriveMatrices, getDPxSceneDParameters, dot with the rates in the
* same parameter order - with the IntersceneLma scaffolding (prepareLMA vector
* plumbing, two ~THREADS_MAX thread-array spawns) stripped: B0 attributed the
* ~6.9 ms/scene of the one-point getMotionBlur to exactly that overhead. The
* double math is identical, so the result matches getMotionBlur bit-for-bit;
* getMotionBlur stays as the one-shot oracle and the per-tile fallback.
* By Claude on 07/16/2026, from Andrey's approved 3-B design.
* @return {mb_x, mb_y} px/s at the point, or null when degenerate (out of the
* getMotionBlur gates or a null/NaN projection - caller falls back)
*/
public static double [] getMotionBlurPoint(
QuadCLT ref_scene,
QuadCLT scene, // can be the same as ref_scene
double [] ref_pXpYD, // ONE point {pX, pY, disparity}
double [] camera_xyz,
double [] camera_atr,
double [] camera_xyz_dt,
double [] camera_atr_dt,
int debug_level)
{
if (ref_pXpYD == null) {
return null;
}
// the same gates the per-tile loop of getMotionBlur() applies
final int [] sensor_wh = ref_scene.getGeometryCorrection().getSensorWH();
if ( Double.isNaN(ref_pXpYD[0]) || (ref_pXpYD[0] < 0) || (ref_pXpYD[0] >= sensor_wh[0]) ||
Double.isNaN(ref_pXpYD[1]) || (ref_pXpYD[1] < 0) || (ref_pXpYD[1] >= sensor_wh[1]) ||
Double.isNaN(ref_pXpYD[2]) || (ref_pXpYD[2] < -0.5) || (ref_pXpYD[2] >= 100.0)) {
return null;
}
final ErsCorrection ers_ref = ref_scene.getErsCorrection();
final ErsCorrection ers_scene = scene.getErsCorrection();
// getFxDerivs() re-writes the instances' ERS rates from the parameter vector
// prepareLMA() read from the same instances - a no-op here; only the line-table
// rebuild has an effect (same call order as getFxDerivs)
ers_scene.setupERS();
ers_ref.setupERS();
final double [] reference_xyz = new double[3]; // reference is 0 (prepareLMA MB overload)
final double [] reference_atr = new double[3];
final Matrix [] reference_matrices_inverse = ErsCorrection.getInterRotDeriveMatrices(
reference_atr, true);
final Matrix [] scene_matrices_inverse = ErsCorrection.getInterRotDeriveMatrices(
camera_atr, true);
final Matrix scene_rot_matrix = ErsCorrection.getInterRotDeriveMatrices(
camera_atr, false)[0];
final boolean is_infinity = ref_pXpYD[2] < IntersceneLma.INFINITY_DISPARITY;
final double [][] deriv_params = ers_ref.getDPxSceneDParameters(
ers_scene, // ErsCorrection ers_scene,
true, // boolean correctDistortions,
is_infinity, // boolean is_infinity,
ref_pXpYD, // double [] pXpYD_reference,
reference_xyz, // double [] reference_xyz,
camera_xyz, // double [] scene_xyz,
reference_matrices_inverse, // Matrix [] reference_matrices_inverse,
scene_matrices_inverse, // Matrix [] scene_matrices_inverse,
scene_rot_matrix, // Matrix scene_rot_matrix,
debug_level); // int debug_level
if (deriv_params == null) {
return null;
}
final int [] par_indices = new int[] {
ErsCorrection.DP_DSAZ,
ErsCorrection.DP_DSTL,
ErsCorrection.DP_DSRL,
ErsCorrection.DP_DSX,
ErsCorrection.DP_DSY,
ErsCorrection.DP_DSZ};
final double [] camera_dt = new double[] {
camera_atr_dt[0], camera_atr_dt[1], camera_atr_dt[2],
camera_xyz_dt[0], camera_xyz_dt[1], camera_xyz_dt[2]};
final double [] mb = new double[2]; // same accumulation order as getMotionBlur()
for (int i = 0; i < par_indices.length; i++) {
mb[0] += camera_dt[i] * deriv_params[par_indices[i] + 1][0];
mb[1] += camera_dt[i] * deriv_params[par_indices[i] + 1][1];
}
if (Double.isNaN(mb[0]) || Double.isNaN(mb[1])) {
return null;
}
return mb;
}
/**
* Get per-tile motion blur vector
* @param ref_scene reference scene
* @param scene current scene (may be the same as reference)
* @param ref_pXpYD per-tile pX, pY, disparity for reference scene (some may be nulls)
* @param ref_pXpYD per-tile pX, pY, disparity for reference scene (some may be nulls)
* @param camera_xyz camera x,y,z relative to the reference
* @param camera_atr camera azimuth, tilt, roll relative to the reference
* @param camera_xyz_dt camera linear velocities: x', y', z'
* @param camera_atr_dt camera angular velocities: azimuth', tilt', roll'
* @param shrink_gaps < 0 fill all gaps, 0 - do not fill gaps, >0 expand using growTiles, do not fill farther.
* @param shrink_gaps < 0 fill all gaps, 0 - do not fill gaps, >0 expand using growTiles, do not fill farther.
* @param debug_level debug level
* @return per-tile array of [2][tiles] of dx/dt, dy/dt, some may be NaN
*/
......
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