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

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: default avatarClaude Fable 5 <noreply@anthropic.com>
parent 194d87a8
Loading
Loading
Loading
Loading
+14 −1
Original line number Original line Diff line number Diff line
@@ -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 {
				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,
+30 −0
Original line number Original line Diff line number Diff line
@@ -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
+4 −117
Original line number Original line Diff line number Diff line
@@ -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 {
				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();