Commit 96b1c3d5 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: renderSceneSequence() hyperstack mode (make_hyper) + per-sensor averages

Add make_hyper parameter (code-selected, not in settings): 0 - flat stack
(old behavior, all pre-existing call sites); >0 - transpose to hyperstack
[sensors][avgs+timestamps][pixel], z (top slider) - timestamps, t (bottom
slider) - sensor channels; 2 - insert per-timestamp average of all used
sensors as first channel (17th), computed from final conditioned slices.
Per-sensor full + center-fraction averages now work in individual mode
(pre-calculated merged-only average falls back to slice computation with
a warning instead of AIOOBE). Number of average frames stays variable
(0/1/2); fopen paths bit-identical by design.
Verified on 495-scene CUAS sequence: INDIVIDUAL debug hyperstack matches
the MERGED convention - to be used as oracle for RT conditioning.
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent eae75ef1
...@@ -139,12 +139,26 @@ public class CuasRanging { ...@@ -139,12 +139,26 @@ public class CuasRanging {
boolean merge_all = false; // clt_parameters.imp.merge_all || !um_mono; // no unsharp mask -> terrain->merge_all boolean merge_all = false; // clt_parameters.imp.merge_all || !um_mono; // no unsharp mask -> terrain->merge_all
int sensor_mask = merge_all? 1 : -1; int sensor_mask = merge_all? 1 : -1;
if (dbg_save_fpixels) { if (dbg_save_fpixels) {
// By Claude on 07/02/2026: per-sensor averages can only be calculated from slices (the precalculated CLT center
// average is merged-channels only), so add_average implies calculating here. Number of average frames is
// variable: 0, 1 (full) or 2 (full + center fraction), matching the merged sequence convention.
boolean dbg_add_average = clt_parameters.imp.add_average || clt_parameters.imp.calculate_average; // By Claude on 07/02/2026
int [] dbg_average_range = (clt_parameters.imp.add_center_average && dbg_add_average)? new int[2] : null; // By Claude on 07/02/2026
if (dbg_average_range != null) { // By Claude on 07/02/2026: same as in renderSceneSequence() wrapper
int num_scenes = scenes.length;
dbg_average_range[0] = (int) Math.round (num_scenes * (1 - clt_parameters.imp.center_avg_frac)/2);
dbg_average_range[0] = Math.max(0, dbg_average_range[0]);
dbg_average_range[0] = Math.min(num_scenes-1, dbg_average_range[0]);
dbg_average_range[1] = num_scenes-1 - dbg_average_range[0];
dbg_average_range[1] = Math.max(dbg_average_range[0], dbg_average_range[1]);
dbg_average_range[1] = Math.min(num_scenes-1, dbg_average_range[1]);
}
ImagePlus imp_targets= OpticalFlow.renderSceneSequence( ImagePlus imp_targets= OpticalFlow.renderSceneSequence(
clt_parameters, // CLTParameters clt_parameters, clt_parameters, // CLTParameters clt_parameters,
true, // center_CLT.hasCenterClt(), // boolean mode_cuas, true, // center_CLT.hasCenterClt(), // boolean mode_cuas,
false, // clt_parameters.imp.um_mono, // boolean um_mono, false, // clt_parameters.imp.um_mono, // boolean um_mono,
clt_parameters.imp.calculate_average, // boolean insert_average, // then add new parameter, keep add average dbg_add_average, // boolean insert_average, // By Claude on 07/02/2026: was clt_parameters.imp.calculate_average
null, // int [] average_range, dbg_average_range, // int [] average_range, // By Claude on 07/02/2026: was null
null, // average_channels, // average_slice, null, // average_channels, // average_slice,
clt_parameters.imp.subtract_average, // boolean subtract_average, clt_parameters.imp.subtract_average, // boolean subtract_average,
clt_parameters.imp.running_average, // int running_average, clt_parameters.imp.running_average, // int running_average,
...@@ -155,6 +169,7 @@ public class CuasRanging { ...@@ -155,6 +169,7 @@ public class CuasRanging {
cuas_atr, // double [] stereo_atr, // offset reference orientation (cuas) cuas_atr, // double [] stereo_atr, // offset reference orientation (cuas)
sensor_mask, // sensor_mask, // int sensor_mask, sensor_mask, // sensor_mask, // int sensor_mask,
merge_all, // boolean merge_all, merge_all, // boolean merge_all,
2, // int make_hyper, // By Claude on 07/02/2026: hyperstack, per-timestamp sensor average as first channel
scenes_suffix, // String suffix, scenes_suffix, // String suffix,
ds_vantage[0], // selected_disparity, // double [] ref_disparity, ds_vantage[0], // selected_disparity, // double [] ref_disparity,
scenes, // QuadCLT [] quadCLTs, scenes, // QuadCLT [] quadCLTs,
...@@ -183,6 +198,7 @@ public class CuasRanging { ...@@ -183,6 +198,7 @@ public class CuasRanging {
cuas_atr, // double [] stereo_atr, // offset reference orientation (cuas) cuas_atr, // double [] stereo_atr, // offset reference orientation (cuas)
sensor_mask, // sensor_mask, // int sensor_mask, sensor_mask, // sensor_mask, // int sensor_mask,
merge_all, // boolean merge_all, merge_all, // boolean merge_all,
0, // int make_hyper, // By Claude on 07/02/2026: flat stack - keep old behavior
scenes_suffix, // String suffix, scenes_suffix, // String suffix,
ds_vantage[0], // selected_disparity, // double [] ref_disparity, ds_vantage[0], // selected_disparity, // double [] ref_disparity,
scenes, // QuadCLT [] quadCLTs, scenes, // QuadCLT [] quadCLTs,
......
...@@ -7537,6 +7537,7 @@ java.lang.NullPointerException ...@@ -7537,6 +7537,7 @@ java.lang.NullPointerException
cuas_atr, // double [] stereo_atr, // offset reference orientation (cuas) cuas_atr, // double [] stereo_atr, // offset reference orientation (cuas)
sensor_mask, // int sensor_mask, sensor_mask, // int sensor_mask,
merge_all, // boolean merge_all, merge_all, // boolean merge_all,
0, // int make_hyper, // By Claude on 07/02/2026: flat stack - keep old behavior
scenes_suffix, // String suffix, scenes_suffix, // String suffix,
ds_vantage[0], // selected_disparity, // double [] ref_disparity, ds_vantage[0], // selected_disparity, // double [] ref_disparity,
quadCLTs, // QuadCLT [] quadCLTs, quadCLTs, // QuadCLT [] quadCLTs,
...@@ -8231,6 +8232,7 @@ java.lang.NullPointerException ...@@ -8231,6 +8232,7 @@ java.lang.NullPointerException
null, // double [] stereo_atr, // offset reference orientation (cuas) null, // double [] stereo_atr, // offset reference orientation (cuas)
1, // int sensor_mask, 1, // int sensor_mask,
merge_all, // boolean merge_all, merge_all, // boolean merge_all,
0, // int make_hyper, // By Claude on 07/02/2026: flat stack - keep old behavior
scenes_suffix, // String suffix, scenes_suffix, // String suffix,
elevation_disparity, // selected_disparity, // double [] ref_disparity, elevation_disparity, // selected_disparity, // double [] ref_disparity,
quadCLTs, // QuadCLT [] quadCLTs, quadCLTs, // QuadCLT [] quadCLTs,
...@@ -10275,6 +10277,7 @@ java.lang.NullPointerException ...@@ -10275,6 +10277,7 @@ java.lang.NullPointerException
null, // double [][] post_rotate, null, // double [][] post_rotate,
sensor_mask, // int sensor_mask, sensor_mask, // int sensor_mask,
merge_all, // boolean merge_all, merge_all, // boolean merge_all,
0, // int make_hyper, // By Claude on 07/02/2026: flat stack - keep old behavior
suffix_in, // String suffix_in, suffix_in, // String suffix_in,
ref_disparity, // double [] ref_disparity, ref_disparity, // double [] ref_disparity,
selected_scenes, // QuadCLT [] quadCLTs, selected_scenes, // QuadCLT [] quadCLTs,
...@@ -10300,6 +10303,7 @@ java.lang.NullPointerException ...@@ -10300,6 +10303,7 @@ java.lang.NullPointerException
double [] stereo_atr_in, // offset reference orientation (cuas) double [] stereo_atr_in, // offset reference orientation (cuas)
int sensor_mask, int sensor_mask,
boolean merge_all, boolean merge_all,
int make_hyper, // By Claude on 07/02/2026: 0 - flat stack (as before); 1 - hyperstack (z - timestamps, t - channels); 2 - + per-timestamp sensor average as first channel
String suffix_in, String suffix_in,
double [] ref_disparity, double [] ref_disparity,
QuadCLT [] quadCLTs, QuadCLT [] quadCLTs,
...@@ -10324,6 +10328,7 @@ java.lang.NullPointerException ...@@ -10324,6 +10328,7 @@ java.lang.NullPointerException
null, // double [][] post_rotate, null, // double [][] post_rotate,
sensor_mask, // int sensor_mask, sensor_mask, // int sensor_mask,
merge_all, // boolean merge_all, merge_all, // boolean merge_all,
make_hyper, // int make_hyper, // By Claude on 07/02/2026
suffix_in, // String suffix_in, suffix_in, // String suffix_in,
ref_disparity, // double [] ref_disparity, ref_disparity, // double [] ref_disparity,
quadCLTs, // QuadCLT [] quadCLTs, quadCLTs, // QuadCLT [] quadCLTs,
...@@ -10349,6 +10354,7 @@ java.lang.NullPointerException ...@@ -10349,6 +10354,7 @@ java.lang.NullPointerException
double [][] post_rotate, double [][] post_rotate,
int sensor_mask, int sensor_mask,
boolean merge_all, boolean merge_all,
int make_hyper, // By Claude on 07/02/2026: 0 - flat stack (as before); >0 and multiple channels: hyperstack [channels][frames]: z (top slider) - timestamps, t (bottom slider) - sensor channels; 2 - additionally insert per-timestamp average of all sensors as the first channel
String suffix_in, String suffix_in,
double [] ref_disparity, // may be ground disparity double [] ref_disparity, // may be ground disparity
QuadCLT [] quadCLTs, QuadCLT [] quadCLTs,
...@@ -10402,6 +10408,7 @@ java.lang.NullPointerException ...@@ -10402,6 +10408,7 @@ java.lang.NullPointerException
int nch = 0; int nch = 0;
for (int i = 0; i < num_sens; i++) if (((sensor_mask >> i) & 1) != 0) channels[nch++] = i; for (int i = 0; i < num_sens; i++) if (((sensor_mask >> i) & 1) != 0) channels[nch++] = i;
ImageStack stack_scenes = null; ImageStack stack_scenes = null;
ArrayList<String> frame_names = new ArrayList<String>(); // By Claude on 07/02/2026: per-frame base names ("average", "average-f:l", timestamps) for hyperstack labels
int dbg_scene = -95; int dbg_scene = -95;
double [][] ref_pXpYD; double [][] ref_pXpYD;
double [][] ref_pXpYD_or_null = null; // debugging cuas mode keeping old double [][] ref_pXpYD_or_null = null; // debugging cuas mode keeping old
...@@ -10618,6 +10625,7 @@ java.lang.NullPointerException ...@@ -10618,6 +10625,7 @@ java.lang.NullPointerException
} }
} }
num_avg_slices++; num_avg_slices++;
frame_names.add("average"); // By Claude on 07/02/2026
if (average_range != null) { if (average_range != null) {
int last_predicted_scene = quadCLTs.length - 1; // possible nulls will be skipped, so final stack may be smaller int last_predicted_scene = quadCLTs.length - 1; // possible nulls will be skipped, so final stack may be smaller
if (average_range.length == 0) { if (average_range.length == 0) {
...@@ -10635,10 +10643,12 @@ java.lang.NullPointerException ...@@ -10635,10 +10643,12 @@ java.lang.NullPointerException
} }
} }
num_avg_slices++; num_avg_slices++;
frame_names.add("average-"+average_range[0]+":"+average_range[1]); // By Claude on 07/02/2026
} }
} }
} }
frame_names.add(ts); // By Claude on 07/02/2026
for (int i = 0; i < channels.length; i++) { for (int i = 0; i < channels.length; i++) {
stack_scenes.addSlice( stack_scenes.addSlice(
ts+"-"+channels[i], ts+"-"+channels[i],
...@@ -10648,8 +10658,12 @@ java.lang.NullPointerException ...@@ -10648,8 +10658,12 @@ java.lang.NullPointerException
if (insert_average) { // calculate average to average slices (one per channel) if (insert_average) { // calculate average to average slices (one per channel)
// calculate average from all scenes // calculate average from all scenes
int num_scenes = (stack_scenes.getSize() / channels.length) - num_avg_slices; // remove averages (1 or 2) int num_scenes = (stack_scenes.getSize() / channels.length) - num_avg_slices; // remove averages (1 or 2)
if ((average_slice != null) && (average_slice.length < channels.length)) { // By Claude on 07/02/2026: pre-calculated average was made for merged channels only
System.out.println("renderSceneSequence(): pre-calculated average has "+average_slice.length+
" channel(s) < "+channels.length+" used channels - calculating averages from slices instead");
}
for (int navg = 0; navg < num_avg_slices; navg++) { for (int navg = 0; navg < num_avg_slices; navg++) {
if ((navg == 0) && (average_slice != null)) { // pre-calculated average if ((navg == 0) && (average_slice != null) && (average_slice.length >= channels.length)) { // pre-calculated average // By Claude on 07/02/2026: added channels condition
for (int nchn = 0; nchn < channels.length; nchn++) { for (int nchn = 0; nchn < channels.length; nchn++) {
float [] avg_slice = (float[]) stack_scenes.getPixels(nchn+1); float [] avg_slice = (float[]) stack_scenes.getPixels(nchn+1);
System.arraycopy( System.arraycopy(
...@@ -10685,7 +10699,10 @@ java.lang.NullPointerException ...@@ -10685,7 +10699,10 @@ java.lang.NullPointerException
} }
} }
if (running_average >1) { if (running_average >1) {
int scene_0 = insert_average? 1:0; // By Claude on 07/02/2026: in the new hyperstack mode skip ALL average slices (may be 2 in fopen-style configs).
// Old (flat) behavior kept bit-identical for fopen "by design": with 2 averages it treated the center-average
// slice as the first scene (suspected pre-existing bug, not fixed here to avoid untested fopen changes).
int scene_0 = insert_average? ((make_hyper > 0)? num_avg_slices : 1) : 0;
int num_scenes = (stack_scenes.getSize() / channels.length) - scene_0; // remove average int num_scenes = (stack_scenes.getSize() / channels.length) - scene_0; // remove average
for (int nchn = 0; nchn < channels.length; nchn++) { for (int nchn = 0; nchn < channels.length; nchn++) {
int num_pix = ((float[]) stack_scenes.getPixels(nchn+1)).length; int num_pix = ((float[]) stack_scenes.getPixels(nchn+1)).length;
...@@ -10742,6 +10759,49 @@ java.lang.NullPointerException ...@@ -10742,6 +10759,49 @@ java.lang.NullPointerException
um_sigma, // final double um_sigma, um_sigma, // final double um_sigma,
um_weight); // final double um_weight) um_weight); // final double um_weight)
} }
// By Claude on 07/02/2026: optionally convert flat [frames*channels][pixel] stack (channel-inner) to a hyperstack
// [channels][frames][pixel] (timestamp-inner): z (top slider) - average(s)+timestamps, t (bottom slider) - sensor channels.
// make_hyper == 2 additionally inserts a per-timestamp average of all used sensors as the first channel
// (orthogonal to the per-sensor "average"/"average-f:l" frames, which stay first along the timestamp axis).
// Averaging uses the final (conditioned: running average/subtract average/UM) slices, so the average channel
// exactly corresponds to what the per-sensor channels show.
if ((make_hyper > 0) && (channels.length > 1) && !toRGB) {
ImageStack stack_flat = imp_scenes.getStack();
int num_frames = stack_flat.getSize() / channels.length;
boolean add_chn_average = (make_hyper > 1);
int out_channels = channels.length + (add_chn_average? 1:0);
int num_pix = stack_flat.getWidth()*stack_flat.getHeight();
ImageStack stack_hyper = new ImageStack(stack_flat.getWidth(), stack_flat.getHeight());
if (add_chn_average) { // per-timestamp average of all used sensors - first channel (to match average frames being first)
for (int nframe = 0; nframe < num_frames; nframe++) {
float [] favg = new float [num_pix];
int [] navg = new int [num_pix];
for (int nchn = 0; nchn < channels.length; nchn++) {
float [] fpix = (float[]) stack_flat.getPixels(nframe*channels.length + nchn + 1);
for (int npix = 0; npix < num_pix; npix++) if (!Float.isNaN(fpix[npix])){
favg[npix] += fpix[npix];
navg[npix]++;
}
}
for (int npix = 0; npix < num_pix; npix++) {
favg[npix] = (navg[npix] > 0)? (favg[npix]/navg[npix]) : Float.NaN;
}
String frame_name = (nframe < frame_names.size())? frame_names.get(nframe) : ("frame"+nframe);
stack_hyper.addSlice(frame_name+"-avg", favg);
}
}
for (int nchn = 0; nchn < channels.length; nchn++) { // transpose: timestamp becomes the inner dimension
for (int nframe = 0; nframe < num_frames; nframe++) {
int indx = nframe*channels.length + nchn + 1;
stack_hyper.addSlice(stack_flat.getSliceLabel(indx), stack_flat.getPixels(indx));
}
}
ImagePlus imp_hyper = new ImagePlus(suffix, stack_hyper);
imp_hyper.setDimensions(1, num_frames, out_channels); // z - averages+timestamps (top slider), t - sensor channels (bottom slider)
imp_hyper.setOpenAsHyperStack(true);
imp_hyper.getProcessor().resetMinAndMax();
return imp_hyper;
}
return imp_scenes; return imp_scenes;
} }
......
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