package com.elphel.imagej.cuas.rt;

import java.awt.Rectangle;
import java.nio.file.Path;

import com.elphel.imagej.cameras.CLTParameters;
import com.elphel.imagej.cameras.EyesisCorrectionParameters.CorrectionParameters;
import com.elphel.imagej.common.ShowDoubleFloatArrays;
import com.elphel.imagej.cuas.CuasMotion;
import com.elphel.imagej.cuas.CuasRanging;
import com.elphel.imagej.ims.UasLogReader;
import com.elphel.imagej.tileprocessor.QuadCLT;
import com.elphel.imagej.tileprocessor.QuadCLTCPU;

import ij.ImagePlus;
import ij.ImageStack;

public class CuasDetectRT {
	public static final String SUFFIX_FPIXELS_TIFF = "-CUAS-MERGED-CUAS.tiff"; 
	public static final String SUFFIX_LOG = "-LoG";
	public static final String SUFFIX_CONV2D = "-CONV2D";
	public static final String SUFFIX_CONV3D3 = "-CONV3D3";
	public static final String SUFFIX_CONV5D =  "-CONV5D";
	public static final String SUFFIX_RECUR =   "-RECUR"; // By Claude on 06/09/2026
	public static final String [] CONV3D3_MODES = {"plain", "winner", "power"};
	public static final int CONV3D3_MODE_PLAIN =  0;
	public static final int CONV3D3_MODE_WINNER = 1;
	public static final int CONV3D3_MODE_POWER =  2;
	public static final int MAXDECIMALS =         4;
	
	CuasRanging     cuasRanging;
	UasLogReader    uasLogReader;
	CLTParameters   clt_parameters;
	String          model_directory;
//	String []       model_names;
	QuadCLT         master_CLT;
	String          fpixels_file = null;
	String          base_name = null; // add suffix and 
	float [][]      fpixels; 
	double [][]     dpixels; // same as fpixels for faster calculations on 64-bit processors 
	int             width;
	int             height;
	String []       time_stamps;
	double          infinity;
	
	public CuasDetectRT(
			CLTParameters     clt_parameters,
			UasLogReader      uasLogReader,
			String            model_directory,
			int               debugLevel) {
		this.uasLogReader =   uasLogReader;
		this.clt_parameters = clt_parameters;
		this.model_directory = model_directory;
		this.infinity = clt_parameters.imp.cuas_infinity;
		String [] fpixels_paths = CorrectionParameters.getFilesByExtensionAsArray(model_directory, SUFFIX_FPIXELS_TIFF);
		if (fpixels_paths.length > 0) {
			if (fpixels_paths.length > 1) {
				System.out.println((fpixels_paths.length)+" files ending with \""+SUFFIX_FPIXELS_TIFF+"\" found in "+model_directory+", will use the first one.");
			}
			fpixels_file = fpixels_paths[0];
			ImagePlus imp = new ImagePlus(fpixels_file);
			width =  imp.getWidth();
			height = imp.getHeight();
			if (width > 0) {
					ImageStack stack = imp.getStack();
					// Skip non-ts ("average") slices;
					int first_slice = 1;
					int num_slices = stack.getSize();
					for (; !Character.isDigit(stack.getSliceLabel(first_slice).charAt(0)); first_slice++);
					time_stamps = new String [num_slices - first_slice + 1];
					fpixels = new float [time_stamps.length][width*height];
					for (int i = 0; i < time_stamps.length; i++) {
						time_stamps[i] = stack.getSliceLabel(first_slice + i);
						fpixels[i] = (float[]) stack.getPixels (first_slice + i);
					}
					String fpixels_name = Path.of(fpixels_file).getFileName().toString();
					base_name = fpixels_name.substring(0,fpixels_name.length() - SUFFIX_FPIXELS_TIFF.length());
			} else {
				System.out.println("Failed to read image data from "+fpixels_file);
				fpixels_file = null;
				fpixels = null;
			}
		} else {
			System.out.println("No files ending with \""+SUFFIX_FPIXELS_TIFF+"\" found in "+model_directory);
			fpixels_file = null;
			fpixels = null;
		}
		if (fpixels != null) {
			dpixels = new double [fpixels.length][width*height];
			for (int i = 0; i < fpixels.length; i++) for (int pix = 0; pix < fpixels[i].length; pix++) {
				dpixels[i][pix] = fpixels[i][pix]; 	
			}
		} else {
			dpixels = null;
		}
		return;
	}
	
	public static String d2s(double d, int maxDecimalsPlaces) {
		String fmt = String.format("%%.%df", maxDecimalsPlaces);
		String s = String.format(fmt, d);
		s = s.trim();
		while (s.endsWith("0")) {
			s = s.substring(0,s.length()-1);
		}
		if (s.endsWith(".")) {
			s = s.substring(0,s.length()-1);
		}
		return s;
	}

	public static String d2s(double d) {
		return d2s(d, MAXDECIMALS);
	}
	
	
	public int getWidth() {
		return width;
	}
	public int getHeight() {
		return height;
	}
	String [] getTimeStamps() {
		return time_stamps;
	}

	String [] getTimeStamps(int from_indx) {
		String [] time_stamps_from = new String[time_stamps.length - from_indx];
		System.arraycopy(time_stamps, from_indx, time_stamps_from, 0, time_stamps_from.length);
		return time_stamps_from;
	}
	String [] getTimeStamps(int from_indx, int tdecimate) {
		String [] time_stamps_decimated = new String[time_stamps.length / tdecimate];
		for (int i = 0; i < time_stamps_decimated.length; i++) {
			time_stamps_decimated[i] = time_stamps[(i+1)* tdecimate - 1];
		}
		return time_stamps_decimated;
	}

	
	
	public double [][] getDPpixels(){
		return dpixels;
	}
	
	public float [][] getFPixels() {
		return fpixels;
	}
	
	public String getBaseName() {
		return base_name;
	}
	
	public String getModelDirectory() {
		return model_directory;
	}
	
	public CuasMotion detectTargets(
			CLTParameters     clt_parameters,
			boolean           batch_mode,
			int               debugLevel) {
		boolean  save_linear_cuas =  true;
		int      curt_pyramid =      clt_parameters.imp.curt_pyramid;
		double   curt_psf_radius =   clt_parameters.imp.curt_psf_radius;
		double   curt_n_sigma =      clt_parameters.imp.curt_n_sigma;
		double   curt_rleak0 =       clt_parameters.imp.curt_rleak0;
		int      curt_pix_decimate = clt_parameters.imp.curt_pix_decimate;
		int      curt_vel_decimate = clt_parameters.imp.curt_vel_decimate;
		int      curt_vel_radius =   clt_parameters.imp.curt_vel_radius;
		boolean  curt_vel_thresh_en =clt_parameters.imp.curt_vel_thresh_en;
		int      curt_vel_suppr_rad =clt_parameters.imp.curt_vel_suppr_rad;
		double   curt_vel_athresh =  clt_parameters.imp.curt_vel_athresh;
		double   curt_vel_rthresh =  clt_parameters.imp.curt_vel_rthresh;
		double   curt_vel_dbg_min =  clt_parameters.imp.curt_vel_dbgmin; // temporary, testing with strong targets, make clean background
		boolean  curt_recur_en =     clt_parameters.imp.curt_recur_en;      // By Claude on 06/09/2026
		int      curt_recur_period = clt_parameters.imp.curt_recur_period;  // By Claude on 06/09/2026
		double   curt_recur_w =      clt_parameters.imp.curt_recur_w;       // By Claude on 06/09/2026
		double   curt_recur_leak =   clt_parameters.imp.curt_recur_leak;    // By Claude on 06/09/2026
		double   curt_recur_wthresh =clt_parameters.imp.curt_recur_wthresh; // By Claude on 06/09/2026
		double   curt_recur_spread = clt_parameters.imp.curt_recur_spread;  // By Claude on 06/09/2026
		int      curt_recur_max_clust = clt_parameters.imp.curt_recur_max_clust; // By Claude on 06/09/2026

		double[] curt_temp_weights = clt_parameters.imp.curt_temp_weights.clone();
      	
		boolean  curt_save_c5full =  clt_parameters.imp.curt_save_c5full;
		boolean  curt_save_c5rect =  clt_parameters.imp.curt_save_c5rect;
		Rectangle curt_save_select =  clt_parameters.imp.curt_save_select;
        final double      min_frac_last = 0.1; // make parameter
		
// Initialize LoG and other kernel parameters, calculate LoG first, show results
       	int      kernel3d3_rad = 1;
       	
       	CuasRTUtils cuasRTUtils = new CuasRTUtils (
    			curt_psf_radius,          // double   psf_radius,
				curt_n_sigma,             // double   n_sigma,
    			kernel3d3_rad,            // int      kernel3d3_rad,
    			curt_temp_weights,        // double[] temporal_weights,
                curt_pix_decimate,        // int      pixel_decimate,
                curt_vel_decimate,        // int      velocity_decimate,
                curt_vel_radius,          // int      velocity_radius,
                curt_vel_suppr_rad,       // int      vel_suppr_rad,
    			getWidth (),              // int      width,
    			getHeight());             // int      height)
       	// For testing, create whole arrays?
       	boolean save_LoG =        debugLevel> -4;
       	boolean save_5d =          debugLevel> -4;
       	boolean save_LoG_pixels = debugLevel> -4;
       	boolean save_3d3_pixels = debugLevel> -4;
   		int num_hist_5d = cuasRTUtils.getNumHist();
       	
       	if (save_LoG) {
       		String title_log = getBaseName()+SUFFIX_LOG+"-PSF"+d2s(cuasRTUtils.getPsfRadius())+"-KR"+d2s(cuasRTUtils.getKernel2dRadius());
       		ImagePlus imp_log = cuasRTUtils.showKernel2d(title_log);
       		QuadCLTCPU.saveImagePlusInDirectory(imp_log,getModelDirectory());
       	}
       	if (save_5d) {
       	    String title_5d = getBaseName()+SUFFIX_CONV5D+
       	            "-NH"+num_hist_5d+
       	            "-VR"+curt_vel_radius+"-VD"+curt_vel_decimate;
       		ImagePlus imp_5d = cuasRTUtils.showKernel5d(title_5d);
       		QuadCLTCPU.saveImagePlusInDirectory(imp_5d,getModelDirectory());
       	}
       	double [][] dpixels = getDPpixels();
       	double [][] dpixels_log = new double [dpixels.length][dpixels[0].length];
    	double []   kernel2d = cuasRTUtils.getKernel2d();
    	double alpha0 = 1.0 - clt_parameters.imp.curt_rleak0;
    	double alpha_pyr = 1.0 - clt_parameters.imp.curt_rleak_pyr;
    	int    curt_3d3_mode = clt_parameters.imp.curt_3d3_mode;
    	double curt_3d3_frac = clt_parameters.imp.curt_3d3_frac;
    	double curt_3d3_thrsh = clt_parameters.imp.curt_3d3_thrsh;
    	double curt_3d3_power = clt_parameters.imp.curt_3d3_power;
       	double w3d3_now =  clt_parameters.imp.curt_3d3_wnow;
       	double w3d3_prev = clt_parameters.imp.curt_3d3_wprev;
    	double alpha1 = 1.0 - clt_parameters.imp.curt_rleak1;

    	for (int nseq = 0; nseq < dpixels_log.length; nseq++) {
       		cuasRTUtils.convolve2DLReLU(
       				dpixels[nseq],     // final double [] data,
       				kernel2d,          // final double [] kernel, // square
       				dpixels_log[nseq], // double []       result_in,
       				alpha0);            // final double    alpha)
       	}
       	if (save_LoG_pixels) {
       		String title_conv2d = getBaseName()+SUFFIX_CONV2D+"-PSF"+d2s(cuasRTUtils.getPsfRadius())+
       				"-KR"+d2s(cuasRTUtils.getKernel2dRadius())+"-ALPHA0_"+d2s(alpha0);
       		
			ImagePlus imp_conv2d = ShowDoubleFloatArrays.makeArrays(
					dpixels_log, // float[][] pixels,
					getWidth(),  // int width,
					getHeight(), // int height,
					title_conv2d,					
					getTimeStamps()); //String [] titles)
			imp_conv2d.setDisplayRange(CuasRTUtils.IMP_MINMAX[0],CuasRTUtils.IMP_MINMAX[1]);

       		QuadCLTCPU.saveImagePlusInDirectory(imp_conv2d, getModelDirectory());
       	}

       	int    dim3d3 = (2*cuasRTUtils.get3d3Radius()+1);
//       	int    size3x3 = dim3d3 * dim3d3;
       	double [][][] dpixels_3d3 = new double [dpixels.length-1][width*height][dim3d3 * dim3d3];
  		String title_conv3d3 = getBaseName()+SUFFIX_CONV3D3+"-PSF"+d2s(cuasRTUtils.getPsfRadius())+
   				"-KR"+d2s(cuasRTUtils.getKernel2dRadius())+"-ALPHA0_"+d2s(alpha0)+
   				"-ALPHAPYR_"+d2s(alpha_pyr)+"-M"+curt_3d3_mode+"-F"+d2s(curt_3d3_frac)+"-T"+d2s(curt_3d3_thrsh)+"-P"+d2s(curt_3d3_power)+
   				"-N"+d2s(w3d3_now)+":"+d2s(w3d3_prev)+
   				"-ALPHA1_"+d2s(alpha1);
   		// getTimeStamps(int from_indx)
   		int dbg_seq = 480;
       	for (int nseq = 1; nseq < dpixels_log.length; nseq++) {
       		int dbg_lev = (nseq == dbg_seq) ? 1 : -10;
       		cuasRTUtils.convolve3D3LReLU(
   				dpixels_log[nseq],   // final double [] data,
   				dpixels_log[nseq-1], // final double [] data_prev,
   				dpixels_3d3[nseq-1], // double [][]     result_in,
   				curt_3d3_mode,       // final int       mode_3d3,
   				curt_3d3_frac,       // final double   curt_3d3_frac, //  =        0.5;   // for stronger signals give this fraction of the total (of 9) weight to the 1-of-9 winner
   				curt_3d3_thrsh,      //final double   curt_3d3_thrsh, //  =       1.0;   // scale down curt_3d3_frac for weaker signals (in ratio of min(data[now]/curt_3d3_thrsh,1.0)
   				curt_3d3_power,      //final double    curt_3d3_power, //  = 2.0; // in mode_3d3=2, redistribute coarse velocities weights as the specified power of their positive values (ignore negative ones)
   				w3d3_now,            // final double    w_now, // normally 0.5, so center will propagate to the next pyramid levels
   				w3d3_prev,           // final double    w_prev,// normally 0.5, so center will propagate to the next pyramid levels
   				alpha1,              // final double    alpha) {
   				dbg_lev); // debugLevel);         // final int       debugLevel)
   		}
   		if (save_3d3_pixels) {
   			ImagePlus imp_3d3 = cuasRTUtils.render3d3Hyperstack(
   					dpixels_3d3,      // double [][][] coarse_vel_seq, // [nser][npix][nvel]
   					getTimeStamps(1), // String []     scene_names,
   					title_conv3d3);   // String        title)
       		QuadCLTCPU.saveImagePlusInDirectory(imp_3d3, getModelDirectory());
   		}
   		
       	// building pyramid
   		int pyramid_levels = 	clt_parameters.imp.curt_pyramid;
   		
   		double [][][]   dpixels_pyramid =     new double [pyramid_levels][][];
   		String [][]     ts_pyramid =          new String [pyramid_levels][];
   		double [][][][] dpixels_3d3_pyramid = new double [pyramid_levels][][][];
   		double [][][][][] dpixels_5d_pyramid =     new double [pyramid_levels][][][][];
   		double [][][][][] dpixels_5d_roi_pyramid = new double [pyramid_levels][][][][];
   		ts_pyramid[0] =          getTimeStamps(1);
   		dpixels_3d3_pyramid[0] = dpixels_3d3;
   		dpixels_pyramid[0] = new double[dpixels_log.length-1][width*height];
		for (int nseq = 1; nseq < dpixels_log.length; nseq++) {
			cuasRTUtils.temporalAverageLReLU(
					dpixels_log[nseq],          // final double [] data_now,
					dpixels_log[nseq-1],        // final double [] data_prev,
					dpixels_pyramid[0][nseq-1], // double []       result_in,
					alpha_pyr);                 // final double    alpha)
		}
//   		System.arraycopy(dpixels_log, 1, dpixels_pyramid[0], 0, dpixels_pyramid[0].length);
//   		int center9 = 4;
   		boolean save_5d_pixels = curt_save_c5full; // debugLevel > -4;
   	    String title_conv5d = getBaseName()+SUFFIX_CONV5D+"-PSF"+d2s(cuasRTUtils.getPsfRadius())+
   	            "-KR"+d2s(cuasRTUtils.getKernel2dRadius())+"-A0_"+d2s(alpha0)+
   	            "-AP_"+d2s(alpha_pyr)+"-M"+curt_3d3_mode+"-F"+d2s(curt_3d3_frac)+"-T"+d2s(curt_3d3_thrsh)+"-P"+d2s(curt_3d3_power)+
   	            "-N"+d2s(w3d3_now)+":"+d2s(w3d3_prev)+
   	            "-A1_"+d2s(alpha1)+   				
   	            "-NH"+num_hist_5d+
   	            "-VR"+curt_vel_radius+"-VD"+curt_vel_decimate+"-MF"+min_frac_last+
   	            "-TE"+(curt_vel_thresh_en?"1":"0") + "-TA"+curt_vel_athresh + "-TR"+curt_vel_rthresh+
   	            "-SR"+curt_vel_suppr_rad+"-D"+curt_vel_dbg_min;
   		// Level-0 5D convolution (fine velocity from coarse-velocity history)
   		int num_5d_lev0 = Math.max(0, dpixels_3d3.length - num_hist_5d + 1);
   		dpixels_5d_pyramid[0] =     new double[num_5d_lev0][][][];
   		dpixels_5d_roi_pyramid[0] = new double[num_5d_lev0][][][];
   		String[] ts_5d_lev0 = new String[num_5d_lev0];
   		// Layer 2 recurrent accumulation buffer (level 0, ROI only). convolve3d() still runs // By Claude on 06/09/2026
   		// at every time step; the buffer consumes every curt_recur_period-th output.         // By Claude on 06/09/2026
   		boolean run_recur = curt_recur_en && (curt_save_select != null);                      // By Claude on 06/09/2026
   		CuasRecurrentBuffer recurrentBuffer = null;                                           // By Claude on 06/09/2026
   		double [][][][] recur_states = null;                                                  // By Claude on 06/09/2026
   		String []       recur_ts =     null;                                                  // By Claude on 06/09/2026
   		int             num_recur =    0;                                                     // By Claude on 06/09/2026
   		if (run_recur) {                                                                      // By Claude on 06/09/2026
   			recurrentBuffer = new CuasRecurrentBuffer(                                        // By Claude on 06/09/2026
   					curt_save_select.width,                  // int roi_width,                // By Claude on 06/09/2026
   					curt_save_select.height,                 // int roi_height,               // By Claude on 06/09/2026
   					curt_pix_decimate * curt_pix_decimate,   // int num_sub,                  // By Claude on 06/09/2026
   					curt_vel_radius,                         // int velocity_radius,          // By Claude on 06/09/2026
   					curt_vel_decimate,                       // int velocity_decimate,        // By Claude on 06/09/2026
   					curt_recur_period,                       // int period,                   // By Claude on 06/09/2026
   					curt_recur_spread,                       // double spread,                // By Claude on 06/09/2026
   					curt_recur_max_clust);                   // int max_clust)                // By Claude on 06/09/2026
   			int max_recur = num_5d_lev0 / curt_recur_period + 1;                              // By Claude on 06/09/2026
   			recur_states = new double[max_recur][][][];                                       // By Claude on 06/09/2026
   			recur_ts =     new String[max_recur];                                             // By Claude on 06/09/2026
   		}                                                                                     // By Claude on 06/09/2026
   		System.out.println("detectTargets(): will run convolve3d()");
   		for (int n5d = 0; n5d < num_5d_lev0; n5d++) {
   			double tmp_min=curt_vel_dbg_min;
   			if ((n5d == 0) || (n5d == 8) || (n5d == 12) || (n5d == 16)) { // disabling outputs for certain timestamps for debugging
   				tmp_min = 1000;
   			}
   			System.out.print ("n5d="+n5d+":"+tmp_min+" ");
   			double[][][] window = new double[num_hist_5d][][];
   			for (int h = 0; h < num_hist_5d; h++) {
   				window[h] = dpixels_3d3[n5d + num_hist_5d - 1 - h];
   			}
   			if (curt_save_c5full) {
   				dpixels_5d_pyramid[0][n5d] = cuasRTUtils.convolve3d(
   						window,
   						null,
   						min_frac_last); // final double      min_frac_last){

   			}
   			if ((curt_save_c5rect || run_recur) && (curt_save_select != null)) { // By Claude on 06/09/2026 (added run_recur)
   				dpixels_5d_roi_pyramid[0][n5d] = cuasRTUtils.convolve3d(
   						window,
   						curt_save_select,
   						null,
   						min_frac_last); // final double        min_frac_last)
   				if (curt_vel_thresh_en) {
   					dpixels_5d_roi_pyramid[0][n5d] = cuasRTUtils.filterConv5dROI(
   							dpixels_5d_roi_pyramid[0][n5d],
   							curt_save_select,
   							curt_vel_athresh,
   							curt_vel_rthresh,
   							tmp_min); // curt_vel_dbg_min); // final double        dbg_min) { // zero if below, for testing with strong signals
   				}
   				if (run_recur && ((n5d % curt_recur_period) == 0)) {                          // By Claude on 06/09/2026
   					recurrentBuffer.update(                                                   // By Claude on 06/09/2026
   							dpixels_5d_roi_pyramid[0][n5d], // double [][][] measurement,     // By Claude on 06/09/2026
   							curt_recur_w,                   // double        w,               // By Claude on 06/09/2026
   							curt_recur_leak,                // double        leak,            // By Claude on 06/09/2026
   							curt_recur_wthresh);            // double        wthresh)         // By Claude on 06/09/2026
   					recur_states[num_recur] = recurrentBuffer.getBufferCopy();                // By Claude on 06/09/2026
   					recur_ts[num_recur++] =   ts_pyramid[0][n5d + num_hist_5d - 1];           // By Claude on 06/09/2026
   				}                                                                             // By Claude on 06/09/2026
   			}
   			ts_5d_lev0[n5d] = ts_pyramid[0][n5d + num_hist_5d - 1];
   		}
   		if (save_5d_pixels && num_5d_lev0 > 0) {
   			ImagePlus imp_5d = cuasRTUtils.showConvKernel5d(
   					dpixels_5d_pyramid[0],
   					ts_5d_lev0,
   					title_conv5d);
   			QuadCLTCPU.saveImagePlusInDirectory(imp_5d, getModelDirectory());
   		}
   		if (curt_save_c5rect && num_5d_lev0 > 0) {
   			ImagePlus imp_5d_rect = cuasRTUtils.showConvKernel5d(
   					dpixels_5d_roi_pyramid[0],
   					curt_save_select,
   					ts_5d_lev0,
   					title_conv5d+"-RECT");
   			QuadCLTCPU.saveImagePlusInDirectory(imp_5d_rect, getModelDirectory());
   			ImagePlus imp_5d_hyper = cuasRTUtils.showConvKernel5dHyperRect(
   					dpixels_5d_roi_pyramid[0],
   					curt_save_select,
   					ts_5d_lev0,
   					title_conv5d+"-HYPER-RECT");
   			QuadCLTCPU.saveImagePlusInDirectory(imp_5d_hyper, getModelDirectory());
   		}
   		if (run_recur && (num_recur > 0)) {                                                   // By Claude on 06/09/2026
   			double [][][][] recur_show = new double[num_recur][][][];                         // By Claude on 06/09/2026
   			String []       recur_ts_show = new String[num_recur];                            // By Claude on 06/09/2026
   			System.arraycopy(recur_states, 0, recur_show,    0, num_recur);                   // By Claude on 06/09/2026
   			System.arraycopy(recur_ts,     0, recur_ts_show, 0, num_recur);                   // By Claude on 06/09/2026
   			String title_recur = title_conv5d+                                                // By Claude on 06/09/2026
   					"-RP"+curt_recur_period+"-RW"+d2s(curt_recur_w)+"-RL"+d2s(curt_recur_leak)+ // By Claude on 06/09/2026
   					"-RWT"+d2s(curt_recur_wthresh)+"-RS"+d2s(curt_recur_spread)+              // By Claude on 06/09/2026
   					"-RC"+curt_recur_max_clust+SUFFIX_RECUR+"-D"+curt_vel_dbg_min;            // By Claude on 06/09/2026
   			ImagePlus imp_recur_rect = cuasRTUtils.showConvKernel5d(                          // By Claude on 06/09/2026
   					recur_show,                                                               // By Claude on 06/09/2026
   					curt_save_select,                                                         // By Claude on 06/09/2026
   					recur_ts_show,                                                            // By Claude on 06/09/2026
   					title_recur+"-RECT");                                                     // By Claude on 06/09/2026
   			QuadCLTCPU.saveImagePlusInDirectory(imp_recur_rect, getModelDirectory());         // By Claude on 06/09/2026
   			ImagePlus imp_recur_hyper = cuasRTUtils.showConvKernel5dHyperRect(                // By Claude on 06/09/2026
   					recur_show,                                                               // By Claude on 06/09/2026
   					curt_save_select,                                                         // By Claude on 06/09/2026
   					recur_ts_show,                                                            // By Claude on 06/09/2026
   					title_recur+"-HYPER-RECT");                                               // By Claude on 06/09/2026
   			QuadCLTCPU.saveImagePlusInDirectory(imp_recur_hyper, getModelDirectory());        // By Claude on 06/09/2026
   		}                                                                                     // By Claude on 06/09/2026
   		for (int nlev = 0; nlev < pyramid_levels; nlev++) {
   	       	if (save_LoG_pixels) {
   	       		String title_conv2d_lev = getBaseName()+SUFFIX_CONV2D+"-PSF"+d2s(cuasRTUtils.getPsfRadius())+
   	       				"-KR"+d2s(cuasRTUtils.getKernel2dRadius())+"-ALPHA0_"+d2s(alpha0)+"-ALPHAPYR_"+d2s(alpha_pyr)+"-LEV"+nlev;
   				ImagePlus imp_conv2d = ShowDoubleFloatArrays.makeArrays(
   						dpixels_pyramid[nlev], // float[][] pixels,
   						getWidth(),  // int width,
   						getHeight(), // int height,
   						title_conv2d_lev,					
   						ts_pyramid[nlev]); //String [] titles)
   				imp_conv2d.setDisplayRange(CuasRTUtils.IMP_MINMAX[0],CuasRTUtils.IMP_MINMAX[1]);
   	       		QuadCLTCPU.saveImagePlusInDirectory(imp_conv2d, getModelDirectory());
   	       	}
   			
   			// run next pyramid level (use half of the temporal slices)
   			if (nlev < (pyramid_levels -1)) {
   				int num_lev_scenes = (dpixels_pyramid[nlev].length)/2 -1;
   				dpixels_pyramid[nlev+1] = new double[num_lev_scenes][width*height];
   				
   				dpixels_3d3_pyramid[nlev+1] = new double [num_lev_scenes][width*height][dim3d3 * dim3d3];
   				ts_pyramid[nlev+1] = new String[num_lev_scenes];
   				for (int nseq = 0; nseq < num_lev_scenes; nseq++) {
   					ts_pyramid[nlev+1][nseq] = ts_pyramid[nlev][2*(nseq + 1)];
   					cuasRTUtils.temporalAverageLReLU(
   							dpixels_pyramid[nlev][2*nseq+2],          // final double [] data_now,
   							dpixels_pyramid[nlev][2*nseq],        // final double [] data_prev,
   							dpixels_pyramid[nlev+1][nseq], // double []       result_in,
   							alpha_pyr);                 // final double    alpha)
   					
   					cuasRTUtils.convolve3D3LReLU(
   							dpixels_pyramid[nlev][2*nseq+2],   // final double [] data,
   							dpixels_pyramid[nlev][2*nseq],     // final double [] data_prev,
   							dpixels_3d3_pyramid[nlev+1][nseq], // double [][]     result_in,
   			   				curt_3d3_mode,                     // final int       mode_3d3,
   			   				curt_3d3_frac,                     // final double   curt_3d3_frac, //  =        0.5;   // for stronger signals give this fraction of the total (of 9) weight to the 1-of-9 winner
   			   				curt_3d3_thrsh,                    //final double   curt_3d3_thrsh, //  =       1.0;   // scale down curt_3d3_frac for weaker signals (in ratio of min(data[now]/curt_3d3_thrsh,1.0)
   			   				curt_3d3_power,                    //final double    curt_3d3_power, //  = 2.0; // in mode_3d3=2, redistribute coarse velocities weights as the specified power of their positive values (ignore negative ones)
   							w3d3_now,                          // final double    w_now, // normally 0.5, so center will propagate to the next pyramid levels
   							w3d3_prev,                         // final double    w_prev,// normally 0.5, so center will propagate to the next pyramid levels
   			   				alpha1,                            // final double    alpha) {
   			   				debugLevel);                       // final int       debugLevel)
   				}
   		   		if (save_3d3_pixels) {
   		   			ImagePlus imp_3d3 = cuasRTUtils.render3d3Hyperstack(
   		   					dpixels_3d3_pyramid[nlev+1],      // double [][][] coarse_vel_seq, // [nser][npix][nvel]
   		   					ts_pyramid[nlev+1], // String []     scene_names,
   		   					title_conv3d3+"-LEV"+(nlev+1));   // String        title)
   		   			QuadCLTCPU.saveImagePlusInDirectory(imp_3d3, getModelDirectory());
   		   		}
   		   		// 5D convolution for pyramid level nlev+1
   		   		int num_5d_lev = Math.max(0, dpixels_3d3_pyramid[nlev+1].length - num_hist_5d + 1);
   		   		dpixels_5d_pyramid[nlev+1] =     new double[num_5d_lev][][][];
   		   		dpixels_5d_roi_pyramid[nlev+1] = new double[num_5d_lev][][][];
   		   		String[] ts_5d_lev = new String[num_5d_lev];
   		   		for (int n5d = 0; n5d < num_5d_lev; n5d++) {
   		   			double[][][] window = new double[num_hist_5d][][];
   		   			for (int h = 0; h < num_hist_5d; h++) {
   		   				window[h] = dpixels_3d3_pyramid[nlev+1][n5d + num_hist_5d - 1 - h];
   		   			}
   		   			if (curt_save_c5full) {
   		   				dpixels_5d_pyramid[nlev+1][n5d] = cuasRTUtils.convolve3d(
   		   						window,
   		   						null,
   		   						min_frac_last); // final double      min_frac_last){
   		   			}
   		   			if (curt_save_c5rect && (curt_save_select != null)) {
   		   				dpixels_5d_roi_pyramid[nlev+1][n5d] = cuasRTUtils.convolve3d(
   		   						window,
   		   						curt_save_select,
   		   						null,
   		   						min_frac_last); // final double        min_frac_last)
   		   				if (curt_vel_thresh_en) {
   		   					dpixels_5d_roi_pyramid[nlev+1][n5d] = cuasRTUtils.filterConv5dROI(
   		   							dpixels_5d_roi_pyramid[nlev+1][n5d],
   		   							curt_save_select,
   		   							curt_vel_athresh,
   		   							curt_vel_rthresh,
   		   							curt_vel_dbg_min); // final double        dbg_min) { // zero if below, for testing with strong signals
   		   				}
   		   			}
   		   			ts_5d_lev[n5d] = ts_pyramid[nlev+1][n5d + num_hist_5d - 1];
   		   		}
   		   		if (save_5d_pixels && num_5d_lev > 0) {
   		   			ImagePlus imp_5d = cuasRTUtils.showConvKernel5d(
   		   					dpixels_5d_pyramid[nlev+1],
   		   					ts_5d_lev,
   		   					title_conv5d+"-LEV"+(nlev+1));
   		   			QuadCLTCPU.saveImagePlusInDirectory(imp_5d, getModelDirectory());
   		   		}
   		   		if (curt_save_c5rect && num_5d_lev > 0) {
   		   			ImagePlus imp_5d_rect = cuasRTUtils.showConvKernel5d(
   		   					dpixels_5d_roi_pyramid[nlev+1],
   		   					curt_save_select,
   		   					ts_5d_lev,
   		   					title_conv5d+"-LEV"+(nlev+1)+"-RECT");
   		   			QuadCLTCPU.saveImagePlusInDirectory(imp_5d_rect, getModelDirectory());
   		   			ImagePlus imp_5d_hyper = cuasRTUtils.showConvKernel5dHyperRect(
   		   					dpixels_5d_roi_pyramid[nlev+1],
   		   					curt_save_select,
   		   					ts_5d_lev,
   		   					title_conv5d+"-LEV"+(nlev+1)+"-HYPER-RECT");
   		   			QuadCLTCPU.saveImagePlusInDirectory(imp_5d_hyper, getModelDirectory());
   		   		}
   			}
   		}
   		return null;
   		/*
		CuasMotion cuasMotion = new CuasMotion (
				clt_parameters,              // CLTParameters     clt_parameters,
				getTimeStamps(),                // String []         scene_titles,
				cuasRanging.getCenter_CLT(), // QuadCLT           parentCLT,
				null, // fpixels_avg,                 // float []          fpixels_avg,
				uasLogReader,                // UasLogReader      uasLogReader,
				debugLevel);                 // int               debugLevel)
		return cuasMotion;
		*/
	}
}
/*
					uasLogReader,   // UasLogReader    uasLogReader,
batch_mode);    // boolean         batch_mode)

*/