Commit e6d2be34 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: CuasPoseRT: cluster-based tile selection (neighbor-aware, edge-first for roll)

Findings from the FILT150 run: (1) scattered rank-150 selection starved the
per-scene neighbor consolidation (min_str_neib/eig_str_neib) - only ~57 tiles
with accidental neighbors were measured per scene (neighbors-vs-measured
corr 0.78); (2) roll degraded (RMS 0.106 vs 0.059 mrad, bias +0.073) - the
selection carried only 11% of the full set's roll information.

deriveSelection() stage 2 now picks disjoint 3x3 CLUSTERS of gate-passing
tiles (>=CLUSTER_MIN_ELIGIBLE=6 of 9), round-robin from three pools:
LEFTMOST, RIGHTMOST (per Andrey - edge tiles have the most roll influence),
BEST-QUALITY (median member fmax), until the tile budget is filled; scattered
best tiles fill any remainder. Offline simulation on the real calibration:
24 clusters (8/8/8), 150 tiles, mean 3.97 in-selection neighbors, roll info
+48% vs scattered rank-150. Measurement code untouched (oracle identical).

mvn compile clean.
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent 1ff4add6
...@@ -86,23 +86,36 @@ public class CuasPoseRT { ...@@ -86,23 +86,36 @@ public class CuasPoseRT {
public static final String [] HYPER_COMPONENTS = public static final String [] HYPER_COMPONENTS =
{"dx", "dy", "strength", "dxy", "sqrt_l0", "sqrt_l1", "elong", "eig0_ang"}; {"dx", "dy", "strength", "dxy", "sqrt_l0", "sqrt_l1", "elong", "eig0_ang"};
// Cluster selection: a 3x3 window is a candidate if at least this many of its tiles
// pass the outlier gate. Tiles are selected in whole clusters so every selected tile
// has in-selection NEIGHBORS - the per-scene neighbor consolidation in the correlation
// processing (min_str_neib/eig_str_neib) starves on scattered tiles (measured on
// 1773135476_186641: tiles with ~2.7 selected neighbors were measured in >90% of
// scenes, scattered tiles with ~0.7 neighbors almost never; corr 0.78).
public static final int CLUSTER_MIN_ELIGIBLE = 6;
/** /**
* Derive the tile selection from the per-tile max-residual calibration array * Derive the tile selection from the per-tile max-residual calibration array
* (-POSE-RT-MAXDXY: max over scenes of measured dxy; NaN = unusable - either not * (-POSE-RT-MAXDXY: max over scenes of measured dxy; NaN = unusable - either not
* selected/measured or NaN in at least one scene, always rejected). Two scale-free * selected/measured or NaN in at least one scene, always rejected). Stages (all
* stages, both adapt to footage quality * scale-free, no absolute pixel constants):
* and sequence length (no absolute pixel constants):
* 1 - robust outlier gate: keep finite values <= median + k_nmad*NMAD of the * 1 - robust outlier gate: keep finite values <= median + k_nmad*NMAD of the
* finite population (the "single high bump + long tail" histogram rule); * finite population (the "single high bump + long tail" histogram rule);
* 2 - compute budget: if more remain, keep the num_tiles best (smallest max). * 2 - compute budget: pick whole 3x3 CLUSTERS of gate-passing tiles (see
* CLUSTER_MIN_ELIGIBLE) round-robin from three priority pools - LEFTMOST,
* RIGHTMOST (per Andrey: edge tiles have the most influence on roll),
* BEST-QUALITY - until num_tiles is reached. Clusters are disjoint. If
* candidates run out, fill the remainder with best scattered tiles.
* By Claude on 07/04/2026, from Andrey's design. * By Claude on 07/04/2026, from Andrey's design.
*/ */
public static boolean [] deriveSelection( public static boolean [] deriveSelection(
final float [] fmax, final float [] fmax,
final int tilesX,
final double k_nmad, // <=0 - skip the gate final double k_nmad, // <=0 - skip the gate
final int num_tiles, // <=0 - no cap final int num_tiles, // <=0 - no cap
final int debugLevel) { final int debugLevel) {
final boolean [] sel = new boolean [fmax.length]; final boolean [] sel = new boolean [fmax.length];
final int tilesY = fmax.length / tilesX;
float [] finite = new float [fmax.length]; float [] finite = new float [fmax.length];
int num_finite = 0; int num_finite = 0;
for (float f : fmax) if (!Float.isNaN(f) && !Float.isInfinite(f)) finite[num_finite++] = f; for (float f : fmax) if (!Float.isNaN(f) && !Float.isInfinite(f)) finite[num_finite++] = f;
...@@ -115,27 +128,98 @@ public class CuasPoseRT { ...@@ -115,27 +128,98 @@ public class CuasPoseRT {
Arrays.sort(adev); Arrays.sort(adev);
final double nmad = 1.4826 * adev[num_finite/2]; final double nmad = 1.4826 * adev[num_finite/2];
final double gate = (k_nmad > 0) ? (median + k_nmad * nmad) : Double.POSITIVE_INFINITY; final double gate = (k_nmad > 0) ? (median + k_nmad * nmad) : Double.POSITIVE_INFINITY;
final boolean [] eligible = new boolean [fmax.length];
int num_gated = 0; int num_gated = 0;
for (int i = 0; i < fmax.length; i++) { for (int i = 0; i < fmax.length; i++) {
sel[i] = !Float.isNaN(fmax[i]) && !Float.isInfinite(fmax[i]) && (fmax[i] <= gate); eligible[i] = !Float.isNaN(fmax[i]) && !Float.isInfinite(fmax[i]) && (fmax[i] <= gate);
if (sel[i]) num_gated++; 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));
}
if ((num_tiles <= 0) || (num_gated <= num_tiles)) {
System.arraycopy(eligible, 0, sel, 0, sel.length);
return sel;
}
// ---- Stage 2: disjoint 3x3 clusters, round-robin left / right / best-quality ----
final ArrayList<int []> cand = new ArrayList<int []>(); // {center_index, n_eligible}
final float [] cquality = new float [fmax.length]; // median fmax of eligible members
for (int cy = 1; cy < tilesY - 1; cy++) {
for (int cx = 1; cx < tilesX - 1; cx++) {
final float [] q = new float [9];
int n = 0;
for (int dy = -1; dy <= 1; dy++) for (int dx = -1; dx <= 1; dx++) {
final int i = (cy + dy) * tilesX + (cx + dx);
if (eligible[i]) q[n++] = fmax[i];
}
if (n >= CLUSTER_MIN_ELIGIBLE) {
final int ci = cy * tilesX + cx;
Arrays.sort(q, 0, n);
cquality[ci] = q[n/2];
cand.add(new int [] {ci, n});
}
}
}
// three priority orders over the same candidates
final ArrayList<int []> by_left = new ArrayList<int []>(cand);
final ArrayList<int []> by_right = new ArrayList<int []>(cand);
final ArrayList<int []> by_quality = new ArrayList<int []>(cand);
by_left.sort ((a,b) -> Integer.compare(a[0] % tilesX, b[0] % tilesX));
by_right.sort ((a,b) -> Integer.compare(b[0] % tilesX, a[0] % tilesX));
by_quality.sort((a,b) -> Float.compare(cquality[a[0]], cquality[b[0]]));
final ArrayList<ArrayList<int []>> pools = new ArrayList<ArrayList<int []>>();
pools.add(by_left); pools.add(by_right); pools.add(by_quality);
final int [] pool_pos = new int [pools.size()];
final int [] pool_used = new int [pools.size()];
int num_sel = 0, num_clusters = 0;
while (num_sel < num_tiles) {
boolean any = false;
for (int p = 0; (p < pools.size()) && (num_sel < num_tiles); p++) {
final ArrayList<int []> pool = pools.get(p);
while (pool_pos[p] < pool.size()) {
final int ci = pool.get(pool_pos[p]++)[0];
final int cy = ci / tilesX, cx = ci % tilesX;
boolean overlap = false;
for (int dy = -1; (dy <= 1) && !overlap; dy++) for (int dx = -1; dx <= 1; dx++) {
if (sel[(cy + dy) * tilesX + (cx + dx)]) { overlap = true; break; }
}
if (overlap) continue;
for (int dy = -1; dy <= 1; dy++) for (int dx = -1; dx <= 1; dx++) {
final int i = (cy + dy) * tilesX + (cx + dx);
if (eligible[i] && !sel[i] && (num_sel < num_tiles)) {
sel[i] = true;
num_sel++;
}
}
num_clusters++;
pool_used[p]++;
any = true;
break;
}
}
if (!any) break; // all pools exhausted
} }
double worst = gate; if (num_sel < num_tiles) { // fill the remainder with best scattered eligible tiles
if ((num_tiles > 0) && (num_gated > num_tiles)) { // rank: keep the num_tiles best
final Integer [] order = new Integer [num_gated];
final int [] idx = new int [num_gated]; final int [] idx = new int [num_gated];
for (int i = 0, j = 0; i < fmax.length; i++) if (sel[i]) idx[j++] = i; int n = 0;
for (int j = 0; j < num_gated; j++) order[j] = j; for (int i = 0; i < fmax.length; i++) if (eligible[i] && !sel[i]) idx[n++] = i;
Arrays.sort(order, (a,b) -> Float.compare(fmax[idx[a]], fmax[idx[b]])); final Integer [] order = new Integer [n];
Arrays.fill(sel, false); for (int j = 0; j < n; j++) order[j] = j;
for (int j = 0; j < num_tiles; j++) sel[idx[order[j]]] = true; final int [] fidx = Arrays.copyOf(idx, n);
worst = fmax[idx[order[num_tiles-1]]]; Arrays.sort(order, (a,b) -> Float.compare(fmax[fidx[a]], fmax[fidx[b]]));
num_gated = num_tiles; for (int j = 0; (j < n) && (num_sel < num_tiles); j++) {
sel[fidx[order[j]]] = true;
num_sel++;
}
} }
if (debugLevel > -4) { if (debugLevel > -4) {
float worst = 0;
for (int i = 0; i < fmax.length; i++) if (sel[i] && (fmax[i] > worst)) worst = fmax[i];
System.out.println(String.format( System.out.println(String.format(
"CuasPoseRT.deriveSelection(): finite=%d, median=%.3f, NMAD=%.3f, gate=%.3f (k=%.2f) -> %d tiles, worst kept max=%.3f pix", "CuasPoseRT.deriveSelection(): %d clusters (left/right/quality = %d/%d/%d) -> %d tiles, worst kept max=%.3f pix",
num_finite, median, nmad, gate, k_nmad, num_gated, worst)); num_clusters, pool_used[0], pool_used[1], pool_used[2], num_sel, worst));
} }
return sel; return sel;
} }
...@@ -213,6 +297,7 @@ public class CuasPoseRT { ...@@ -213,6 +297,7 @@ public class CuasPoseRT {
final float [] fmax = (float []) imp_max.getProcessor().getPixels(); final float [] fmax = (float []) imp_max.getProcessor().getPixels();
final boolean [] filt = deriveSelection( final boolean [] filt = deriveSelection(
fmax, fmax,
center_CLT.getTilesX(),
clt_parameters.imp.curt_pose_dxy_k, clt_parameters.imp.curt_pose_dxy_k,
clt_parameters.imp.curt_pose_num_tiles, clt_parameters.imp.curt_pose_num_tiles,
debugLevel); debugLevel);
...@@ -488,6 +573,7 @@ public class CuasPoseRT { ...@@ -488,6 +573,7 @@ public class CuasPoseRT {
center_CLT.getImageName()+"-POSE-RT-MAXDXY", new FloatProcessor(tilesX, tilesY, fmax))); center_CLT.getImageName()+"-POSE-RT-MAXDXY", new FloatProcessor(tilesX, tilesY, fmax)));
final boolean [] keep = deriveSelection( final boolean [] keep = deriveSelection(
fmax, fmax,
tilesX,
clt_parameters.imp.curt_pose_dxy_k, clt_parameters.imp.curt_pose_dxy_k,
clt_parameters.imp.curt_pose_num_tiles, clt_parameters.imp.curt_pose_num_tiles,
debugLevel); debugLevel);
......
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