Commit f32c1f18 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: 3-B rung B1 - lean path switches to the resident GPU task build

leanMeasure (uniform-MB lean path, JNA backend): the per-cycle CPU front
end - transformToScenePxPyD (full 5120-tile grid), setInterTasksMotionBlur
+ sort, and the two task H2D uploads - is replaced by ONE
gpuQuad.execPoseTaskUpdate() call: pose_task_update projects the
per-sequence template on-device from the Java-tracked measure pose and
rewrites both resident task slots in place; geometry + convert then run
on ACTIVATED slots with no upload (ImageDtt.interCorrTDResident). Per
cycle the only H2D left is the 12-float pose vector. Per scene:
setupERS + camera-block upload (rung C2 registers, shared with
prepare_resident) + skeleton slot re-upload (~120 KB, guards against
other pipeline stages using the task slots between scenes). The
per-scene uniform-MB 6-float descriptor replicates the exact
setInterTasksMotionBlur double crank math; margin/projection failures
become task=0 holes (missing peak -> conditioning abstains, D3 design).
Fallbacks: JCuda backend, pose_mb_uniform off, NaN/non-uniform MB
(degenerate uniformMotionBlur fallback) -> unchanged legacy CPU build.

- GpuQuad/GpuQuadJna/TpJna: execPoseTaskUpdate + activateTaskSlot
  (base returns false; JNA marshals rung-C2 nullable groups).
- IntersceneLmaFloat.buildTasks: serial float task-build oracle (the
  Java float clone of the kernel, worldFromPixel -> pixelFromWorld +
  descriptor/margin/hole packing).
- One-shot oracle at pose_lma_debug>=1: GPU stream vs the legacy Java
  DOUBLE build (txy-matched field compare, D3 gate <=1e-5 px print) and
  vs the float-serial clone; once-per-program path-active note.
- pose_corr export stays the explicit diagnostic exception: armed runs
  read the GPU-built pre-offset streams back and capture task words RAW
  (new iterTasksPre or511 overload - a |511-ed hole would diverge in a
  tol-0 replay).

mvn -DskipTests clean package: BUILD SUCCESS. Real-scene gate (D3,
ratified): poses < 0.003 px drift, aggregate RMS unchanged at print
precision, same QC count, 497/497 - Andrey's run.

CUDA side: tile_processor_gpu lwir16_2 @ ef36688 (kernel + JNA API +
two-tier tests, all regressions PASS incl. pose_corr @tol 0).
Design: internal handoffs/2026-07-16_3b_measure_chain_residency_design.md.
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent 6c10fe38
......@@ -28,6 +28,7 @@ import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; // B1 task-oracle txy matching // By Claude on 07/16/2026
import java.util.concurrent.locks.LockSupport;
import com.elphel.imagej.cameras.CLTParameters;
......@@ -594,15 +595,30 @@ public class CuasPoseRT {
long measureAccounted = 0L;
final GpuQuad gpuQuad = center_CLT.getGPUQuad();
final int num_pix = pXpYD_center.length;
// 1. scene grid at the current pose estimate (average-camera coordinates)
final double disparity_corr = clt_parameters.imp.disparity_corr + center_CLT.getDispInfinityRef(); // hoisted for B1 // By Claude on 07/16/2026
long profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
final double [][] scene_pXpYD = OpticalFlow.transformToScenePxPyD(
// 3-B rung B1 (design 2026-07-16): resident GPU task build - pose_task_update
// projects the per-sequence template on-device from the current pose and
// rewrites both native task slots in place; the full-grid transform and the
// CPU task build below become the fallback (JCuda backend, non-uniform MB,
// degenerate uniform fallback). By Claude on 07/16/2026.
TpTask [][] b1_tasks = null;
if ((mb_vectors != null) && clt_parameters.curt.pose_mb_uniform &&
gpuQuad.supportsResidentTaskSlots()) {
b1_tasks = gpuTaskBuild(clt_parameters, image_dtt, center_CLT, scene,
center_disparity, pXpYD_center, selection, scene_xyz, scene_atr, margin,
mb_vectors, mb_tau, mb_max_gain, disparity_corr, gpuQuad, debugLevel);
}
final boolean b1_built = (b1_tasks != null);
// 1. scene grid at the current pose estimate (average-camera coordinates) -
// LEGACY CPU front end only (B1 projects on-device) // By Claude on 07/16/2026
final double [][] scene_pXpYD = b1_built ? null : OpticalFlow.transformToScenePxPyD(
null, center_disparity, scene_xyz, scene_atr, scene, center_CLT);
if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.GRID_TRANSFORM, profileStart);
if (!b1_built && (rtProfile != null)) measureAccounted += rtProfile.addElapsed(RtPoseProfile.GRID_TRANSFORM, profileStart);
// B0 grid-transform attribution (one-shot): full-grid vs selection-only,
// multi-thread vs single-thread. churn ~= multi - single/threads;
// full-grid waste ~= full - selected-only. By Claude on 07/16/2026.
if ((clt_parameters.curt.pose_lma_debug >= 1) && !b0_grid_reported) {
if (!b1_built && (clt_parameters.curt.pose_lma_debug >= 1) && !b0_grid_reported) { // legacy-path diagnostic // By Claude on 07/16/2026
b0_grid_reported = true;
int n_def = 0;
for (double [] p : scene_pXpYD) if (p != null) n_def++;
......@@ -626,11 +642,37 @@ public class CuasPoseRT {
n_def, n_sel, (b0_t1-b0_t0)*1e-6, (b0_t2-b0_t1)*1e-6,
(b0_t3-b0_t2)*1e-6, (b0_t4-b0_t3)*1e-6, ImageDtt.THREADS_MAX));
}
final double disparity_corr = clt_parameters.imp.disparity_corr + center_CLT.getDispInfinityRef();
final TpTask [][] cons_tasks; // the convert task sets, hoisted for the consolidation OOB filter // By Claude on 07/12/2026
final boolean resident_tasks; // post-offset slots stay native on the JNA MB path // By Codex on 07/15/2026
profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
if (mb_vectors != null) {
if (b1_built) {
// 3-B rung B1: tasks are already GPU-built in the resident slots. The
// Java-side skeleton only carries count/txy/task_size for consolidation
// and the armed pose_corr export. By Claude on 07/16/2026.
cons_tasks = b1_tasks;
// armed pose_corr capture = the explicit diagnostic exception to
// residency: read the GPU-built PRE-OFFSET streams back and capture the
// task words RAW (no |511 re-OR - holes must stay 0 for a tol-0 replay).
if (PoseCorrExport.isArmed()) {
gpuQuad.updateTasks(cons_tasks[0], false, 0);
gpuQuad.updateTasks(cons_tasks[1], false, 1);
PoseCorrExport.iterTasksPre(clt_parameters, image_dtt, gpuQuad, scene, cons_tasks, false);
}
if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.TASK_BUILD_SORT, profileStart);
profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
image_dtt.interCorrTDResident(
1, // erase_clt_first: NaN outside the task set
clt_parameters.gpu_sigma_r,
clt_parameters.gpu_sigma_b,
clt_parameters.gpu_sigma_g,
clt_parameters.gpu_sigma_m,
scene.isMonochrome() ? 1.0 : clt_parameters.gpu_sigma_rb_corr,
clt_parameters.getGpuCorrSigma(scene.isMonochrome()),
clt_parameters.getGpuCorrLoGSigma(scene.isMonochrome()),
debugLevel);
if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.CONVERT_GEOMETRY, profileStart);
resident_tasks = true;
} else if (mb_vectors != null) {
// Motion-blur compensation (lean v2, the certified CuasRender chain): two task
// sets, double convert (positive SET + negative shifted SUBTRACT), erase-first
// so undefined tiles reach downstream (consolidation, renders) as NaN.
......@@ -932,6 +974,317 @@ public class CuasPoseRT {
private static boolean b0_grid_reported = false;
private static boolean b0_task_reported = false;
private static boolean b0_mb_reported = false;
// 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
/**
* Per-sequence state of the resident GPU task build (rung B1): the sorted task
* template (packed txy + reference centers per selected tile), the Java-side
* skeleton (carries count/txy/task_size for consolidation and the armed
* pose_corr export - the live task content is GPU-resident), and the
* configuration-register bookkeeping (which scene's camera block + skeleton
* are resident). Keyed by the per-sequence array/object identities.
* By Claude on 07/16/2026.
*/
private static final class GpuTaskTemplate {
final boolean [] selectionKey;
final double [][] centersKey;
final GpuQuad gpuKey;
final int count;
final int [] map; // packed txy: tx | (ty<<16), (tileY,tileX)-ascending
final float [] centers; // reference px,py,disparity per task
final TpTask [][] skeleton; // [2][count]
IntersceneLmaFloat.Camera refCamera = null; // captured once per sequence (after setupERS)
String uploadedScene = null; // scene whose camera block + skeleton slots are current
GpuTaskTemplate(boolean [] selectionKey, double [][] centersKey, GpuQuad gpuKey,
int count, int [] map, float [] centers, TpTask [][] skeleton) {
this.selectionKey = selectionKey;
this.centersKey = centersKey;
this.gpuKey = gpuKey;
this.count = count;
this.map = map;
this.centers = centers;
this.skeleton = skeleton;
}
}
private static GpuTaskTemplate gpu_task_template = null;
/**
* 3-B rung B1 (design 2026-07-16): build the correlation tasks ON-DEVICE.
* Verifies the MB vectors are uniform (the pose_mb_uniform broadcast), builds/
* caches the per-sequence template + skeleton, refreshes the per-scene
* configuration registers (scene camera block after setupERS + skeleton
* re-upload, guarding against other pipeline stages having used the task
* slots between scenes), computes the per-scene 6-float uniform-MB descriptor
* with the EXACT setInterTasksMotionBlur double math, and launches
* pose_task_update to rewrite both resident task slots from the current pose.
* Zero per-cycle H2D beyond the 12-float pose vector.
*
* @return the Java-side skeleton TpTask[2][count] to use as cons_tasks, or
* null to fall back to the legacy CPU build (non-uniform/NaN MB).
* By Claude on 07/16/2026.
*/
private static TpTask [][] gpuTaskBuild(
final CLTParameters clt_parameters,
final ImageDtt image_dtt,
final QuadCLT center_CLT,
final QuadCLT scene,
final double [] center_disparity,
final double [][] pXpYD_center,
final boolean [] selection,
final double [] scene_xyz,
final double [] scene_atr,
final int margin,
final double [][] mb_vectors,
final double mb_tau,
final double mb_max_gain,
final double disparity_corr,
final GpuQuad gpuQuad,
final int debugLevel) {
// 1. uniform-MB verification: the descriptor is per-scene ONLY if every
// selected tile carries the same (broadcast) vector; the degenerate
// per-tile fallback of uniformMotionBlur() goes back to the CPU build.
double mb0 = Double.NaN, mb1 = Double.NaN;
for (int nTile = 0; nTile < selection.length; nTile++) {
if (!selection[nTile] || (pXpYD_center[nTile] == null)) continue;
final double vx = mb_vectors[0][nTile];
final double vy = mb_vectors[1][nTile];
if (Double.isNaN(vx) || Double.isNaN(vy)) return null; // NaN would drop tiles per-tile
if (Double.isNaN(mb0)) { mb0 = vx; mb1 = vy; }
else if ((vx != mb0) || (vy != mb1)) return null; // not uniform
}
if (Double.isNaN(mb0)) return null; // no selected tiles with centers
// 2. per-sequence template + skeleton (cached by array/object identity)
GpuTaskTemplate tpl = gpu_task_template;
if ((tpl == null) || (tpl.selectionKey != selection) ||
(tpl.centersKey != pXpYD_center) || (tpl.gpuKey != gpuQuad)) {
final int sensor_w = scene.getGeometryCorrection().getSensorWH()[0];
final int tilesX = sensor_w / GPUTileProcessor.DTT_SIZE;
final int task_code = (1 << GPUTileProcessor.TASK_CORR_EN) | (1 << GPUTileProcessor.TASK_INTER_EN);
int count = 0;
for (int nTile = 0; nTile < selection.length; nTile++) {
if (selection[nTile] && (pXpYD_center[nTile] != null) &&
!Double.isNaN(pXpYD_center[nTile][2])) count++;
}
if (count == 0) return null;
final int [] map = new int [count];
final float [] centers = new float [3 * count];
final TpTask [][] skeleton = new TpTask [2][count];
int i = 0;
for (int nTile = 0; nTile < selection.length; nTile++) { // nTile-ascending == (tileY,tileX) sort (3-A3)
if (!selection[nTile] || (pXpYD_center[nTile] == null) ||
Double.isNaN(pXpYD_center[nTile][2])) continue;
final int tileX = nTile % tilesX;
final int tileY = nTile / tilesX;
map[i] = tileX | (tileY << 16);
for (int k = 0; k < 3; k++) centers[3 * i + k] = (float) pXpYD_center[nTile][k];
for (int set = 0; set < 2; set++) {
skeleton[set][i] = new TpTask(scene.getNumSensors(), tileX, tileY);
skeleton[set][i].task = task_code;
}
i++;
}
center_CLT.getErsCorrection().setupERS(); // reference line tables (transformToScenePxPyD parity)
tpl = new GpuTaskTemplate(selection, pXpYD_center, gpuQuad,
count, map, centers, skeleton);
tpl.refCamera = IntersceneLmaFloat.Camera.capture(center_CLT.getErsCorrection());
gpu_task_template = tpl;
}
// 3. per-scene configuration registers: camera block (after setupERS) +
// skeleton slots (re-uploaded per scene: other pipeline stages may have
// replaced the task slots between scenes; ~120 KB, once per scene)
final boolean new_scene = !scene.getImageName().equals(tpl.uploadedScene);
final boolean new_template = (tpl.uploadedScene == null);
IntersceneLmaFloat.Camera scene_camera = null;
if (new_scene) {
scene.getErsCorrection().setupERS(); // per-scene ERS line tables (transformToScenePxPyD parity)
scene_camera = IntersceneLmaFloat.Camera.capture(scene.getErsCorrection());
gpuQuad.setTasks(tpl.skeleton[0], false, false, 0); // sizes slots + |511 task words
gpuQuad.setTasks(tpl.skeleton[1], false, false, 1);
}
// 4. per-scene uniform-MB descriptor - the EXACT setInterTasksMotionBlur
// double math (accumulate=false, global_scale=1.0 as the leanMeasure
// convenience overload passes them), cast to float at the end like
// setCenterXY/setScale would
final int task_code_or = ((1 << GPUTileProcessor.TASK_CORR_EN) | (1 << GPUTileProcessor.TASK_INTER_EN)) | 511;
final double min_sub = 1e-12;
final double min_len = 0.001; // pix
double dxm, dym, dxs, dys, gain, gain_sub;
int task_code_sub;
double dx = mb0;
double dy = mb1;
double mb_len = Math.sqrt(dx * dx + dy * dy);
if (mb_len < min_len) { // no-MB: sub set disabled (task 0 -> |511 = 511)
dxm = 0.0; dym = 0.0; dxs = 0.0; dys = 0.0;
gain = 1.0;
gain_sub = -min_sub;
task_code_sub = 511;
} else {
dx /= mb_len; // unit vector
dy /= mb_len;
mb_len *= mb_tau; // now in pixels
final double mb_len_scale = -Math.log(1.0 - 1.0 / mb_max_gain);
double mb_offs = 1.0;
final double min_offs = mb_len_scale * mb_len;
if (mb_offs < min_offs) {
mb_offs = min_offs;
}
dx *= mb_offs;
dy *= mb_offs;
final double exp_offs = Math.exp(-mb_offs / mb_len);
final double rel_cent = mb_len * (1.0 - (mb_offs / mb_len + 1.0) * exp_offs) / (mb_offs * (1 - exp_offs));
dxm = -rel_cent * dx; // centerXY(main) = projected - rel_cent*d
dym = -rel_cent * dy;
dxs = dxm + (1 - rel_cent) * dx; // centerXY(sub) = main + (1-rel_cent)*d (Java order preserved)
dys = dym + (1 - rel_cent) * dy;
gain = 1.0 / (1.0 - exp_offs);
gain_sub = -gain * exp_offs;
if (gain_sub > -min_sub) {
gain_sub = -min_sub;
}
task_code_sub = task_code_or;
}
// 5. margin gate bounds - GPU port-coords mode Java bounds (no WOI tops)
final int [] wh = scene.getGeometryCorrection().getSensorWH();
final float min_px = margin;
final float max_px = wh[0] - 1 - margin;
final float min_py = margin;
final float max_py = wh[1] - 1 - margin;
// 6. launch: template on rebuild, camera block per scene, pose every call
// (48 B - the Java-tracked measure pose; B3/DP chain will drop it)
final float [] ref_xyz = IntersceneLmaFloat.toFloat3(ZERO3);
final float [] ref_atr = IntersceneLmaFloat.toFloat3(ZERO3);
final float [] scene_xyzf = IntersceneLmaFloat.toFloat3(scene_xyz);
final float [] scene_atrf = IntersceneLmaFloat.toFloat3(scene_atr);
final boolean built = gpuQuad.execPoseTaskUpdate(
new_template ? tpl.refCamera : null,
scene_camera, // null = keep resident (same scene)
ref_xyz, ref_atr, scene_xyzf, scene_atrf,
new_template ? tpl.map : null,
new_template ? tpl.centers : null,
tpl.count,
(float) disparity_corr,
min_px, max_px, min_py, max_py,
task_code_or, task_code_sub,
(float) dxm, (float) dym, (float) dxs, (float) dys,
(float) gain, (float) gain_sub);
if (!built) return null; // base/JCuda backend - legacy CPU build
if (new_scene) tpl.uploadedScene = scene.getImageName();
if (!task_build_path_reported) {
task_build_path_reported = true;
System.out.println("CuasPoseRT: resident GPU task build active (pose_task_update: "+
tpl.count+" template tasks, per-cycle H2D = 12-float pose only; "+
"grid transform + CPU task build + task upload eliminated on the lean path)");
}
// 7. one-shot oracle (pose_lma_debug>=1): build the tasks BOTH legacy ways
// and compare - GPU vs the Java double build (the D3-ratified <=1e-5 px
// field compare) and GPU vs the serial float clone (buildTasks)
if ((clt_parameters.curt.pose_lma_debug >= 1) && !b1_task_oracle_reported) {
b1_task_oracle_reported = true;
b1TaskOracle(clt_parameters, center_CLT, scene, center_disparity, pXpYD_center,
selection, scene_xyz, scene_atr, margin, mb_vectors, mb_tau, mb_max_gain,
disparity_corr, gpuQuad, tpl,
min_px, max_px, min_py, max_py, task_code_or, task_code_sub,
(float) dxm, (float) dym, (float) dxs, (float) dys,
(float) gain, (float) gain_sub);
}
return tpl.skeleton;
}
/**
* One-shot B1 task-build oracle (pose_lma_debug>=1, first GPU-built cycle):
* reads the GPU-built pre-offset streams back and compares them against
* (a) the legacy Java DOUBLE build (transformToScenePxPyD +
* setInterTasksMotionBlur, txy-matched - the D3-ratified <=1e-5 px field
* compare) and (b) the serial FLOAT clone (IntersceneLmaFloat.buildTasks,
* differences are float-scheduling only). Prints one line.
* By Claude on 07/16/2026.
*/
private static void b1TaskOracle(
final CLTParameters clt_parameters,
final QuadCLT center_CLT,
final QuadCLT scene,
final double [] center_disparity,
final double [][] pXpYD_center,
final boolean [] selection,
final double [] scene_xyz,
final double [] scene_atr,
final int margin,
final double [][] mb_vectors,
final double mb_tau,
final double mb_max_gain,
final double disparity_corr,
final GpuQuad gpuQuad,
final GpuTaskTemplate tpl,
final float min_px, final float max_px, final float min_py, final float max_py,
final int task_code_main, final int task_code_sub,
final float dxm, final float dym, final float dxs, final float dys,
final float scale_main, final float scale_sub) {
// GPU-built pre-offset streams (diagnostic readback)
final TpTask [] gpu0 = new TpTask [tpl.count];
final TpTask [] gpu1 = new TpTask [tpl.count];
gpuQuad.updateTasks(gpu0, false, 0);
gpuQuad.updateTasks(gpu1, false, 1);
// legacy Java DOUBLE build at the same pose
final double [][] scene_pXpYD = OpticalFlow.transformToScenePxPyD(
null, center_disparity, scene_xyz, scene_atr, scene, center_CLT);
final TpTask [][] cpu = GpuQuad.setInterTasksMotionBlur(
scene.getNumSensors(), scene.getGeometryCorrection().getSensorWH()[0],
false, scene_pXpYD, selection, mb_tau, mb_max_gain, mb_vectors,
scene.getGeometryCorrection(), disparity_corr, margin, null,
ImageDtt.THREADS_MAX);
double max_d_center = 0.0, max_d_disp = 0.0;
int matched = 0, gpu_only = 0, cpu_only = 0;
if ((cpu != null) && (cpu[0] != null)) {
final HashMap<Integer,Integer> cpu_index = new HashMap<>();
for (int j = 0; j < cpu[0].length; j++) {
if (cpu[0][j] != null) cpu_index.put(cpu[0][j].getTileX() | (cpu[0][j].getTileY() << 16), j);
}
for (int i = 0; i < tpl.count; i++) {
final boolean gpu_present = (gpu0[i].task != 0);
final Integer j = cpu_index.get(tpl.map[i]);
if (gpu_present && (j != null)) {
matched++;
max_d_center = Math.max(max_d_center, Math.abs(gpu0[i].getCenterXY()[0] - cpu[0][j].getCenterXY()[0]));
max_d_center = Math.max(max_d_center, Math.abs(gpu0[i].getCenterXY()[1] - cpu[0][j].getCenterXY()[1]));
max_d_center = Math.max(max_d_center, Math.abs(gpu1[i].getCenterXY()[0] - cpu[1][j].getCenterXY()[0]));
max_d_center = Math.max(max_d_center, Math.abs(gpu1[i].getCenterXY()[1] - cpu[1][j].getCenterXY()[1]));
max_d_disp = Math.max(max_d_disp, Math.abs(gpu0[i].target_disparity - cpu[0][j].target_disparity));
} else if (gpu_present) gpu_only++;
else if (j != null) cpu_only++;
}
}
// serial FLOAT clone of the kernel
final float [][] oracle = IntersceneLmaFloat.buildTasks(
tpl.refCamera, IntersceneLmaFloat.Camera.capture(scene.getErsCorrection()),
IntersceneLmaFloat.toFloat3(ZERO3), IntersceneLmaFloat.toFloat3(ZERO3),
IntersceneLmaFloat.toFloat3(scene_xyz), IntersceneLmaFloat.toFloat3(scene_atr),
tpl.map, tpl.centers, TpTask.getSize(scene.getNumSensors()),
(float) disparity_corr, min_px, max_px, min_py, max_py,
task_code_main, task_code_sub, dxm, dym, dxs, dys, scale_main, scale_sub);
final int task_size = TpTask.getSize(scene.getNumSensors());
double max_d_float = 0.0;
int hole_mismatch = 0;
for (int i = 0; i < tpl.count; i++) {
final int base = i * task_size;
final boolean gpu_present = (gpu0[i].task != 0);
final boolean oracle_present = (Float.floatToRawIntBits(oracle[0][base]) != 0);
if (gpu_present != oracle_present) { hole_mismatch++; continue; }
if (!gpu_present) continue;
max_d_float = Math.max(max_d_float, Math.abs(gpu0[i].getCenterXY()[0] - oracle[0][base + 3]));
max_d_float = Math.max(max_d_float, Math.abs(gpu0[i].getCenterXY()[1] - oracle[0][base + 4]));
max_d_float = Math.max(max_d_float, Math.abs(gpu1[i].getCenterXY()[0] - oracle[1][base + 3]));
max_d_float = Math.max(max_d_float, Math.abs(gpu1[i].getCenterXY()[1] - oracle[1][base + 4]));
max_d_float = Math.max(max_d_float, Math.abs(gpu0[i].target_disparity - oracle[0][base + 2]));
}
System.out.println(String.format(
"CuasPoseRT B1 task oracle (one-shot): %d template tasks; GPU vs Java-double:"+
" matched=%d max|dCenterXY|=%.3g px max|dDisp|=%.3g (gate <=1e-5), gpu-only=%d cpu-only=%d;"+
" GPU vs float-serial: max|d|=%.3g px, set-mismatch=%d",
tpl.count, matched, max_d_center, max_d_disp, gpu_only, cpu_only,
max_d_float, hole_mismatch));
}
private static int GPUTileProcessorDttSize() {
return com.elphel.imagej.gpu.GPUTileProcessor.DTT_SIZE;
......
......@@ -2984,6 +2984,51 @@ public class GpuQuad{ // quad camera description
return null;
}
/**
* 3-B rung B1 (design 2026-07-16): resident task build - pose_task_update runs
* the C1-validated projection chain over the per-sequence task template and
* rewrites the 6-float task headers of BOTH resident slots in place (slot 0 =
* MB main, slot 1 = MB partner; margin/projection failures become task=0
* holes). Nullable groups follow the rung-C2 configuration-register contract.
* Task slots must hold the per-sequence skeleton (setTasks to slots 0 and 1)
* with exactly num_tasks entries. Base/JCuda returns false so the Java task
* build remains the fallback. By Claude on 07/16/2026.
*/
public boolean execPoseTaskUpdate(
IntersceneLmaFloat.Camera reference, // null = keep resident
IntersceneLmaFloat.Camera scene, // null = keep resident
float [] reference_xyz, // pose group: all four null = device current set
float [] reference_atr,
float [] scene_xyz,
float [] scene_atr,
int [] task_map, // null = keep resident template
float [] task_centers, // null = keep resident template
int num_tasks,
float disparity_corr,
float min_px,
float max_px,
float min_py,
float max_py,
int task_code_main, // pre-encoded (|511) task words
int task_code_sub,
float dx_main, // uniform-MB descriptor (offsets from the projected point)
float dy_main,
float dx_sub,
float dy_sub,
float scale_main, // pre-encoded scale fields (TpTask.setScale semantics)
float scale_sub) {
return false;
}
/**
* 3-B rung B1: make a resident task slot the live kernel stream WITHOUT an
* H2D upload (after execPoseTaskUpdate rewrote it in place). Base/JCuda
* returns false (no resident slots). By Claude on 07/16/2026.
*/
public boolean activateTaskSlot(int slot) {
return false;
}
public void execRBGA(
double [] color_weights,
boolean is_lwir,
......
......@@ -634,6 +634,83 @@ public class GpuQuadJna extends GpuQuad {
outY, outWeights, outEigen);
}
// ---- 3-B rung B1: resident task build (design 2026-07-16) ---- // By Claude on 07/16/2026
@Override public boolean execPoseTaskUpdate(
final IntersceneLmaFloat.Camera reference, // null = keep resident
final IntersceneLmaFloat.Camera scene, // null = keep resident
final float[] reference_xyz, // all four null = keep device current set
final float[] reference_atr,
final float[] scene_xyz,
final float[] scene_atr,
final int[] task_map, // null = keep resident template
final float[] task_centers, // null = keep resident template
final int num_tasks,
final float disparity_corr,
final float min_px,
final float max_px,
final float min_py,
final float max_py,
final int task_code_main,
final int task_code_sub,
final float dx_main,
final float dy_main,
final float dx_sub,
final float dy_sub,
final float scale_main,
final float scale_sub) {
if ((num_tasks <= 0) ||
((task_map != null) != (task_centers != null)) ||
((task_map != null) && ((task_map.length != num_tasks) ||
(task_centers.length != 3 * num_tasks)))) {
throw new IllegalArgumentException("GpuQuadJna.execPoseTaskUpdate: inconsistent inputs");
}
final boolean have_pose = (scene_xyz != null) || (scene_atr != null) ||
(reference_xyz != null) || (reference_atr != null);
float[] poseVectors = null;
if (have_pose) {
poseVectors = new float[12];
copyPoseVector3(reference_xyz, poseVectors, 0);
copyPoseVector3(reference_atr, poseVectors, 3);
copyPoseVector3(scene_xyz, poseVectors, 6);
copyPoseVector3(scene_atr, poseVectors, 9);
}
final int rc = lib.tp_proc_exec_pose_task_update(
proc,
(reference != null) ? poseCameraMeta(reference) : null,
(reference != null) ? reference.radial : null,
(reference != null) ? reference.rByRDist : null,
(reference != null) ? reference.rByRDist.length : 0,
(reference != null) ? reference.ers : null,
(reference != null) ? reference.ers.length : 0,
(scene != null) ? poseCameraMeta(scene) : null,
(scene != null) ? scene.radial : null,
(scene != null) ? scene.rByRDist : null,
(scene != null) ? scene.rByRDist.length : 0,
(scene != null) ? scene.ers : null,
(scene != null) ? scene.ers.length : 0,
poseVectors, task_map, task_centers,
num_tasks, getNumSensors(),
disparity_corr,
min_px, max_px, min_py, max_py,
task_code_main, task_code_sub,
dx_main, dy_main, dx_sub, dy_sub,
scale_main, scale_sub);
if (rc != 0) {
throw new IllegalStateException("GpuQuadJna.execPoseTaskUpdate rc="+rc+": "+
lib.tp_last_error());
}
return true;
}
@Override public boolean activateTaskSlot(final int slot) { // By Claude on 07/16/2026
final int rc = lib.tp_proc_activate_tasks_slot(proc, slot);
if (rc != 0) {
throw new IllegalStateException("GpuQuadJna.activateTaskSlot("+slot+") rc="+rc+": "+
lib.tp_last_error());
}
return true;
}
// ---- resident float pose preparation/reduction continuation (roadmap rung 3-A4e) ----
@Override public IntersceneLmaFloat.ResidentLmaStepResult execPoseLmaResidentStep(
final float lambda,
......
......@@ -89,6 +89,9 @@ public interface TpJna extends Library {
/** Explicit persistent task slots (0/1); the selected slot becomes the live kernel stream. */
int tp_proc_set_tasks_slot(Pointer proc, float[] ftasks, int ntiles, int totalFloats, int slot);
int tp_proc_get_tasks_slot(Pointer proc, float[] out, int totalFloats, int slot);
/** 3-B rung B1: make a resident slot the live kernel task stream with NO upload
* (after pose_task_update rewrote it in place). By Claude on 07/16/2026. */
int tp_proc_activate_tasks_slot(Pointer proc, int slot);
int tp_proc_exec_geometry(Pointer proc, int uniformGrid);
/** convert_direct: ref_scene (0/1 -> clt vs clt_ref), erase_clt (-1/0/1), no_kernels (0/1). */
int tp_proc_exec_convert_direct(Pointer proc, int refScene, int eraseClt, int noKernels);
......@@ -182,6 +185,26 @@ public interface TpJna extends Library {
int light, // rung C3: 1 = frozen conditioning, fresh y only // By Claude on 07/17/2026
float[] prepResult, int capture,
float[] outY, float[] outWeights, float[] outEigen);
/** 3-B rung B1 (design 2026-07-16): resident task build. pose_task_update runs the
* C1-validated projection chain over the per-sequence task template and rewrites
* the 6-float task headers of BOTH resident slots in place. Nullable groups follow
* the rung-C2 configuration-register contract: camera blocks (shared with
* prepare_resident), taskMap/taskCenters (per-sequence template), poseVectors
* (null = device-authoritative current set). Task slots must hold the
* per-sequence skeleton (set_tasks_slot) with exactly numTasks entries.
* By Claude on 07/16/2026. */
int tp_proc_exec_pose_task_update(Pointer proc,
float[] refMeta, float[] refRadial,
float[] refRbr, int refRbrLen, float[] refErs, int refErsLen,
float[] sceneMeta, float[] sceneRadial,
float[] sceneRbr, int sceneRbrLen, float[] sceneErs, int sceneErsLen,
float[] poseVectors, int[] taskMap, float[] taskCenters,
int numTasks, int numCams,
float disparityCorr,
float minPx, float maxPx, float minPy, float maxPy,
int taskCodeMain, int taskCodeSub,
float dxMain, float dyMain, float dxSub, float dySub,
float scaleMain, float scaleSub);
/** Deterministic double normal-equation products for the lean pose LMA.
* jt is parameter-major [numParams][numValues]; out is row-major H followed by b.
* Damping and the small solve remain in Java. 0 on success. */
......
......@@ -184,6 +184,24 @@ public class PoseCorrExport {
GpuQuad gpuQuad,
QuadCLT scene,
TpTask [][] tp_tasks) {
iterTasksPre(clt_parameters, image_dtt, gpuQuad, scene, tp_tasks, true);
}
/**
* 3-B rung B1 overload: or511=false captures the task words RAW. On the
* GPU-task-build path the pre streams are read back from the resident slots
* (updateTasks D2H) - the |511 OR already happened when the per-sequence
* skeleton was uploaded, and task=0 HOLES must stay 0 so a tol-0 replay
* uploads exactly what the production GPU held (a |511-ed hole would get
* converted by index_direct and diverge). By Claude on 07/16/2026.
*/
public static synchronized void iterTasksPre(
CLTParameters clt_parameters,
ImageDtt image_dtt,
GpuQuad gpuQuad,
QuadCLT scene,
TpTask [][] tp_tasks,
boolean or511) {
if (!armed || done) return;
if (bound_scene == null) { // bind to this scene
if ((images_scene == null) || !images_scene.equals(scene.getImageName())) {
......@@ -200,8 +218,8 @@ public class PoseCorrExport {
} else if (!bound_scene.equals(scene.getImageName())) {
return; // another scene (bound scene failed mid-fit and was reported) - keep what we have
}
tasks0_pre.add(flattenTasks(tp_tasks[0], true));
tasks1_pre.add((tp_tasks.length > 1) && (tp_tasks[1] != null) ? flattenTasks(tp_tasks[1], true) : null);
tasks0_pre.add(flattenTasks(tp_tasks[0], or511));
tasks1_pre.add((tp_tasks.length > 1) && (tp_tasks[1] != null) ? flattenTasks(tp_tasks[1], or511) : null);
}
/**
......
......@@ -1449,9 +1449,66 @@ public class ImageDtt extends ImageDttCPU {
}
return;
}
/**
* 3-B rung B1 (design 2026-07-16): the interCorrTDMotionBlur convert flow for
* tasks that are ALREADY resident - pose_task_update rewrote both native task
* slots on-device, so there is no per-cycle task H2D: each slot is only
* ACTIVATED (no upload), then geometry + convert run exactly as in
* interCorrTDMotionBlur (slot 0 = MB main, erase per erase_clt_first, SET;
* slot 1 = MB partner, no erase, SUBTRACT). LPF constant setup is kept
* identical to the legacy flow. Convert-only (the leanMeasure caller
* correlates through the consolidation bridge). Requires a backend with
* resident task slots (GpuQuad.activateTaskSlot). By Claude on 07/16/2026.
*/
public void interCorrTDResident(
final int erase_clt_first,
final double gpu_sigma_r,
final double gpu_sigma_b,
final double gpu_sigma_g,
final double gpu_sigma_m,
final double gpu_sigma_rb_corr,
final double gpu_sigma_corr,
final double gpu_sigma_log_corr,
final int globalDebugLevel)
{
final float [][] lpf_rgb = new float[][] {
floatGetCltLpfFd(gpu_sigma_r),
floatGetCltLpfFd(gpu_sigma_b),
floatGetCltLpfFd(gpu_sigma_g),
floatGetCltLpfFd(gpu_sigma_m)
};
gpuQuad.setLpfRbg( // constants memory - same for all cameras
lpf_rgb,
globalDebugLevel > 2);
final float [] lpf_flat = floatGetCltLpfFd(gpu_sigma_corr);
gpuQuad.setLpfCorr(
"lpf_corr",
lpf_flat,
globalDebugLevel > 2);
final float [] lpf_rb_flat = floatGetCltLpfFd(gpu_sigma_rb_corr);
gpuQuad.setLpfCorr(
"lpf_rb_corr",
lpf_rb_flat,
globalDebugLevel > 2);
final float [] log_flat = floatGetCltHpfFd(gpu_sigma_log_corr);
gpuQuad.setLpfCorr(
"LoG_corr",
log_flat,
globalDebugLevel > 2);
// primary set (MB main): activate resident slot 0, offsets, convert-SET
if (!gpuQuad.activateTaskSlot(0)) {
throw new IllegalStateException("interCorrTDResident: backend has no resident task slots");
}
gpuQuad.execSetTilesOffsets(false); // NON-UNIFORM grid (centerXY from pose_task_update)
gpuQuad.execConvertDirect(false, null, erase_clt_first);
// secondary set (MB partner): activate resident slot 1, offsets, convert-SUBTRACT
gpuQuad.activateTaskSlot(1);
gpuQuad.execSetTilesOffsets(false);
gpuQuad.execConvertDirect(false, null, -1);
}
/**
* Convert reference scene to FD and save result in extra GPU array for the future interscene correlation
* Geometry correction and images will come from gpuQuad instance -
......
......@@ -793,6 +793,96 @@ public final class IntersceneLmaFloat {
return new Result(numTiles, fx, jt, valid, numValid);
}
/**
* 3-B rung B1 (design 2026-07-16): serial float task-build oracle - the Java
* float clone of the pose_task_update CUDA kernel. Runs the SAME float
* projection chain as {@link #getFxDerivs} (worldFromPixel -> pixelFromWorld)
* over the per-sequence task template and packs the 6-float task headers of
* both MB sets exactly as the kernel does: centerXY = projected point + the
* uniform-MB descriptor offsets, target_disparity = scene disparity +
* disparity_corr, pre-encoded task/scale words, task=0 HOLES (all header
* floats zero except txy) for failed projections and margin-gate rejects.
* Differences vs the CUDA stream are float-scheduling only (FMA contraction);
* the one-shot leanMeasure compare prints the max field deltas.
* @return float[2][num_tasks*task_size] header-filled task streams
* (set 0 = MB main, set 1 = MB partner); xy/disp_dist tails zero.
* By Claude on 07/16/2026.
*/
public static float [][] buildTasks(
final Camera reference,
final Camera scene,
final float [] referenceXyz,
final float [] referenceAtr,
final float [] sceneXyz,
final float [] sceneAtr,
final int [] taskMap, // packed txy: tx | (ty<<16)
final float [] taskCenters, // reference px,py,disparity per task
final int taskSize,
final float disparityCorr,
final float minPx,
final float maxPx,
final float minPy,
final float maxPy,
final int taskCodeMain,
final int taskCodeSub,
final float dxMain,
final float dyMain,
final float dxSub,
final float dySub,
final float scaleMain,
final float scaleSub) {
if ((taskMap == null) || (taskCenters == null) ||
(taskCenters.length != 3 * taskMap.length) || (taskSize < 6)) {
throw new IllegalArgumentException("buildTasks: inconsistent template");
}
checkVector3(referenceXyz, "referenceXyz");
checkVector3(referenceAtr, "referenceAtr");
checkVector3(sceneXyz, "sceneXyz");
checkVector3(sceneAtr, "sceneAtr");
final int numTasks = taskMap.length;
final float [] refInverse = rotationMatrices(referenceAtr, true);
final float [] sceneDirect = rotationMatrices(sceneAtr, false);
final float [][] streams = new float [2][numTasks * taskSize];
for (int task = 0; task < numTasks; task++) {
final int base = task * taskSize;
streams[0][base + 1] = Float.intBitsToFloat(taskMap[task]); // txy in both sets
streams[1][base + 1] = streams[0][base + 1];
final int ci = 3 * task;
final float px = taskCenters[ci];
final float py = taskCenters[ci + 1];
final float disparity = taskCenters[ci + 2];
if (!finite(px) || !finite(py) || !finite(disparity)) continue; // hole
final boolean infinity = disparity < INFINITY_DISPARITY;
final WorldDerivs referenceWorld = worldFromPixel(
reference, px, py, disparity, infinity, referenceXyz, refInverse);
if (referenceWorld == null) continue; // hole
final float [] scenePixel = pixelFromWorld(
scene, referenceWorld.world, infinity, sceneXyz, sceneDirect);
if (scenePixel == null) continue; // hole
final float cxMain = scenePixel[0] + dxMain;
final float cyMain = scenePixel[1] + dyMain;
final float cxSub = scenePixel[0] + dxSub;
final float cySub = scenePixel[1] + dySub;
if ((cxMain < minPx) || (cxMain > maxPx) || (cyMain < minPy) || (cyMain > maxPy) ||
(cxSub < minPx) || (cxSub > maxPx) || (cySub < minPy) || (cySub > maxPy)) {
continue; // margin-gate hole
}
final float targetDisparity = scenePixel[2] + disparityCorr;
if (!finite(targetDisparity)) continue; // hole
streams[0][base] = Float.intBitsToFloat(taskCodeMain);
streams[1][base] = Float.intBitsToFloat(taskCodeSub);
streams[0][base + 2] = targetDisparity;
streams[1][base + 2] = targetDisparity;
streams[0][base + 3] = cxMain;
streams[0][base + 4] = cyMain;
streams[1][base + 3] = cxSub;
streams[1][base + 4] = cySub;
streams[0][base + 5] = scaleMain;
streams[1][base + 5] = scaleSub;
}
return streams;
}
/** Compare a float result with the exact legacy double geometry call. */
public static Comparison compareToDouble(
final Result result,
......
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