Commit 66bd669a authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: disparity conditioning: fill small foreground clusters with background (Laplacian)

Where a small foreground object (the 3-tile roadside sign, disp ~22)
stands in front of a distant background both disparities are legitimate,
but for CUAS the background matters: a drone flying exactly behind the
sign must stay detectable. conditionDisparityCuas now detects connected
clusters of local near-outliers (disparity above the local 7x7 median by
curt.disp_fg_over, default 2 px) up to curt.disp_fg_tiles (default 8,
0 = off) tiles and re-fills them with zero-curvature background
disparity (TileProcessor.fillNaNs Laplacian relaxation). All three RT
call sites (render, pose, appropriation) share the one conditioned
source as before.
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent 275f812b
......@@ -634,7 +634,10 @@ public class CuasPoseRT {
// negatives clamped). NOT gated by use_lma_dsi (the LMA slice is NaN over the sky).
// By Claude on 07/05/2026.
final double [] center_disparity = CuasRender.conditionDisparityCuas(
center_dsi[is_aux ? TwoQuadCLT.DSI_DISPARITY_AUX : TwoQuadCLT.DSI_DISPARITY_MAIN]);
center_dsi[is_aux ? TwoQuadCLT.DSI_DISPARITY_AUX : TwoQuadCLT.DSI_DISPARITY_MAIN],
center_CLT.getTilesX(), // By Claude on 07/07/2026 (small-FG Laplacian fill)
clt_parameters.curt.disp_fg_tiles,
clt_parameters.curt.disp_fg_over);
final boolean [] reliable_ref = new boolean [strength.length];
int num_reliable = 0;
for (int i = 0; i < strength.length; i++) {
......
......@@ -177,7 +177,10 @@ public class CuasRT {
// full-grid tasks from the ONE conditioned disparity source (same as render/pose)
final boolean is_aux = center_CLT.isAux();
final double [] center_disparity = CuasRender.conditionDisparityCuas(
center_CLT.dsi[is_aux ? TwoQuadCLT.DSI_DISPARITY_AUX : TwoQuadCLT.DSI_DISPARITY_MAIN]);
center_CLT.dsi[is_aux ? TwoQuadCLT.DSI_DISPARITY_AUX : TwoQuadCLT.DSI_DISPARITY_MAIN],
center_CLT.getTilesX(), // By Claude on 07/07/2026 (small-FG Laplacian fill)
clt_parameters.curt.disp_fg_tiles,
clt_parameters.curt.disp_fg_over);
final boolean [] all_tiles = new boolean [center_disparity.length];
for (int i = 0; i < all_tiles.length; i++) all_tiles[i] = !Double.isNaN(center_disparity[i]);
final double [][] pXpYD_center = OpticalFlow.transformToScenePxPyD(
......
......@@ -34,6 +34,7 @@ import com.elphel.imagej.tileprocessor.ErsCorrection;
import com.elphel.imagej.tileprocessor.ImageDtt;
import com.elphel.imagej.tileprocessor.OpticalFlow;
import com.elphel.imagej.tileprocessor.QuadCLT;
import com.elphel.imagej.tileprocessor.TileProcessor; // By Claude on 07/07/2026 (fillNaNs - FG-cluster Laplacian fill)
import com.elphel.imagej.tileprocessor.TwoQuadCLT;
import ij.ImagePlus;
......@@ -74,18 +75,97 @@ public class CuasRender {
* infinity (disparity 0); NaN is acceptable only outside the image. The raw plain
* DSI slice has no NaN but carries garbage (crazy tiles < -1 in the sky, negatives
* near high-contrast posts) - all non-physical: clamp to >= 0.
* SMALL FOREGROUND clusters (Andrey 07/07/2026): where a small foreground object (the
* roadside "bomb" sign - 3 tiles at disp ~22, later lost) stands in front of a distant
* background, BOTH disparities are legitimate - but for CUAS the far background is what
* matters: in some sequences the drone flies exactly behind the sign and must stay
* detectable. Connected clusters of local NEAR-outliers (disparity above the local
* 7x7 median by fg_over) of at most max_fg_tiles tiles are re-filled with zero-curvature
* (Laplacian, TileProcessor.fillNaNs) interpolation of the surrounding background.
* @param disparity plain disparity (slice 0 of -INTER-INTRA-LMA), NOT the LMA slice
* (which is NaN wherever the LMA did not fit - the whole sky)
* @return conditioned copy: NaN -> 0.0 (infinity), negatives -> 0.0
* @param tilesX tile grid width
* @param max_fg_tiles largest connected near-outlier cluster to fill; <=0 - disable
* @param fg_over disparity above the local median that makes a tile a near-outlier (px)
* @return conditioned copy: NaN -> 0.0 (infinity), negatives -> 0.0, small FG filled
*/
public static double [] conditionDisparityCuas(final double [] disparity) {
public static double [] conditionDisparityCuas(
final double [] disparity,
final int tilesX,
final int max_fg_tiles,
final double fg_over) {
final double [] cond = new double [disparity.length];
for (int i = 0; i < disparity.length; i++) {
final double d = disparity[i];
cond[i] = (Double.isNaN(d) || (d < 0.0)) ? 0.0 : d;
}
if ((max_fg_tiles <= 0) || (tilesX <= 0)) {
return cond;
}
final int tilesY = cond.length / tilesX;
// 1. local near-outlier candidates: disparity > (7x7 neighborhood median) + fg_over
// (a <=9-tile cluster stays a minority of the 49-tile window)
final int med_rad = 3;
final boolean [] cand = new boolean [cond.length];
final double [] wnd = new double [(2*med_rad+1)*(2*med_rad+1)];
for (int ty = 0; ty < tilesY; ty++) {
for (int tx = 0; tx < tilesX; tx++) {
int nw = 0;
for (int dy = -med_rad; dy <= med_rad; dy++) {
final int yy = ty + dy;
if ((yy < 0) || (yy >= tilesY)) continue;
for (int dx = -med_rad; dx <= med_rad; dx++) {
final int xx = tx + dx;
if ((xx < 0) || (xx >= tilesX)) continue;
wnd[nw++] = cond[yy*tilesX + xx];
}
}
java.util.Arrays.sort(wnd, 0, nw);
cand[ty*tilesX + tx] = cond[ty*tilesX + tx] > (wnd[nw/2] + fg_over);
}
}
// 2. connected (4-conn) candidate clusters of at most max_fg_tiles -> NaN holes
final boolean [] visited = new boolean [cond.length];
final int [] stack = new int [cond.length];
final int [] comp = new int [cond.length];
final double [] holed = cond.clone();
int num_clusters = 0, num_tiles = 0;
for (int seed = 0; seed < cond.length; seed++) if (cand[seed] && !visited[seed]) {
int sp = 0, nc = 0;
stack[sp++] = seed;
visited[seed] = true;
while (sp > 0) {
final int t = stack[--sp];
comp[nc++] = t;
final int ty = t/tilesX, tx = t%tilesX;
if ((tx > 0) && cand[t-1] && !visited[t-1]) { visited[t-1]=true; stack[sp++] = t-1; }
if ((tx < tilesX-1) && cand[t+1] && !visited[t+1]) { visited[t+1]=true; stack[sp++] = t+1; }
if ((ty > 0) && cand[t-tilesX] && !visited[t-tilesX]) { visited[t-tilesX]=true; stack[sp++] = t-tilesX; }
if ((ty < tilesY-1) && cand[t+tilesX] && !visited[t+tilesX]) { visited[t+tilesX]=true; stack[sp++] = t+tilesX; }
}
if (nc <= max_fg_tiles) {
for (int i = 0; i < nc; i++) holed[comp[i]] = Double.NaN;
num_clusters++;
num_tiles += nc;
}
}
if (num_clusters == 0) {
return cond;
}
// 3. zero-curvature (Laplacian) fill of the holes from the surrounding background
final double [] filled = TileProcessor.fillNaNs(
holed, // final double [] data,
null, // final boolean [] prohibit,
tilesX, // int width,
2*tilesX, // final int grow (large - fill everything)
0.7, // double diagonal_weight, // relative to ortho
100, // int num_passes,
0.03); // final double max_rchange
System.out.println("conditionDisparityCuas(): filled "+num_clusters+" small foreground cluster(s), "+
num_tiles+" tile(s) total, with zero-curvature background disparity (max_fg_tiles="+
max_fg_tiles+", fg_over="+fg_over+")");
return filled;
}
/**
* Render ONE scene (already ingested to the GPU by CuasConditioning.conditionSceneToGpu)
......@@ -249,7 +329,10 @@ public class CuasRender {
// wherever the LMA did not fit (the whole sky) and would drop those tiles from the
// render. By Claude on 07/05/2026, per Andrey.
final double [] center_disparity = conditionDisparityCuas(
center_dsi[is_aux ? TwoQuadCLT.DSI_DISPARITY_AUX : TwoQuadCLT.DSI_DISPARITY_MAIN]);
center_dsi[is_aux ? TwoQuadCLT.DSI_DISPARITY_AUX : TwoQuadCLT.DSI_DISPARITY_MAIN],
center_CLT.getTilesX(), // By Claude on 07/07/2026 (small-FG Laplacian fill)
clt_parameters.curt.disp_fg_tiles,
clt_parameters.curt.disp_fg_over);
final ErsCorrection ers_center = center_CLT.getErsCorrection();
final ImageDtt image_dtt = new ImageDtt(
center_CLT.getNumSensors(),
......
......@@ -41,6 +41,8 @@ public class CuasRtParameters {
public double rowcol_um_sigma = 10.0; // RT conditioning: unsharp-mask sigma for the row/col profile MEASUREMENT (content suppression borrowed from the oracle path; the max_abs gate then acts on residual counts); 0 = measure on the data as-is (pre-07/07 behavior). // By Claude on 07/07/2026
public double rowcol_max_abs = 50.0; // RT conditioning: only average pixels with |UM residual| within this limit (outlier reject, calibrated counts - rowcol now runs AFTER photometric+FPN); 0 = no limit. NOTE: configs saved before 07/07/2026 evening carry 0 (the gate was unusable on raw counts then) - set 50 to enable. // By Claude on 07/07/2026
public double rowcol_outlier = 0.01; // RT conditioning: weight of out-of-range pixels in the line averages (prevents undefined values on all-outlier lines). // By Claude on 07/07/2026
public int disp_fg_tiles = 8; // disparity conditioning: fill connected FOREGROUND clusters up to this many tiles (local near-outliers, e.g. the 3-tile roadside sign) with zero-curvature background disparity so a drone flying exactly behind stays detectable; 0 = off (Andrey 07/07/2026). // By Claude on 07/07/2026
public double disp_fg_over = 2.0; // disparity conditioning: a tile is a near-outlier when its disparity exceeds the local 7x7 median by this many pixels. // By Claude on 07/07/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
......@@ -152,6 +154,10 @@ public class CuasRtParameters {
"Only average pixels with |UM residual| within this limit (outlier reject, calibrated counts - row/col runs after photometric+FPN). 0 = no limit. Configs saved before 07/07 evening carry 0 - set 50 to enable.");
gd.addNumericField("Row/col outlier weight", this.rowcol_outlier, 5,7,"x", // By Claude on 07/07/2026
"Weight of out-of-range pixels in the line averages (prevents undefined values on all-outlier lines).");
gd.addNumericField("Fill small foreground clusters, tiles", this.disp_fg_tiles, 0,3,"tiles", // By Claude on 07/07/2026
"Disparity conditioning: fill connected foreground clusters up to this many tiles (local near-outliers, e.g. the 3-tile roadside sign) with zero-curvature background disparity - a drone flying exactly behind stays detectable. 0 = off.");
gd.addNumericField("Foreground outlier threshold", this.disp_fg_over, 3,7,"pix", // By Claude on 07/07/2026
"A tile is a near-outlier when its disparity exceeds the local 7x7 median by this many pixels.");
gd.addMessage("=== LoG prefilter ===");
gd.addNumericField("Optical PSF radius", this.psf_radius, 6,8,"pix",
......@@ -301,6 +307,8 @@ public class CuasRtParameters {
this.rowcol_um_sigma = gd.getNextNumber(); // By Claude on 07/07/2026
this.rowcol_max_abs = gd.getNextNumber(); // By Claude on 07/07/2026
this.rowcol_outlier = gd.getNextNumber(); // By Claude on 07/07/2026
this.disp_fg_tiles = (int) gd.getNextNumber(); // By Claude on 07/07/2026
this.disp_fg_over = gd.getNextNumber(); // By Claude on 07/07/2026
this.psf_radius = gd.getNextNumber();
this.n_sigma = gd.getNextNumber();
......@@ -394,6 +402,8 @@ public class CuasRtParameters {
properties.setProperty(prefix+"rowcol_um_sigma", this.rowcol_um_sigma+"");// double // By Claude on 07/07/2026
properties.setProperty(prefix+"rowcol_max_abs", this.rowcol_max_abs+""); // double // By Claude on 07/07/2026
properties.setProperty(prefix+"rowcol_outlier", this.rowcol_outlier+""); // double // By Claude on 07/07/2026
properties.setProperty(prefix+"disp_fg_tiles", this.disp_fg_tiles+""); // int // By Claude on 07/07/2026
properties.setProperty(prefix+"disp_fg_over", this.disp_fg_over+""); // double // By Claude on 07/07/2026
properties.setProperty(prefix+"psf_radius", this.psf_radius+""); // double
properties.setProperty(prefix+"n_sigma", this.n_sigma+""); // double
......@@ -487,6 +497,8 @@ public class CuasRtParameters {
if (properties.getProperty(prefix+"rowcol_um_sigma")!=null) this.rowcol_um_sigma=Double.parseDouble(properties.getProperty(prefix+"rowcol_um_sigma")); // By Claude on 07/07/2026
if (properties.getProperty(prefix+"rowcol_max_abs")!=null) this.rowcol_max_abs=Double.parseDouble(properties.getProperty(prefix+"rowcol_max_abs")); // By Claude on 07/07/2026
if (properties.getProperty(prefix+"rowcol_outlier")!=null) this.rowcol_outlier=Double.parseDouble(properties.getProperty(prefix+"rowcol_outlier")); // By Claude on 07/07/2026
if (properties.getProperty(prefix+"disp_fg_tiles")!=null) this.disp_fg_tiles=Integer.parseInt(properties.getProperty(prefix+"disp_fg_tiles")); // By Claude on 07/07/2026
if (properties.getProperty(prefix+"disp_fg_over")!=null) this.disp_fg_over=Double.parseDouble(properties.getProperty(prefix+"disp_fg_over")); // By Claude on 07/07/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"));
......@@ -582,6 +594,8 @@ public class CuasRtParameters {
cp.rowcol_um_sigma = this.rowcol_um_sigma; // By Claude on 07/07/2026
cp.rowcol_max_abs = this.rowcol_max_abs; // By Claude on 07/07/2026
cp.rowcol_outlier = this.rowcol_outlier; // By Claude on 07/07/2026
cp.disp_fg_tiles = this.disp_fg_tiles; // By Claude on 07/07/2026
cp.disp_fg_over = this.disp_fg_over; // By Claude on 07/07/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