Commit 22d49ee1 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: CuasRT - single RT entry point + borrow-boundary appropriation + calib fixes

- NEW cuas/rt/CuasRT.java: the ENTIRE CUAS RT mode moved out of
  OpticalFlow.buildSeries (curt_en branch) into one readable top method:
  1. INITIALIZATION (ingest, photometric calibration, center appropriation)
  2. DIAGNOSTICS (rend/pose/cond tests - exclusive, never grow the top)
  3. PRODUCTION (detection; the MVP per-scene jp4 loop will replace the
     bulk oracle ingest without changing this structure).
- Borrow boundary (Andrey ruling): all transforms of borrowed QuadCLT data
  happen at CuasRT initialization ONLY. appropriateCenter() zeroes the
  center CLT IN MEMORY (setCenterClt; -CLT-UPDATED is saved upstream, so
  the zeroed copy never persists) - the pose test drops its per-run GPU
  appropriation and uploads an already-zeroed reference. The pure math is
  CuasConditioning.zeroDcClt(fclt, pattern, level) - knows no QuadCLT.
- FIX (POSE-09 silent no-op): the DC-pattern uniform image must be set on
  gpuQuad.getQuadCLT() - what both backends actually read (GpuQuadJna
  setBayerImages(center=true), JCuda base, CorrectionFPN recipe) - not on
  center_CLT; plus an all-zero-pattern guard (warn loudly, never fake-offset).
- FIX (POSE-09 1470-count per-sensor sky spread vs 17 calibrated):
  rtPhotometricCalibration applies the fit to the ALREADY-SPAWNED per-scene
  instances too (created by ingest before the calib step). The recalibrated
  photometric persists only via ref INTERFRAME + a config save after a
  calib-ON run - all current configs still carry the stale values.
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent 194d87a8
......@@ -4174,6 +4174,7 @@ public class CuasMotion {
QuadCLT master_CLT,
QuadCLT ref_scene,
QuadCLT quadCLT_main,
QuadCLT [] scenes, // already-spawned per-scene instances (may be null) // By Claude on 07/05/2026
boolean save_stacks,
int threadsMax,
int debugLevel) {
......@@ -4203,9 +4204,21 @@ public class CuasMotion {
debugLevel, // int debugLevel
true); // boolean keep_averages: average offset = 0, average scale = 1.0
if (ab != null) {
// Apply to the ALREADY-SPAWNED scene instances too: prepareFpixels creates them
// BEFORE this step with the pre-calibration template values, so without this the
// SAME run's measurements still condition with stale photometric (POSE-09: 1470-
// count per-sensor sky spread vs the 17-count calibrated state; the recalibrated
// values live only in the ref INTERFRAME + memory unless the main config is saved
// after a calib-ON run). By Claude on 07/05/2026.
final int num_extra = (scenes != null) ? scenes.length : 0;
final QuadCLT [] apply_to = new QuadCLT [3 + num_extra];
apply_to[0] = master_CLT;
apply_to[1] = ref_scene;
apply_to[2] = quadCLT_main;
for (int i = 0; i < num_extra; i++) apply_to[3 + i] = scenes[i]; // nulls are skipped
applyLwirLinearCalibration(
phys, // QuadCLT phys_scene
new QuadCLT [] {master_CLT, ref_scene, quadCLT_main}, // QuadCLT [] apply_to (in-memory)
apply_to, // QuadCLT [] apply_to (in-memory)
ref_scene, // QuadCLT save_scene -> INTERFRAME corr-xml
ab[0], // double [] a,
ab[1], // double [] b,
......
......@@ -141,6 +141,36 @@ public class CuasConditioning {
}
}
/**
* Pure data-domain appropriation kernel for the BORROWED center CLT: subtract
* level*pattern per TD point, NaN-aware (only where both terms are defined). Knows
* NOTHING of QuadCLT/GPU - the caller (CuasRT initialization, the borrow boundary)
* extracts the arrays and stores the result back. Returns a NEW array; the input is
* not modified. By Claude on 07/05/2026, per Andrey's clean-method ruling.
* @param fclt borrowed center CLT (single merged-sensor slice, tile-major TD points)
* @param pattern CLT of a uniform 1.0 image over the same task tiles (NaN elsewhere)
* @param level DC level to subtract (counts)
* @return zeroed copy of fclt (fclt - level*pattern where both defined)
*/
public static float [] zeroDcClt(
final float [] fclt,
final float [] pattern,
final double level) {
final float [] zeroed = fclt.clone();
final int len = Math.min(zeroed.length, pattern.length);
int n_off = 0;
for (int i = 0; i < len; i++) {
final float p = pattern[i];
if (!Float.isNaN(p) && !Float.isNaN(zeroed[i])) {
zeroed[i] -= (float) (level * p);
n_off++;
}
}
System.out.println("CuasConditioning.zeroDcClt(): CLT offset by "+level+
" over "+n_off+" TD points");
return zeroed;
}
/**
* The A2/RT ingest: read a scene's RAW /jp4/ source, condition it with the CURRENT
* calibration (curt_calib-updated lwir scales/offsets/scales2 + per-pixel FPN from the
......
......@@ -442,116 +442,6 @@ public class CuasPoseRT {
return com.elphel.imagej.gpu.GPUTileProcessor.DTT_SIZE;
}
/**
* Appropriate the borrowed center reference: subtract the DC level from the
* reference CLT already uploaded to the GPU, so it matches the zero-sky conditioned
* scenes (the MCLT has no clean DC/AC separation - a DC constant spreads over many
* TD points and costs float32 mantissa bits; LWIR is ADDITIVE: day/night and seasons
* shift the level, they do not scale it - the borrowed center reflects its own
* footage vintage).
* Level: curt.dc_center; 0 = AUTO - median of the top (sky) half of the reference
* buffer render itself (the exact data being zeroed; the borrowed center average
* image is NOT available in the pose flow), then ADOPTED into curt.dc_center
* (persisted on the next configuration save - borrowable later, like the
* photometric calibration).
* Mechanics: the CLT of a UNIFORM image (the DC pattern - identical for every
* converted tile) is obtained through the existing back-propagation convert
* (setImageCenter + no_kernels + use_center_image, the CorrectionFPN recipe), then
* fclt_zeroed = fclt - level*pattern is re-uploaded to all reference slots. Only the
* task tiles get the pattern (erase=NaN elsewhere) - sufficient: the correlation
* reads task tiles only. Failure-safe: any missing piece leaves the reference as-is.
* By Claude on 07/05/2026, from Andrey's design.
*/
public static void appropriateCenterDC(
final CLTParameters clt_parameters,
final QuadCLT center_CLT,
final TpTask [] tp_tasks_center,
final float [] fclt0,
final int debugLevel) {
final GpuQuad gpuQuad = center_CLT.getGPUQuad();
double level = clt_parameters.curt.dc_center;
if (level == 0.0) { // auto: median of the top (sky) half of the reference buffer render
// Level from the reference render ITSELF (the exact data being zeroed):
// getCenterAverage() is null in the pose flow, which silently no-op'd the
// appropriation (composite virtual sky stayed at the raw center level).
// The reference CLT was just uploaded by setReferenceGPU. By Claude on 07/05/2026.
final float [] apx = com.elphel.imagej.cuas.CuasMotion.perSensorImagesFromTD(gpuQuad, true)[0];
if (apx == null) {
System.out.println("appropriateCenterDC(): reference render unavailable - reference NOT offset");
return;
}
final int width = gpuQuad.getImageWidth(); // valid after the imclt above
final int top_pix = (gpuQuad.getImageHeight() / 2) * width;
final float [] samples = new float [top_pix];
int n = 0;
for (int i = 0; i < top_pix; i++) if (!Float.isNaN(apx[i])) samples[n++] = apx[i];
if (n == 0) {
System.out.println("appropriateCenterDC(): reference render all-NaN in the sky half - reference NOT offset");
return;
}
Arrays.sort(samples, 0, n);
level = samples[n/2];
clt_parameters.curt.dc_center = level; // adopt: persisted on the next config save
System.out.println("appropriateCenterDC(): AUTO center DC level = "+level+
" (median of the sky half of the reference buffer render), adopted into curt.dc_center");
}
// the DC pattern: CLT of a uniform image through the back-propagation convert
final int [] wh = center_CLT.getGeometryCorrection().getSensorWH();
final double [][] saved_center = center_CLT.getImageCenter();
float [] pattern = null;
try {
final double [][] uniform = new double [][] {new double [wh[0]*wh[1]]};
Arrays.fill(uniform[0], 1.0);
center_CLT.setImageCenter(uniform);
final ImageDtt image_dtt = new ImageDtt(
center_CLT.getNumSensors(),
clt_parameters.transform_size,
clt_parameters.img_dtt,
center_CLT.isAux(),
center_CLT.isMonochrome(),
center_CLT.isLwir(),
clt_parameters.getScaleStrength(center_CLT.isAux()),
center_CLT.getGPU());
image_dtt.preSetReferenceTD( // tasks + offsets, no convert
clt_parameters.img_dtt,
tp_tasks_center,
true, // keep_tiles_offsets
clt_parameters.gpu_sigma_r,
clt_parameters.gpu_sigma_b,
clt_parameters.gpu_sigma_g,
clt_parameters.gpu_sigma_m,
debugLevel);
image_dtt.execConvertDirect(
false, // use_reference_buffer: SCENE buffer (scratch)
null, // wh
1, // erase_clt: NaN outside tasks
true, // no_kernels
true); // use_center_image (uniform)
pattern = gpuQuad.getCltData(false)[0];
} catch (Exception e) {
System.out.println("appropriateCenterDC(): DC-pattern conversion FAILED ("+e.getMessage()+
") - reference NOT offset");
pattern = null;
} finally {
center_CLT.setImageCenter(saved_center); // never leave back-propagate mode on
}
if (pattern == null) return;
final float [] fclt_zeroed = fclt0.clone();
int n_off = 0;
for (int i = 0; i < fclt_zeroed.length; i++) {
final float p = pattern[i];
if (!Float.isNaN(p) && !Float.isNaN(fclt_zeroed[i])) {
fclt_zeroed[i] -= (float) (level * p);
n_off++;
}
}
for (int ncam = 0; ncam < center_CLT.getNumSensors(); ncam++) {
gpuQuad.setCltData(ncam, fclt_zeroed, true); // reference buffer, all slots (merge_channels convention)
}
System.out.println("appropriateCenterDC(): reference CLT offset by "+level+
" over "+n_off+" TD points (task tiles)");
}
/**
* 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
......@@ -777,13 +667,10 @@ public class CuasPoseRT {
mb_max_gain, // double mb_max_gain,
null, // double [][] mb_vectors (center does not move)
debugLevel); // int debug_level
// APPROPRIATE the borrowed center: offset the reference CLT so it matches the
// zero-sky conditioned scenes (curt.dc_center; 0 = auto from the borrowed center
// average, adopted for persistence). Firewall: the borrowed data is zeroed ONCE
// here at the boundary - nothing downstream compensates again. Failure-safe: any
// missing piece leaves the reference unchanged (a warning, not a break).
// By Claude on 07/05/2026, from Andrey's design.
appropriateCenterDC(clt_parameters, center_CLT, tp_tasks_center[0], fclt[0], debugLevel);
// The borrowed center arrives ALREADY APPROPRIATED (zero-DC, in memory) from
// CuasRT.appropriateCenter() - the borrow boundary at RT initialization; the
// getCenterClt()/setReferenceGPU above therefore uploaded the zeroed reference.
// Nothing to compensate here. By Claude on 07/05/2026, per Andrey's ruling.
// ---- Stored poses (comparison truth + seed) ----
final ErsCorrection ers_center = center_CLT.getErsCorrection();
......
This diff is collapsed.
......@@ -7260,99 +7260,21 @@ java.lang.NullPointerException
}
}
// CUAS RT (our code) - coexists with the oracle CuasRanging above (separate buttons/modes, no interference).
// Generate the merged-CUAS stack on the GPU EXPLICITLY here (CuasRanging.prepareFpixels() uses the CUDA
// tile-processor kernels - may be incompatible with a future CUDA), then hand the plain ImagePlus to the
// CUDA-free CuasDetectRT. By Claude on 06/24/2026
// CUAS RT (our code) - coexists with the oracle CuasRanging above (separate buttons/modes,
// no interference). The ENTIRE RT mode lives in cuas.rt.CuasRT (single entry:
// initialization -> optional diagnostics -> production) - do not grow RT code here.
// By Claude on 07/05/2026, per Andrey's structure ruling.
if (clt_parameters.curt.en && master_CLT.hasCenterClt()) {
System.out.println("===== Running CUAS RT detection (curt_en). =====");
CuasRanging cuasRangingRT = new CuasRanging(
clt_parameters, // CLTParameters clt_parameters,
master_CLT, // QuadCLT center_CLT,
quadCLTs, // QuadCLT [] scenes,
debugLevel);
ImagePlus imp_targets = cuasRangingRT.prepareFpixels(); // GPU generator (explicit, CUDA-sensitive); also builds the QuadCLT instances = borrowed-calibration source
if (clt_parameters.curt.calib) {
// By Claude on 07/03/2026: first step of the CUAS RT processing flow - per-sensor photometric
// (re)calibration: fit a+b*x over safe (weak/far) tiles, fold into the 16+16 lwir offsets/scales,
// apply in memory (master_CLT + reference scene + quadCLT_main -> top-menu config, template for
// new instances) and save the reference scene's INTERFRAME corr-xml. Idempotent (a~0, b~1 when
// already calibrated). Debug stacks (-CUAS-PERSENSOR[-ADJ]) saved only in curt_cond_test mode.
CuasMotion.rtPhotometricCalibration(
clt_parameters, // CLTParameters clt_parameters,
master_CLT, // QuadCLT master_CLT (combo DSI source, in-memory apply)
quadCLTs[ref_index], // QuadCLT ref_scene (photometric owner, INTERFRAME corr-xml)
quadCLT_main, // QuadCLT quadCLT_main (main config + new-instance template)
clt_parameters.curt.cond_test, // boolean save_stacks
ImageDtt.THREADS_MAX, // int threadsMax,
debugLevel); // int debugLevel
}
if (clt_parameters.curt.rend_test) {
// MINIMAL RT ingest+render certification (curt_rend_test, precedence over the pose
// test): per scene raw /jp4/ -> conditionSceneToGpu -> virtual-grid render at
// BORROWED stored pose + ERS rates -> -CUAS-RT-RENDER hyperstack, comparable
// slice-by-slice to the -CUAS-INDIVIDUAL/MERGED-CUAS-DBG oracle products.
// Certify/debug THIS chain first, only then the pose test (Andrey 07/05/2026).
// By Claude on 07/05/2026.
System.out.println("===== CUAS RT render test (curt_rend_test): ingest+render sequence vs oracle DBG =====");
com.elphel.imagej.cuas.rt.CuasRender.testRenderSequence(
clt_parameters, // CLTParameters clt_parameters,
master_CLT, // QuadCLT center_CLT,
quadCLTs, // QuadCLT [] quadCLTs,
debugLevel); // int debugLevel
} else if (clt_parameters.curt.pose_test) {
// RT pose-adjustment prototype phase A (curt_pose_test), runs INSTEAD of RT detection:
// re-generate per-scene 3-angle poses against the virtual-center reference, ascending,
// prediction-seeded, single pass on the final combo DSI. Output: -POSE-RT-TEST.csv +
// fitted-vs-stored summary (truth = scenes_poses restored from INTERFRAME corr-xml).
// By Claude on 07/03/2026, from Andrey's design.
System.out.println("===== CUAS RT pose test (curt_pose_test): per-scene 3-angle fit vs virtual center =====");
com.elphel.imagej.cuas.rt.CuasPoseRT.testPoseSequence(
clt_parameters, // CLTParameters clt_parameters,
master_CLT, // QuadCLT center_CLT,
quadCLTs, // QuadCLT [] quadCLTs,
debugLevel); // int debugLevel
} else if (clt_parameters.curt.cond_test) {
// Conditioning/calibration diagnostic (curt_cond_test), runs INSTEAD of RT detection:
// raw /jp4/ baseline (no photometric/FPN/conditioning), saved as -CUAS-PERSENSOR-RAW for
// side-by-side compare with the conditioned -CUAS-PERSENSOR (saved by the calibration step when
// curt_calib is on, or rendered here otherwise). Both stacks use the same uniform sensor-domain
// task grid - never leftover GPU task state. By Claude on 07/01/2026, restructured 07/03/2026.
System.out.println("===== CUAS RT conditioning test (curt_cond_test): per-sensor average spread =====");
if (!clt_parameters.curt.calib) { // conditioned baseline not yet rendered by the calibration step
QuadCLT cond_phys = master_CLT.getGPUQuad().getQuadCLT(); // physical scene bound to the GPU
if (cond_phys != null) { // the actual image_data (bypass image_data_alt), same grid as raw
CuasMotion.perSensorFromData(clt_parameters, master_CLT, cond_phys.getOrigImageData(),
"-CUAS-PERSENSOR", ImageDtt.THREADS_MAX, debugLevel);
}
}
CuasMotion.perSensorFromRawJp4(clt_parameters, master_CLT, ImageDtt.THREADS_MAX, debugLevel);
// A2 step 2 validation: NaN-aware cross-sensor TD consolidation (CPU oracle of the
// future clt_average_sensors kernel) checked against the imclt linearity oracle:
// imclt(TD-average) must equal pixel-average of per-sensor imclt renders.
// Uses the raw-jp4 16-sensor TD just built above. By Claude on 07/05/2026.
com.elphel.imagej.cuas.rt.CuasTD.validateConsolidation(
master_CLT.getGPUQuad(), // GpuQuad with the current 16-sensor TD
master_CLT, // debug-stack save target (-CUAS-TDAVG-CHECK)
debugLevel);
} else {
cuasRangingRT.saveUasFlightLogCsv(uasLogReader, imp_targets); // UAS flight-log truth -> <name>-UAS_DATA.tsv (mode-0 only; needs QuadCLT pose). By Claude on 06/24/2026
new CuasDetectRT(
com.elphel.imagej.cuas.rt.CuasRT.run(
clt_parameters, // CLTParameters clt_parameters,
master_CLT, // QuadCLT master_CLT (virtual "-CENTER", borrowed-data owner)
quadCLTs, // QuadCLT [] quadCLTs (per-scene instances)
quadCLT_main, // QuadCLT quadCLT_main (top-menu template)
ref_index, // int ref_index,
uasLogReader, // UasLogReader uasLogReader,
imp_targets, // ImagePlus imp_targets (no GPU inside CuasDetectRT)
master_CLT.getX3dDirectory(), // String model_directory (outputs land like oracle)
master_CLT.getImageName(), // String core_base_name
master_CLT.correctionsParameters.cuasSynth, // String cuas_synth_dir (shared, list SET; "" -> model_directory)
master_CLT.correctionsParameters.cuasNoise, // String cuas_noise (inline per-level scales, list SET; "" -> sqrt default). By Claude on 06/24/2026
debugLevel).detectTargets(
clt_parameters, // CLTParameters clt_parameters,
batch_mode, // boolean batch_mode,
debugLevel); // int debugLevel
}
}
if (generate_mapped || reuse_video) { // modifies combo_dsn_final ?
int tilesX = master_CLT.getTileProcessor().getTilesX();
......
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