package com.elphel.imagej.cuas;

import java.awt.Color;
import java.awt.Font;
import java.awt.Rectangle;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.net.URISyntaxException;
import java.net.URL;

import com.elphel.imagej.cameras.CLTParameters;
import com.elphel.imagej.common.GenericJTabbedDialog;
import com.elphel.imagej.common.ShowDoubleFloatArrays;
import com.elphel.imagej.gpu.GPUTileProcessor;
import com.elphel.imagej.gpu.GpuQuad;
import com.elphel.imagej.gpu.TpTask;
import com.elphel.imagej.readers.ImagejJp4Tiff;
import com.elphel.imagej.tileprocessor.Correlation2d;
import com.elphel.imagej.tileprocessor.ImageDtt;
import com.elphel.imagej.tileprocessor.IntersceneMatchParameters;
import com.elphel.imagej.tileprocessor.QuadCLT;
import com.elphel.imagej.tileprocessor.TDCorrTile;
import com.elphel.imagej.tileprocessor.TileNeibs;

import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.process.ByteProcessor;
import ij.process.ColorProcessor;
import ij.process.FloatProcessor;
import ij.process.ImageConverter;
import ij.process.ImageProcessor;
import loci.formats.FormatException;

public class CuasMotion {
	final static private int INDX_VX =         0;
	final static private int INDX_VY =         1;
	final static private int INDX_STRENGTH =   2;
	final static private int INDX_FRAC =       3;
	final static private int INDX_SPEED =      4; // calculated separately
	final static private int INDX_CONFIDENCE = 5; // calculated separately
	final static String [] VF_TOP_TITLES = {"vX","vY","strength","fraction","speed", "confidence"};
	
	final static public int TARGET_X =         0;
	final static public int TARGET_Y =         1;
	final static public int TARGET_VX =        2;
	final static public int TARGET_VY =        3;
	final static public int TARGET_STRENGTH =  4;
	final static public int TARGET_LENGTH = TARGET_STRENGTH+1;
	
	public static String RESOURCE_GRAPICS_DIR ="graphics";
	public static String ICON_TARGET_1X =      "TDbox_dashed21x21_bold.png";
	public static String ICON_TARGET_2X =      "TDbox_dashed43x43_2px.png";
	public static String [][] TARGET_ICONS = {
			{"TDbox_dashed21x21_bold.png","TDbox_dashed43x43_2px.png"}, // unknown
			{"TDbox21x21.png",            "TDbox43x43_2px.png"},        // selected 
			{"Circle21x21.png",           "Circle43x43_2px.png"},       // friend
			{"diamond21x17.png",          "diamond43x35_2px.png"}       // foe
	};
	

	private final  GPUTileProcessor gpuTileProcessor;
	private CLTParameters           clt_parameters=null;
	private QuadCLT                 parentCLT = null;
	private int                     debugLevel = 0;
	private GpuQuad                 gpuQuad = null;
	private ImageDtt                image_dtt = null;
	private int                     gpu_max_width;         
	private int                     gpu_max_height;
	private int                     tilesX;
	private int                     tilesY;
	
	public CuasMotion (
			CLTParameters     clt_parameters,
			QuadCLT           parentCLT,
			int               debugLevel) {
		this.debugLevel =     debugLevel;
		this.clt_parameters = clt_parameters;
		this.parentCLT =      parentCLT;  
		
		
    	System.out.println("Setting up GPU");
    	gpuTileProcessor =    parentCLT.getGPUQuad().getGpuTileProcessor();
    	tilesX =              parentCLT.getTilesX();
    	tilesY =              parentCLT.getTilesY();
    	gpu_max_width =       tilesX * GPUTileProcessor.DTT_SIZE;
    	gpu_max_height =      tilesY * GPUTileProcessor.DTT_SIZE;
    	
		try {
			gpuQuad = new GpuQuad(//single camera
					gpuTileProcessor,   // GPUTileProcessor gpuTileProcessor,
					gpu_max_width,        // final int        max_width,
					gpu_max_height,       // final int        max_height,
					1,                    // final int        num_colors, // normally 1?
					this.clt_parameters.gpu_debug_level);
		} catch (Exception e) {
			System.out.println("Failed to initialize GpuQuad class");
			// TODO Auto-generated catch block
			e.printStackTrace();
			return;
		} // final int debugLevel);
		boolean is_aux =  true;
		boolean is_mono = true;
		boolean is_lwir = true;
		image_dtt = new ImageDtt(
				1,                                       // int numSensors,
				GPUTileProcessor.DTT_SIZE,               // clt_parameters.transform_size,
				clt_parameters.img_dtt,
				is_aux,                                  // ref_scene.isAux(),
				is_mono,                                 // ref_scene.isMonochrome(),
				is_lwir,
				clt_parameters.getScaleStrength(is_aux), // maybe something else
				gpuQuad);
	}
	
	
	
	
	public static boolean testCuasMotion(
			ImagePlus         imp_sel,
			CLTParameters     clt_parameters,
			QuadCLT           parentCLT,
			int               debugLevel) {
		int     framecent =         180;
		int     corr_offset =       clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =        clt_parameters.imp.cuas_corr_pairs;
		double  cuas_fat_zero =     clt_parameters.imp.cuas_fat_zero;
		double  cuas_cent_radius =  clt_parameters.imp.cuas_cent_radius;
		int     cuas_n_recenter =   clt_parameters.imp.cuas_n_recenter;
		double  cuas_rstr =         clt_parameters.imp.cuas_rstr;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
		boolean smooth =            clt_parameters.imp.cuas_smooth; // true;
		
		GenericJTabbedDialog gd = new GenericJTabbedDialog("Motion detect parameters");
		gd.addNumericField("Center frame",        framecent,        0,  5, "frame", "Center frame");
		gd.addNumericField("Number of pairs",     corr_pairs,       0,  3, "",      "The number of correlation pairs to accumulate.");
		gd.addNumericField("Pairs offset",        corr_offset,      0,  3, "",      "Offset between the correlation pairs");
		gd.addCheckbox    ("Smooth weights",      smooth, 			"Apply cosine weights when averaging a sequence of correlation pairs.");
		gd.addNumericField("Fat zero",            cuas_fat_zero,    7, 10, "",      "Fat zero for TD->PD conversion");
		gd.addNumericField("Center radius",       cuas_cent_radius, 3,  5, "pix",   "Center radius");
		gd.addNumericField("Number of recenters", cuas_n_recenter,  0,  5, "",      "Number of centroid re-center iterations");
		gd.addNumericField("Relative strength",   cuas_rstr,        3,  5, "x",     "Minimal relative strength of the 2D correlation");
		gd.showDialog();
		if (gd.wasCanceled())
			return false;
		framecent =           (int) gd.getNextNumber();
		corr_pairs =          (int) gd.getNextNumber();
		corr_offset =         (int) gd.getNextNumber();
		smooth =                    gd.getNextBoolean();
		cuas_fat_zero =             gd.getNextNumber();
		cuas_cent_radius =          gd.getNextNumber();
		cuas_n_recenter =     (int) gd.getNextNumber();
		cuas_rstr =            gd.getNextNumber();
		
		final boolean batch_mode = false;
		int frame0 = framecent - (corr_offset + corr_pairs)/2;
		int frame1 = framecent + (corr_offset - corr_pairs)/2;
		String suffix_param = "-"+framecent+"-"+corr_offset+"-"+corr_pairs;
		String dbg_suffix = imp_sel.getTitle()+suffix_param;
		
		if (parentCLT.getTileProcessor() == null) {
			parentCLT.setTiles (imp_sel, // set global tp.tilesX, tp.tilesY
					parentCLT.getNumSensors(), // tp.getNumSensors(),
					  clt_parameters,
					  QuadCLT.THREADS_MAX); // where to get it? Use instance member
		}
		
		CuasMotion cuasMotion = new CuasMotion (
				clt_parameters, // CLTParameters     clt_parameters,
				parentCLT, // QuadCLT           parentCLT,
				debugLevel); // int               debugLevel)
		
		int num_slices = imp_sel.getStack().getSize();
		float [][] fpixels = new float[num_slices][];
		for (int nslice = 0; nslice < fpixels.length; nslice++) {
			fpixels[nslice] = (float[]) imp_sel.getStack().getPixels(nslice+1);
		}
		int           corr_ra_step = 1;
		TDCorrTile [] tdCorrTiles = cuasMotion.correlatePairs(
				clt_parameters, // CLTParameters clt_parameters,
				fpixels,        // float [][]    fpixels,
				frame0,         // int           frame0,
				frame1,         // int           frame1,
				corr_pairs,      // int           frame_len,
				corr_ra_step,   // int           corr_ra_step,
				smooth,         // boolean       smooth, // use cosine mask
				true,           // batch_mode,     // final boolean batch_mode,
				dbg_suffix,     // final String  dbg_suffix, // for image_names
				debugLevel);    // int           debugLevel)
		// convert to pixel domain and display 
		double [][] corr_tiles_pd = cuasMotion.convertTDtoPD(
				tdCorrTiles,          // final TDCorrTile [] tiles,
				0xFE,                // final int           corr_type, // 0xFE
				cuas_fat_zero,       // final double        gpu_fat_zero,
				debugLevel);         // final int           debug_level

		double [][] vector_field = TDCorrTile.getMismatchVector( // full tiles in gpu (512*512)
				corr_tiles_pd,    // final double[][] tiles,
				cuas_rstr,        // double              rmax, 
				cuas_cent_radius, // final double        centroid_radius, // 0 - all same weight, > 0 cosine(PI/2*sqrt(dx^2+dy^2)/rad)
				cuas_n_recenter,  // final int           n_recenter); //  re-center window around new maximum. 0 -no refines (single-pass)
				true);            // final boolean     calc_fraction	){ // calculate fraction inside center circle

		final int corr_size = 2 * GPUTileProcessor.DTT_SIZE -1; 
    	boolean show_vector_field = true; // (debugLevel>100); // true;
    	boolean show_2d_correlations = true; // (debugLevel>0); // true;
		if (show_2d_correlations) {
    		double [][] dbg_2d_corrs = ImageDtt.corr_partial_dbg( // not used in lwir
    				new double [][][] {corr_tiles_pd},                       // final double [][][]     corr_data,       // [layer][tile][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
    				cuasMotion.tilesX,                              // final int               tilesX,
    				corr_size,                           //final int               corr_size,       // 15
    				clt_parameters.corr_border_contrast, // final double            border_contrast,
    				debugLevel);                         // final int               globalDebugLevel)
    		String [] dbg_titles= {"raw"}; // ,"neibs"};
    		ShowDoubleFloatArrays.showArrays(
    				dbg_2d_corrs,
    				cuasMotion.tilesX * (corr_size + 1),
    				cuasMotion.tilesY * (corr_size + 1),
    				true,
    				imp_sel.getTitle()+"-corr2d"+suffix_param, // "-corr2d"+"-"+frame0+"-"+frame1+"-"+corr_pairs,
    				dbg_titles);
    	}
		
		if (show_vector_field) {
//			double [][] dbg_vf = new double [3 * vector_field.length][cuasMotion.tilesX * cuasMotion.tilesY];
			double [][] dbg_vf = new double [4][cuasMotion.tilesX * cuasMotion.tilesY];
			String [] dbg_titles = new String[dbg_vf.length];
				dbg_titles [0] = "vX";	
				dbg_titles [1] = "vY";	
				dbg_titles [2] = "conf";
				dbg_titles [3] = "frac";
			for (int i = 0; i < dbg_vf.length; i++) {
				Arrays.fill(dbg_vf[i], Double.NaN);
			}

			for (int t=0; t<dbg_vf[0].length; t++) {
				if (vector_field[t] != null) {
					for (int k = 0; k < dbg_titles.length; k++) {
						dbg_vf[k][t] = vector_field[t][k];
					}
				}
			}
    		ShowDoubleFloatArrays.showArrays(
    				dbg_vf,
    				cuasMotion.tilesX,
    				cuasMotion.tilesY,
    				true,
    				imp_sel.getTitle()+"-vector_field"+suffix_param,
    				dbg_titles);
		}
//		return vector_field; // corr_tiles;
		return true;
	}
	
	public static boolean testCuasScanMotion(
			ImagePlus         imp_sel,
			CLTParameters     clt_parameters,
			QuadCLT           parentCLT,
			int               debugLevel) {
		String image_name = imp_sel.getTitle().substring(0,17);
		parentCLT.setImageName(image_name);
		
		String x3d_path = parentCLT.getX3dDirectory();
		System.out.println ("Model directory = "+x3d_path);
		
		int     corr_pairs =     clt_parameters.imp.cuas_corr_pairs;
		int     corr_offset =    clt_parameters.imp.cuas_corr_offset;
		int     precorr_ra =     clt_parameters.imp.cuas_precorr_ra;
		int     corr_ra_step =   clt_parameters.imp.cuas_corr_step;
		int     temporal_um =    clt_parameters.imp.cuas_temporal_um;
		double  tum_threshold =  clt_parameters.imp.cuas_tum_threshold;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
		
		double  fat_zero =       clt_parameters.imp.cuas_fat_zero;
		double  cent_radius =    clt_parameters.imp.cuas_cent_radius;
		int     n_recenter =     clt_parameters.imp.cuas_n_recenter;
		double  rstr =           clt_parameters.imp.cuas_rstr;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
		double  speed_min =      clt_parameters.imp.cuas_speed_min;
		double  speed_pref =     clt_parameters.imp.cuas_speed_pref;
		double  speed_boost =    clt_parameters.imp.cuas_speed_boost;
		boolean smooth =         clt_parameters.imp.cuas_smooth; // true;
		boolean half_step =      clt_parameters.imp.cuas_half_step; // true;
		int     max_range =      clt_parameters.imp.cuas_max_range;
		int     num_cycles =     clt_parameters.imp.cuas_num_cycles;
		double target_radius =   clt_parameters.imp.cuas_target_radius;
		double target_strength = clt_parameters.imp.cuas_target_strength;
		double [][] target_frac = new double [clt_parameters.imp.cuas_target_frac.length][2];
		boolean no_border=       clt_parameters.imp.cuas_no_border; // true;
	// Moving target LMA	
	    double  lma_sigma =      clt_parameters.imp.cuas_lma_sigma; // =     3.0;
	    double  wnd_pedestal =   clt_parameters.imp.cuas_wnd_pedestal;    // =  0.1;
	    double  lma_r0 =         clt_parameters.imp.cuas_lma_r0; // =        3.0;  //maximum with with overshoot
	    double  lma_ovrsht =     clt_parameters.imp.cuas_lma_ovrsht; // =    2.0;
	// CUAS Motion LMA parameters
		boolean lma_fit_xy=      clt_parameters.imp.cuas_lma_fit_xy; // true;
		boolean lma_fit_a=       clt_parameters.imp.cuas_lma_fit_a; // true;
		boolean lma_fit_c=       clt_parameters.imp.cuas_lma_fit_c; // true;
		boolean lma_fit_r=       clt_parameters.imp.cuas_lma_fit_r; // true;
		boolean lma_fit_k=       clt_parameters.imp.cuas_lma_fit_k; // true;
	    double  lambda =         clt_parameters.imp.cuas_lambda; // =        0.1;
	    double  lambda_good =    clt_parameters.imp.cuas_lambda_good; // =   0.5;
	    double  lambda_bad =     clt_parameters.imp.cuas_lambda_bad; // =    8;
	    double  lambda_max =     clt_parameters.imp.cuas_lambda_max; // =  100;
	    double  rms_diff =       clt_parameters.imp.cuas_rms_diff; // =      0.001; // relative RMS improvement
	    int     num_iter =       clt_parameters.imp.cuas_num_iter; // =     20;
	// CUAS Motion LMA filter parameters
	    double  lma_rms =        clt_parameters.imp.cuas_lma_rms; // =       1.5;  // Maximal RMS, regardless of amplitude
	    double  lma_arms =       clt_parameters.imp.cuas_lma_arms; // =      0.06; // Maximal absolute RMS, sufficient for any amplitude
	    double  lma_rrms =       clt_parameters.imp.cuas_lma_rrms; // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
	    double  lma_mina =       clt_parameters.imp.cuas_lma_mina; // =      1.0;  // Minimal A (amplitude)
	    double  lma_maxr =       clt_parameters.imp.cuas_lma_maxr; // =      5.0;  // Maximal radius (>3.8)
	    double  lma_mink =       clt_parameters.imp.cuas_lma_mink; // =      0.0;  // Minimal K (overshoot)  <0.007
	    double  lma_maxk =       clt_parameters.imp.cuas_lma_maxk; // =      5.0;  // Minimal K (overshoot) > 3.8
		boolean remove_isolated= clt_parameters.imp.cuas_isolated;  // false;
	    
		double  mask_width =     clt_parameters.imp.cuas_mask_width;  // 9;
		double  mask_blur =      clt_parameters.imp.cuas_mask_blur;   // 3;
		boolean mask_round =     clt_parameters.imp.cuas_mask_round;  // false;
	    
		int     target_type =    clt_parameters.imp.cuas_target_type; //0; // 0 - unknown, 1 - known, 2 - friend, 3 - foe
		double  input_range =    clt_parameters.imp.cuas_input_range; // 5;
		boolean scale2x =        clt_parameters.imp.cuas_scale2x;      //true;
		
		boolean ra_background =  clt_parameters.imp.cuas_ra_background;   //true;

		int     iter_show =      clt_parameters.imp.cuas_iter_show;       //1; // Maximal enhancement iteration to show intermediate result (0 - none) 
		boolean corr2d_save_show =  clt_parameters.imp.cuas_2d_save_show;    //true;
		boolean intermed_low =   clt_parameters.imp.cuas_intermed_low;    //true;
		boolean intermed_high =  clt_parameters.imp.cuas_intermed_high;   //true;
		boolean intermed_giga =  clt_parameters.imp.cuas_intermed_giga;   //false;
		boolean save_mono =      clt_parameters.imp.cuas_save_mono;       //true;
		boolean save_color =     clt_parameters.imp.cuas_save_color;      //true;
		boolean save_video =     clt_parameters.imp.cuas_save_video;      //true;
	    
		for (int i = 0; i < target_frac.length; i++) {
			if (clt_parameters.imp.cuas_target_frac[i].length >= 2) {
				target_frac[i][0] = clt_parameters.imp.cuas_target_frac[i][0];
				target_frac[i][1] = clt_parameters.imp.cuas_target_frac[i][1];
			} else {
				System.out.println("testCuasScanMotion(): wrong format for a pair of strength, fraction values.");
			}
		}
		
		
		String  data_dir = "/media/elphel/NVME/lwir16-proc/eagle_mountain/linked/movement/selected/13";
//file:///media/elphel/NVME/lwir16-proc/eagle_mountain/linked/movement/selected/12/INPUT-1747803449_165687.tiff-vector_extended-offs20-pairs50-rstr0.01-fz300.0-cr3.0-mr2-ms0.3-sp1.0-2.5.tiff
//		gd.addStringField("KML document title",            name, 100, "Provide title of the KML file");
// srt_path =  gd.getNextString();

		boolean save_params = true;
		boolean test_lma = false;

		while (true) {
			GenericJTabbedDialog gd = new GenericJTabbedDialog("Motion scan parameters");
			//		gd.addNumericField("Center frame",        framecent,        0,  5, "frame", "Center frame");
			gd.addNumericField("Number of pairs",     corr_pairs,       0,  3, "",      "The number of correlation pairs to accumulate.");
			gd.addNumericField("Pairs offset",        corr_offset,      0,  3, "scenes",      "Offset between the correlation pairs");
			gd.addNumericField("Pre-correlation running average", precorr_ra, 0,3,"scenes", 	"Smoothing input data by running average before correlation for motion vectors calculations. Target extraction wil still use individual scenes.");
			gd.addNumericField("Correlation step after RA", corr_ra_step, 0,3,"scenes", 	"Decimate correlations after running average");
			
			gd.addCheckbox    ("Smooth weights",      smooth, 			"Apply cosine weights when averaging a sequence of correlation pairs.");
			gd.addCheckbox    ("Half scan step",      half_step,    	"Reduce step for motion detection = offset/2, if false = offset.");
			gd.addNumericField("Fat zero",            fat_zero,    7, 10, "",      "Fat zero for TD->PD conversion");
			gd.addNumericField("Center radius",       cent_radius, 3,  5, "pix",   "Center radius");
			gd.addNumericField("Number of recenters", n_recenter,  0,  5, "",      "Number of centroid re-center iterations");
			gd.addNumericField("Relative strength",   rstr,        3,  5, "x",     "Minimal absoute strength of the 2D correlation (negative - relative)");
			
			gd.addNumericField("Minimal speed",       speed_min, 5,8,"ppr",  "Minimal target speed in pixels per range (per cuas_corr_offset).");
			gd.addNumericField("Preferable speed",    speed_pref, 5,8,"ppr", "Boost effective strength when speed is above this.");
			gd.addNumericField("Maximal speed boost", speed_boost, 5,8,"",   "Maximal speed-caused effective strength boost.");
			gd.addNumericField("Local max range",     max_range, 0,3,"", "While filtering local correlation maximums: 1 - 3x3 neighbors, 2 - 5x5 ones.");
			gd.addNumericField("Number of enhancement cycles",   num_cycles, 0,3,"","Number of cycles of testing and removing bad targets from compoetition with weaker neighbors.");
			

			gd.addNumericField("Target radius",       target_radius, 5,8,"pix","Target radius, also used to calculate fraction of totals inside (windowed) to all positive values.");
			gd.addNumericField("Minimal target strength", target_strength, 5,8,"","Minimal value of the target image.");
			gd.addStringField ("Fraction for strengths", IntersceneMatchParameters.double2dToString(target_frac), 100,
					"Variable number of (strength, fraction) pairs separated by \":\". Each pair of strength, minimal fraction is separated by \",\".");
			gd.addCheckbox    ("Target not on the tile edge",            no_border, "Exclude targets with centers on the edge of 16x16 tiles.");
			
			
			gd.addMessage("=== Moving target LMA ===");
			gd.addNumericField("Weight Gaussian sigma",                  lma_sigma, 5,8,"pix",
					"Weights to emphasize maximum center area when fitting.");
			gd.addNumericField("Weight pedestal",                        wnd_pedestal, 5,8,"",
					"Add constant to Gaussian weights.");
			gd.addNumericField("Target typical radius",                  lma_r0, 5,8,"pix",
					"Typical target radius including negative overshoot (caused by UM filter).");
			gd.addNumericField("Target maximum overshoot",               lma_ovrsht, 5,8,"",
					"Hos much smaller is the first zero than total maximum with overshoot (2.0 - first zero radius 1/2 of the full.");
			gd.addMessage("--- Moving target LMA fitting parameters ---");
			gd.addCheckbox    ("LMA fit X,Y",                            lma_fit_xy, "Fit target center position.");
			gd.addCheckbox    ("LMA fit amplitude (strength)",           lma_fit_a,  "Fit maximum amplitude.");
			gd.addCheckbox    ("LMA fit offset",                         lma_fit_c,  "Fit out-of-maximum level (offset).");
			gd.addCheckbox    ("LMA fit radius",                         lma_fit_r,  "Fit target total radius - includes negative overshoot caused by UM filter.");
			gd.addCheckbox    ("LMA fit overshoot",                      lma_fit_k,  "Fit target overshoot (2.0 - first zero crossing at half radius.");
			
			gd.addNumericField("LMA lambda",                             lambda, 5,8,"",
					"LMA initial lambda.");
			gd.addNumericField("Scale lambda after success",             lambda_good, 5,8,"",
					"Multiply lambda if RMS decreaed.");
			gd.addNumericField("Scale lambda after failure",             lambda_bad, 5,8,"",
					"Multiply lambda if RMS increaed.");
			gd.addNumericField("Maximal lambda",                         lambda_max, 5,8,"",
					"Give up if lambda gets higher value.");
			gd.addNumericField("Relative RMS improvement",               rms_diff, 5,8,"",
					"Finish fitting when the relative RMS improvement drops below this value.");
			gd.addNumericField("LMA iterations",                         num_iter, 0,3,"",
					"Maximal number of the LMA iterations.");
			gd.addMessage("--- Moving target discrimination parameters theresholds ---");
			gd.addNumericField("Maximal RMS",                            lma_rms, 5,8,"",
					"Maximal RMS for target that should match always, regardless of the amplitude.");
			gd.addNumericField("Maximal sufficient RMS",                 lma_arms, 5,8,"",
					"Maximal sufficient RMS for target. Satisfying any of the sufficient or relative is enough");
			gd.addNumericField("Maximal relative RMS",                   lma_rrms, 5,8,"",
					"Maximal relative (to amplitude) RMS for target. Satisfying any of the absolute and relative is sufficient");
			gd.addNumericField("Minimal target amplitude",               lma_mina, 5,8,"",
					"Filter out weak targets.");
			gd.addNumericField("Maximal radius",                         lma_maxr, 5,8,"",
					"Maximal target radius including negative overshoot.");
			gd.addNumericField("Minimal overshoot ratio",                lma_mink, 5,8,"",
					"Minimal ratio of the overshoot radius to the first 0 radius (typical 1.0).");
			gd.addNumericField("Maximal overshoot ratio",                lma_maxk, 5,8,"",
					"Maximal ratio of the overshoot radius to the first 0 radius (typical 3.0).");
			gd.addCheckbox ("Remove single-frame targets",               remove_isolated,
					"Remove targets that do not have neighbors before/afte.");
			
			
			gd.addNumericField("Mask diameter",                         mask_width, 5,8,"pix",
					"Taget mask to replace static background with moving target.");
			gd.addNumericField("Mask transition width",                 mask_blur, 5,8,"pix",
					"Transition between opaque and transparent mask.");
			gd.addCheckbox ("Mask round",                               mask_round,
					"Use round mask. Unchesked - use sharp square mask without any transition.");
			gd.addNumericField("Targets icon type",                     target_type, 0,3,"",
					"0 - unknown (dashed square), 1 - known (square), 2 - friend (circle), 3 - foe (diamond).");
			gd.addNumericField("Image range",                           input_range, 5,8,"10mK",
					"Dispaly pixel values range (1.0 ~= 10 mK).");
			gd.addCheckbox ("Scale images twice",                       scale2x,
					"Scale imges to 1280x1024 for higher annotation quality.");
			gd.addCheckbox ("Smooth background with runnong average",   ra_background,
					"Apply running average to the background of the moving targets (false - use high-noise no-averaged images.");

			gd.addMessage("=== Saving intermediate and final images and video ===");
			gd.addNumericField("Maximal iteration to save/show",        iter_show, 0,3,"",
					"Maximal enhancement iteration to show intermediate result (0 - none).");
			gd.addCheckbox ("Save/show 2D correlations",                corr2d_save_show,
					"Save and optionally show 2D correlations.");
			gd.addCheckbox ("Save tile-resolution intermediate images", intermed_low,
					"Save intermediate vector fields and target coordinates from the LMA (80x64 layers).");
			gd.addCheckbox ("Save pixel-resolution intermediate images",intermed_high,
					"Save pixel-resolution accumulated images (640x512).");
			gd.addCheckbox ("Save gigabyte files",                      intermed_giga,
					"Save pixel-resolution huge files, such as hyperstack comparison.");
			
			gd.addCheckbox ("Save monochrome targets+background",       save_mono,
					"Save 32-bit monochrome targets+background Tiffs (before optional scaling).");
			gd.addCheckbox ("Save color targets+background",            save_color,
					"Save color rendered images (same as videos), optionally scaled.");
			gd.addCheckbox ("Save videos",                              save_video,
					"Save video with moving targets.");

			
			
			
			
			gd.addStringField("Data directory",       data_dir,100,"Intermediate results directory (to bypass first stages during debugging).");
			
			gd.addMessage("=== Currently some methods use clt_parameters directly, so without saving parameters they will not work! ===");
			
			gd.addCheckbox    ("Save_params",         save_params,    	"Save edited parameters");
			gd.addCheckbox    ("Test LMA",    test_lma, "Test LMA from known files instead of normal operation.");


			gd.showDialog();
			if (gd.wasCanceled()) {
				return false;
			}
			corr_pairs =          (int) gd.getNextNumber();
			corr_offset =         (int) gd.getNextNumber();
			precorr_ra =          (int) gd.getNextNumber();
			corr_ra_step =        (int) gd.getNextNumber();
			
			smooth =                    gd.getNextBoolean();
			half_step =                 gd.getNextBoolean();
			fat_zero =                  gd.getNextNumber();
			cent_radius =               gd.getNextNumber();
			n_recenter =          (int) gd.getNextNumber();
			rstr =                      gd.getNextNumber();
			
			speed_min =                 gd.getNextNumber();
			speed_pref =                gd.getNextNumber();
			speed_boost =               gd.getNextNumber();
			
			max_range =           (int) gd.getNextNumber();
			num_cycles =          (int) gd.getNextNumber();

			target_radius =             gd.getNextNumber();
			target_strength =           gd.getNextNumber();
			target_frac =   IntersceneMatchParameters.stringToDouble2d(gd.getNextString());
			no_border =                 gd.getNextBoolean();   
			lma_sigma =                 gd.getNextNumber();
			wnd_pedestal =              gd.getNextNumber();
			lma_r0 =                    gd.getNextNumber();
			lma_ovrsht =                gd.getNextNumber();
			lma_fit_xy =                gd.getNextBoolean();
			lma_fit_a =                 gd.getNextBoolean();
			lma_fit_c =                 gd.getNextBoolean();
			lma_fit_r =                 gd.getNextBoolean();
			lma_fit_k =                 gd.getNextBoolean();			
			lambda =                    gd.getNextNumber();
			lambda_good =               gd.getNextNumber();
			lambda_bad =                gd.getNextNumber(); 
			lambda_max =                gd.getNextNumber(); 
			rms_diff =                  gd.getNextNumber();
			num_iter =            (int) gd.getNextNumber();
			lma_rms =                   gd.getNextNumber();
			lma_arms =                  gd.getNextNumber();
			lma_rrms =                  gd.getNextNumber();
			lma_mina =                  gd.getNextNumber();
			lma_maxr =                  gd.getNextNumber();
			lma_mink =                  gd.getNextNumber();
			lma_maxk =                  gd.getNextNumber();
			remove_isolated =           gd.getNextBoolean();
			
			mask_width =                gd.getNextNumber();
			mask_blur =                 gd.getNextNumber();
			mask_round =                gd.getNextBoolean();
			target_type=          (int) gd.getNextNumber();
			input_range =               gd.getNextNumber();
			scale2x =                   gd.getNextBoolean();
			ra_background =             gd.getNextBoolean();

			iter_show=            (int) gd.getNextNumber();
			corr2d_save_show =          gd.getNextBoolean();
			intermed_low =              gd.getNextBoolean();
			intermed_high =             gd.getNextBoolean();
			intermed_giga =             gd.getNextBoolean();
			save_mono =                 gd.getNextBoolean();
			save_color =                gd.getNextBoolean();
			save_video =                gd.getNextBoolean();
			
			
			data_dir =                  gd.getNextString();
			save_params =               gd.getNextBoolean();
			
			test_lma = gd.getNextBoolean();

			if (save_params) {
				clt_parameters.imp.cuas_corr_offset	= corr_offset;
				clt_parameters.imp.cuas_corr_pairs =  corr_pairs;
				clt_parameters.imp.cuas_precorr_ra =  precorr_ra;
				clt_parameters.imp.cuas_corr_step =   corr_ra_step;
				clt_parameters.imp.cuas_fat_zero =	  fat_zero;
				clt_parameters.imp.cuas_cent_radius	= cent_radius;
				clt_parameters.imp.cuas_n_recenter =  n_recenter;
				clt_parameters.imp.cuas_rstr =        rstr;
				
				clt_parameters.imp.cuas_speed_min =   speed_min;
				clt_parameters.imp.cuas_speed_pref =  speed_pref;
				clt_parameters.imp.cuas_speed_boost = speed_boost;
				clt_parameters.imp.cuas_smooth =      smooth;
				clt_parameters.imp.cuas_max_range =   max_range;
				clt_parameters.imp.cuas_num_cycles =  num_cycles;
				
				clt_parameters.imp.cuas_half_step =   half_step;
				clt_parameters.imp.cuas_target_radius =   target_radius;
				clt_parameters.imp.cuas_target_strength = target_strength;
				
				clt_parameters.imp.cuas_target_frac = new double [target_frac.length][2];
				for (int i = 0; i < target_frac.length; i++) {
					if (target_frac[i].length >= 2) {
						clt_parameters.imp.cuas_target_frac[i][0] = target_frac[i][0];
						clt_parameters.imp.cuas_target_frac[i][1] = target_frac[i][1];
					} else {
						System.out.println("testCuasScanMotion(): 2.wrong format for a pair of strength, fraction values.");
					}
				}
				clt_parameters.imp.cuas_no_border =   no_border; 
				clt_parameters.imp.cuas_lma_sigma =   lma_sigma;
				clt_parameters.imp.cuas_wnd_pedestal= wnd_pedestal;
				clt_parameters.imp.cuas_lma_r0 =      lma_r0;
				clt_parameters.imp.cuas_lma_fit_xy=   lma_fit_xy;
				clt_parameters.imp.cuas_lma_fit_a=    lma_fit_a; 
				clt_parameters.imp.cuas_lma_fit_c=    lma_fit_c; 
				clt_parameters.imp.cuas_lma_fit_r=    lma_fit_r; 
				clt_parameters.imp.cuas_lma_fit_k=    lma_fit_k; 
				clt_parameters.imp.cuas_lma_ovrsht =  lma_ovrsht;
				clt_parameters.imp.cuas_lambda =      lambda;
				clt_parameters.imp.cuas_lambda_good = lambda_good;
				clt_parameters.imp.cuas_lambda_bad =  lambda_bad;
				clt_parameters.imp.cuas_lambda_max =  lambda_max;
				clt_parameters.imp.cuas_rms_diff =    rms_diff;
				clt_parameters.imp.cuas_num_iter =    num_iter;
				clt_parameters.imp.cuas_lma_rms =     lma_rms;
				clt_parameters.imp.cuas_lma_arms =    lma_arms;
				clt_parameters.imp.cuas_lma_rrms =    lma_rrms;
				clt_parameters.imp.cuas_lma_mina =    lma_mina;
				clt_parameters.imp.cuas_lma_maxr =    lma_maxr;
				clt_parameters.imp.cuas_lma_mink =    lma_mink;
				clt_parameters.imp.cuas_lma_maxk =    lma_maxk;
				clt_parameters.imp.cuas_isolated =    remove_isolated; 
				clt_parameters.imp.cuas_mask_width =   mask_width;
				clt_parameters.imp.cuas_mask_blur =    mask_blur;
				clt_parameters.imp.cuas_mask_round =   mask_round;
				clt_parameters.imp.cuas_target_type	=  target_type;
				clt_parameters.imp.cuas_input_range	=  input_range;
				clt_parameters.imp.cuas_scale2x =      scale2x;
				clt_parameters.imp.cuas_ra_background =ra_background;
				clt_parameters.imp.cuas_iter_show =    iter_show;
				clt_parameters.imp.cuas_2d_save_show = corr2d_save_show; 
				clt_parameters.imp.cuas_intermed_low = intermed_low; 
				clt_parameters.imp.cuas_intermed_high =intermed_high;
				clt_parameters.imp.cuas_intermed_giga =intermed_giga;
				clt_parameters.imp.cuas_save_mono =    save_mono;    
				clt_parameters.imp.cuas_save_color =   save_color;   
				clt_parameters.imp.cuas_save_video =   save_video;   
				
				
			}

			int start_frame = 0;
			if (parentCLT.getTileProcessor() == null) {
				parentCLT.setTiles (imp_sel, // set global tp.tilesX, tp.tilesY
						parentCLT.getNumSensors(), // tp.getNumSensors(),
						clt_parameters,
						QuadCLT.THREADS_MAX); // where to get it? Use instance member
			}
			int first_corr = 1; // skip average
			int num_scenes = imp_sel.getStack().getSize()- first_corr; // includes average
			int seq_length = corr_offset + corr_pairs;
			int corr_inc =  half_step ? (corr_offset/2) : corr_offset;
			int num_corr_samples = (num_scenes - seq_length - start_frame) / corr_inc;
			if (debugLevel > -4) {
				System.out.println("corr_pairs=      "+corr_pairs);
				System.out.println("corr_offset=     "+corr_offset);
				System.out.println("precorr_ra=      "+precorr_ra);
				System.out.println("corr_ra_step=    "+corr_ra_step);
				System.out.println("seq_length=      "+seq_length);
				System.out.println("corr_inc=        "+corr_inc);
				System.out.println("num_scenes=      "+num_scenes);
				System.out.println("start_frame=     "+start_frame);
				System.out.println("num_corr_samples="+num_corr_samples);
				System.out.println("rstr=            "+rstr);
				System.out.println("speed_min=       "+speed_min);
				System.out.println("speed_pref=      "+speed_pref);
				System.out.println("speed_boost=     "+speed_boost);
				System.out.println("fat_zero=        "+fat_zero);
				System.out.println("max_range=       "+max_range);
				System.out.println("num_cycles=      "+num_cycles);
				System.out.println("target_radius=   "+target_radius);
				System.out.println("target_strength= "+target_strength);
				System.out.println("target_frac=     "+IntersceneMatchParameters.double2dToString(target_frac));
				System.out.println("no_border=       "+no_border);
				System.out.println("lma_sigma=       "+lma_sigma);
				System.out.println("wnd_pedestal=    "+wnd_pedestal);
				System.out.println("lma_r0=          "+lma_r0);
				System.out.println("lma_ovrsht=      "+lma_ovrsht);
				System.out.println("lma_fit_xy=      "+lma_fit_xy);
				System.out.println("lma_fit_a=       "+lma_fit_a);
				System.out.println("lma_fit_c=       "+lma_fit_c);
				System.out.println("lma_fit_r=       "+lma_fit_r);
				System.out.println("lma_fit_k=       "+lma_fit_k);
				System.out.println("lambda=          "+lambda);
				System.out.println("lambda_good=     "+lambda_good);
				System.out.println("lambda_bad=      "+lambda_bad);
				System.out.println("lambda_max=      "+lambda_max);
				System.out.println("rms_diff=        "+rms_diff);
				System.out.println("num_iter=        "+num_iter);
				System.out.println("lma_rms=         "+lma_rms);
				System.out.println("lma_arms=        "+lma_arms);
				System.out.println("lma_rrms=        "+lma_rrms);
				System.out.println("lma_mina=        "+lma_mina);
				System.out.println("lma_maxr=	     "+lma_maxr);
				System.out.println("lma_mink=        "+lma_mink);
				System.out.println("lma_maxk=	     "+lma_maxk);
				System.out.println("remove_isolated= "+remove_isolated);
				System.out.println("mask_width=	     "+mask_width);
				System.out.println("mask_blur=	     "+mask_blur);
				System.out.println("mask_round=	     "+mask_round);
				System.out.println("target_type=     "+target_type);
				System.out.println("input_range=     "+input_range);
				System.out.println("scale2x=	     "+scale2x);
				System.out.println("ra_background=   "+ra_background);
				System.out.println("iter_show=       "+iter_show);
				System.out.println("corr2d_save_show="+corr2d_save_show);
				System.out.println("intermed_low=    "+intermed_low);
				System.out.println("intermed_high=   "+intermed_high);
				System.out.println("intermed_giga=   "+intermed_giga);
				System.out.println("save_mono=	     "+save_mono);
				System.out.println("save_color=	     "+save_color);
				System.out.println("save_video=	     "+save_video);
			}
			float [][] fpixels = new float[num_scenes][];
			String [] scene_titles = new String [num_scenes];
			for (int nscene = 0; nscene < fpixels.length; nscene++) {
				fpixels[nscene] = (float[]) imp_sel.getStack().getPixels(nscene+first_corr+1);
				scene_titles[nscene] = imp_sel.getStack().getSliceLabel(nscene+first_corr+1);
			}
			String [] slice_titles= new String [num_corr_samples];
			for (int nscan = 0; nscan < num_corr_samples; nscan++) {
				int frame_cent = start_frame + corr_inc * nscan + seq_length/2; // debug only
				slice_titles[nscan] =  imp_sel.getStack().getSliceLabel(frame_cent+1+first_corr); // wrong! should be imp_sel.getStack().getSliceLabel(frame_cent+1+first_corr)
			}
			
			
			CuasMotion cuasMotion = new CuasMotion (
					clt_parameters, // CLTParameters     clt_parameters,
					parentCLT, // QuadCLT           parentCLT,
					debugLevel); // int               debugLevel)
//			String suffix_param = "-offs"+corr_offset+"-pairs"+corr_pairs+"-rstr"+rstr+"-fz"+fat_zero+"-cr"+cent_radius+"-mr"+max_range +
//					"-ms"+speed_min+"-sp"+speed_pref+"-sb"+speed_boost+"-tr"+target_radius+"-ts"+target_strength+
//					"-tf"+IntersceneMatchParameters.double2dToString(target_frac);
			String imp_name = imp_sel.getTitle();
			imp_name = trimSuffix(imp_name,".tif");
			imp_name = trimSuffix(imp_name,".tiff");
//			String title_vf =           imp_name+"-vector_field"+suffix_param; //
//			final int     frame0 = start_frame + seq_length/2;
			data_dir= trimSuffix(data_dir,"/");
//			String vf_extended_path = data_dir + "/" + vf_extended+".tiff";
//			String vf_filtered_path = data_dir + "/" + title_vf_filtered+".tiff";
//			String vf_path = data_dir + "/" + title_vf+".tiff";
//			motion_scan_filtered = getVectorFieldHyper(vf_filtered_path); // String path)
			boolean batch_mode = true;
			
			if (test_lma) {
				String path_vf =  "/media/elphel/NVME/lwir16-proc/eagle_mountain/linked/movement/selected/25_r1.5/I-1747803449_165687-vector_field_good-offs20-pairs50-rstr0.01-fz300.0-cr3.0-mr1-ms0.0-sp0.0-sb1.0-tr2.0-ts0.8-tf0.0,0.12:2.5,0.15:5.0,0.25.tiff";
//				String path_acc = "/media/elphel/NVME/lwir16-proc/eagle_mountain/linked/movement/selected/25_r1.5/I-1747803449_165687-accumulated-offs20-pairs50-rstr0.01-fz300.0-cr3.0-mr1-ms0.0-sp0.0-sb1.0-tr2.0-ts0.8-tf0.0,0.12:2.5,0.15:5.0,0.25-n20.tiff";
				double [][][] vf_sequence = getVectorFieldHyper(path_vf); // String path)
				if (vf_sequence == null) {
					System.out.println("Failed to motion vectors from  "+path_vf);
					continue;
				}
				generateExtractFilterMovingTargets( // move parameters to clt_parameters
					       clt_parameters,          // CLTParameters       clt_parameters,
					       batch_mode,              // final boolean       batch_mode,
					       parentCLT,               // QuadCLT             parentCLT,          //
					       // three arrays needed
					       fpixels,                 // final float [][]    fpixels,
					       vf_sequence,             // final double [][][] vf_sequence,  // center tiles (not extended), null /non-null only
//					       frame0,                  // final int           frame0,      // for vector_field[0] // source scene corresponding to the first sequence 
					       cuasMotion,              // final CuasMotion    cuasMotion,
					       scene_titles,            // String []           scene_titles, // recreate slice_titles from scene titles?
					       slice_titles,            // String []           slice_titles,
					       debugLevel);             // final int            debugLevel)
				continue;
			}
			
			processMovingTargets(
					clt_parameters, // CLTParameters         clt_parameters,
					batch_mode,     // final boolean         batch_mode,
					parentCLT,      // QuadCLT               parentCLT,          //
					fpixels,        // final float [][]      fpixels,
					scene_titles,   // String []             scene_titles, // recreate slice_titles from scene titles?
					debugLevel);    // final int             debugLevel)
			
			continue;
			
		} // while (true) {
	}
	
	public static int printRemain(int [] remain, String s, boolean all) {
		int remained_total = 0;
		for (int nseq = 0; nseq < remain.length; nseq++) {
			remained_total += remain[nseq];
		}
		if (s != null) {
			System.out.println(s+": total tiles for "+remain.length+" scene sequences is "+remained_total);
		}
		if (all) { 
			for (int nscan = 0; nscan < remain.length; nscan++) {
				System.out.print(nscan+":"+remain[nscan]+", ");
			}
			System.out.println();
		}
		return remained_total;
	}
	
	
	public static double [][][] cloneMotionScan(
			double [][][] motionScan,
			int []        remain){
		double [][][] cloned = new double[motionScan.length][motionScan[0].length][];
		for (int nseq = 0; nseq<cloned.length; nseq++) {
			int n = 0;
			for (int ntile = 0; ntile < cloned[nseq].length; ntile++) {
				if (motionScan[nseq][ntile] != null) {
					cloned[nseq][ntile] = motionScan[nseq][ntile].clone();
					n++;
				}
			}
			if (remain != null) remain[nseq] = n;
		}
		return cloned;
	}

	public static double [][][] combineMotionScans(
			double [][][] scan0,
			double [][][] scan1,
			int []        remain){ // preference
		double [][][] comboScan = new double[scan0.length][scan0[0].length][];
		for (int nseq = 0; nseq < comboScan.length; nseq++) {
			int n = 0;
			for (int ntile = 0; ntile < comboScan[nseq].length; ntile++) {
				if (scan1[nseq][ntile] != null) {
					comboScan[nseq][ntile] = scan1[nseq][ntile].clone();
					n++;
				} else if (scan0[nseq][ntile] != null) {
					comboScan[nseq][ntile] = scan0[nseq][ntile].clone();
					n++;
				}
			}
			if (remain != null) remain[nseq] = n;
		}
		return comboScan;
	}

	public static double [][][] subtractMotionScans(
			double [][][] scan0,  
			double [][][] scan1,
			int []        remain){ // keep scan0 that is not in scan1
		double [][][] comboScan = new double[scan0.length][scan0[0].length][];
		for (int nseq = 0; nseq < comboScan.length; nseq++) {
			int n = 0;
			for (int ntile = 0; ntile < comboScan[nseq].length; ntile++) {
				if ((scan0[nseq][ntile] != null) && (scan1[nseq][ntile] == null)) {
					comboScan[nseq][ntile] = scan0[nseq][ntile].clone();
					n++;
				}
			}
			if (remain != null) remain[nseq] = n;
		}
		return comboScan;
	}
	public static double [][][] filterMotionScans(
			double [][][] scan0,  
			double [][][] scan1,
			int []        remain){ // keep scan0 that is in scan1
		double [][][] comboScan = new double[scan0.length][scan0[0].length][];
		for (int nseq = 0; nseq < comboScan.length; nseq++) {
			int n = 0;
			for (int ntile = 0; ntile < comboScan[nseq].length; ntile++) {
				if ((scan0[nseq][ntile] != null) && (scan1[nseq][ntile] != null)) {
					comboScan[nseq][ntile] = scan0[nseq][ntile].clone();
					n++;
				}
			}
			if (remain != null) remain[nseq] = n;
		}
		return comboScan;
	}
	
	
	
	
	
	
	
	public static double[][][] filterAndShowTargets( // for motion vectors
			double [][][] coord_data,
			double        target_strength,
			double [][]   target_frac,
			String []     slice_titles, //
			String        title_targets_filt,
			int []        targets_remain,
			int           tilesX,
			boolean       show,
			QuadCLT       parentCLT) {
		boolean save = (parentCLT != null);
			
		boolean [][] good_targets = filterTargets(
				coord_data,         // final double [][][] target_coords,
				target_strength,    // final double        target_strength,
				target_frac,        // final double [][]   target_frac, // pairs - strength, minimal fraction for that strength
				tilesX,  // final int           tilesX,
				targets_remain);    // final int     []    remain) {
		double [][][] coord_data_filtered = applyFilter(
				coord_data,         // double [][][] motion_scan,
				good_targets);      // boolean [][] filter5)
		if (show || save) {
			ImagePlus imp =	showVectorFieldsSequence(
					coord_data_filtered,  // double [][][] vector_fields_sequence,
					0.0,                  // double        speed_min,		
					0.0,                  // double        speed_pref,		
					1.0,                  // double        speed_boost, 
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					title_targets_filt,   // String        title,
					false,                // boolean       nan_effective_strength,
					show,                 // boolean       show,
					tilesX);   //  int           tilesX) {
			if (save) {
				parentCLT.saveImagePlusInModelDirectory(imp);  // ImagePlus   imp)
			}
		}
		return coord_data_filtered; // manually restart with different parameters
	}
	
	public static double[][][] filterAndShowTargetsLMA(
			double [][][] coord_data,
			double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
			double        lma_arms, // =      0.06; // Maximal absolute RMS (should match one of cuas_lma_arms OR cuas_lma_rrms (0.484)
		    double        lma_rrms, // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
		    double        lma_mina, // =      1.0;  // Minimal A (amplitude)
		    double        lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
		    double        lma_mink, // =      0.0;  // Minimal K (overshoot) = 1.0
		    double        lma_maxk, // =      5.0;  // Minimal K (overshoot) = 3.0
			String []     slice_titles, //
			String        title_targets_filt,
			int []        targets_remain,
			int           tilesX,
			boolean       show,
			QuadCLT       parentCLT) {
		boolean save = (parentCLT != null);
		double lma_minxy = 0.8;
		boolean [][] good_targets = filterTargetsLMA(
				coord_data,         // final double [][][] target_coords,
				lma_rms,   // double  lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
                lma_arms,  // double  lma_arms, // =      0.03; // Maximal absolutre RMS
                lma_rrms,  // double  lma_rrms, // =      0.03; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
                lma_mina,  // double  lma_mina, // =      1.0;  // Minimal A (amplitude)
                lma_maxr,  // double  lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
                lma_mink,  // double  lma_mink, // =      1.0;  // Minimal K (overshoot) = 1.0
                lma_maxk,  // double  lma_maxk, // =      1.0;  // Minimal K (overshoot) = 3.0
                lma_minxy, // final double lma_minxy, // 0.8
				tilesX,   // final int           tilesX,
				targets_remain);    // final int     []    remain) {
		double [][][] coord_data_filtered = applyFilter(
				coord_data,         // double [][][] motion_scan,
				good_targets);      // boolean [][] filter5)
		if (save || show) {
			ImagePlus imp =	showVectorFieldsSequenceLMA(
					coord_data_filtered,  // double [][][] target_scene_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					title_targets_filt,   // String        title,
					show,                 // boolean       show,
					tilesX);   //  int           tilesX) {
			if (save) {
					parentCLT.saveImagePlusInModelDirectory(imp);  // ImagePlus   imp)
			}
		}
		return coord_data_filtered; // manually restart with different parameters
	}
	

	
	
	
	
	
	public static double [][][] getAccumulatedCoordinates(
			final double [][][] vector_fields, // centers
			final double [][]   accum_data,   // should be around 0, no low-freq
			final double        centroid_radius,
			final int           n_recenter, //  re-center window around new maximum. 0 -no refines (single-pass)
			final int           tilesX){
		final int tile2 = 2 * GPUTileProcessor.DTT_SIZE;
		final int num_seq = vector_fields.length; // same as accum_data.length
		final int num_tiles = vector_fields[0].length;
//		final int num_pix = accum_data[0].length;
//		final int tilesY = num_tiles/tilesX;
		final int width = GPUTileProcessor.DTT_SIZE * tilesX;
//		final int height = num_pix/width; 
		final double [][][] acc_coord = new double[num_seq][num_tiles][];
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					double [] pix_tile = new double [tile2 * tile2];
					// may be faster if process only where vector_field[nseq][ntile] is not null
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if (vector_fields[nSeq][ntile] != null) {
								int tileX = ntile % tilesX;
								int tileY = ntile / tilesX;
								int px0 = tileX * GPUTileProcessor.DTT_SIZE + GPUTileProcessor.DTT_SIZE/2; 
								int py0 = tileY * GPUTileProcessor.DTT_SIZE + GPUTileProcessor.DTT_SIZE/2;
								// copy 16x16 tile
								for (int y = 0; y < tile2; y++) {
									System.arraycopy(
											accum_data[nSeq],
											px0 + (py0 +y) * width,
											pix_tile,
											y * tile2,
											tile2);
								}
								// find max
								double [] mv = Correlation2d.getMaxXYCm( // last, average may be null
										pix_tile,        // corrs.length-1], // double [] data,
										tile2,           // int       data_width,      //  = 2 * transform_size - 1; // negative - will return center fraction
										centroid_radius, // double    radius, // 0 - all same weight, > 0 cosine(PI/2*sqrt(dx^2+dy^2)/rad)
										n_recenter,      // int       refine, //  re-center window around new maximum. 0 -no refines (single-pass)
										null,            // boolean [] fpn_mask,
										false,           // boolean    ignore_border, // only if fpn_mask != null - ignore tile if maximum touches fpn_mask
										false);          // boolean   debug)
								acc_coord[nSeq][ntile] = mv; // no filtering here
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return acc_coord;
	}
	
	public static double [][][][]   getAccumulatedCoordinates(
			final double [][][] vector_fields, // centers
			final float  [][]   accum_data,    // should be around 0, no low-freq
			final double        centroid_radius,
			final int           n_recenter,    //  re-center window around new maximum. 0 -no refines (single-pass)
			final int           tilesX,
			final boolean       no_border,
		// Moving target LMA	
			final double        lma_sigma,
			final double        wnd_pedestal,
			final double        lma_r0,
			final double        lma_ovrsht,
		// CUAS Motion LMA parameters
			final boolean       lma_fit_xy,		
			final boolean       lma_fit_a,						
			final boolean       lma_fit_c,
			final boolean       lma_fit_r,
			final boolean       lma_fit_k,			
			final double        lambda,
			final double        lambda_good,
			final double        lambda_bad,
			final double        lambda_max,
			final double        rms_diff,
			final int           num_iter,
			final int           debugLevel){
		final int tile2 = 2 * GPUTileProcessor.DTT_SIZE;
		final int num_seq = vector_fields.length; // same as accum_data.length
		final int num_tiles = vector_fields[0].length;
//		final int num_pix = accum_data[0].length;
//		final int tilesY = num_tiles/tilesX;
		final int width = GPUTileProcessor.DTT_SIZE * tilesX;
//		final int height = num_pix/width; 
		final double [][][] acc_coord = new double[num_seq][num_tiles][];
		final double [][][] lma_coord = new double[num_seq][num_tiles][];
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		
		final boolean [] param_select = new boolean [CuasMotionLMA.INDX_LEN];
		param_select[CuasMotionLMA.INDX_X0] =  lma_fit_xy;
		param_select[CuasMotionLMA.INDX_Y0] =  lma_fit_xy;
		param_select[CuasMotionLMA.INDX_A] =   lma_fit_a;
		param_select[CuasMotionLMA.INDX_C] =   lma_fit_c;
		param_select[CuasMotionLMA.INDX_RR0] = lma_fit_r;
		param_select[CuasMotionLMA.INDX_K] =   lma_fit_k;
		
		final boolean[] fpn_mask= no_border? (new boolean[0]) : null;
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					double [] pix_tile = new double [tile2 * tile2];
					CuasMotionLMA cuasMotionLMA = new CuasMotionLMA(
							tile2,      // int         width,
							lma_sigma, // double     sigma);
							wnd_pedestal);
					// may be faster if process only where vector_field[nseq][ntile] is not null
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if (vector_fields[nSeq][ntile] != null) {
								int tileX = ntile % tilesX;
								int tileY = ntile / tilesX;
								int px0 = tileX * GPUTileProcessor.DTT_SIZE - GPUTileProcessor.DTT_SIZE/2; // top-left of 16x16 tile 
								int py0 = tileY * GPUTileProcessor.DTT_SIZE - GPUTileProcessor.DTT_SIZE/2; // top-left of 16x16 tile
								// copy 16x16 tile
								int indx_dst = 0;
								for (int y = 0; y < tile2; y++) {
									int indx_src = px0 + (py0 +y) * width;
									for (int x = 0; x < tile2; x++) {
										pix_tile[indx_dst++] = accum_data[nSeq][indx_src++]; // float-> double
									}
								}
								// find max negative data_width to calculate fraction (mv[])
								double [] mv = Correlation2d.getMaxXYCm( // last, average (Will be relative to the center of the tile)
										pix_tile,        // corrs.length-1], // double [] data,
										-tile2,           // int       data_width,      //  = 2 * transform_size - 1; // negative - will return center fraction
										centroid_radius, // double    radius, // 0 - all same weight, > 0 cosine(PI/2*sqrt(dx^2+dy^2)/rad)
										n_recenter,      // int       refine, //  re-center window around new maximum. 0 -no refines (single-pass)
										fpn_mask,        // boolean [] fpn_mask,
										false,           // boolean    ignore_border, // only if fpn_mask != null - ignore tile if maximum touches fpn_mask
										false);          // boolean   debug)
								acc_coord[nSeq][ntile] = mv; // no filtering here
								if (mv != null) {
									cuasMotionLMA.prepareLMA(
											param_select, // boolean [] param_select,
											pix_tile, // double []  tile_data,
											mv[0], // double     xc, // relative to center =width/2
											mv[1], // double     yc, // relative to center =width/2
											lma_r0, // double     r0,
											lma_ovrsht, // double     k,
											debugLevel); // int        debugLevel)
									int rslt = cuasMotionLMA. runLma( // <0 - failed, >=0 iteration number (1 - immediately)
											lambda, // double lambda,           // 0.1
											lambda_good, // double lambda_scale_good,// 0.5
											lambda_bad, // double lambda_scale_bad, // 8.0
											lambda_max, // double lambda_max,       // 100
											rms_diff, // double rms_diff,         // 0.001
											num_iter, // int    num_iter,         // 20
											debugLevel); // int    debug_level)
									if (rslt >= 0) {
										lma_coord[nSeq][ntile] = cuasMotionLMA.getResult();
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return new double [][][][] {acc_coord, lma_coord};
	}

	public static float  [][] getTargetImages(
			final double [][][] vector_fields, // centers , just null/not null
			final float  [][]   accum_data,   // should be around 0, no low-freq
			final int           tilesX){
		final int tile2 = 2 * GPUTileProcessor.DTT_SIZE;
		final int num_seq = vector_fields.length; // same as accum_data.length
		final int num_tiles = vector_fields[0].length;
		final int num_pix =   accum_data[0].length;
		final int width = GPUTileProcessor.DTT_SIZE * tilesX;
		float [][] taret_images = new float[num_seq][num_pix];
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					// may be faster if process only where vector_field[nseq][ntile] is not null
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						Arrays.fill(taret_images[nSeq], Float.NaN);
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if (vector_fields[nSeq][ntile] != null) {
								int tileX = ntile % tilesX;
								int tileY = ntile / tilesX;
								int px0 = tileX * GPUTileProcessor.DTT_SIZE - GPUTileProcessor.DTT_SIZE/2; // top-left of 16x16 tile 
								int py0 = tileY * GPUTileProcessor.DTT_SIZE - GPUTileProcessor.DTT_SIZE/2; // top-left of 16x16 tile
								// copy 16x16 tile
								for (int y = 0; y < tile2; y++) {
									System.arraycopy(
											accum_data[nSeq],
											px0 + (py0 +y) * width,
											taret_images[nSeq],
											px0 + (py0 +y) * width,
											tile2);
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return taret_images;
	}
	
	
	
	
	
	
	public static double [][][] applyFilter(
			double [][][] motion_scan,
			boolean [][] filter5){
		double [][][] filtered_scan = new double [motion_scan.length][motion_scan[0].length][];
		for (int nscan = 0; nscan < motion_scan.length; nscan++) {
			for (int t=0; t<motion_scan[nscan].length; t++) {
				if ((motion_scan[nscan][t] != null) && filter5[nscan][t]) {
					filtered_scan[nscan][t] = motion_scan[nscan][t].clone();
				}
			}
		}
		return filtered_scan;
		
	}
	
	public static ImagePlus showVectorFieldsSequence(
			double [][][] vector_fields_sequence,
			double        speed_min,		
			double        speed_pref,		
			double        speed_boost, 
			String []     titles, // all slices*frames titles or just slice titles or null
			String        title,
			boolean       nan_effective_strength,
			boolean       show,
			int           tilesX) {
		double [][][] img_data = new double [VF_TOP_TITLES.length][vector_fields_sequence.length][vector_fields_sequence[0].length];
		for (int nscan = 0; nscan < vector_fields_sequence.length; nscan++) {
			for (int k = 0; k < VF_TOP_TITLES.length; k++) {
				Arrays.fill(img_data[k][nscan], Double.NaN);
			}
			for (int t=0; t<vector_fields_sequence[nscan].length; t++) {
				if (vector_fields_sequence[nscan][t] != null) {
					double es = getEffectiveStrength(
							vector_fields_sequence[nscan][t],  // double [] mv_tile, // 4
							speed_min,              // double speed_min,		
							speed_pref,             // double speed_pref,		
							speed_boost);           //double speed_boost)
					if ((es > 0) || !nan_effective_strength) {
						for (int k = 0; k <  INDX_SPEED; k++) {
							img_data[k][nscan][t] = vector_fields_sequence[nscan][t][k];
						}
						double vx = vector_fields_sequence[nscan][t][INDX_VX];
						double vy = vector_fields_sequence[nscan][t][INDX_VY];
						img_data[INDX_SPEED]     [nscan][t] = Math.sqrt(vx*vx+vy*vy);
						img_data[INDX_CONFIDENCE][nscan][t] = es;
					}
				}

			}
		}
		ImagePlus imp = ShowDoubleFloatArrays.showArraysHyperstack(
				img_data,       // double[][][] pixels, 
				tilesX,         // int          width, 
				title,          // String       title, "time_derivs-rt"+diff_time_rt+"-rxy"+diff_time_rxy,
				titles,         // String []    titles, // all slices*frames titles or just slice titles or null
				VF_TOP_TITLES,  // String []    frame_titles, // frame titles or null
				show);          // boolean      show)
		return imp;
	}
	
	public static ImagePlus showVectorFieldsSequenceLMA(
			double [][][] target_scene_sequence, // rename
			String []     titles, // all slices*frames titles or just slice titles or null
			String        title,
			boolean       show,
			int           tilesX) {
		int num_fileds = CuasMotionLMA.LMA_TITLES.length;
		int num_scenes = target_scene_sequence.length;
		int num_tiles = target_scene_sequence[0].length;
		double [][][] img_data = new double [num_fileds][num_scenes][num_tiles];
		for (int nscene = 0; nscene < num_scenes; nscene++) {
			for (int nfiled = 0; nfiled < num_fileds; nfiled++) {
				Arrays.fill(img_data[nfiled][nscene], Double.NaN);
			}
			for (int ntile=0; ntile<target_scene_sequence[nscene].length; ntile++) {
				if (target_scene_sequence[nscene][ntile] != null) {
						for (int nfield = 0; nfield <  CuasMotionLMA.RSLT_LEN; nfield++) {
							img_data[nfield][nscene][ntile] = target_scene_sequence[nscene][ntile][nfield];
						}
				}
			}
		}
		ImagePlus imp = ShowDoubleFloatArrays.showArraysHyperstack(
				img_data,       // double[][][] pixels, 
				tilesX,         // int          width, 
				title,          // String       title, "time_derivs-rt"+diff_time_rt+"-rxy"+diff_time_rxy,
				titles,         // String []    titles, // all slices*frames titles or just slice titles or null
				CuasMotionLMA.LMA_TITLES,  // String []    frame_titles, // frame titles or null
				show);          // boolean      show)
		return imp;
	}

	
	
	
	
	public static double [][][] getVectorFieldHyper(String path){
		int [] wh = new int [2];
		String [][] pvf_top_titles = new String[1][];
		String [][] pvf_titles = new String[1][];
		double [][][] vf_file = ShowDoubleFloatArrays.readDoubleHyperstack(
						path, // String      path,
						wh,            // int []      wh,            // should be null or int[2] 
						pvf_top_titles,   // String [][] ptop_titles,   // should be null or String [1][]
						pvf_titles);      // String [][] pslice_titles){// should be null or String [1][]
		if (vf_file == null) {
			return null;
		}
		int vect_len = INDX_SPEED; // vf_file.length;
		int num_seq = vf_file[0].length;
		int num_tiles = vf_file[0][0].length;
		double [][][] vf = new double [num_seq][num_tiles][];
		for (int nseq=0; nseq < num_seq; nseq++) {
			for (int ntile = 0; ntile < num_tiles; ntile++) if (!Double.isNaN(vf_file[0][nseq][ntile])){
				double [] v = new double[vect_len]; // cut to originally calculated fields
				for (int i = 0; i < vect_len; i++) {
					v[i] = vf_file[i][nseq][ntile];
				}
				vf[nseq][ntile] = v;
			}
		}
		return vf;
	}
	
	public static String trimSuffix(
			String name,
			String suffix) {
		while (name.endsWith(suffix)) {
			name = name.substring(0, name.length()-suffix.length());
		}
		return name;
	}

	public static boolean [][] filterTargets(
			final double [][][] target_coords,
			final double        target_strength,
			final double [][]   target_frac, // pairs - strength, minimal fraction for that strength
			final int           tilesX,
			final int     []    remain) {
		final int num_seq = target_coords.length;
		final int num_tiles = target_coords[0].length;
		final int tilesY = num_tiles/ tilesX;
		final boolean [][] filter_target = new boolean [num_seq][num_tiles];
		
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						int num = 0;
						for (int tileY = 0; tileY < tilesY; tileY++) {
							for (int tileX = 0; tileX < tilesX; tileX++) {
								int ntile = tileX + tilesX * tileY;
								double [] tc = target_coords[nSeq][ntile];
								if (tc != null) {
									double strength = target_coords[nSeq][ntile][INDX_STRENGTH];
									if (strength > target_strength) {
										check_target: {
											double fraction = target_coords[nSeq][ntile][INDX_FRAC];
											for (double [] frac_pair : target_frac) {
												if ((strength > frac_pair[0]) && (fraction < frac_pair[1])) {
													break check_target;
												}
											}
											filter_target[nSeq][ntile] = true;
											num++;
										}
									}
								}
							}
						}
						if (remain != null) {
							remain[nSeq] = num;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return filter_target;
	}

	public static boolean [][] filterTargetsLMA(
			final double [][][] target_coords,
			double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
			double        lma_arms, // =      0.06; // Maximal absolute RMS (should match one of cuas_lma_arms OR cuas_lma_rrms (0.484)
		    double        lma_rrms, // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
		    double        lma_mina, // =      1.0;  // Minimal A (amplitude)
		    double        lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
		    double        lma_mink, // =      0.0;  // Minimal K (overshoot) = 1.0
		    double        lma_maxk, // =      5.0;  // Minimal K (overshoot) = 3.0
			final double lma_minxy, // 0.8
			final int           tilesX,
			final int     []    remain) {
		final int num_seq = target_coords.length;
		final int num_tiles = target_coords[0].length;
		final int tilesY = num_tiles/ tilesX;
		final boolean [][] filter_target = new boolean [num_seq][num_tiles];
		final double maxxy =   GPUTileProcessor.DTT_SIZE - 1 -lma_minxy; // lma_minxy=1 - prevent bottom row/righth column
		final double minxy =  -GPUTileProcessor.DTT_SIZE     +lma_minxy; // lma_minxy=1 - prevent top row/left column 
		
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						int num = 0;
						for (int tileY = 0; tileY < tilesY; tileY++) {
							for (int tileX = 0; tileX < tilesX; tileX++) {
								int ntile = tileX + tilesX * tileY;
								double [] lma_rslts = target_coords[nSeq][ntile];
								if (lma_rslts != null) {
									if (lma_rslts[CuasMotionLMA.RSLT_ITERS] < 0) { // fitting has not failed
										continue; // LMA failed
									}
									double A = lma_rslts[CuasMotionLMA.RSLT_A];
									if (A < lma_mina) {
										continue; // too weak
									}
									if (lma_rslts[CuasMotionLMA.RSLT_RMS] > lma_rms) {
										continue; // too high RMSE regardless of A
									}
									double max_RMS = Math.max(lma_arms, A * lma_rrms);
									if (lma_rslts[CuasMotionLMA.RSLT_RMS] > max_RMS) {
										continue; // too high RMSE
									}
									if (lma_rslts[CuasMotionLMA.RSLT_R0] > lma_maxr) {
										continue; // Radius is too high
									}
									if (lma_rslts[CuasMotionLMA.RSLT_K] < lma_mink) {
										continue; // K is too low
									}
									if (lma_rslts[CuasMotionLMA.RSLT_K] > lma_maxk) {
										continue; // K is too high
									}
									double x = lma_rslts[CuasMotionLMA.RSLT_X];
									double y = lma_rslts[CuasMotionLMA.RSLT_Y];
									if ((x < minxy) || (y < minxy) || (x > maxxy) || (y > maxxy)) {
										continue; // center outside of allowed range (on the very edges)
									}
									filter_target[nSeq][ntile]	= true;
									num++;	
								}
							}
						}
						if (remain != null) {
							remain[nSeq] = num;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return filter_target;
	}
	
	
	
	public static boolean [][] filterMotionScan(
			final double [][][] motion_scan,
			final int           tilesX,
			final int           range, // 1 or 2
			final boolean [][]  filtered, // same format as output, previously selected
			final int     []    remain,
			double              speed_min,		
			double              speed_pref,		
			double              speed_boost	){
		final int num_seq = motion_scan.length;
		final int num_tiles = motion_scan[0].length;
		final int tilesY = num_tiles/ tilesX;
		final boolean [][] filter5 = new boolean [num_seq][num_tiles];
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					TileNeibs tn = new TileNeibs(tilesX, tilesY);
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						int num = 0;
						double [][] ms = motion_scan[nSeq];
						for (int tileY = 0; tileY < tilesY; tileY++) {
							for (int tileX = 0; tileX < tilesX; tileX++) {
								int ntile = tileX + tilesX * tileY;
								if ((ms[ntile] != null) && ((filtered == null) || !filtered[nSeq][ntile])) {
									boolean ismax = true;
									check_max:{
//										double cval = ms[ntile][INDX_STRENGTH]*ms[ntile][INDX_FRAC]; // quality
										double cval = getEffectiveStrength(
												ms[ntile],  // double [] mv_tile, // 4
												speed_min,  // double speed_min,		
												speed_pref, // double speed_pref,		
												speed_boost); //double speed_boost)
										if (cval == 0) {
											ismax = false;
											break check_max;
										}
										for (int dy = -range; dy <= range; dy++) {
											for (int dx = -range; dx <= range; dx++) {
												int indx = tn.getNeibIndex(ntile, dx, dy);
												if ((indx >= 0) && (ms[indx] != null) && ((filtered == null) || !filtered[nSeq][indx])){													if ((filtered == null) || !filtered[nSeq][indx]) {
//														double val = ms[indx][INDX_STRENGTH]*ms[indx][INDX_FRAC]; // quality
													    // FIXME: calculate once all values before this cycle
														double val = getEffectiveStrength(
																ms[indx],  // double [] mv_tile, // 4
																speed_min,  // double speed_min,		
																speed_pref, // double speed_pref,		
																speed_boost); //double speed_boost)
														if (val > cval) {
															ismax = false;
															break check_max;
														}
													}
												}
											}
										}
									}
									filter5[nSeq][ntile] = ismax;
									if (ismax) {
										num++;
									}
								}
							}
						}
						if (remain != null) {
							remain[nSeq] = num;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return filter5;
	}
	
	private static double getEffectiveStrength(
			double [] mv_tile, // 4
			double speed_min,		
			double speed_pref,		
			double speed_boost) {
		if (mv_tile == null ) {
			return 0.0;
		}
		double val = mv_tile[INDX_STRENGTH];
		if (mv_tile.length > INDX_STRENGTH) {
			val *= mv_tile[INDX_FRAC];
		}
		if ((speed_min > 0) ||(speed_pref >0)) {
			double vx = mv_tile[INDX_VX];
			double vy = mv_tile[INDX_VY];
			double v =  Math.sqrt(vx*vx + vy*vy);
			if (v < speed_min) {
				return 0;
			}
			if (v > speed_pref) {
				val *= Math.min(v / speed_pref, speed_boost);
			}
		}
	
		return val;
	}
	
	
	
	
	public static double [][][] extendMotionScan(
			final double [][][] motion_scan,
			final boolean [][]  filtered, // centers, should be non-overlapped . May be [motion_scan.length][] - will be calculated and returned
			final int           tilesX,
			final int           range, // 1 or 2
			final int     []    remain){ // number of center tiles (3x3 groups in the output)
		final int num_seq = motion_scan.length;
		final int num_tiles = motion_scan[0].length;
		final int tilesY = num_tiles/ tilesX;
		final double [][][] extended = new double [num_seq][num_tiles][];
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					TileNeibs tn = new TileNeibs(tilesX, tilesY);
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						int num = 0;
						double [][] ms = motion_scan[nSeq];
						boolean has_filter = (filtered != null) && (filtered[nSeq] != null);
						boolean [] centers = has_filter ? filtered[nSeq] : new boolean [ms.length];
						if (!has_filter) {
							for (int i = 0; i < centers.length; i++) {
								centers[i] = ms[i] != null;
							}
						}
						if ((filtered != null) && (filtered[nSeq] == null)) {
							filtered[nSeq] = centers;
						}
						for (int tileY = 0; tileY < tilesY; tileY++) {
							for (int tileX = 0; tileX < tilesX; tileX++) {
								int ntile = tileX + tilesX * tileY;
								if (centers[ntile]) {
									num++;
									double [] mv = motion_scan[nSeq][ntile];
									for (int dy = -range; dy <= range; dy++) {
										for (int dx = -range; dx <= range; dx++) {
											int indx = tn.getNeibIndex(ntile, dx, dy);
											if (indx >=0) {
												extended[nSeq][indx] = mv.clone(); // does not need to ne cloned
											}
										}
									}
								}
							}
						}
						if (remain != null) {
							remain[nSeq] = num;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return extended;
	}
	
	
	
	
	
	public static double [][][] getMotionScan(
			boolean           batch_mode,
			CLTParameters     clt_parameters,
			CuasMotion        cuasMotion,
			float [][]        fpixels, // may be running average
			int               start_frame,
			int               corr_pairs,
			int               corr_offset,
			int               corr_inc,
		    int               corr_ra_step, //    5;      // correlation step when using rolling average
			boolean           smooth,
			double            fat_zero,
			double            cent_radius,
			int               n_recenter,
			double            rstr,
			String            title,
			double [][][]     corr2d, // null or [(fpixels.length - seq_length - start_frame) / corr_step)[][]
			int               debugLevel) {
		
		int seq_length = corr_offset + corr_pairs;
		int num_scenes = fpixels.length;
		int num_corr_samples = (num_scenes - seq_length - start_frame) / corr_inc;
		double [][][] motion_scan = new double [num_corr_samples][][];
		IJ.showStatus("Performing Motion Scan");
  		IJ.showProgress(0.0);
		for (int nscan = 0; nscan < motion_scan.length; nscan++) {
			int frame0 = start_frame + corr_inc * nscan;
			int frame1 = frame0 + corr_offset;
			int frame_cent = frame0 + seq_length/2; // debug only
			String suffix_param = "-"+frame_cent+"-"+corr_offset+"-"+corr_pairs;
			String dbg_suffix = (title != null) ? (title+suffix_param) : null;
			TDCorrTile [] tdCorrTiles = cuasMotion.correlatePairs(
					clt_parameters, // CLTParameters clt_parameters,
					fpixels,        // float [][]    fpixels,
					frame0,         // int           frame0,
					frame1,         // int           frame1,
					corr_pairs,     // int           frame_len,
					corr_ra_step,   // int           corr_ra_step,
					smooth,         // boolean       smooth, // use cosine mask
					true,           // batch_mode,     // final boolean batch_mode,
					dbg_suffix,     // final String  dbg_suffix, // for image_names
					debugLevel);    // int           debugLevel)
			// convert to pixel domain and display 
			double [][] corr_tiles_pd = cuasMotion.convertTDtoPD(
					tdCorrTiles,    // final TDCorrTile [] tiles,
					0xFE,           // final int           corr_type, // 0xFE
					fat_zero,       // final double        gpu_fat_zero,
					debugLevel);    // final int           debug_level

			double [][] vector_field = TDCorrTile.getMismatchVector( // full tiles in gpu (512*512)
					corr_tiles_pd,  // final double[][] tiles,
					rstr,           // double              rmax, 
					cent_radius,    // final double        centroid_radius, // 0 - all same weight, > 0 cosine(PI/2*sqrt(dx^2+dy^2)/rad)
					n_recenter,     // final int           n_recenter); //  re-center window around new maximum. 0 -no refines (single-pass)
					true);            // final boolean     calc_fraction	){ // calculate fraction inside center circle
			final int corr_size = 2 * GPUTileProcessor.DTT_SIZE -1;
			
			motion_scan[nscan] = vector_field;
			// for debugging: to see each 2d correlation
			if (corr2d != null) {
				corr2d[nscan] = corr_tiles_pd;
			}
			
			boolean show_vector_field =    (debugLevel>100); // true;
			boolean show_2d_correlations = (debugLevel>0); // true;
			if (show_2d_correlations) {
				double [][] dbg_2d_corrs = ImageDtt.corr_partial_dbg( // not used in lwir
						new double [][][] {corr_tiles_pd},   // final double [][][]     corr_data,       // [layer][tile][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
						cuasMotion.tilesX,                   // final int               tilesX,
						corr_size,                           //final int               corr_size,       // 15
						clt_parameters.corr_border_contrast, // final double            border_contrast,
						debugLevel);                         // final int               globalDebugLevel)
				String [] dbg_titles= {"raw"}; // ,"neibs"};
				ShowDoubleFloatArrays.showArrays(
						dbg_2d_corrs,
						cuasMotion.tilesX * (corr_size + 1),
						cuasMotion.tilesY * (corr_size + 1),
						true,
						title+"-corr2d"+suffix_param, // "-corr2d"+"-"+frame0+"-"+frame1+"-"+corr_pairs,
						dbg_titles);
			}

			if (show_vector_field) {
				double [][] dbg_vf = new double [3][cuasMotion.tilesX * cuasMotion.tilesY];
				String [] dbg_titles = new String[dbg_vf.length];
				String [] prefix= {"single"}; // ,"neibs"};
				for (int n = 0; n < 1; n++) { // vector_field.length; n++) {
					dbg_titles [3*n+0] = prefix[n]+"-vx";	
					dbg_titles [3*n+1] = prefix[n]+"-vy";	
					dbg_titles [3*n+2] = prefix[n]+"-str";	
				}
				for (int i = 0; i < dbg_vf.length; i++) {
					Arrays.fill(dbg_vf[i], Double.NaN);

				}
				for (int t=0; t<dbg_vf[0].length; t++) {
					if (vector_field[t] != null) {
						for (int k = 0; k < 3; k++) {
							dbg_vf[k][t] = vector_field[t][k];
						}
					}
				}
				ShowDoubleFloatArrays.showArrays(
						dbg_vf,
						cuasMotion.tilesX,
						cuasMotion.tilesY,
						true,
						title+"-vector_field"+suffix_param,
						dbg_titles);
			}
			IJ.showProgress(1.0*(nscan+1) / motion_scan.length);	
		}
		IJ.showProgress(1.0);	
		return motion_scan;
	}
	
	
	
	
	
	
	/**
	 * Convert Transform-domain 2D correlation to phase correlation,
	 * inverse-transform it to pixel domain and return result as 
	 * sparse array of double[] tiles mapped in linescan order. Empty
	 * tiles are null. 
	 * @param tiles
	 * @param corr_type
	 * @param gpu_fat_zero
	 * @param debug_level
	 * @return sparse array in line-scan order. Each element either null or double[225]
	 */
	public double [][]  convertTDtoPD(
			final TDCorrTile [] tiles,
			final int           corr_type, // 0xFE
			final double        gpu_fat_zero,
			final int           debug_level
			) {
		return TDCorrTile.convertTDtoPD(
				gpuQuad,       // final GpuQuad       gpuQuad,
				tiles,         // final TDCorrTile [] tiles,
				corr_type,     // 0xFEfinal int           corr_type, // 0xFE
				gpu_fat_zero,  // final double        gpu_fat_zero,
				debug_level);  // final int           debug_level)
	}
	
	
	
	public TDCorrTile [] correlatePairs(
			CLTParameters clt_parameters,
			float [][]    fpixels,
			int           frame0,
			int           frame1,
			int           frame_len,
			int           corr_ra_step,
			boolean       smooth, // use cosine mask
			final boolean batch_mode,
			final String  dbg_suffix, // for image_names
			int           debugLevel) {
		TDCorrTile [] tdCorrTiles = new TDCorrTile[tilesX * tilesY];
		double sw = 0;
		for (int dframe = corr_ra_step/2; dframe < frame_len; dframe+= corr_ra_step) {
			double weight = smooth ? Math.sin((dframe+0.5)/frame_len*Math.PI): 1.0;
			if (((frame0+dframe) >= 0) && ((frame1+dframe) < fpixels.length)) {
				String dbg_n_suffix = (dbg_suffix != null)? (dbg_suffix+"-"+dframe) : null;
				TDCorrTile [] pairTiles = correlatePair(
						clt_parameters,         // CLTParameters clt_parameters,
						fpixels[frame0+dframe], // float []      fpixels_ref,
						fpixels[frame1+dframe], // float []      fpixels_img,
						batch_mode,             // final boolean batch_mode,
						dbg_n_suffix,           // final String  dbg_suffix, // for image_names
						debugLevel);            // int           debugLevel)
				TDCorrTile.accumulate (
						tdCorrTiles,            // final TDCorrTile [] dst,
						pairTiles,              // final TDCorrTile [] src,
						weight);                   // final double src_weight)
				sw+= weight;
			}
		}
		double scale = 1.0/sw;
		for (TDCorrTile tile:tdCorrTiles) if (tile !=null){
			tile.scale(scale);
		}
		return tdCorrTiles;
	}
	
	public void  accumulateMotionScene(
			CLTParameters     clt_parameters,
			float []          fpixels,
			final double [][] vector_field,
			final int         erase, // -1 - no, 0 - to 0, 1 - to NaN
			final boolean     batch_mode,
			final double      offset_scale,
			final double      magnitude_scale,
			final int         tilesX) {
		int [] wh = {gpu_max_width, gpu_max_height};
			TpTask [] tp_tasks = GpuQuad.setRectilinearMovingTasks(
					vector_field,    // final double [][] vector_field,
					offset_scale,    // final double      offset_scale,
					magnitude_scale, // final double      magnitude_scale,
					tilesX);         // final int         tilesX)
			image_dtt.setRectilinearReferenceTD(
					erase,                      // final int                 erase_clt,
					fpixels,                    // final float []            fpixels_ref,
					wh,                         // final int []              wh,               // null (use sensor dimensions) or pair {width, height} in pixels
					clt_parameters.img_dtt,     // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
					false,                       // final boolean             use_reference_buffer,
					tp_tasks,                   // final TpTask[]            tp_tasks,
					clt_parameters.gpu_sigma_r, // final double              gpu_sigma_r,     // 0.9, 1.1
					clt_parameters.gpu_sigma_b, // final double              gpu_sigma_b,     // 0.9, 1.1
					clt_parameters.gpu_sigma_g, // final double              gpu_sigma_g,     // 0.6, 0.7
					clt_parameters.gpu_sigma_m, // final double              gpu_sigma_m,     //  =       0.4; // 0.7;
					batch_mode? -3: debugLevel);                // final int                 globalDebugLevel)
			boolean frame_debug = false;
			if (frame_debug) {
				renderFromTD (
						false, // boolean             use_reference,
						"render-from-TD"); // String              suffix)
			}
			return;
	}

	
	/**
	 * Shift keyframe according to the vector_field, render and return result image. Does not need to reload keyframe image
	 * if it is already loaded in the GPU
	 * @param clt_parameters all the parameters
	 * @param fkeyframe keyframe data or null (if  keyframe did not change)
	 * @param vector_field [scene number][tile number] {vx, vy, ...}. nulls for empty tiles
	 * @param erase     -1 - no erase, 0 - to 0, 1 - to NaN  
	 * @param batch_mode disables graphics debug in batch mode
	 * @param offset_scale frame offset to multiply vX, vY when calculating total tile offsets
	 * @return
	 */
	public float [] renderMovingKeyframe(
			CLTParameters     clt_parameters,
			float []          fkeyframe,   // if null, will assume it is already in the GPU
			final double [][] vector_field,
			final int         erase, // -1 - no, 0 - to 0, 1 - to NaN
			final boolean     batch_mode,
			final double      offset_scale) {
		int [] wh = {gpu_max_width, gpu_max_height};
			TpTask [] tp_tasks = GpuQuad.setRectilinearMovingTasks(
					vector_field,    // final double [][] vector_field,
					offset_scale,    // final double      offset_scale,
					0.0,             // final double      magnitude_scale,
					tilesX);         // final int         tilesX)
			image_dtt.setRectilinearReferenceTD(
					erase,                      // final int                 erase_clt,
					fkeyframe,                  // final float []            fpixels_ref,
					wh,                         // final int []              wh,               // null (use sensor dimensions) or pair {width, height} in pixels
					clt_parameters.img_dtt,     // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
					false,                      // final boolean             use_reference_buffer,
					tp_tasks,                   // final TpTask[]            tp_tasks,
					clt_parameters.gpu_sigma_r, // final double              gpu_sigma_r,     // 0.9, 1.1
					clt_parameters.gpu_sigma_b, // final double              gpu_sigma_b,     // 0.9, 1.1
					clt_parameters.gpu_sigma_g, // final double              gpu_sigma_g,     // 0.6, 0.7
					clt_parameters.gpu_sigma_m, // final double              gpu_sigma_m,     //  =       0.4; // 0.7;
					batch_mode? -3: debugLevel);                // final int                 globalDebugLevel)
			boolean frame_debug = false;
			if (frame_debug) {
				renderFromTD (
						false, // boolean             use_reference,
						"render-from-TD"); // String              suffix)
			}
			float [] rendered_frame = floatFromTD(false); 
			return rendered_frame;
	}
	
	
	
	
	public TDCorrTile [] correlatePair(
			CLTParameters clt_parameters,
			float []      fpixels_ref,
			float []      fpixels_img,
			final boolean batch_mode,
			final String  dbg_suffix, // for image_names
			int           debugLevel) {
		int [] wh = {gpu_max_width, gpu_max_height};
		Rectangle woi = new Rectangle (0,0,wh[0], wh[1]);
		float [][] fpixels = {fpixels_ref, fpixels_img};
		TpTask [][] tp_tasks = GpuQuad.setRectilinearInterTasks(
				fpixels, // final float  [][]   fpixels, // to check for empty (no processing where it images have NaN
				wh[0],   // final int           img_width,
				woi,     // Rectangle           woi,
				null);   // final double [][][] affine  // [2][2][3] affine coefficients to translate common to 2 images
//		if (tp_tasks_o != null) {
//			for (int i = 0; i < tp_tasks_o.length; i++) tp_tasks_o[i] = tp_tasks[i];
//		}
		int erase_cltr=-1;
		int erase_clt= -1;
		image_dtt.setRectilinearReferenceTD(
				erase_cltr,                  // final int                 erase_clt,
				fpixels_ref, // final float []            fpixels_ref,
				wh,                         // final int []              wh,               // null (use sensor dimensions) or pair {width, height} in pixels
				clt_parameters.img_dtt,     // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
				true,                       // final boolean             use_reference_buffer,
				tp_tasks[0],                // final TpTask[]            tp_tasks,
				clt_parameters.gpu_sigma_r, // final double              gpu_sigma_r,     // 0.9, 1.1
				clt_parameters.gpu_sigma_b, // final double              gpu_sigma_b,     // 0.9, 1.1
				clt_parameters.gpu_sigma_g, // final double              gpu_sigma_g,     // 0.6, 0.7
				clt_parameters.gpu_sigma_m, // final double              gpu_sigma_m,     //  =       0.4; // 0.7;
				batch_mode? -3: debugLevel);                // final int                 globalDebugLevel)
    	if (!batch_mode && (dbg_suffix != null)) {
    		renderFromTD (
    				true, // boolean             use_reference,
    				"ref"+dbg_suffix); //String              suffix
    	}
    	float  [][][][]     fcorr_td = new float [tilesY][tilesX][][];  
    	//null; // no accumulation, use data in GPU
    	final double gpu_sigma_corr =     clt_parameters.getGpuCorrSigma(image_dtt.isMonochrome());
    	final double gpu_sigma_rb_corr =  image_dtt.isMonochrome()? 1.0 : clt_parameters.gpu_sigma_rb_corr;
    	final double gpu_sigma_log_corr = clt_parameters.getGpuCorrLoGSigma(image_dtt.isMonochrome());
		image_dtt.interRectilinearCorrTD(
				clt_parameters.img_dtt,     //final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
				batch_mode,                 // final boolean batch_mode,
				erase_clt,                  // final int                 erase_clt,
				fpixels_img,                 // final float []            fpixels,
				wh,                         // final int []              wh,               // null (use sensor dimensions) or pair {width, height} in pixels
				tp_tasks[1],                // final TpTask[]            tp_tasks,
				fcorr_td,                   // final float  [][][][]     fcorr_td,        // [tilesY][tilesX][pair][4*64] transform domain representation of 6 corr pairs
				clt_parameters.gpu_sigma_r, // final double              gpu_sigma_r,     // 0.9, 1.1
				clt_parameters.gpu_sigma_b, // final double              gpu_sigma_b,     // 0.9, 1.1
				clt_parameters.gpu_sigma_g, // final double              gpu_sigma_g,     // 0.6, 0.7
				clt_parameters.gpu_sigma_m, // final double              gpu_sigma_m,     //  =       0.4; // 0.7;
				gpu_sigma_rb_corr,          // final double              gpu_sigma_rb_corr,    //  = 0.5; // apply LPF after accumulating R and B correlation before G, monochrome ? 1.0 :
				gpu_sigma_corr,             // final double              gpu_sigma_corr,       //  =    0.9;gpu_sigma_corr_m
				gpu_sigma_log_corr,         // final double              gpu_sigma_log_corr,   // hpf to reduce dynamic range for correlations
				clt_parameters.corr_red,    // final double              corr_red, // +used
				clt_parameters.corr_blue,   // final double              corr_blue,// +used
				-1,                         // final int                 sensor_mask_inter, // The bitmask - which sensors to correlate, -1 - all.
				debugLevel);                // final int                 globalDebugLevel)
		if (!batch_mode && (dbg_suffix != null)) {
			renderFromTD (
					false, // boolean             use_reference,
					"img"+dbg_suffix); //String              suffix
		}
		TDCorrTile [] tdCorrTiles = TDCorrTile.getFromGpu(gpuQuad);
		return tdCorrTiles;
	}
	
	public float [][] shiftAndRenderTest(
			CLTParameters       clt_parameters,
			final float [][]    fpixels,
			final double [][][] vector_field,
			final int           frame0,      // for vector_field[0]
			final int           frame_step,
			final int           corr_offset, // interframe distance for correlation
			final boolean       batch_mode) {
		double magnitude_scale = -1.0; // /(2 * half_step); // here just 1.0 - will use cosine window when accumulating 
		float [] nan_frame = new float [fpixels[0].length];
		float [][] result_frames = new float [fpixels.length][];			
		Arrays.fill(nan_frame, Float.NaN);
		Arrays.fill(result_frames, nan_frame);
		int half_step = frame_step/2;
		final int erase = 1; //NaN
		for (int nseq = 0; nseq < vector_field.length; nseq++) {
			int frame_center = frame0 + nseq * frame_step;
			for (int dframe = -half_step; dframe < half_step; dframe++) {
				int frame = frame_center + dframe;
				if ((frame >=0) && (frame < result_frames.length)) {
					double offset_scale = (1.0 * dframe) / corr_offset;
					accumulateMotionScene(
							clt_parameters,     // CLTParameters     clt_parameters,
							fpixels[frame],     // float []          fpixels,
							vector_field[nseq], // final double [][] vector_field,
							erase,              // final int     erase,
							batch_mode,         // final boolean     batch_mode,
							offset_scale,       // final double      offset_scale,
							magnitude_scale,    // final double      magnitude_scale,
							tilesX) ;           // final int         tilesX) {
					result_frames[frame] = floatFromTD(false); // boolean  use_reference)
					boolean frame_debug = false;
					if (frame_debug) {
						renderFromTD (
								false, // boolean             use_reference,
								"render-from-TD"); // String              suffix)
					}
				}
			}
		}
		return result_frames;
	}
	
	public float [][] shiftAndRenderAccumulate(
			CLTParameters       clt_parameters,
			final boolean       zero_fill,
			final float [][]    fpixels,
			final double [][][] vector_field,
			final int           frame0,      // for vector_field[0]
			final int           frame_step,
			final int           half_range,
			final boolean       smooth,
			final int           corr_offset, // interframe distance for correlation
			final boolean       batch_mode) {
		float [][] frames_accum = new float [vector_field.length][];
		final double [] window_full = new double [2*half_range+1];
		double s0 = 1.0;
		window_full[half_range] = 1.0;
		double k = Math.PI/2/(half_range +0.5);
		for (int i = 1; i <= half_range; i ++) {
			window_full[half_range+i] = smooth ? (Math.cos(i*k)):1.0;
			s0+=2 * window_full[half_range+i]; 
		}
		for (int i = 0; i < window_full.length; i ++) {
			window_full[i] /= s0;
		}
		
//		final int erase = 1; //NaN
		for (int nseq = 0; nseq < vector_field.length; nseq++) {
			int frame_center = frame0 + nseq * frame_step;
			boolean fits = (frame_center >= half_range) && (frame_center < (fpixels.length-half_range));
			double [] window = fits? window_full: window_full.clone();
			if (!fits) {
				s0=0;
				for (int i = 0; i < window.length; i++) {
					int i1 = frame_center - half_range + i;
					if ((i1 >= 0) && (i1 < fpixels.length)) {
						s0+= window[i];
					}
				}
				for (int i = 0; i < window.length; i++) {
					window[i] /= s0;
				}				
			}
			boolean first = true;
			for (int dframe = -half_range; dframe < half_range; dframe++) {
				double magnitude_scale = window [dframe + half_range];
				if (magnitude_scale > 0) { // to make sure <=0 did not get there accidentally
					int frame = frame_center + dframe;
					if ((frame >= 0) && (frame < fpixels.length) ) {
						if ((frame >=0) && (frame < fpixels.length)) {
							double offset_scale = (1.0 * dframe) / corr_offset;
							int erase = first ? 1 : -1; // NaN on first pass
							float [] fpix = first ? fpixels[frame].clone() : fpixels[frame];
							if (first) {
								for (int i = 0; i < fpix.length; i++) {
									fpix[i] *= -1.0;
								}
								magnitude_scale *= -1.0; // with >0 will set, not accumulate
							}
							accumulateMotionScene(
									clt_parameters,     // CLTParameters     clt_parameters,
									fpix,     // float []          fpixels,
									vector_field[nseq], // final double [][] vector_field,
									erase,              // final int     erase,
									batch_mode,         // final boolean     batch_mode,
									offset_scale,       // final double      offset_scale,
									magnitude_scale,    // final double      magnitude_scale, // first time - negative (will set)
									tilesX) ;           // final int         tilesX) {
							first = false;
//							result_frames[frame] = floatFromTD(false); // boolean  use_reference)
							boolean frame_debug = false;
							if (frame_debug) {
								renderFromTD (
										false, // boolean             use_reference,
										"render-from-TD_"+frame); // String              suffix)
							}
						}
					}
				}
			}
			// get form TD and invert
			frames_accum[nseq] =  floatFromTD(false); // boolean  use_reference)
			boolean frame_debug = false;
			if (frame_debug) {
				renderFromTD (
						false, // boolean             use_reference,
						"render-from-TD_accum"); // String              suffix)
			}
			
		}
		// invert multithreaded
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					// may be faster if process only where vector_field[nseq][ntile] is not null
					for (int nSeq = ai.getAndIncrement(); nSeq < frames_accum.length; nSeq = ai.getAndIncrement()) {
						for (int i = 0; i < frames_accum[nSeq].length; i++) {
							frames_accum[nSeq][i] *=-1;
							if (zero_fill && Double.isNaN(frames_accum[nSeq][i])){
								frames_accum[nSeq][i] = 0f;
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		
		return frames_accum;
	}
	
	/**
	 * Generate target images
	 * @param clt_parameters
	 * @param mask_width       mask width around the target    
	 * @param mask_blur        mask transition width    
	 * @param round            false - square (no blur), true - round
	 * @param target_keyframes accumulated target images
	 * @param vector_field     [scene][tile][] first two elements - targets vX, vY in pixels per frame. Empty are nulls, same velocities for 5x5  
	 * @param target_positions [scene][tile][] target positions of the targets relative to the tile center
	 * @param background       static scenes to be overlaid by the targets
	 * @param frame0           frame number of the background corresponding to the first keyframe 
	 * @param frame_step       numer of frames between key frames
	 * @param velocity_scale   velocity scale to apply to vX, vY. It is equal to 1.0/corr_ofset, where corr_offset is disparity in frames
	 *                         used for vector field generation
	 * @param targets60hz      target data for each 60hz scene 
	 * @param batch_mode       batch mode - disable graphic debugging
	 * @param debugLevel       debug level
	 * @return float [][] rendered  
	 */
	public float [][] shiftAndRenderTargets(
			CLTParameters       clt_parameters,
			final double        mask_width,
			final double        mask_blur,
			final boolean       round,
			final float  [][]   target_keyframes,
			final double [][][] vector_field,
			final double [][][] target_positions,
			final float  [][]   background, // background image
			final int           frame0,
			final int           frame_step,
			final double        velocity_scale, // 1.0/(disparity in frames)
			final double [][][] targets60hz, 
			final boolean       batch_mode,
			final int debugLevel) {
		final float [][] fpix_out = new float [background.length][];
		for (int i = 0; i < fpix_out.length; i++) {
			fpix_out[i]= background[i].clone();
		}
		final int tileSize = GPUTileProcessor.DTT_SIZE;		
		final int tileSize2 = 2 * tileSize;		
		final int half_step0 = -(frame_step+1)/2;
		final int half_step1 = frame_step + half_step0;
		final int num_scenes = background.length;
		final int erase = 0; // probably not needed
		int [] poffs = new int[1];
		final double [] mask = createTargetMask (
				mask_width, // final double        mask_width,
				mask_blur,  // final double        mask_blur,
				round,      // final boolean       round,
				poffs);      // final int []        offs)
		final int offs = poffs[0];
		final int isize = 2* offs +1; 
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int nseq = 0; nseq < vector_field.length; nseq++) {
			int fnseq = nseq;
			int frame_center = frame0 + nseq * frame_step;
			float [] keyframe = target_keyframes[nseq];
			ArrayList<Integer> target_list = new ArrayList<Integer>();
			for (int ntile = 0; ntile < target_positions[nseq].length; ntile++) if (target_positions[nseq][ntile] !=null){
				target_list.add(ntile);
			}
			final int[] targets = target_list.stream().mapToInt(Integer::intValue).toArray();
			for (int dscene = half_step0; dscene < half_step1; dscene ++) {
				final int fdscene = dscene;
				int nscene = frame_center + dscene;
				if ((nscene >=0) && (nscene < num_scenes)) {
					double [][] targets_data = new double[targets.length][TARGET_LENGTH];
					float [] frame = renderMovingKeyframe(
							clt_parameters, // CLTParameters     clt_parameters,
							keyframe, // float []          fkeyframe,   // if null, will assume it is already in the GPU
							vector_field[nseq], // final double [][] vector_field,
							erase,    //final int         erase, // -1 - no, 0 - to 0, 1 - to NaN
							batch_mode, // final boolean     batch_mode,
							-dscene*velocity_scale); // final double      offset_scale);
					
					ai.set(0);
					// previous renderMovingKeyframe() uses GPU, can not use threads
					for (int ithread = 0; ithread < threads.length; ithread++) {
						threads[ithread] = new Thread() {
							public void run() {
								for (int nTarget = ai.getAndIncrement(); nTarget < targets.length; nTarget = ai.getAndIncrement()) {
									int ntile = targets[nTarget];
									double [] vvector = vector_field[fnseq][ntile];
									double [] target_pos = target_positions[fnseq][ntile];
									int tileX = ntile % tilesX;
									int tileY = ntile / tilesX;
									double xc = tileSize * tileX + tileSize/2;  
									double yc = tileSize * tileY + tileSize/2;
									double xtk = xc + target_pos[CuasMotionLMA.RSLT_X];  
									double ytk = yc + target_pos[CuasMotionLMA.RSLT_Y];
									double dx = vvector[INDX_VX] * fdscene * velocity_scale;
									double dy = vvector[INDX_VY] * fdscene * velocity_scale;
									targets_data[nTarget][TARGET_X] =        xtk + dx;
									targets_data[nTarget][TARGET_Y] =        ytk + dy;
									targets_data[nTarget][TARGET_VX] =       vvector[INDX_VX];
									targets_data[nTarget][TARGET_VY] =       vvector[INDX_VY];
									targets_data[nTarget][TARGET_STRENGTH] = target_pos[CuasMotionLMA.RSLT_A];
									int pxl = (int) Math.round(targets_data[nTarget][TARGET_X]) - offs;
									int pyt = (int) Math.round(targets_data[nTarget][TARGET_Y]) - offs;
									for (int ym = 0; ym < isize; ym++) {
										int py = pyt + ym;
										if ((py >= 0) && (py < gpu_max_height)) {
											for (int xm = 0; xm < isize; xm++) {
												int px = pxl + xm;
												if ((px >= 0) && (px < gpu_max_width)) {
													int pix = py * gpu_max_width + px;
													int pix_mask = ym * isize + xm;
													if ((mask[pix_mask] > 0) && !Double.isNaN(frame[pix])) {
														if (Float.isNaN(fpix_out[nscene][pix])) {
															fpix_out[nscene][pix] = frame[pix];
														} else {
															double a = mask[pix_mask];
															fpix_out[nscene][pix] = (float) ((1 - a) * fpix_out[nscene][pix] + a * frame[pix]); 
														}
													}
												}
											}
										}
									}									
								}
							}
						};
					}		      
					ImageDtt.startAndJoin(threads);
					if (targets60hz != null) {
						targets60hz[nscene] = targets_data;
					}
					keyframe = null; // to prevent additional transfers to the GPU
				}
			}
		}	
		return fpix_out;
	}
	
	public static float [][] runningAverage(
			final float [][] fpixels,
			final int        ra_length,
			final int        width){
		final int num_scenes = fpixels.length;
		final int num_pixels = fpixels[0].length;
		final int height =  num_pixels/width;
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		final float [][] out_pix = new float [num_scenes][num_pixels];
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_scenes; nSeq = ai.getAndIncrement()) {
						Arrays.fill(out_pix[nSeq],Float.NaN);
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					float [] line_ra = new float[width];
					for (int nLine = ai.getAndIncrement(); nLine < height; nLine = ai.getAndIncrement()) {
						Arrays.fill(line_ra, 0.0f);
						int pix0 = nLine* width;
						int head = 0;
						int tail = 0;
						for (; (head < num_scenes) || (tail < num_scenes);) {
							if (head < num_scenes) {
								float [] fpixels_head = fpixels[head];
								int pix = pix0; 
								for (int x = 0; x < width; x++) {
									line_ra[x] += fpixels_head[pix++]; 
								}
								head++;
							}
							if ((tail < (head - ra_length)) || (tail >= (num_scenes - ra_length))) {
								float [] fpixels_tail = fpixels[tail];
								int pix = pix0; 
								for (int x = 0; x < width; x++) {
									line_ra[x] -= fpixels_tail[pix++]; 
								}
								tail++;
							}
							int avg_len = head - tail;
							int avg_pos = (head + tail) / 2;
							if ((avg_pos < num_scenes) && (((avg_len + ra_length) % 2 == 0 ) || (head == 1))) { // update output array
								int pix = pix0; 
								for (int x = 0; x < width; x++) {
									out_pix[avg_pos][pix++] = line_ra[x]/avg_len; 
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);

		return out_pix;
	}
	
	/**
	 * Version of runningAverage) that tolerates NaN value. It modifies fpixels[][] replacing first/last scene values if they were NaNs 
	 * @param fpixels
	 * @param ra_length
	 * @param width
	 * @return
	 */
	public static float [][] runningAverageNaN(
			final float [][] fpixels,
			final int        ra_length,
			final int        width){
		final int num_scenes = fpixels.length;
		final int num_pixels = fpixels[0].length;
		final int height =  num_pixels/width;
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		final float [][] out_pix = new float [num_scenes][num_pixels];
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_scenes; nSeq = ai.getAndIncrement()) {
						Arrays.fill(out_pix[nSeq],Float.NaN);
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					float [] line_ra = new float[width];
					int [] line_ra_length = new int [width];
					for (int nLine = ai.getAndIncrement(); nLine < height; nLine = ai.getAndIncrement()) {
						Arrays.fill(line_ra, 0.0f);
						Arrays.fill(line_ra_length, 0);
						int pix0 = nLine* width;
						// fix first and last pixels if they ar nulls;
						for (int x = 0; x < width; x++) {
							if (Float.isNaN(fpixels[0][pix0+x])) {
								int i;
								for (i = 1; Float.isNaN(fpixels[i][pix0+x]) && (i < num_scenes); i++);
								fpixels[0][pix0+x] = fpixels[i][pix0+x];
							}
							if (Float.isNaN(fpixels[num_scenes-1][pix0+x])) {
								int i;
								for (i = num_scenes-1; Float.isNaN(fpixels[i][pix0+x]) && (i >=0); i--);
								fpixels[num_scenes-1][pix0+x] = fpixels[i][pix0+x];
							}							
						}
						
						int head = 0;
						int tail = 0;
						for (; (head < num_scenes) || (tail < num_scenes);) {
							if (head < num_scenes) {
								float [] fpixels_head = fpixels[head];
								int pix = pix0; 
								for (int x = 0; x < width; x++) {
									float d = fpixels_head[pix++];
									if (!Float.isNaN(d)) {
										line_ra[x] += d;
										line_ra_length[x] ++;
									}
								}
								head++;
							}
							if ((tail < (head - ra_length)) || (tail >= (num_scenes - ra_length))) {
								float [] fpixels_tail = fpixels[tail];
								int pix = pix0; 
								for (int x = 0; x < width; x++) {
									float d = fpixels_tail[pix++];
									if (!Float.isNaN(d)) {
										line_ra[x] -= d;
										line_ra_length[x]--;
									}
								}
								tail++;
							}
							int avg_len = head - tail;
							int avg_pos = (head + tail) / 2;
							if ((avg_pos < num_scenes) && (((avg_len + ra_length) % 2 == 0 ) || (head == 1))) { // update output array
								int pix = pix0; 
								for (int x = 0; x < width; x++) {
									out_pix[avg_pos][pix++] = line_ra[x]/line_ra_length[x]; // avg_len;// should never be 0 as the first and the last are not NaN; 
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return out_pix;
	}
	
	public static float [][] removeOutliers(
			final float [][] fpixels,
			final float [][] fpixels_avg,
			double      threshold){
		final float fthreshold = (float) threshold;
		final int num_scenes = fpixels.length;
		final int num_pixels = fpixels[0].length;
		float [][] fpixels_filtered = new float [num_scenes][];
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_scenes; nSeq = ai.getAndIncrement()) {
						float [] fpix = fpixels[nSeq];
						float [] apix = fpixels_avg[nSeq];
						fpixels_filtered[nSeq]= fpix.clone();
						for (int pix = 0; pix < num_pixels; pix++) {
							if (Math.abs(fpix[pix] - apix[pix]) > fthreshold) {
								fpixels_filtered[nSeq][pix] = Float.NaN;
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		return fpixels;
	}
	
	public static float [][] subtract(
			float [][] fpixels,
			float [][] fpixels_avg){
		final int num_scenes = fpixels.length;
		final int num_pixels = fpixels[0].length;
		float [][] fpixels_diff = new float [num_scenes][num_pixels];
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_scenes; nSeq = ai.getAndIncrement()) {
						float [] fpix = fpixels[nSeq];
						float [] apix = fpixels_avg[nSeq];
						float [] dpix = fpixels_diff[nSeq];
						for (int pix = 0; pix < num_pixels; pix++) {
							dpix[pix] = fpix[pix] - apix[pix];
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		return fpixels_diff;
	}
	
	
	

	private static double [] createTargetMask (
			final double        mask_width,
			final double        mask_blur,
			final boolean       round,
			final int []        offs) {
		int iwidth = 2*(((int) Math.ceil(mask_width))/2) + 1; // minimal odd
		double [] mask = new double [iwidth*iwidth];
		double r1 = mask_width/2;
		double r0 = r1 - mask_blur;
		double r02 = r0*r0;
		double r12 = r1*r1;
		int c = iwidth/2;
		if (offs != null) {
			offs[0] = c;
		}
		if (!round) {
			Arrays.fill(mask,  1.0);
		} else {
			for (int i = 0; i < mask.length; i++) {
				double x = (i % iwidth) - c;
				double y = (i / iwidth) - c;
				double r2 = x*x+y*y;
				if (r2 < r02) {
					mask[i] = 1.0;
				} else if (r2 >= r12) {
					mask[i] = 0.0;
				} else if (mask_blur > 0){
					double r = Math.sqrt(r2);
					double a = Math.PI * (r - r0) / mask_blur;
					mask[i] = 0.5 * (1.0 + Math.cos(a));
				}
			}
		}
		return mask;
	}
	
	
	public void renderFromTD (
			boolean             use_reference,
			String              suffix) {
		gpuQuad.execImcltRbgAll(
        		(gpuQuad.getNumColors() <=1), // isMonochrome(),
        		use_reference,
				null); // wh); //int [] wh
		// get data back from GPU
		final float [][][] iclt_fimg = new float [gpuQuad.getNumSensors()][][];
		for (int ncam = 0; ncam < iclt_fimg.length; ncam++) {
			iclt_fimg[ncam] = gpuQuad.getRBG(ncam); // updated window
		}
		int out_width =  gpuQuad.getImageWidth();
		int out_height = gpuQuad.getImageHeight();
        ShowDoubleFloatArrays.showArrays( 
        		iclt_fimg[0],
        		out_width,
        		out_height,
        		true,
        		suffix);
	}

	public double [] doubleFromTD (
			boolean  use_reference) {
		gpuQuad.execImcltRbgAll(
        		(gpuQuad.getNumColors() <=1), // isMonochrome(),
        		use_reference,
				null); // wh); //int [] wh
		// get data back from GPU
		final float [][][] iclt_fimg = new float [gpuQuad.getNumSensors()][][];
		for (int ncam = 0; ncam < iclt_fimg.length; ncam++) {
			iclt_fimg[ncam] = gpuQuad.getRBG(ncam); // updated window
		}
		double [] dpixels = new double [iclt_fimg[0].length];
		for (int i = 0; i < dpixels.length; i++) {
			dpixels[i] = iclt_fimg[0][0][i];
		}
		return dpixels;
	}
	
	public float [] floatFromTD (
			boolean  use_reference) {
		gpuQuad.execImcltRbgAll(
        		(gpuQuad.getNumColors() <=1), // isMonochrome(),
        		use_reference,
				null); // wh); //int [] wh
		// get data back from GPU
		final float [][][] iclt_fimg = new float [gpuQuad.getNumSensors()][][];
		for (int ncam = 0; ncam < iclt_fimg.length; ncam++) {
			iclt_fimg[ncam] = gpuQuad.getRBG(ncam); // updated window
		}
		return iclt_fimg[0][0].clone();
	}

	public static ImagePlus convertToRgbAnnotateTargets(
			CLTParameters       clt_parameters,
			final double        input_range, // 5
			final boolean       scale2x,
			final int           target_type, //  = 0; // 0 - unknown, 1 - known, 2 - friend, 3 - foe
			final float  [][]   fpixels,
			final double [][][] targets60hz,
			final int           frame0,
			final int           frame_step,
			final int           width_src,
			String              title,
			String []           titles,
			String              diamond_path,
			final int           debugLevel) {
		if (diamond_path == null) {
			String resource_name = TARGET_ICONS[target_type][scale2x? 1 : 0];
			URL resourceUrl = CuasMotion.class.getClassLoader().getResource("graphics/"+resource_name);
			Path resourcePath = null;
			try {
				resourcePath = Paths.get(resourceUrl.toURI());
			} catch (URISyntaxException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			diamond_path = resourcePath.toString();
		}
		final int scale = scale2x? 2 : 1;
		final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
//		String diamond_path = "/media/elphel/NVME/lwir16-proc/eagle_mountain/graphics/diamond21x17.png";
		final boolean annotate_mono =             clt_parameters.imp.annotate_mono; 
		final boolean annotate_transparent_mono = clt_parameters.imp.annotate_transparent_mono;
		final Color annotate_color_mono =         clt_parameters.imp.annotate_color_mono;
		
		final boolean annotate =   clt_parameters.imp.cuas_annotate;
		final Color   text_color = clt_parameters.imp.cuas_text_color;
		final String  font_name =  clt_parameters.imp.cuas_font_name;
		final int     font_size =  clt_parameters.imp.cuas_font_size;
		final int     font_type =  clt_parameters.imp.cuas_font_type;
		final double  ifov =       clt_parameters.imp.cuas_ifov;
		final int     px0 =        clt_parameters.imp.cuas_px0;
		final int     py0 =        clt_parameters.imp.cuas_py0;
		final double  az0 =        clt_parameters.imp.cuas_az0;
		final double  el0 =        clt_parameters.imp.cuas_el0;
		final int     space_before_text = 2;
		
		ColorProcessor diamond_cp = null;
		try {
			diamond_cp = new ColorProcessor(ImageIO.read(new File(diamond_path)));
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		final int diamond_width = diamond_cp.getWidth(); 
		final int diamond_height = diamond_cp.getHeight(); 

		
		
		final int [] diamond_pixels = ((int []) diamond_cp.getPixels());
		final int [][] diamond_rgba = new int [diamond_pixels.length][4]; 
		for (int i = 0; i < diamond_rgba.length; i++) {
			for (int s = 0; s <4; s++) {
				diamond_rgba[i][s] = (diamond_pixels[i] >> (8 * s)) & 0xff;
			}
		}
		
		final int num_scenes = fpixels.length;
		final int num_pixels = fpixels[0].length;
		final int num_seq = targets60hz.length;
//		final int height = num_pixels/width;
		ImagePlus imp = ShowDoubleFloatArrays.makeArrays(
				fpixels, // float[][] pixels,
				width_src,   // int width,
				num_pixels/width_src,  // int height,
				title,   // String title,
				titles); // String [] titles)
		imp.getProcessor().setMinAndMax(-input_range/2, input_range/2);
		String imp_title = imp.getTitle();
		int width_new =  scale * imp.getWidth(); 
		int height_new = scale * imp.getHeight(); 
		if (scale2x) {
			ImageStack stack = imp.getStack();
			String [] labels = new String [stack.getSize()];
			for (int i = 0; i < labels.length; i++) {
				labels[i] = stack.getSliceLabel(i+1);
			}
			final FloatProcessor [] stackfp =  new FloatProcessor[stack.getSize()];
			for (int i = 0; i < stackfp.length; i++) {
				stackfp[i] = (FloatProcessor) stack.getProcessor(i+1);
			}
			System.out.println("stackfp.length="+stackfp.length);
			ai.set(0);
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						for (int nSlice = ai.getAndIncrement(); nSlice < stackfp.length; nSlice = ai.getAndIncrement()) {
							stackfp[nSlice] = (FloatProcessor) stackfp[nSlice].resize(stackfp[nSlice].getWidth() * 2, stackfp[nSlice].getHeight()*2);
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
			stack = new ImageStack(width_new,height_new);
			for (int i = 0; i < stackfp.length; i++) {
				stack.addSlice(labels[i],stackfp[i]);
			}			
			imp = new ImagePlus(imp_title, stack); // imp.getTitle(),stack);
		}
		final FloatProcessor [] float_processors = new FloatProcessor[imp.getStack().getSize()];
		final ColorProcessor [] color_processors = new ColorProcessor[float_processors.length];
		final String [] cp_labels = new String [imp.getStack().getSize()];
		for (int i = 0; i < float_processors.length; i++) {
			float_processors[i] = (FloatProcessor) imp.getStack().getProcessor(i+1);
			cp_labels[i] = imp.getStack().getSliceLabel(i+1);
		}
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSlice = ai.getAndIncrement(); nSlice < float_processors.length; nSlice = ai.getAndIncrement()) {
						ImagePlus imp1 = new ImagePlus(cp_labels[nSlice],float_processors[nSlice]);
						ImageConverter imageConverter = new ImageConverter(imp1);
						imageConverter.convertToRGB();
						color_processors[nSlice] = (ColorProcessor) imp1.getProcessor();
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ImageStack color_stack = new ImageStack(width_new,height_new);
		for (int i = 0; i < color_processors.length; i++) {
			color_stack.addSlice(cp_labels[i],color_processors[i]);
		}			
		imp = new ImagePlus(imp_title, color_stack); // imp.getTitle(),stack);

		
		
		final int width = imp.getProcessor().getWidth();
		final int height = imp.getProcessor().getHeight();
//		imp.show();
		final Color fcolor = annotate_color_mono;
		final ImageStack fstack_scenes = imp.getImageStack();
		final int posX=  width - scale * 119; // 521;
		final int posY= height + scale *   1;  // 513;
		final Font font = new Font("Monospaced", Font.PLAIN, scale * 12);
		final Font font_target = new Font(font_name, font_type, scale * font_size);
		final boolean target_text_transparent = true;
		final int nSlices = fstack_scenes.getSize();
		if (annotate_mono) {
			ai.set(0);
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						for (int nSlice = ai.getAndIncrement(); nSlice < nSlices; nSlice = ai.getAndIncrement()) {
							String scene_title = fstack_scenes.getSliceLabel(nSlice+1);
							ImageProcessor ip = fstack_scenes.getProcessor(nSlice+1);
							ip.setColor(fcolor); // Color.BLUE);
							ip.setFont(font);
							if (annotate_transparent_mono) {
								ip.drawString(scene_title, posX, posY); // transparent
							} else {
								ip.drawString(scene_title, posX, posY, Color.BLACK); // transparent
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		}
		ImageStack stack = imp.getStack();
		int [][] ipixels = new int [num_scenes][];
		for (int i = 0; i < num_scenes; i++) {
			ipixels[i] = (int[]) stack.getPixels(i+1);
		}
		// 		final int num_seq = targets60hz.length;
		final int half_step0 = -(frame_step+1)/2;
		final int half_step1 = frame_step + half_step0;
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						int frame_center = frame0 + nSeq * frame_step;
						for (int dscene = half_step0; dscene < half_step1; dscene ++) {
							int nscene = frame_center + dscene;
							if ((nscene >=0) && (nscene < num_scenes)) {
								double [][] targets = targets60hz[nscene];
								if (targets != null) { // TODO: find why
									for (int ntarget = 0; ntarget < targets.length; ntarget++) {
										int xc = (int) Math.round(scale * targets[ntarget][TARGET_X]);
										int yc = (int) Math.round(scale * targets[ntarget][TARGET_Y]);
										int xl = xc - diamond_width/2;
										int yt = yc - diamond_height/2;
										for (int y = 0; y < diamond_height; y++) {
											int py = yt + y;
											if ((py >= 0) && (py < height)) {
												for (int x = 0; x< diamond_width; x++) {
													int px = xl +x;
													int dpix = x + y * diamond_width;
													int ipix = px + py*width;
													if ((px >=0) && (px < width)) {
														int alpha = diamond_rgba[dpix][3];
														if (alpha > 0) { // alpha
															int dp = diamond_pixels[x + y * diamond_width];
															if (alpha == 255) {
																ipixels[nscene][ipix] = dp;
															} else {
																double k = alpha/255.0;
																int img_pix = ipixels[nscene][ipix];
																int new_pix = 0xff000000;
																for (int c = 0; c < 3; c++) {
																	int rgb = (img_pix >> (8 * c)) & 0xff;
																	rgb = (int) Math.round((1-k)*rgb + k* diamond_rgba[dpix][c]);
																	if (rgb > 255) rgb = 255;
																	new_pix |= (rgb << (8*c));
																}
																ipixels[nscene][ipix] = new_pix;
															}
														}
													}
												}
											}
										}
										if (annotate) {
											int text_left = xl + diamond_width + space_before_text * scale;
											int text_top =  yt + scale * font_size; // text start from the bottom of the first line
											ImageProcessor ip = fstack_scenes.getProcessor(nscene+1);
											String txt = getTargetText(
													clt_parameters,    // CLTParameters       clt_parameters,
													targets[ntarget]); // double [] target);
											ip.setColor(text_color);
											ip.setFont(font_target);
											if (target_text_transparent) {
												ip.drawString(txt, text_left, text_top); // transparent.
											} else {
												ip.drawString(txt, text_left, text_top, Color.BLACK); // on black background
											}
										}
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
//		imp.show();
		return imp;
	}
	
	public static String getTargetText(
			CLTParameters       clt_parameters,
			double [] target) {
		double target_x = target[TARGET_X];
		double target_y = target[TARGET_Y];
		double target_vx = target[TARGET_VX];
		double target_vy = target[TARGET_VY];
		String omega = "\u03A9";
		double fps = 60.0;
		String number_format = "%3.0f";
		String omega_format =  "%3.1f";
		
		double   ifov =  clt_parameters.imp.cuas_ifov; // 0.05;  // degree per pixel
		int      px0 =   clt_parameters.imp.cuas_px0;  // 283;   // pixel with known azimuth 
		int      py0 =   clt_parameters.imp.cuas_py0;  // 386;   // pixel with known elevation
		double   az0 =   clt_parameters.imp.cuas_az0;  // 201.5; // degrees for cuas_px0;
		double   el0 =   clt_parameters.imp.cuas_el0;  // 0.0;   // degrees for cuas_px0;
		double az =  (target_x - px0)*ifov+az0;
		double el =  -(target_y - py0)*ifov+el0;
		double omega_az =  target_vx * ifov * fps;
		double omega_el = -target_vy * ifov * fps;
		while (az < 0) {
			az += 360;
		}
		while (az >= 360) {
			az -= 360;
		}
		while (el < -180) {
			el += 360;
		}
		while (el > 180) {
			el -= 360;
		}
		String txt = "";
		txt += " AZ  "+String.format(number_format,az)+"\n";
		txt += " EL "+ getSignedDouble(el,number_format)+"\n";
		txt += omega+"AZ "+getSignedDouble(omega_az,omega_format)+"\n";
		txt += omega+"EL "+getSignedDouble(omega_el,omega_format);
		return txt;
	}
	public static String getSignedDouble(
			double d,
			String format) {
		String s = (d > 0)? "+" : ( (d <0)? "-":" ");
		return s+ String.format(format, Math.abs(d));
	}
	
	
	public static String saveAsVideo(
			CLTParameters       clt_parameters,
			QuadCLT             scene,     // if not null - use it's model directory
			String              file_path, // if not null, use it regardless of scene
			ImagePlus           imp_color,
			int debugLevel) {
		double  video_fps =          clt_parameters.imp.video_fps;
		int     mode_avi =           clt_parameters.imp.mode_avi;
		int     avi_JPEG_quality =   clt_parameters.imp.avi_JPEG_quality; // 90;
	    boolean run_ffmpeg =         clt_parameters.imp.run_ffmpeg;
		String  video_ext =          clt_parameters.imp.video_ext;
		String  video_codec =        clt_parameters.imp.video_codec.toLowerCase();
		int     video_crf =          clt_parameters.imp.video_crf;
		double  video_bitrate_m =    clt_parameters.imp.video_bitrate_m;		
		boolean remove_avi =         clt_parameters.imp.remove_avi;
		boolean dry_run = false;
		String avi_path = null;
		
		if (file_path != null) {
			try {
				avi_path=QuadCLT.saveAVI(
						file_path, // String      file_path, // forces avi, removes .tif, tiff, and .avi
						dry_run, // !generate_mapped, // boolean     dry_run, 
						mode_avi,         // int         avi_mode,
						avi_JPEG_quality, // int         avi_JPEG_quality,
						video_fps,        // double      fps,
						imp_color); // imp_scenes_pair[nstereo]);      // ImagePlus   imp)
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			}
		} else {
			try {
				avi_path=scene.saveAVIInModelDirectory(
						dry_run,          // !generate_mapped, // boolean     dry_run, 
						null,             // String      suffix, // null - use title from the imp
						mode_avi,         // int         avi_mode,
						avi_JPEG_quality, // int         avi_JPEG_quality,
						video_fps,        // double      fps,
						imp_color);       // imp_scenes_pair[nstereo]);      // ImagePlus   imp)
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			}
		}
		if (avi_path == null) {
			return null;
		}
		if (!run_ffmpeg) {
			return avi_path; // webm not requested
		}
		String webm_path = avi_path.substring(0, avi_path.length()-4)+video_ext;
		String stereo_meta = "";
		
		String shellCommand = String.format("ffmpeg -y -i %s -c %s -b:v %fM -crf %d %s %s",
				avi_path, video_codec, video_bitrate_m, video_crf, stereo_meta, webm_path);
		Process p = null;
		if (!dry_run) {
			int exit_code = -1;
			try {
				p = Runtime.getRuntime().exec(shellCommand);
			} catch (IOException e) {
				System.out.println("Failed shell command: \""+shellCommand+"\"");
			}
			if (p != null) {
				try {
					p.waitFor();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				exit_code = p.exitValue();
			}
			System.out.println("Ran shell command: \""+shellCommand+"\" -> "+exit_code);
			// Check if webm file exists
			if ((exit_code != 0) || !(new File(webm_path)).exists()) {
				System.out.println("Failed to create : \""+webm_path+"\"");
				return avi_path;
			}
		} else {
			System.out.println("Simulated shell command: \""+shellCommand);

		}
		if (remove_avi && !dry_run) {
			(new File(avi_path)).delete();
			System.out.println("Deleted AVI video file: \""+avi_path+"\"");
		}
		System.out.println("Saved video to "+webm_path);
		return webm_path;
	}
	
	
	public static void processMovingTargets(
			CLTParameters         clt_parameters,
			final boolean         batch_mode,
			QuadCLT               parentCLT,          //
			final float [][]      fpixels,
			String []             scene_titles, // recreate slice_titles from scene titles?
			final int             debugLevel) {
		
		CuasMotion cuasMotion = new CuasMotion (
				clt_parameters, // CLTParameters     clt_parameters,
				parentCLT,      // QuadCLT           parentCLT,
				debugLevel);    // int               debugLevel)

		int start_frame = 0;
		int     corr_offset =    clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =     clt_parameters.imp.cuas_corr_pairs;
		boolean half_step =      clt_parameters.imp.cuas_half_step; // true;

		int seq_length = corr_offset + corr_pairs;
		int corr_inc = half_step ? (corr_offset/2) : corr_offset;
		int num_corr_samples = (fpixels.length - seq_length - start_frame) / corr_inc;
		String [] slice_titles = new String [num_corr_samples];
		for (int nscan = 0; nscan < num_corr_samples; nscan++) {
			int frame_cent = start_frame + corr_inc * nscan + seq_length/2; // debug only
			slice_titles[nscan] =  scene_titles[frame_cent];
		}
		
		double [][][] vf_sequence =   locateAndFreezeTargets(
				clt_parameters, // CLTParameters         clt_parameters,
				batch_mode,     // final boolean         batch_mode,
				parentCLT,      // QuadCLT               parentCLT,          //
				fpixels,        // final float [][]      fpixels,
				cuasMotion,     // final CuasMotion      cuasMotion,
				scene_titles,   // String []             scene_titles, // recreate slice_titles from scene titles?
				slice_titles,   // String []             slice_titles,
				debugLevel);    // final int             debugLevel)
		
		generateExtractFilterMovingTargets( // move parameters to clt_parameters
			       clt_parameters,          // CLTParameters       clt_parameters,
			       batch_mode,              // final boolean       batch_mode,
			       parentCLT,               // QuadCLT             parentCLT,          //
			       // three arrays needed
			       fpixels,                 // final float [][]    fpixels,
			       vf_sequence,             // final double [][][] vf_sequence,  // center tiles (not extended), null /non-null only
//			       frame0,                  // final int           frame0,      // for vector_field[0] // source scene corresponding to the first sequence 
			       cuasMotion,              // final CuasMotion    cuasMotion,
			       scene_titles,            // String []           scene_titles, // recreate slice_titles from scene titles?
			       slice_titles,            // String []           slice_titles,
			       debugLevel);             // final int            debugLevel)

	}	
	
	
	
	
	public static double [][][] locateAndFreezeTargets(
			CLTParameters         clt_parameters,
			final boolean         batch_mode,
			QuadCLT               parentCLT,          //
			float [][]            fpixels,
			final CuasMotion      cuasMotion,
			String []             scene_titles, // recreate slice_titles from scene titles?
			String []             slice_titles,
			final int             debugLevel) {
		int     corr_offset =    clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =     clt_parameters.imp.cuas_corr_pairs;
		int     precorr_ra =     clt_parameters.imp.cuas_precorr_ra;
		int     temporal_um =    clt_parameters.imp.cuas_temporal_um;
		double  tum_threshold =  clt_parameters.imp.cuas_tum_threshold;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
		int     corr_ra_step =   clt_parameters.imp.cuas_corr_step;
		double  fat_zero =       clt_parameters.imp.cuas_fat_zero;
		double  cent_radius =    clt_parameters.imp.cuas_cent_radius;
		int     n_recenter =     clt_parameters.imp.cuas_n_recenter;
		double  rstr =           clt_parameters.imp.cuas_rstr;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
		double  speed_min =      clt_parameters.imp.cuas_speed_min;
		double  speed_pref =     clt_parameters.imp.cuas_speed_pref;
		double  speed_boost =    clt_parameters.imp.cuas_speed_boost;
		boolean smooth =         clt_parameters.imp.cuas_smooth; // true;
		boolean half_step =      clt_parameters.imp.cuas_half_step; // true;
		int     max_range =      clt_parameters.imp.cuas_max_range;
		int     num_cycles =     clt_parameters.imp.cuas_num_cycles;
		double target_radius =   clt_parameters.imp.cuas_target_radius;
		double target_strength = clt_parameters.imp.cuas_target_strength;
		double [][] target_frac = new double [clt_parameters.imp.cuas_target_frac.length][2];
		boolean no_border=       clt_parameters.imp.cuas_no_border; // true;
	// Moving target LMA	
	    double  lma_sigma =      clt_parameters.imp.cuas_lma_sigma; // =     3.0;
	    double  wnd_pedestal =   clt_parameters.imp.cuas_wnd_pedestal; // =     3.0;
	    double  lma_r0 =         clt_parameters.imp.cuas_lma_r0; // =        3.0;  //maximum with with overshoot
	    double  lma_ovrsht =     clt_parameters.imp.cuas_lma_ovrsht; // =    2.0;
	// CUAS Motion LMA parameters
		boolean lma_fit_xy=      clt_parameters.imp.cuas_lma_fit_xy; // true;
		boolean lma_fit_a=       clt_parameters.imp.cuas_lma_fit_a; // true;
		boolean lma_fit_c=       clt_parameters.imp.cuas_lma_fit_c; // true;
		boolean lma_fit_r=       clt_parameters.imp.cuas_lma_fit_r; // true;
		boolean lma_fit_k=       clt_parameters.imp.cuas_lma_fit_k; // true;
	    double  lambda =         clt_parameters.imp.cuas_lambda; // =        0.1;
	    double  lambda_good =    clt_parameters.imp.cuas_lambda_good; // =   0.5;
	    double  lambda_bad =     clt_parameters.imp.cuas_lambda_bad; // =    8;
	    double  lambda_max =     clt_parameters.imp.cuas_lambda_max; // =  100;
	    double  rms_diff =       clt_parameters.imp.cuas_rms_diff; // =      0.001; // relative RMS improvement
	    int     num_iter =       clt_parameters.imp.cuas_num_iter; // =     20;
	// CUAS Motion LMA filter parameters
	    double  lma_rms =        clt_parameters.imp.cuas_lma_rms; // =       1.5;  // Maximal RMS, regardless of amplitude
	    double  lma_arms =       clt_parameters.imp.cuas_lma_arms; // =      0.06; // Maximal absolute RMS, sufficient for any amplitude
	    double  lma_rrms =       clt_parameters.imp.cuas_lma_rrms; // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
	    double  lma_mina =       clt_parameters.imp.cuas_lma_mina; // =      1.0;  // Minimal A (amplitude)
	    double  lma_maxr =       clt_parameters.imp.cuas_lma_maxr; // =      5.0;  // Maximal radius (>3.8)
	    double  lma_mink =       clt_parameters.imp.cuas_lma_mink; // =      0.0;  // Minimal K (overshoot)  <0.007
	    double  lma_maxk =       clt_parameters.imp.cuas_lma_maxk; // =      5.0;  // Minimal K (overshoot) > 3.8
//	    boolean remove_isolated= clt_parameters.imp.cuas_isolated;
		double  input_range =    clt_parameters.imp.cuas_input_range; // 5;
		int     iter_show =      clt_parameters.imp.cuas_iter_show;       //1; // Maximal enhancement iteration to show intermediate result (0 - none) 
		boolean corr2d_save_show =  clt_parameters.imp.cuas_2d_save_show;    //true;
		boolean intermed_low =   clt_parameters.imp.cuas_intermed_low;    //true;
		boolean intermed_high =  clt_parameters.imp.cuas_intermed_high;   //true;
		boolean intermed_giga =  clt_parameters.imp.cuas_intermed_giga;   //false;
		for (int i = 0; i < target_frac.length; i++) {
			if (clt_parameters.imp.cuas_target_frac[i].length >= 2) {
				target_frac[i][0] = clt_parameters.imp.cuas_target_frac[i][0];
				target_frac[i][1] = clt_parameters.imp.cuas_target_frac[i][1];
			} else {
				System.out.println("testCuasScanMotion(): wrong format for a pair of strength, fraction values.");
			}
		}
		int start_frame = 0;
		int num_scenes = fpixels.length; // .getStack().getSize()- first_corr; // includes average
		int seq_length = corr_offset + corr_pairs;
		int corr_inc = half_step ? (corr_offset/2) : corr_offset;
		int num_corr_samples = (num_scenes - seq_length - start_frame) / corr_inc;
		final int     frame0 = start_frame + seq_length/2;
		final int     half_accum_range = corr_pairs/2;		
		int [] remain = new int [num_corr_samples];
		boolean debug_tum= false;
		String model_prefix = parentCLT.getImageName()+getParametersSuffix(clt_parameters,null);
		if (temporal_um > 0) {
			if (intermed_high && debug_tum) {
				ImagePlus imp_src = ShowDoubleFloatArrays.makeArrays(
						fpixels,                     // float[][] pixels,
						cuasMotion.gpu_max_width,        // int width,
						cuasMotion.gpu_max_height,       // int height,
						model_prefix+"-SOURCE", //String title,
						scene_titles); //String [] titles)
				imp_src.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_src.show();
				}		
				parentCLT.saveImagePlusInModelDirectory(imp_src);            // ImagePlus   imp)
			}
			float [][] fpixels_ra = runningAverage(
					fpixels,                   // final float [][] fpixels,
					temporal_um,               // final int        ra_length,
					cuasMotion.gpu_max_width); // final int        width);
			if (intermed_high && debug_tum) {
				ImagePlus imp_src_ra = ShowDoubleFloatArrays.makeArrays(
						fpixels_ra,                     // float[][] pixels,
						cuasMotion.gpu_max_width,        // int width,
						cuasMotion.gpu_max_height,       // int height,
						model_prefix+"-SOURCE-RA"+temporal_um, //String title,
						scene_titles); //String [] titles)
				imp_src_ra.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_src_ra.show();
				}		
				parentCLT.saveImagePlusInModelDirectory(imp_src_ra);            // ImagePlus   imp)
			}
			
			if (tum_threshold > 0) {
				float [][] fpixels_nan = removeOutliers(
						fpixels,                // float [][] fpixels,
						fpixels_ra,             // float [][] fpixels_avg,
						tum_threshold);         // float      threshold)
				if (intermed_high && debug_tum) {
					ImagePlus imp_outliers = ShowDoubleFloatArrays.makeArrays(
							fpixels_nan,                     // float[][] pixels,
							cuasMotion.gpu_max_width,        // int width,
							cuasMotion.gpu_max_height,       // int height,
							model_prefix+"-SOURCE-OUTLIERS", //String title,
							scene_titles); //String [] titles)
					imp_outliers.getProcessor().setMinAndMax(-input_range/2, input_range/2);
					if (!batch_mode) {
						imp_outliers.show();
					}		
					parentCLT.saveImagePlusInModelDirectory(imp_outliers);            // ImagePlus   imp)
				}
				fpixels_ra = runningAverageNaN(
						fpixels_nan,               // final float [][] fpixels,
						temporal_um,               // final int        ra_length,
						cuasMotion.gpu_max_width); // final int        width);
				if (intermed_high && debug_tum) {
					ImagePlus imp_src_ra = ShowDoubleFloatArrays.makeArrays(
							fpixels_ra,                     // float[][] pixels,
							cuasMotion.gpu_max_width,        // int width,
							cuasMotion.gpu_max_height,       // int height,
							model_prefix+"-SOURCE-RA-NOOUTLIERS"+temporal_um, //String title,
							scene_titles); //String [] titles)
					imp_src_ra.getProcessor().setMinAndMax(-input_range/2, input_range/2);
					if (!batch_mode) {
						imp_src_ra.show();
					}		
					parentCLT.saveImagePlusInModelDirectory(imp_src_ra);            // ImagePlus   imp)
				}

				// modify original fpixels
				fpixels =  subtract(
						fpixels, // float [][] fpixels,
						fpixels_ra); // float [][] fpixels_avg)
				if (intermed_high) {
					ImagePlus imp_diff = ShowDoubleFloatArrays.makeArrays(
							fpixels,                     // float[][] pixels,
							cuasMotion.gpu_max_width,        // int width,
							cuasMotion.gpu_max_height,       // int height,
							model_prefix+"-SOURCE-DIFF", //String title,
							scene_titles); //String [] titles)
					imp_diff.getProcessor().setMinAndMax(-input_range/2, input_range/2);
					if (!batch_mode) {
						imp_diff.show();
					}		
					parentCLT.saveImagePlusInModelDirectory(imp_diff);            // ImagePlus   imp)
				}
			}
		}
		
		float [][] fpixels_ra = fpixels;
		if (precorr_ra > 1) {
			fpixels_ra = runningAverage(
					fpixels,                   // final float [][] fpixels,
					precorr_ra,                // final int        ra_length,
					cuasMotion.gpu_max_width); // final int        width)
			if (intermed_high) {
				ImagePlus imp_ra = ShowDoubleFloatArrays.makeArrays(
						fpixels_ra, // float[][] pixels,
						cuasMotion.gpu_max_width, // int width,
						cuasMotion.gpu_max_height, // int height,
						model_prefix+"-SOURCE-RA", //String title,
						scene_titles); //String [] titles)
				imp_ra.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_ra.show();
				}		
				parentCLT.saveImagePlusInModelDirectory(imp_ra);            // ImagePlus   imp)
			}
		}
		
		double [][][] corr2d = corr2d_save_show? new double [num_corr_samples][][] : null;
		double [][][] motion_scan = getMotionScan(
				false, // batch_mode,     // boolean           batch_mode,
				clt_parameters, // CLTParameters     clt_parameters,
				cuasMotion,     // CuasMotion        cuasMotion,
				fpixels_ra,     // float [][]        fpixels,
				start_frame,    // int               start_frame,
				corr_pairs,     // int               corr_pairs,
				corr_offset,    // int               corr_offset,
				corr_inc,       // int               corr_inc,
				corr_ra_step,   // int               corr_ra_step, //    5;      // correlation step when using rolling average
				smooth,         // boolean           smooth,
				fat_zero,       // double            fat_zero,
				cent_radius,    // double            cent_radius,
				n_recenter,     // int               n_recenter,
				-rstr,          // double            rstr,
				model_prefix,   // String            title,
				corr2d,         // double [][][]     corr2d, // null or [(fpixels.length - seq_length - start_frame) / corr_inc)[][]
				debugLevel);    // int               debugLevel) {

		if (corr2d_save_show) { // may be read to re-process with vector field
			int corr_size = 2 * GPUTileProcessor.DTT_SIZE -1; 
			double [][] dbg_2d_corrs = ImageDtt.corr_partial_dbg( // not used in lwir
					corr2d,                       // final double [][][]     corr_data,       // [layer][tile][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
					cuasMotion.tilesX,                              // final int               tilesX,
					corr_size,                           //final int               corr_size,       // 15
					clt_parameters.corr_border_contrast, // final double            border_contrast,
					debugLevel);                         // final int               globalDebugLevel)

			ImagePlus imp_corr2d = ShowDoubleFloatArrays.makeArrays(
					dbg_2d_corrs, // double[][] pixels,
					cuasMotion.tilesX * (corr_size + 1),
					cuasMotion.tilesY * (corr_size + 1),
					model_prefix+"-CORR2D", // -OFFS"+corr_offset+"-PAIRS"+corr_pairs,   // String title,
					slice_titles);
			if (!batch_mode) {
				imp_corr2d.show();
			}
			parentCLT.saveImagePlusInModelDirectory(imp_corr2d);  // ImagePlus   imp)
		}
		if (debugLevel > -4) {
			System.out.println("scan DONE");
		}

		// starting from the motion_scan[][][] - calculated or read from file. corr2d is curently unused, maybe we'll have to save restore
		double [][][] motion_scan_original = cloneMotionScan(motion_scan, remain);
		printRemain(remain, "motion_scan_original", true);
		
		int niter;
//		int max_iter = 20; // 100;
		double [][][] targets_good = new double [motion_scan.length][motion_scan[0].length][]; // will combine good targets lma
		double [][][] targets_bad =  new double [motion_scan.length][motion_scan[0].length][]; // will combine good targets centroids
///		float  [][] fpixels_accumulated_filtered = null;
		for (niter=0; niter < num_cycles; niter++) {
//			boolean no_improve = false;
			boolean save_filtered_low = intermed_low && (niter < iter_show);
			boolean save_filtered_high = intermed_high && (niter < iter_show);
			boolean save_filtered_giga = intermed_giga && (niter < iter_show);
			
			// remove known bad targets. TODO: maybe will need to modify corr2d and recalculate, if the same tile is shared by a target and background 
			motion_scan  = subtractMotionScans( // motion vectors
					motion_scan, // double [][][] scan0, // all tried for targets
					targets_bad, // double [][][] scan1) // good targets
					remain);
			if (debugLevel > -4) printRemain(remain, "motion_scan wo bad", true);
			// filter remaining (no known bad ones)
			boolean[][] filter5 = filterMotionScan(
					motion_scan,       // final double [][][] motion_scan,
					cuasMotion.tilesX, // final int           tilesX)
					max_range,         // final int           range, // 1 or 2
					null,              // final boolean [][]  filtered, // same format as output, previously selected
					remain,            // final int     []    remain){
					speed_min,         // double              speed_min,		
					speed_pref,        // double              speed_pref,		
					speed_boost);      // double              speed_boost);
			if (debugLevel > -4) printRemain(remain, "filter5", true);
			double [][][] motion_scan_filtered = applyFilter( // motion vectors
					motion_scan,          // double [][][] motion_scan,
					filter5);             // boolean [][] filter5)
			// remove known good (already processed)
			motion_scan_filtered = subtractMotionScans( // motion vectors
					motion_scan_filtered, // double [][][] scan0, // all tried for targets
					targets_good, // double [][][] scan1) // good targets (lma-vect)
					remain);
			if (debugLevel > -4) printRemain(remain, "filtered wo good", true);
			// Anything remains? we'll see after extension
			double [][][] extended_scan = extendMotionScan(
					motion_scan_filtered, // final double [][][] motion_scan,
					null, // filter5,      // final boolean [][]  filtered, // centers, should be non-overlapped
					cuasMotion.tilesX,    // final int           tilesX)
					1,                    // final int           range, // 1 or 2
					remain);              // final int     []    remain)
			int remained_total =  0;
			if (debugLevel > -4)  remained_total =  printRemain(remain, "extended_scan", true);
			else remained_total =  printRemain(remain, null, false);
			if (remained_total == 0) {
//				no_improve = true;
//				if (fpixels_accumulated_filtered != null) {
					break;
//				}
			}
			// there are some tiles left to try
			
			if (save_filtered_low) {
				//title_vf_all					
				ImagePlus imp_vf = showVectorFieldsSequence(
						motion_scan,          // double [][][] vector_fields_sequence,
						speed_min,            // double        speed_min,		
						speed_pref,           // double        speed_pref,		
						speed_boost,          // double        speed_boost, 
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"VF-ALL-n"+niter,// String        title,
						false,                // boolean       nan_effective_strength,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_vf);
				ImagePlus imp_vf_nan = showVectorFieldsSequence(
						motion_scan,          // double [][][] vector_fields_sequence,
						speed_min,            // double        speed_min,		
						speed_pref,           // double        speed_pref,		
						speed_boost,          // double        speed_boost, 
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"VF-ALL-NAN-n"+niter,  // String        title,
						true,                 // boolean       nan_effective_strength,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_vf_nan);
				ImagePlus imp_vf_filt = showVectorFieldsSequence(
						motion_scan_filtered, // double [][][] vector_fields_sequence,
						speed_min,            // double        speed_min,		
						speed_pref,           // double        speed_pref,		
						speed_boost,          // double        speed_boost, 
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"VF-FILT-n"+niter,    // String        title,
						false,                // boolean       nan_effective_strength,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_vf_filt);
				ImagePlus imp_vf_ext = showVectorFieldsSequence(
						extended_scan,        // double [][][] vector_fields_sequence,
						speed_min,            // double        speed_min,		
						speed_pref,           // double        speed_pref,		
						speed_boost,          // double        speed_boost, 
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"VF-EXT-n"+niter, // String        title,
						false,                // boolean       nan_effective_strength,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_vf_ext);
				// show good and bad accumulated here too?
			}
			
			float [][] fpixels_accumulated = cuasMotion.shiftAndRenderAccumulate(
					clt_parameters,   // CLTParameters       clt_parameters,
					false,            // final boolean       fill_zeros,
					fpixels,          // final float [][]    fpixels,
					extended_scan,    // final double [][][] vector_field,
					frame0,           // final int           frame0,      // for vector_field[0]
					corr_inc,        // final int           frame_step,
					half_accum_range, // final int           half_range,
					smooth,           // final boolean       smooth,
					corr_offset,      // final int           corr_offset, // interframe distance for correlation
					true);            // final boolean       batch_mode) {
			if (save_filtered_high) {
				ImagePlus imp_acc = ShowDoubleFloatArrays.makeArrays(
						fpixels_accumulated, // double[][] pixels,
						cuasMotion.gpu_max_width,
						cuasMotion.gpu_max_height, 
						model_prefix+"-ACCUMULATED-n"+niter,   // String title,
						slice_titles);
				imp_acc.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_acc.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_acc);  // ImagePlus   imp)
			}
			// replace center frames with the accumulated ones
			if (debugLevel > -4) {
				System.out.println("Starting render, iteration = "+niter);
			}

			// show just fpixels_accumulated
			// move outside? compare to slice_titles known
			if (save_filtered_giga) { // huge file
				float [][] fpixels_rendered = cuasMotion.shiftAndRenderTest(
						clt_parameters,   // CLTParameters       clt_parameters,
						fpixels,          // final float [][]    fpixels,
						extended_scan,    // final double [][][] vector_field,
						frame0,           // final int           frame0,      // for vector_field[0]
						corr_inc,        // final int           frame_step,
						corr_offset,      // final int           corr_offset, // interframe distance for correlation
						true);            // final boolean       batch_mode) {


				if (debugLevel > -4) {
					System.out.println("Render DONE, iteration "+niter);
				}

				for (int nseq = 0; nseq < fpixels_accumulated.length; nseq++){
					fpixels_rendered[frame0 + nseq * corr_inc] = fpixels_accumulated[nseq];
				}
				if (debugLevel > -4) {
					System.out.println("Accumulation DONE iteration "+niter);
				}

				float [][][] fpixels_hyper = {fpixels,fpixels_rendered};
				String [] hyper_titles_top = {"Source","Rendered"}; 

				ImagePlus imp_hyper = ShowDoubleFloatArrays.showArraysHyperstack(
						fpixels_hyper,            // float[][][] pixels, 
						cuasMotion.gpu_max_width, // int          width, 
						model_prefix+"-OVERLAP-n"+niter,     // String       title, "time_derivs-rt"+diff_time_rt+"-rxy"+diff_time_rxy,
						scene_titles,             // String []    titles, // all slices*frames titles or just slice titles or null
						hyper_titles_top,         // String []    frame_titles, // frame titles or null
						!batch_mode);                    // boolean      show)
				imp_hyper.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				parentCLT.saveImagePlusInModelDirectory(imp_hyper);  // ImagePlus   imp)
			}

			
			// Calculate both centroid and LMA target parametes, return both
			double [][][][] coord_data2 = 	getAccumulatedCoordinates(
					motion_scan_filtered, // final double [][][] vector_fields, // centers
					fpixels_accumulated,  // final double [][]   accum_data,   // should be around 0, no low-freq
					target_radius,        // final double        centroid_radius,
					n_recenter,           // final int           n_recenter, //  re-center window around new maximum. 0 -no refines (single-pass)
					cuasMotion.tilesX,    // final int           tilesX){
					no_border,            // final boolean       no_border,
					// Moving target LMA	
				    lma_sigma,            // final double        lma_sigma,
					wnd_pedestal,         // final double        wnd_pedestal,
				    lma_r0,               // final double        lma_r0,
				    lma_ovrsht,  // final double        lma_ovrsht,
				// CUAS Motion LMA parameters
				    lma_fit_xy,           // final boolean       lma_fit_xy,		
				    lma_fit_a,            // final boolean       lma_fit_a,						
				    lma_fit_c,            // final boolean       lma_fit_c,
				    lma_fit_r,            // final boolean       lma_fit_r,
				    lma_fit_k,            // final boolean       lma_fit_k,		
				    lambda,               // final double        lambda,
				    lambda_good,          // final double        lambda_good,
				    lambda_bad,           // final double        lambda_bad,
				    lambda_max,           // final double        lambda_max,
				    rms_diff,             // final double        rms_diff,
				    num_iter,             // final int           num_iter,
				    debugLevel);          // final int           debugLevel);
			double [][][] coord_centroid = 	coord_data2[0];	// centroid
			double [][][] coord_lma =    	coord_data2[1];	// lma
			if (save_filtered_low) {
				// Does not use target speed preference at all
				ImagePlus imp_centroid = showVectorFieldsSequence(
						coord_centroid,          // double [][][] vector_fields_sequence,
						0.0,                  // double        speed_min,	// 	
						0.0,                  // double        speed_pref,		
						1.0,                  // double        speed_boost, 
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-CENTROID-n"+niter,    // String        title,
						false,                // boolean       nan_effective_strength,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_centroid);  // ImagePlus   imp)
				ImagePlus imp_lma = showVectorFieldsSequenceLMA(
						coord_lma,            // double [][][] target_scene_sequence, // rename
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-LMA-n"+niter, // String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_lma);  // ImagePlus   imp)
			}
			double [][][] coord_centroids_filtered = filterAndShowTargets( // generates motion vectors?
					coord_centroid,           // double [][][] coord_data,
					target_strength,      // double        target_strength,
					target_frac,          // double [][]   target_frac,
					slice_titles,         // String []     slice_titles, //
					model_prefix+"-CENTROIDS-FILTERED-n"+niter,   // String        title_targets_filt,
					remain,       // int []        targets_remain,
					cuasMotion.tilesX,    // int           tilesX)
					save_filtered_low && !batch_mode,   // boolean       show)
					(save_filtered_low? parentCLT : null)); // do not save //QuadCLT       parentCLT)
			remained_total =  0;
			if (debugLevel > -4) remained_total =  printRemain(remain, "coord_data_filtered: Total new targets", true);
			else remained_total =  printRemain(remain, null, false);
			if (remained_total == 0) {
//				no_improve = true;
//				if (fpixels_accumulated_filtered != null) {
					break;
//				}
			}

			double [][][] coord_data_filtered_lma = filterAndShowTargetsLMA(
					coord_lma,           // double [][][] coord_data,
					lma_rms,  // double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
                    lma_arms, // double  lma_arms, // =      0.03; // Maximal absolutre RMS
                    lma_rrms, // double  lma_rrms, // =      0.03; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
                    lma_mina, // double  lma_mina, // =      1.0;  // Minimal A (amplitude)
                    lma_maxr, // double  lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
                    lma_mink, // double  lma_mink, // =      1.0;  // Minimal K (overshoot) = 1.0
                    lma_maxk, // double  lma_maxk, // =      1.0;  // Minimal K (overshoot) = 3.0
					slice_titles,         // String []     slice_titles, //
					model_prefix+"-TARGETS-FILTERED-LMA-n"+niter,   // String        title_targets_filt,
					remain,       // int []        targets_remain,
					cuasMotion.tilesX,    // int           tilesX)
					save_filtered_low && !batch_mode,   // boolean       show)
					(save_filtered_low? parentCLT : null)); // do not save //QuadCLT       parentCLT)
			if (debugLevel > -4)  remained_total =  printRemain(remain, "coord_data_filtered_lma: Total new targets", true);
			
			// add good targets to targets_good
			targets_good  = combineMotionScans( // lma-vectors
					targets_good,         // double [][][] scan0,
					coord_data_filtered_lma, // coord_centroids_filtered, // double [][][] scan1)
					remain);
			if (debugLevel > -4) printRemain(remain, "targets_good", true);
			
			if (save_filtered_low) { // targets_good now has
				ImagePlus imp_good = showVectorFieldsSequenceLMA(
						targets_good,         // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-TARGETS-GOOD-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_good);  // ImagePlus   imp)
			}
			// remove all good (by the LMA) 
			double [][][] targets_bad_last  = subtractMotionScans( // centroids
					motion_scan_filtered, // double [][][] scan0, // all tried for targets
					targets_good, // coord_centroids_filtered, // double [][][] scan1) // good targets
					remain);
			
			
			if (debugLevel > -4) printRemain(remain, "targets_bad_last", true);
			// add bad targets to targets_bad
			targets_bad  = combineMotionScans( // centroids
					targets_bad,         // double [][][] scan0,
					targets_bad_last,   // double [][][] scan1)
					remain); //
			if (debugLevel > -4) printRemain(remain, "targets_bad", true);
			
			// TODO See if they are needed here at all 
			/*
			fpixels_accumulated_filtered = getTargetImages(
					coord_centroids_filtered,  // final double [][][] vector_fields, // centers , just null/not null
					fpixels_accumulated,  // final float  [][]   accum_data,   // should be around 0, no low-freq
					cuasMotion.tilesX);   // final int           tilesX)
			if (save_filtered_high) {
				ImagePlus imp_acc_filt = ShowDoubleFloatArrays.makeArrays(
						fpixels_accumulated_filtered, // double[][] pixels,
						cuasMotion.gpu_max_width,
						cuasMotion.gpu_max_height, 
						model_prefix+"-ACCUMULATED-FILTERED-n"+niter,   // String title,
						slice_titles);
				imp_acc_filt.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_acc_filt.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_acc_filt);  // ImagePlus   imp)
			}
			*/
			if (debugLevel > -4) {
				System.out.println("Iteration "+niter+" DONE.");
			}
//			if (no_improve) {
//				if (debugLevel > -4) {
//					System.out.println("There was no improvement, exiting the loop, iteration "+niter+" .");
//				}
//			}

		} //for (niter=0; niter < max_iter; niter++)
		
		double [][][] motion_good = filterMotionScans(
				motion_scan_original, // double [][][] scan1   // or just use motion_scan - it has only bad removed
				targets_good,         // double [][][] scan1){ // keep scan0 that is in scan1
				remain);
		if (debugLevel > -4) printRemain(remain, "All done: motion_good", true);

		if (intermed_low) { // save final 
			ImagePlus imp_good = showVectorFieldsSequenceLMA(
					targets_good,         // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-TARGETS-GOOD",// String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_good);  // ImagePlus   imp)
			ImagePlus imp_mv_good = showVectorFieldsSequence(
					targets_good,         // double [][][] vector_fields_sequence,
					speed_min,            // double        speed_min,		
					speed_pref,           // double        speed_pref,		
					speed_boost,          // double        speed_boost, 
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-MOTIONS-GOOD",// String        title,
					false,                // boolean       nan_effective_strength,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_mv_good);  // ImagePlus   imp)
		}
		return motion_good;
	}
	
	

	public static void generateExtractFilterMovingTargets( // move parameters to clt_parameters
			CLTParameters       clt_parameters,
			final boolean       batch_mode,
			QuadCLT             parentCLT,          //
			final float [][]    fpixels,
			final double [][][] vf_sequence,  // 
//			final int           frame0,      // for vector_field[0] // source scene corresponding to the first sequence 
			final CuasMotion    cuasMotion,
			String []           scene_titles, // recreate slice_titles from scene titles?
			String []           slice_titles,
			final int           debugLevel) {
		String model_prefix = parentCLT.getImageName()+getParametersSuffix(clt_parameters,null);

		final int     corr_offset =       clt_parameters.imp.cuas_corr_offset;
		final int     corr_pairs =        clt_parameters.imp.cuas_corr_pairs;
		
		final boolean half_step =         clt_parameters.imp.cuas_half_step; // true;
		final boolean smooth =            clt_parameters.imp.cuas_smooth; // true;
		final int corr_inc =             half_step ? (corr_offset/2) : corr_offset;
		final int     half_accum_range =  corr_pairs/2;
		final double target_radius =      clt_parameters.imp.cuas_target_radius;
		final int     n_recenter =        clt_parameters.imp.cuas_n_recenter;
		final boolean no_border=          clt_parameters.imp.cuas_no_border; // true;
		
		final double  mask_width =        clt_parameters.imp.cuas_mask_width;  // 9;
		final double  mask_blur =         clt_parameters.imp.cuas_mask_blur;   // 3;
		final boolean mask_round =        clt_parameters.imp.cuas_mask_round;  // false;
		
		final double target_strength = clt_parameters.imp.cuas_target_strength;
		final double [][] target_frac = new double [clt_parameters.imp.cuas_target_frac.length][2];
		
		// Moving target LMA	
	    double  lma_sigma =      clt_parameters.imp.cuas_lma_sigma;       // =  3.0;
	    double  wnd_pedestal =   clt_parameters.imp.cuas_wnd_pedestal;    // =  0.1;
	    
	    double  lma_r0 =         clt_parameters.imp.cuas_lma_r0; // =        3.0;  //maximum with with overshoot
	    double  lma_ovrsht =     clt_parameters.imp.cuas_lma_ovrsht; // =    2.0;
	// CUAS Motion LMA parameters
		boolean lma_fit_xy=      clt_parameters.imp.cuas_lma_fit_xy; // true;
		boolean lma_fit_a=       clt_parameters.imp.cuas_lma_fit_a; // true;
		boolean lma_fit_c=       clt_parameters.imp.cuas_lma_fit_c; // true;
		boolean lma_fit_r=       clt_parameters.imp.cuas_lma_fit_r; // true;
		boolean lma_fit_k=       clt_parameters.imp.cuas_lma_fit_k; // true;
	    double  lambda =         clt_parameters.imp.cuas_lambda; // =        0.1;
	    double  lambda_good =    clt_parameters.imp.cuas_lambda_good; // =   0.5;
	    double  lambda_bad =     clt_parameters.imp.cuas_lambda_bad; // =    8;
	    double  lambda_max =     clt_parameters.imp.cuas_lambda_max; // =  100;
	    double  rms_diff =       clt_parameters.imp.cuas_rms_diff; // =      0.001; // relative RMS improvement
	    int     num_iter =       clt_parameters.imp.cuas_num_iter; // =     20;
		// CUAS Motion LMA filter parameters
	    double  lma_rms =        clt_parameters.imp.cuas_lma_rms; // =       1.5;  // Maximal RMS, regardless of amplitude
	    double  lma_arms =       clt_parameters.imp.cuas_lma_arms; // =      0.06; // Maximal absolute RMS, sufficient for any amplitude
	    double  lma_rrms =       clt_parameters.imp.cuas_lma_rrms; // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
	    double  lma_mina =       clt_parameters.imp.cuas_lma_mina; // =      1.0;  // Minimal A (amplitude)
	    double  lma_maxr =       clt_parameters.imp.cuas_lma_maxr; // =      5.0;  // Maximal radius (>3.8)
	    double  lma_mink =       clt_parameters.imp.cuas_lma_mink; // =      0.0;  // Minimal K (overshoot)  <0.007
	    double  lma_maxk =       clt_parameters.imp.cuas_lma_maxk; // =      5.0;  // Minimal K (overshoot) > 3.8
	    boolean remove_isolated= clt_parameters.imp.cuas_isolated;
		int     target_type =    clt_parameters.imp.cuas_target_type; //0; // 0 - unknown, 1 - known, 2 - friend, 3 - foe
		double  input_range =    clt_parameters.imp.cuas_input_range; // 5;
		boolean scale2x =        clt_parameters.imp.cuas_scale2x;      //true;
		boolean ra_background =  clt_parameters.imp.cuas_ra_background;   //true;
		
		boolean intermed_low =   clt_parameters.imp.cuas_intermed_low;    //true;
		boolean intermed_high =  clt_parameters.imp.cuas_intermed_high;   //true;
//		boolean intermed_giga =  clt_parameters.imp.cuas_intermed_giga;   //false;
		
		boolean save_mono =      clt_parameters.imp.cuas_save_mono;       //true;
		boolean save_color =     clt_parameters.imp.cuas_save_color;      //true;
		boolean save_video =     clt_parameters.imp.cuas_save_video;      //true;
		
		int start_frame = 0;
		int seq_length = corr_offset + corr_pairs;
		final int     frame0 = start_frame + seq_length/2;
		
		// create accum from fpixels
		double [][][] extended_scan = extendMotionScan(
				vf_sequence,          // final double [][][] motion_scan,
				null, // filter5,     // final boolean [][]  filtered, // centers, should be non-overlapped
				cuasMotion.tilesX,    // final int           tilesX)
				1,                    // final int           range, // 1 or 2
				null); // remain);              // final int     []    remain)
		float [][]accum = cuasMotion.shiftAndRenderAccumulate(
				clt_parameters,   // CLTParameters       clt_parameters,
				false,            // final boolean       fill_zeros,
				fpixels,          // final float [][]    fpixels,
				extended_scan,    // final double [][][] vector_field,
				frame0,           // final int           frame0,      // for vector_field[0]
				corr_inc,        // final int           frame_step,
				half_accum_range, // final int           half_range,
				smooth,           // final boolean       smooth,
				corr_offset,      // final int           corr_offset, // interframe distance for correlation
				true);            // final boolean       batch_mode) {
		if (intermed_high) {
			ImagePlus imp_accumulated = ShowDoubleFloatArrays.makeArrays(
					accum, // float[][] pixels,
					cuasMotion.gpu_max_width, // int width,
					cuasMotion.gpu_max_height, // int height,
					model_prefix+"-ACCUMULATED", //String title,
					slice_titles); //String [] titles)
			imp_accumulated.getProcessor().setMinAndMax(-input_range/2, input_range/2);
			if (!batch_mode) {
				imp_accumulated.show();
			}		
			parentCLT.saveImagePlusInModelDirectory(imp_accumulated);            // ImagePlus   imp)
		}
		
		
		
        double [][][][] coord_data2 = 	getAccumulatedCoordinates(
        		vf_sequence,          // final double [][][] vector_fields, // centers
				accum,                // final double [][]   accum_data,   // should be around 0, no low-freq
				target_radius,        // final double        centroid_radius,
				n_recenter,           // final int           n_recenter, //  re-center window around new maximum. 0 -no refines (single-pass)
				cuasMotion.tilesX,               // final int           tilesX){
				no_border,            // final boolean       no_border,
				// Moving target LMA	
			    lma_sigma,            // final double        lma_sigma,
				wnd_pedestal,         // final double        wnd_pedestal,
			    lma_r0,               // final double        lma_r0,
			    lma_ovrsht,  // final double        lma_ovrsht,
			// CUAS Motion LMA parameters
			    lma_fit_xy,           // final boolean       lma_fit_xy,		
			    lma_fit_a,            // final boolean       lma_fit_a,						
			    lma_fit_c,            // final boolean       lma_fit_c,
			    lma_fit_r,            // final boolean       lma_fit_r,
			    lma_fit_k,            // final boolean       lma_fit_k,		
			    lambda,               // final double        lambda,
			    lambda_good,          // final double        lambda_good,
			    lambda_bad,           // final double        lambda_bad,
			    lambda_max,           // final double        lambda_max,
			    rms_diff,             // final double        rms_diff,
			    num_iter,             // final int           num_iter,
			    debugLevel);          // final int           debugLevel);
		double [][][] coord_data = 	coord_data2[0];	
		double [][][] coord_lma = 	coord_data2[1];	
		if (intermed_low) {
			ImagePlus imp_vf = showVectorFieldsSequence(
					coord_data,          // double [][][] vector_fields_sequence,
					0.0,                  // double        speed_min,		
					0.0,                  // double        speed_pref,		
					1.0,                  // double        speed_boost, 
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-VF-UNFILTERED",    // String        title,
					false,                // boolean       nan_effective_strength,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_vf);
			ImagePlus imp_lma = showVectorFieldsSequenceLMA(
					coord_lma,            // double [][][] target_scene_sequence, // rename
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-LMA-UNFILTERED", // String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			if (intermed_low) {
				parentCLT.saveImagePlusInModelDirectory(imp_lma);
			}
		}
		
		int [] remain = new int [coord_data.length];
		double [][][] coord_data_filtered = filterAndShowTargets(
				coord_data,           // double [][][] coord_data,
				target_strength,      // double        target_strength,
				target_frac,          // double [][]   target_frac,
				slice_titles,         // String []     slice_titles, //
				model_prefix+"-VF-FILTERED",   // String        title_targets_filt,
				remain,       // int []        targets_remain,
				cuasMotion.tilesX,   // int           tilesX)
				intermed_low && !batch_mode,   // boolean       show)
				intermed_low?parentCLT:null); // parentCLT to save in model directory, null - do not save
		if (debugLevel > -4) printRemain(remain, "Total good targets", true);

		double [][][] coord_data_filtered_lma = filterAndShowTargetsLMA(
				coord_lma,// double [][][] coord_data,
				lma_rms,  // double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
                lma_arms, // double  lma_arms, // =      0.03; // Maximal absolutre RMS
                lma_rrms, // double  lma_rrms, // =      0.03; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
                lma_mina, // double  lma_mina, // =      1.0;  // Minimal A (amplitude)
                lma_maxr, // double  lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
                lma_mink, // double  lma_mink, // =      1.0;  // Minimal K (overshoot) = 1.0
                lma_maxk, // double  lma_maxk, // =      1.0;  // Minimal K (overshoot) = 3.0
				slice_titles,         // String []     slice_titles, //
				model_prefix+"-LMA-FILTERED",   // String        title_targets_filt,
				remain,       // int []        targets_remain,
				cuasMotion.tilesX,    // int           tilesX)
				intermed_low && !batch_mode, // show_vector_field);   // boolean       show)
				intermed_low?parentCLT:null); // parentCLT to save in model directory, null - do not save
		if (debugLevel > -4) printRemain(remain, "coord_data_filtered_lma: Total new targets LMA", true);
		
		// remove tiles that did not pass old vf filter
		double [][][] targets_lma_combo  = filterMotionScans( // should keep LMA coordinates
				coord_data_filtered_lma,  // double [][][] scan0,
				coord_data_filtered, // double [][][] scan1)
				remain);
		// create vector field sequence by masking after the last filter
		double [][][] targets_vf_combo  = filterMotionScans( // should keep LMA coordinates
				vf_sequence,        // double [][][] scan0,
				targets_lma_combo,  // double [][][] scan1)
				remain);
		
		if (debugLevel > -4) {
			System.out.println();
			printRemain(remain, "targets_lma_combo", true);
		}
		if (intermed_low)  {
			/*
			ImagePlus imp_filtered_combo= showVectorFieldsSequenceLMA(
					targets_vf_combo,    // double [][][] target_scene_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-FILTERED-COMBO",   // String        title,
					!batch_mode,    // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
					*/
			ImagePlus imp_filtered_combo = showVectorFieldsSequence(
					targets_vf_combo, // double [][][] vector_fields_sequence,
					0.0,                  // double        speed_min,		
					0.0,                  // double        speed_pref,		
					1.0,                  // double        speed_boost, 
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-FILTERED-COMBO",   // String        title,
					false,                // boolean       nan_effective_strength,
					!batch_mode,                 // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {

			parentCLT.saveImagePlusInModelDirectory(imp_filtered_combo);  // ImagePlus   imp)
		}
		if (remove_isolated) {
			targets_vf_combo =  filterIsolatedTargets(
					targets_vf_combo,     // final double [][][] all_tiles,
					remain,               // final int [] remain,
					cuasMotion.tilesX);   // final int tilesX) 
			// synchronize targets_lma_combo with targets_vf_combo
			targets_lma_combo = filterMotionScans( // should keep LMA coordinates
					targets_lma_combo,    // double [][][] scan0,
					targets_vf_combo,     // double [][][] scan1)
					null);
			
			if (debugLevel > -4) printRemain(remain, "Remained after removing single-frame targets", true);
			if (intermed_low)  {
				ImagePlus imp_no_singles = showVectorFieldsSequence(
						targets_vf_combo, // double [][][] vector_fields_sequence,
						0.0,                  // double        speed_min,		
						0.0,                  // double        speed_pref,		
						1.0,                  // double        speed_boost, 
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-NOSINGLES",    // String        title,
						false,                // boolean       nan_effective_strength,
						!batch_mode,                 // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_no_singles);  // ImagePlus   imp)
				ImagePlus imp_no_singles_lma= showVectorFieldsSequenceLMA(
						targets_lma_combo,    // double [][][] target_scene_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-NOSINGLES-LMA",   // String        title,
						!batch_mode,    // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_no_singles_lma);  // ImagePlus   imp)
				if (debugLevel > -4) System.out.println();
			}
		}
		// create larger, 5x5 vector field for accumulation
		double [][][] extended_vf_sequence = extendMotionScan(
				targets_vf_combo,     // final double [][][] motion_scan,
				null, // filter5,     // final boolean [][]  filtered, // centers, should be non-overlapped
				cuasMotion.tilesX,    // final int           tilesX)
				2,                    // final int           range, // 1 or 2
				remain);              // final int     []    remain)
		
		ImagePlus imp_extended = showVectorFieldsSequence(
				extended_vf_sequence, // double [][][] vector_fields_sequence,
				0.0,                  // double        speed_min,		
				0.0,                  // double        speed_pref,		
				1.0,                  // double        speed_boost, 
				slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
				model_prefix+"-EXTENDED",    // String        title,
				false,                // boolean       nan_effective_strength,
				intermed_low && !batch_mode,                 // boolean       show,
				cuasMotion.tilesX);   //  int           tilesX) {
		if (intermed_low) 	parentCLT.saveImagePlusInModelDirectory(imp_extended);
				
		float [][] fpixels_accumulated5x5 = cuasMotion.shiftAndRenderAccumulate(
				clt_parameters,       // CLTParameters       clt_parameters,
				true, // final boolean       fill_zeros,
				fpixels,              // final float [][]    fpixels,
				extended_vf_sequence, // final double [][][] vector_field,
				frame0,               // final int           frame0,      // for vector_field[0]
				corr_inc,            // final int           frame_step,
				half_accum_range,     // final int           half_range,
				smooth,               // final boolean       smooth,
				corr_offset,          // final int           corr_offset, // interframe distance for correlation
				true);                // final boolean       batch_mode) {
		// zero-fill undefined tiles
		
		
		ImagePlus imp_accumulated5x5 = ShowDoubleFloatArrays.makeArrays(
				fpixels_accumulated5x5, // float[][] pixels,
				cuasMotion.gpu_max_width, // int width,
				cuasMotion.gpu_max_height, // int height,
				model_prefix+"-TARGETS5x5", //String title,
				slice_titles); //String [] titles)
		imp_accumulated5x5.getProcessor().setMinAndMax(-input_range/2, input_range/2);
		if (intermed_high) {
			if (!batch_mode) imp_accumulated5x5.show();
			parentCLT.saveImagePlusInModelDirectory(imp_accumulated5x5);            // ImagePlus   imp)
		}
		
		double  velocity_scale = 1.0/corr_offset;
		double [][][] targets60hz = new double [fpixels.length][][];
		
		
		
		float  [][]  background = fpixels;
		String ra_bg_suffix=(ra_background? "-RABG":"");
		if (ra_background) {
			background = runningAverage(
					fpixels,                   // final float [][] fpixels,
					corr_pairs,                // final int        ra_length,
					cuasMotion.gpu_max_width); // final int        width)
		}
		
		
		float [][] replaced_targets = cuasMotion.shiftAndRenderTargets(
				clt_parameters,          // CLTParameters       clt_parameters,
				mask_width,              // final double        mask_width,
				mask_blur,               // final double        mask_blur,
				mask_round,                   // final boolean       round,
				fpixels_accumulated5x5,  // final float  [][]   target_keyframes,
				extended_vf_sequence,    // final double [][][] vector_field,
				targets_lma_combo,       // final double [][][] target_positions,
				background,              // final float  [][]   background, // background image
				frame0,                  // final int           frame0,
				corr_inc,               // final int           frame_step,
				velocity_scale,          // final double        velocity_scale, // 1.0/(disparity in frames)
				targets60hz,             // final double [][][] targets60hz, 
				batch_mode,              // final boolean       batch_mode,
				debugLevel);             // final int debugLevel)
		if (save_mono) {
			ImagePlus imp_replaced_targets = ShowDoubleFloatArrays.makeArrays(
					replaced_targets, // float[][] pixels,
					cuasMotion.gpu_max_width, // int width,
					cuasMotion.gpu_max_height, // int height,
					model_prefix+"-REPLACED-TARGETS"+(ra_background? "-RABG":""), //String title,
					scene_titles); //String [] titles)
			imp_replaced_targets.getProcessor().setMinAndMax(-input_range/2, input_range/2);			
			if (!batch_mode) {
				imp_replaced_targets.show();
			}
			parentCLT.saveImagePlusInModelDirectory(imp_replaced_targets);            // ImagePlus   imp)
		}
		
		ImagePlus imp_color = convertToRgbAnnotateTargets(
				clt_parameters,             // CLTParameters       clt_parameters,
				input_range,                // final double        input_range, // 5
				scale2x, // boolean       scale2x,
				target_type,                // final int target_type, //  = 0; // 0 - unknown, 1 - known, 2 - friend, 3 - foe
				replaced_targets,           // final float  [][]   fpixels,
				targets60hz,                // final double [][][] targets60hz,
				frame0,                     // final int           frame0,
				corr_inc,                   // final int           frame_step,
				cuasMotion.gpu_max_width,   // final int           width,
				model_prefix+"-RGB"+ra_bg_suffix, // String              title,
				scene_titles,               // String []           titles,
				null, // diamond_path, // //String              diamond_path, null - use resources
				debugLevel);                // final int           debugLevel) {
		// save tiff in model directory
		if (imp_color != null) {
			if (save_color) {
				if (!batch_mode) {
					imp_color.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_color);      // ImagePlus   imp)
			}
			// temporary, will use model directory
			if (save_video) {
				saveAsVideo( // will take name from the image title
						clt_parameters, // CLTParameters       clt_parameters,
						parentCLT,      // QuadCLT             scene,     // if not null - use it's model directory
						null, // video_path_tmp, // String              file_path,
						imp_color,      // ImagePlus           imp_color,
						debugLevel);    // int debugLevel) {
			}
		}
	}
	
	public static double [][][] filterIsolatedTargets(
			final double [][][] all_tiles,
			final int [] remain,
			final int tilesX) {
		final int num_seq = all_tiles.length;
		final int num_tiles = all_tiles[0].length;
		final double [][][] filtered_tiles = new double [num_seq][num_tiles][];
		
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					TileNeibs tn = new TileNeibs(tilesX, num_tiles/tilesX); 
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						if (remain != null) {
							remain[nSeq] = 0;
						}
						for (int ntile = 0; ntile < num_tiles; ntile++) if (all_tiles[nSeq][ntile] != null) {
							boolean has_neib = true;
							look_for_neibs: {
								for (int dseq = -1; dseq <= 1; dseq+=2) {
									int nSeq1 = nSeq+dseq;
									if ((nSeq1 >= 0) && (nSeq1 < num_seq)) {
										if (all_tiles[nSeq1][ntile] != null) {
											break look_for_neibs;
										}
										for (int dir = 0; dir < 8; dir++) {
											int tile1 = tn.getNeibIndex(ntile, dir);
											if ((tile1 >= 0) && (all_tiles[nSeq1][tile1] != null)) {
												break look_for_neibs;
											}
										}
									}
								}
								has_neib = false;
							}
							if (has_neib) {
								filtered_tiles[nSeq][ntile] = all_tiles[nSeq][ntile]; // clone()?
								if (remain != null) {
									remain[nSeq]++;
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return filtered_tiles;
	}
	
	
	public static String getParametersSuffix(
			 CLTParameters       clt_parameters,	
			 String              prefix) {
		int     corr_offset =    clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =     clt_parameters.imp.cuas_corr_pairs;
		
		int     temporal_um =    clt_parameters.imp.cuas_temporal_um;
		double  tum_threshold =  clt_parameters.imp.cuas_tum_threshold;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
		
		int     precorr_ra =     clt_parameters.imp.cuas_precorr_ra;
		int     corr_ra_step =   clt_parameters.imp.cuas_corr_step;
		double  rstr =           clt_parameters.imp.cuas_rstr;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
	    double  lma_rms =        clt_parameters.imp.cuas_lma_rms; // =       1.5;  // Maximal RMS, regardless of amplitude
	    double  lma_arms =       clt_parameters.imp.cuas_lma_arms; // =      0.06; // Maximal absolute RMS, sufficient for any amplitude
	    double  lma_rrms =       clt_parameters.imp.cuas_lma_rrms; // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
	    double  lma_mina =       clt_parameters.imp.cuas_lma_mina; // =      1.0;  // Minimal A (amplitude)
	    double  fat_zero =       clt_parameters.imp.cuas_fat_zero;
		String ps = ((prefix != null)?prefix:"") + "-OFFS"+corr_offset+"-PAIRS"+corr_pairs+"-RSTR"+rstr+"-RMS"+lma_rms+
				"-SRMS"+lma_arms+"-RRMS"+lma_rrms+"-AMP"+lma_mina+"-FZ"+fat_zero+"-PCRA"+precorr_ra+"-PS"+corr_ra_step+
				"-TUM"+temporal_um+"-TT"+tum_threshold;
		return ps;
	}

	
	
}
