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 { ...@@ -4174,6 +4174,7 @@ public class CuasMotion {
QuadCLT master_CLT, QuadCLT master_CLT,
QuadCLT ref_scene, QuadCLT ref_scene,
QuadCLT quadCLT_main, QuadCLT quadCLT_main,
QuadCLT [] scenes, // already-spawned per-scene instances (may be null) // By Claude on 07/05/2026
boolean save_stacks, boolean save_stacks,
int threadsMax, int threadsMax,
int debugLevel) { int debugLevel) {
...@@ -4203,9 +4204,21 @@ public class CuasMotion { ...@@ -4203,9 +4204,21 @@ public class CuasMotion {
debugLevel, // int debugLevel debugLevel, // int debugLevel
true); // boolean keep_averages: average offset = 0, average scale = 1.0 true); // boolean keep_averages: average offset = 0, average scale = 1.0
if (ab != null) { 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( applyLwirLinearCalibration(
phys, // QuadCLT phys_scene 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 ref_scene, // QuadCLT save_scene -> INTERFRAME corr-xml
ab[0], // double [] a, ab[0], // double [] a,
ab[1], // double [] b, ab[1], // double [] b,
......
...@@ -141,6 +141,36 @@ public class CuasConditioning { ...@@ -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 * 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 * calibration (curt_calib-updated lwir scales/offsets/scales2 + per-pixel FPN from the
......
...@@ -442,116 +442,6 @@ public class CuasPoseRT { ...@@ -442,116 +442,6 @@ public class CuasPoseRT {
return com.elphel.imagej.gpu.GPUTileProcessor.DTT_SIZE; 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 * 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 * measurement engine and the same IntersceneLma solver/exit rules as the oracle
...@@ -777,13 +667,10 @@ public class CuasPoseRT { ...@@ -777,13 +667,10 @@ public class CuasPoseRT {
mb_max_gain, // double mb_max_gain, mb_max_gain, // double mb_max_gain,
null, // double [][] mb_vectors (center does not move) null, // double [][] mb_vectors (center does not move)
debugLevel); // int debug_level debugLevel); // int debug_level
// APPROPRIATE the borrowed center: offset the reference CLT so it matches the // The borrowed center arrives ALREADY APPROPRIATED (zero-DC, in memory) from
// zero-sky conditioned scenes (curt.dc_center; 0 = auto from the borrowed center // CuasRT.appropriateCenter() - the borrow boundary at RT initialization; the
// average, adopted for persistence). Firewall: the borrowed data is zeroed ONCE // getCenterClt()/setReferenceGPU above therefore uploaded the zeroed reference.
// here at the boundary - nothing downstream compensates again. Failure-safe: any // Nothing to compensate here. By Claude on 07/05/2026, per Andrey's ruling.
// 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);
// ---- Stored poses (comparison truth + seed) ---- // ---- Stored poses (comparison truth + seed) ----
final ErsCorrection ers_center = center_CLT.getErsCorrection(); final ErsCorrection ers_center = center_CLT.getErsCorrection();
......
/**
** CuasRT.java - the single entry point of the CUAS RT (real-time) mode
**
** Copyright (C) 2026 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** CuasRT.java is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
** -----------------------------------------------------------------------------**
**
*/
package com.elphel.imagej.cuas.rt;
import java.util.Arrays;
import com.elphel.imagej.cuas.CuasMotion;
import com.elphel.imagej.cuas.CuasRanging;
import com.elphel.imagej.gpu.GpuQuad;
import com.elphel.imagej.gpu.TpTask;
import com.elphel.imagej.ims.UasLogReader;
import com.elphel.imagej.cameras.CLTParameters;
import com.elphel.imagej.tileprocessor.ImageDtt;
import com.elphel.imagej.tileprocessor.Interscene;
import com.elphel.imagej.tileprocessor.OpticalFlow;
import com.elphel.imagej.tileprocessor.QuadCLT;
import com.elphel.imagej.tileprocessor.TwoQuadCLT;
import ij.ImagePlus;
/**
* The SINGLE entry point of the CUAS RT mode. OpticalFlow.buildSeries() delegates
* here on (curt.en && hasCenterClt) and knows nothing else about RT.
*
* Top-level flow (keep {@link #run} READABLE - it is the map of the RT mode):
* 1. INITIALIZATION - transform the borrowed data (center ingest, per-scene
* instances) and fix missing calibration (per-sensor photometric).
* 2. DIAGNOSTICS - optional exclusive test modes (rend_test / pose_test /
* cond_test). They REPLACE the production run and return; they must never
* leak into the production flow nor grow the top method.
* 3. PRODUCTION - the per-input(scene) processing loop and target detection.
* MVP target shape: for each input scene, a multi-threaded read of the 16
* sensor /jp4/*.tiff (or their hardware-stream equivalents later) ->
* CuasConditioning -> GPU -> pose -> detection. Today the detection still
* consumes the bulk oracle product built in INITIALIZATION - replace
* per-piece without changing this top structure.
*
* Moved from OpticalFlow.buildSeries() (was inline at the curt_en branch)
* by Claude on 07/05/2026, per Andrey's structure ruling.
*/
public class CuasRT {
/** See the class comment: initialization -> optional diagnostics -> production. */
public static void run(
final CLTParameters clt_parameters,
final QuadCLT master_CLT, // virtual "-CENTER" (borrowed-data owner)
final QuadCLT [] quadCLTs, // per-scene instances (built during initialization)
final QuadCLT quadCLT_main, // top-menu parameter-transfer instance / new-instance template
final int ref_index,
final UasLogReader uasLogReader,
final boolean batch_mode,
final int debugLevel) {
System.out.println("===== Running CUAS RT detection (curt_en). =====");
// ---- 1. INITIALIZATION: borrowed data + calibration ----
final CuasRanging cuasRanging = new CuasRanging(
clt_parameters, // CLTParameters clt_parameters,
master_CLT, // QuadCLT center_CLT,
quadCLTs, // QuadCLT [] scenes,
debugLevel); // int debugLevel
final ImagePlus imp_targets = ingest(cuasRanging);
calibratePhotometric(clt_parameters, master_CLT, quadCLTs, quadCLT_main, ref_index, debugLevel);
appropriateCenter(clt_parameters, master_CLT, debugLevel);
// ---- 2. DIAGNOSTICS (optional, exclusive - replace production and return) ----
if (diagnostics(clt_parameters, master_CLT, quadCLTs, debugLevel)) {
return;
}
// ---- 3. PRODUCTION: per-scene processing + target detection ----
detect(clt_parameters, cuasRanging, master_CLT, imp_targets, uasLogReader, batch_mode, debugLevel);
}
/**
* Borrowed-data ingest. Today: the GPU oracle generator CuasRanging.prepareFpixels()
* (explicit here, CUDA-sensitive - may be incompatible with a future CUDA; the result
* is a plain ImagePlus handed to the CUDA-free detection). It also builds the per-scene
* QuadCLT instances = the borrowed-calibration source. MVP replacement: per-scene raw
* /jp4/ ingest (CuasConditioning.conditionSceneToGpu) inside the production loop, with
* only the center borrow remaining here. By Claude on 06/24/2026 (moved 07/05/2026).
*/
private static ImagePlus ingest(final CuasRanging cuasRanging) {
return cuasRanging.prepareFpixels();
}
/**
* Per-sensor photometric (re)calibration (curt.calib): 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 + the ALREADY-SPAWNED per-scene instances) and save
* the reference scene's INTERFRAME corr-xml. Idempotent (a~0, b~1 when already
* calibrated). NOTE: the recalibrated values persist across sessions only via the
* ref INTERFRAME and a top-menu configuration save after a calib-ON run - a stale
* main config silently re-imposes the old photometric on every fresh session
* (POSE-09 lesson: 1470-count per-sensor sky spread vs 17 calibrated).
* By Claude on 07/03/2026 (moved 07/05/2026).
*/
private static void calibratePhotometric(
final CLTParameters clt_parameters,
final QuadCLT master_CLT,
final QuadCLT [] quadCLTs,
final QuadCLT quadCLT_main,
final int ref_index,
final int debugLevel) {
if (!clt_parameters.curt.calib) {
return;
}
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)
quadCLTs, // QuadCLT [] scenes (already spawned by the ingest above)
clt_parameters.curt.cond_test, // boolean save_stacks
ImageDtt.THREADS_MAX, // int threadsMax,
debugLevel); // int debugLevel
}
private static final double [] ZERO3 = {0.0, 0.0, 0.0};
/**
* APPROPRIATE the borrowed center (the borrow boundary - the ONLY place the RT flow
* transforms the borrowed QuadCLT data): offset the center CLT so it matches the
* zero-sky conditioned scenes, IN MEMORY (master_CLT.setCenterClt) - everything
* downstream (pose test, render, lean measure) then sees an already-zeroed center and
* never compensates again. The -CLT-UPDATED disk save happens upstream of CuasRT.run,
* so the zeroed copy never persists.
*
* Rationale (doc SS5a): 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, never 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), then ADOPTED into curt.dc_center
* (persisted on the next configuration save - borrowable later, like the photometric
* calibration).
* Mechanics: full-grid conditioned-disparity tasks (the ONE RT disparity source) ->
* setReferenceGPU (raw center up, tasks built) -> AUTO level from the reference render
* -> DC pattern = CLT of a UNIFORM image through the back-propagation convert
* (setImageCenter on the GPU-ASSOCIATED QuadCLT + no_kernels + use_center_image, the
* CorrectionFPN recipe) -> the PURE kernel CuasConditioning.zeroDcClt (knows no
* QuadCLT) -> store back in memory + re-upload the reference slots. Failure-safe: any
* missing piece leaves the center as-is (a warning, not a break).
* By Claude on 07/05/2026, from Andrey's design (moved here from CuasPoseRT per the
* borrow-boundary ruling).
*/
private static void appropriateCenter(
final CLTParameters clt_parameters,
final QuadCLT center_CLT,
final int debugLevel) {
final float [] fclt0 = center_CLT.getCenterClt();
if (fclt0 == null) {
System.out.println("appropriateCenter(): no center CLT - center NOT offset");
return;
}
final GpuQuad gpuQuad = center_CLT.getGPUQuad();
// 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]);
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(
null, // final Rectangle [] extra_woi,
center_disparity, // final double [] disparity_ref,
ZERO3, // final double [] scene_xyz,
ZERO3, // final double [] scene_atr,
center_CLT, // final QuadCLT scene_QuadClt,
center_CLT); // final QuadCLT reference_QuadClt
final TpTask [][] tp_tasks_center = Interscene.setReferenceGPU(
clt_parameters, // CLTParameters clt_parameters,
center_CLT, // QuadCLT ref_scene,
center_disparity, // double [] ref_disparity,
pXpYD_center, // double [][] ref_pXpYD,
new float [][] {fclt0}, // final float [][] fclt,
all_tiles, // final boolean [] selection (FULL grid - the appropriation covers every tile)
clt_parameters.imp.margin, // final int margin,
clt_parameters.imp.mb_tau, // double mb_tau,
clt_parameters.imp.mb_max_gain_inter, // double mb_max_gain,
null, // double [][] mb_vectors (center does not move)
debugLevel); // int debug_level
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),
// just uploaded by setReferenceGPU above. By Claude on 07/05/2026.
final float [] apx = CuasMotion.perSensorImagesFromTD(gpuQuad, true)[0];
if (apx == null) {
System.out.println("appropriateCenter(): reference render unavailable - center 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("appropriateCenter(): reference render all-NaN in the sky half - center 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("appropriateCenter(): 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.
// The uniform image MUST be set on the GpuQuad's ASSOCIATED QuadCLT: both backends
// read gpuQuad.getQuadCLT().getImageCenter() (GpuQuadJna.setBayerImages(center=true),
// JCuda base alike) - the CorrectionFPN recipe sets it there too. Setting it on
// center_CLT reads a null/stale center -> pattern ~0 -> silent no-op (POSE-09).
// By Claude on 07/05/2026.
final QuadCLT gpu_CLT = gpuQuad.getQuadCLT();
final int [] wh = center_CLT.getGeometryCorrection().getSensorWH();
final double [][] saved_center = gpu_CLT.getImageCenter();
float [] pattern = null;
try {
final double [][] uniform = new double [][] {new double [wh[0]*wh[1]]};
Arrays.fill(uniform[0], 1.0);
gpu_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[0],
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("appropriateCenter(): DC-pattern conversion FAILED ("+e.getMessage()+
") - center NOT offset");
pattern = null;
} finally {
gpu_CLT.setImageCenter(saved_center); // never leave back-propagate mode on
}
if (pattern == null) return;
// Guard against the silent-no-op class: a uniform-1.0 image converts to TD
// coefficients of order ~1; an (effectively) empty GPU center buffer converts to
// exact zeros. Warn loudly instead of "offsetting" by nothing. By Claude on 07/05/2026.
float pattern_max = 0f;
for (int i = 0; i < pattern.length; i++) {
final float ap = Math.abs(pattern[i]);
if (!Float.isNaN(ap) && (ap > pattern_max)) pattern_max = ap;
}
if (pattern_max < 1e-6) {
System.out.println("appropriateCenter(): DC pattern is all-zero (max |p| = "+pattern_max+
") - the uniform center image did not reach the GPU. Center NOT offset");
return;
}
final float [] fclt_zeroed = CuasConditioning.zeroDcClt(fclt0, pattern, level); // the pure kernel
center_CLT.setCenterClt(fclt_zeroed); // in-memory only: -CLT-UPDATED was saved upstream, raw stays on disk
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("appropriateCenter(): borrowed center CLT zeroed in memory (level "+level+
") - downstream consumers see the appropriated center only");
}
/**
* Optional EXCLUSIVE diagnostic modes - each replaces the production run. Precedence
* (unchanged from the OpticalFlow inline era): rend_test > pose_test > cond_test.
* Keep new tests HERE - the production path and the top method must not grow.
* @return true if a diagnostic ran (caller returns without production processing).
*/
private static boolean diagnostics(
final CLTParameters clt_parameters,
final QuadCLT master_CLT,
final QuadCLT [] quadCLTs,
final int debugLevel) {
if (clt_parameters.curt.rend_test) {
// MINIMAL RT ingest+render certification: 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 =====");
CuasRender.testRenderSequence(
clt_parameters, // CLTParameters clt_parameters,
master_CLT, // QuadCLT center_CLT,
quadCLTs, // QuadCLT [] quadCLTs,
debugLevel); // int debugLevel
return true;
}
if (clt_parameters.curt.pose_test) {
// RT pose-adjustment prototype: 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 =====");
CuasPoseRT.testPoseSequence(
clt_parameters, // CLTParameters clt_parameters,
master_CLT, // QuadCLT center_CLT,
quadCLTs, // QuadCLT [] quadCLTs,
debugLevel); // int debugLevel
return true;
}
if (clt_parameters.curt.cond_test) {
// Conditioning/calibration diagnostic: 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.
CuasTD.validateConsolidation(
master_CLT.getGPUQuad(), // GpuQuad with the current 16-sensor TD
master_CLT, // debug-stack save target (-CUAS-TDAVG-CHECK)
debugLevel);
return true;
}
return false;
}
/**
* Production target detection (CUDA-free from here on: consumes the plain ImagePlus).
* By Claude on 06/24/2026 (moved 07/05/2026).
*/
private static void detect(
final CLTParameters clt_parameters,
final CuasRanging cuasRanging,
final QuadCLT master_CLT,
final ImagePlus imp_targets,
final UasLogReader uasLogReader,
final boolean batch_mode,
final int debugLevel) {
cuasRanging.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(
clt_parameters, // CLTParameters clt_parameters,
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
}
}
...@@ -7260,98 +7260,20 @@ java.lang.NullPointerException ...@@ -7260,98 +7260,20 @@ java.lang.NullPointerException
} }
} }
// CUAS RT (our code) - coexists with the oracle CuasRanging above (separate buttons/modes, no interference). // CUAS RT (our code) - coexists with the oracle CuasRanging above (separate buttons/modes,
// Generate the merged-CUAS stack on the GPU EXPLICITLY here (CuasRanging.prepareFpixels() uses the CUDA // no interference). The ENTIRE RT mode lives in cuas.rt.CuasRT (single entry:
// tile-processor kernels - may be incompatible with a future CUDA), then hand the plain ImagePlus to the // initialization -> optional diagnostics -> production) - do not grow RT code here.
// CUDA-free CuasDetectRT. By Claude on 06/24/2026 // By Claude on 07/05/2026, per Andrey's structure ruling.
if (clt_parameters.curt.en && master_CLT.hasCenterClt()) { if (clt_parameters.curt.en && master_CLT.hasCenterClt()) {
System.out.println("===== Running CUAS RT detection (curt_en). ====="); com.elphel.imagej.cuas.rt.CuasRT.run(
CuasRanging cuasRangingRT = new CuasRanging( clt_parameters, // CLTParameters clt_parameters,
clt_parameters, // CLTParameters clt_parameters, master_CLT, // QuadCLT master_CLT (virtual "-CENTER", borrowed-data owner)
master_CLT, // QuadCLT center_CLT, quadCLTs, // QuadCLT [] quadCLTs (per-scene instances)
quadCLTs, // QuadCLT [] scenes, quadCLT_main, // QuadCLT quadCLT_main (top-menu template)
debugLevel); ref_index, // int ref_index,
ImagePlus imp_targets = cuasRangingRT.prepareFpixels(); // GPU generator (explicit, CUDA-sensitive); also builds the QuadCLT instances = borrowed-calibration source uasLogReader, // UasLogReader uasLogReader,
batch_mode, // boolean batch_mode,
debugLevel); // int debugLevel
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(
clt_parameters, // CLTParameters clt_parameters,
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 ? if (generate_mapped || reuse_video) { // modifies combo_dsn_final ?
......
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