Commit 8fb0eea4 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: 3-A3 interior tile gate (curt.pose_shrink) + deterministic lean task order

Tile selection (deriveSelection): new interior gate per Andrey's 07/13 ruling -
usable = finite calibration (measured in EVERY scene) shrunk by curt.pose_shrink
tiles (TileNeibs 8-dir, default 2; array-border ring cleared explicitly). Excludes
the partial-OOB band (soft margin 12 px = 1.5 tiles) so every selected tile keeps
all 16 sensors interior at every scene pose - enables the fixed per-sequence GPU
tile list with the consolidation OOB pass bypassed by host policy.

leanMeasure: sort task streams by (tileY, tileX) - setInterTasks* fill from worker
threads, so tile order was nondeterministic run-to-run; the fixed selection + sort
= the static task-stream contract for the pose_corr checkpoint (rung 1).

New dialog knob (CUAS RT tab): "Pose tile selection shrink (tiles)"
(curt.pose_shrink, corr-xml _curt_pose_shrink).
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent f4be977b
......@@ -41,6 +41,7 @@ import com.elphel.imagej.tileprocessor.OpticalFlow;
import com.elphel.imagej.tileprocessor.QuadCLT;
import com.elphel.imagej.tileprocessor.QuadCLTCPU;
import com.elphel.imagej.tileprocessor.TDCorrTile;
import com.elphel.imagej.tileprocessor.TileNeibs; // By Claude on 07/13/2026
import com.elphel.imagej.tileprocessor.TwoQuadCLT;
import ij.ImagePlus;
......@@ -121,14 +122,38 @@ public class CuasPoseRT {
public static boolean [] deriveSelection(
final float [] fmax,
final int tilesX,
final double k_nmad, // <=0 - skip the gate
final int num_tiles, // <=0 - no cap
final double k_nmad, // <=0 - skip the gate
final int num_tiles, // <=0 - no cap
final int shrink_tiles,// interior gate: shrink the usable mask by this many tiles (8-dir), 0 - off // By Claude on 07/13/2026
final int debugLevel) {
final boolean [] sel = new boolean [fmax.length];
final int tilesY = fmax.length / tilesX;
// Interior gate (3-A3, Andrey's ruling 07/13/2026): usable = finite (measured in
// EVERY scene - NaN-in-any-scene tiles are already non-finite here) SHRUNK by
// shrink_tiles. Tiles adjacent to the missing/edge band may still carry partial
// 16-sensor OOB masks (consolidation soft margin 12 px = 1.5 tiles); excluding
// the band guarantees all-16-interior tiles at every scene pose, so the GPU pose
// chain can use a FIXED per-sequence tile list with the OOB pass bypassed.
// The array-border ring is cleared explicitly: TileNeibs shrink cannot reach in
// from outside the tile array (normally moot - margin-dropped edge tiles are NaN).
final boolean [] usable = new boolean [fmax.length];
for (int i = 0; i < fmax.length; i++) {
usable[i] = !Float.isNaN(fmax[i]) && !Float.isInfinite(fmax[i]);
}
if (shrink_tiles > 0) {
for (int i = 0; i < fmax.length; i++) {
final int y = i / tilesX, x = i % tilesX;
if ((y < shrink_tiles) || (y >= tilesY - shrink_tiles) ||
(x < shrink_tiles) || (x >= tilesX - shrink_tiles)) usable[i] = false;
}
(new TileNeibs(tilesX, tilesY)).shrinkSelection(
2 * shrink_tiles, // TileNeibs convention: 2 = one tile in 8 directions
usable,
null); // no prohibit
}
float [] finite = new float [fmax.length];
int num_finite = 0;
for (float f : fmax) if (!Float.isNaN(f) && !Float.isInfinite(f)) finite[num_finite++] = f;
for (int i = 0; i < fmax.length; i++) if (usable[i]) finite[num_finite++] = fmax[i];
if (num_finite == 0) return sel;
finite = Arrays.copyOf(finite, num_finite);
Arrays.sort(finite);
......@@ -141,13 +166,13 @@ public class CuasPoseRT {
final boolean [] eligible = new boolean [fmax.length];
int num_gated = 0;
for (int i = 0; i < fmax.length; i++) {
eligible[i] = !Float.isNaN(fmax[i]) && !Float.isInfinite(fmax[i]) && (fmax[i] <= gate);
eligible[i] = usable[i] && (fmax[i] <= gate); // usable folds in the interior shrink // By Claude on 07/13/2026
if (eligible[i]) num_gated++;
}
if (debugLevel > -4) {
System.out.println(String.format(
"CuasPoseRT.deriveSelection(): finite=%d, median=%.3f, NMAD=%.3f, gate=%.3f (k=%.2f) -> %d gated tiles",
num_finite, median, nmad, gate, k_nmad, num_gated));
"CuasPoseRT.deriveSelection(): usable=%d (interior shrink %d), median=%.3f, NMAD=%.3f, gate=%.3f (k=%.2f) -> %d gated tiles",
num_finite, shrink_tiles, median, nmad, gate, k_nmad, num_gated));
}
if ((num_tiles <= 0) || (num_gated <= num_tiles)) {
System.arraycopy(eligible, 0, sel, 0, sel.length);
......@@ -313,6 +338,8 @@ public class CuasPoseRT {
System.out.println("leanMeasure(): no MB tasks for scene "+scene.getImageName());
return null;
}
sortTasksByTile(tp_tasks2[0]); // fixed task-stream order (3-A3, GPU static shape) // By Claude on 07/13/2026
sortTasksByTile(tp_tasks2[1]);
cons_tasks = tp_tasks2; // By Claude on 07/12/2026
image_dtt.interCorrTDMotionBlur(
clt_parameters.img_dtt,
......@@ -347,6 +374,7 @@ public class CuasPoseRT {
System.out.println("leanMeasure(): no tasks for scene "+scene.getImageName());
return null;
}
sortTasksByTile(tp_tasks); // fixed task-stream order (3-A3, GPU static shape) // By Claude on 07/13/2026
cons_tasks = new TpTask [][] {tp_tasks, null}; // no-MB: single set, [1] = null // By Claude on 07/12/2026
// 2. convert only: LPFs + tasks + offsets + convert_direct, NO correlation (mask=0)
image_dtt.interCorrTD(
......@@ -450,6 +478,23 @@ public class CuasPoseRT {
return com.elphel.imagej.gpu.GPUTileProcessor.DTT_SIZE;
}
/**
* Deterministic task-stream order (3-A3, GPU static shape): the setInterTasks*
* builders fill their output from worker threads (aTiles.getAndIncrement()), so the
* tile ORDER varies run-to-run and scene-to-scene even for identical selections.
* Sort ascending by (tileY, tileX): with the interior-shrunk fixed selection the
* pose chain then produces the SAME task stream every scene/iteration - the fixed
* per-sequence indices contract the GPU consolidation list and the pose_corr
* checkpoint test rely on. Correlation output stays index-addressed (packed
* tile+pair), so Java-side consumers are unaffected. By Claude on 07/13/2026.
*/
private static void sortTasksByTile(final TpTask [] tasks) {
if (tasks == null) return;
Arrays.sort(tasks, (a,b) -> (a.getTileY() != b.getTileY()) ?
Integer.compare(a.getTileY(), b.getTileY()) :
Integer.compare(a.getTileX(), b.getTileX()));
}
/**
* 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
......@@ -836,6 +881,7 @@ public class CuasPoseRT {
center_CLT.getTilesX(),
clt_parameters.curt.pose_dxy_k,
clt_parameters.curt.pose_num_tiles,
clt_parameters.curt.pose_shrink, // interior gate (3-A3) // By Claude on 07/13/2026
debugLevel);
int num_filt = 0;
for (int i = 0; i < reliable_ref.length; i++) {
......@@ -1348,6 +1394,7 @@ public class CuasPoseRT {
tilesX,
clt_parameters.curt.pose_dxy_k,
clt_parameters.curt.pose_num_tiles,
clt_parameters.curt.pose_shrink, // interior gate (3-A3) // By Claude on 07/13/2026
debugLevel);
final float [] fkeep = new float [num_pix];
for (int i = 0; i < num_pix; i++) fkeep[i] = keep[i] ? 1.0f : 0.0f;
......
......@@ -22,6 +22,7 @@ public class CuasRtParameters {
public double pose_str = 1.0; // reliable-tile strength threshold over the combo-DSI strength for the pose test tile selection (1.0 ~ old getReliableTiles population). // By Claude on 07/03/2026
public double pose_dxy_k = 0.75; // tile-selection outlier gate: keep tiles with max-over-scenes residual <= median + k*NMAD of the finite per-tile maxes (scale-free - adapts to footage quality/sequence length; NaN-in-any-scene = +inf, always rejected). <=0 - skip the gate. // By Claude on 07/04/2026
public int pose_num_tiles = 150; // tile-selection compute budget: after the gate, keep this many BEST (smallest max-residual) tiles; threshold-free rank - always yields the best available population. <=0 - no cap. // By Claude on 07/04/2026
public int pose_shrink = 2; // tile-selection interior gate (3-A3, Andrey's ruling 07/13/2026): shrink the usable (finite-calibration = measured in EVERY scene) tile mask by this many tiles (8-direction) before selection. Tiles measured everywhere but adjacent to the missing/edge band may still carry PARTIAL 16-sensor OOB masks (soft margin 12 px = 1.5 tiles); shrinking excludes that band so every selected tile has all 16 sensors interior at every scene pose - the GPU pose chain can then bypass the consolidation OOB pass with a FIXED per-sequence tile list. 0 = no shrink (legacy selection). // By Claude on 07/13/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
......@@ -125,6 +126,8 @@ public class CuasRtParameters {
"Keep tiles with max-over-scenes residual <= median + k*NMAD of finite per-tile maxes (scale-free; NaN in any scene always rejected). <=0 - skip the gate.");
gd.addNumericField("Pose test number of best tiles", this.pose_num_tiles, 0,7,"", // By Claude on 07/04/2026
"After the gate keep this many best (smallest max-residual) tiles - the RT compute budget. <=0 - no cap.");
gd.addNumericField("Pose tile selection shrink (tiles)", this.pose_shrink, 0,7,"", // By Claude on 07/13/2026
"Shrink the usable (measured-in-every-scene) tile mask by this many tiles (8-dir) before selection: excludes the partial-OOB edge band so every selected tile keeps all 16 sensors interior at every scene pose (enables the fixed GPU tile list + OOB-pass bypass). 0 = off (legacy).");
gd.addCheckbox ("Pose test raw-jp4 ingest (A2)", this.pose_raw, // By Claude on 07/05/2026
"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
......@@ -319,6 +322,7 @@ public class CuasRtParameters {
this.pose_str = gd.getNextNumber(); // By Claude on 07/03/2026
this.pose_dxy_k = gd.getNextNumber(); // By Claude on 07/04/2026
this.pose_num_tiles =(int) gd.getNextNumber(); // By Claude on 07/04/2026
this.pose_shrink = (int) gd.getNextNumber(); // By Claude on 07/13/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
......@@ -422,6 +426,7 @@ public class CuasRtParameters {
properties.setProperty(prefix+"pose_str", this.pose_str+""); // double // By Claude on 07/03/2026
properties.setProperty(prefix+"pose_dxy_k", this.pose_dxy_k+""); // double // By Claude on 07/04/2026
properties.setProperty(prefix+"pose_num_tiles", this.pose_num_tiles+""); // int // By Claude on 07/04/2026
properties.setProperty(prefix+"pose_shrink", this.pose_shrink+""); // int // By Claude on 07/13/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
......@@ -525,6 +530,7 @@ public class CuasRtParameters {
if (properties.getProperty(prefix+"pose_str")!=null) this.pose_str=Double.parseDouble(properties.getProperty(prefix+"pose_str")); // By Claude on 07/03/2026
if (properties.getProperty(prefix+"pose_dxy_k")!=null) this.pose_dxy_k=Double.parseDouble(properties.getProperty(prefix+"pose_dxy_k")); // By Claude on 07/04/2026
if (properties.getProperty(prefix+"pose_num_tiles")!=null) this.pose_num_tiles=Integer.parseInt(properties.getProperty(prefix+"pose_num_tiles")); // By Claude on 07/04/2026
if (properties.getProperty(prefix+"pose_shrink")!=null) this.pose_shrink=Integer.parseInt(properties.getProperty(prefix+"pose_shrink")); // By Claude on 07/13/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
......@@ -631,6 +637,7 @@ public class CuasRtParameters {
cp.pose_str = this.pose_str;
cp.pose_dxy_k = this.pose_dxy_k;
cp.pose_num_tiles = this.pose_num_tiles;
cp.pose_shrink = this.pose_shrink; // By Claude on 07/13/2026
cp.pose_raw = this.pose_raw;
cp.pose_lean = this.pose_lean;
cp.pose_full = this.pose_full; // By Claude on 07/04/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