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.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;

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.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;

	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);
		}
		
		TDCorrTile [] tdCorrTiles = cuasMotion.correlatePairs(
				clt_parameters, // CLTParameters clt_parameters,
				fpixels,        // float [][]    fpixels,
				frame0,         // int           frame0,
				frame1,         // int           frame1,
				corr_pairs,      // int           frame_len,
				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) {
		int     corr_offset =    clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =     clt_parameters.imp.cuas_corr_pairs;
		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;
		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  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
		
		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, "",      "Offset between the correlation pairs");
			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("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("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.addStringField("Data directory",       data_dir,100,"Intermediate results directory (to bypass first stages during debugging).");
			
			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();
			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();

			target_radius =             gd.getNextNumber();
			target_strength =           gd.getNextNumber();
			target_frac =   IntersceneMatchParameters.stringToDouble2d(gd.getNextString());
			no_border =           gd.getNextBoolean();   
			lma_sigma =                 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();
			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_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_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_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;
			}

			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_step = half_step ? (corr_offset/2) : corr_offset;
			int num_corr_samples = (num_scenes - seq_length - start_frame) / corr_step;
			if (debugLevel > -4) {
				System.out.println("corr_pairs=      "+corr_pairs);
				System.out.println("corr_offset=     "+corr_offset);
				System.out.println("seq_length=      "+seq_length);
				System.out.println("corr_step=       "+corr_step);
				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("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("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);
			}
			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_step * nscan + seq_length/2; // debug only
				slice_titles[nscan] =  imp_sel.getStack().getSliceLabel(frame_cent+1);
			}
			
			
			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 [] vf_top_titles = {"vX","vY","strength","fraction","speed", "confidence"};
			String title_cor2d =        imp_name+"-corr2d"+suffix_param; // "-corr2d"+"-"+frame0+"-"+frame1+"-"+corr_pairs,
			String title_vf_all =       imp_name+"-vector_field_all"+suffix_param; //
			String title_vf =           imp_name+"-vector_field"+suffix_param; //
			String title_vf_filtered =  imp_name+"-vector_field_filtered"+suffix_param; //
			String title_vf_extended =  imp_name+"-vector_extended"+suffix_param; //
			String title_targets_all =  imp_name+"-targets_all"+suffix_param; //
			String title_targets_filt = imp_name+"-targets_filt"+suffix_param; //
			String title_accumulated =  imp_name+"-accumulated"+suffix_param;
			String title_rendered_hyper=imp_name+"-rendered_hyper"+suffix_param; //
			String title_acc_targets =  imp_name+"-accumulated_targets"+suffix_param;
			String title_vf_good =      imp_name+"-vector_field_good"+suffix_param; //
			String title_extended_good =imp_name+"-vector_field_extended_good"+suffix_param; //
			String title_targets_good = imp_name+"-targets_good"+suffix_param; //

			final int     frame0 = start_frame + seq_length/2;
			final int     half_accum_range = corr_pairs/2;
			final boolean smooth_accum = true;
			
			double [][][] corr2d =               null; // to read it if bypassing calculations
			double [][][] motion_scan =          null;
			boolean [][]  filter5 =              new boolean [num_corr_samples][]; // may be calculated in  extendMotionScan when read from file
			double [][][] motion_scan_filtered = null;
			double [][][] extended_scan =        null;
			int [] remain = new int [num_corr_samples];
			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)
			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;
				}
				ImagePlus imp_accum = new ImagePlus(path_acc);
				if (imp_accum.getWidth()==0) {
					System.out.println("Failed to read accum data from "+path_acc);
					continue;
				}
				int [] wh = new int[2];
				float [][] accum =  ShowDoubleFloatArrays.readFloatArray(
						imp_accum, // ImagePlus   imp,
						0, // int         num_slices, // (0 - all)
						wh); // int []      wh)
				
                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,
					    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];	
				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
						title_targets_all+"-VF-TEST",    // String        title,
						false,                // boolean       nan_effective_strength,
						true,                 // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				showVectorFieldsSequenceLMA(
						coord_lma,            // double [][][] target_scene_sequence, // rename
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						title_targets_all+"-LMA-TEST", // String        title,
						true,                 // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				//				int [] targets_remain = new int [coord_data.length];
				
				int [] targets_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, //
						title_targets_filt+"-VF-TEST-FILTERED",   // String        title_targets_filt,
						targets_remain,       // int []        targets_remain,
						cuasMotion.tilesX,   // int           tilesX)
						true); // show_vector_field);   // boolean       show)
				int remained_total = printRemain(targets_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, //
						title_targets_filt+"-LMA-TEST-FILTERED",   // String        title_targets_filt,
						targets_remain,       // int []        targets_remain,
						cuasMotion.tilesX,    // int           tilesX)
						true); // show_vector_field);   // boolean       show)
				remained_total = printRemain(targets_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);
				printRemain(remain, "targets_lma_combo", true);
				showVectorFieldsSequenceLMA(
						targets_lma_combo,    // double [][][] target_scene_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						title_targets_good+"-LMA-TEST-COMBO",   // String        title,
						true,                 // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				// 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)
				printRemain(remain, "extended5x5_vf_sequence", true);
				
				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_step,            // final int           frame_step,
						half_accum_range,     // final int           half_range,
						smooth_accum,         // final boolean       smooth,
						corr_offset,          // final int           corr_offset, // interframe distance for correlation
						true);               // final boolean       batch_mode) {
				// zero-fill undefined tiles
			
				ShowDoubleFloatArrays.showArrays(
						fpixels_accumulated5x5,
						cuasMotion.gpu_max_width,
						cuasMotion.gpu_max_height, 
						true,
						title_accumulated+"-TARGETS5x5", // "-corr2d"+"-"+frame0+"-"+frame1+"-"+corr_pairs,
						slice_titles); // titles_accum);
				
				double  mask_width = 9;
				double  mask_blur =  3;
				boolean round =      false;
				double  velocity_scale = 1.0/corr_offset;
				double [][][] targets60hz = new double [fpixels.length][][];
				boolean ra_background = true;
				float  [][]  background = fpixels;
				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,
						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_step,               // final int           frame_step,
						velocity_scale,          // final double        velocity_scale, // 1.0/(disparity in frames)
						targets60hz,             // final double [][][] targets60hz, 
						false,                   // final boolean       batch_mode,
						debugLevel);             // final int debugLevel)
//				if (show_full_images) {
				ShowDoubleFloatArrays.showArrays(
						replaced_targets,
						cuasMotion.gpu_max_width,
						cuasMotion.gpu_max_height, 
						true,
						title_accumulated+"-REPLACED-TARGETS", // "-corr2d"+"-"+frame0+"-"+frame1+"-"+corr_pairs,
						scene_titles); // titles_accum);
				double   input_range = 5;
				boolean  scale2x = true;
				String diamond_path_1x = "/media/elphel/NVME/lwir16-proc/eagle_mountain/graphics/diamond21x17.png";
//				String diamond_path_2x = "/media/elphel/NVME/lwir16-proc/eagle_mountain/graphics/diamond43x35_2px.png";
				String diamond_path_2x = "/media/elphel/NVME/lwir16-proc/eagle_mountain/graphics/TDbox_dashed43x43_2px.png";
				String diamond_path = scale2x? diamond_path_2x : diamond_path_1x;
				ImagePlus imp_color = convertToRgbAnnotateTargets(
						clt_parameters,             // CLTParameters       clt_parameters,
						input_range,                // final double        input_range, // 5
						scale2x, // boolean       scale2x,
						replaced_targets,           // final float  [][]   fpixels,
						targets60hz,                // final double [][][] targets60hz,
						frame0,                     // final int           frame0,
						corr_step,                   // final int           frame_step,
						cuasMotion.gpu_max_width,   // final int           width,
						title_accumulated+"-COLOR", // String              title,
						scene_titles,               // String []           titles,
						diamond_path, // //String              diamond_path,
						debugLevel);                // final int           debugLevel) {
				
				if (imp_color != null) {
					// temporary, will use model directory
					String save_directory = "/media/elphel/NVME/lwir16-proc/eagle_mountain/linked/movement/selected/video/";
					String video_path_tmp = save_directory + imp_name+"-TARGETS";
					String video_path = saveAsVideo(
							clt_parameters, // CLTParameters       clt_parameters,
							video_path_tmp, // String              file_path,
							imp_color,      // ImagePlus           imp_color,
							debugLevel);    // int debugLevel) {

					imp_color.show();
				}
				continue;
			}
			
			
			motion_scan = getVectorFieldHyper(vf_path); // title_vf); // String path)
			
			
			/*
			
			if (motion_scan_filtered != null) {
				extended_scan = extendMotionScan(
						motion_scan_filtered,  // final double [][][] motion_scan,
						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)
			}
			*/
//			extended_scan = getVectorFieldHyper(vf_extended_path); // String path)
//			if (extended_scan == null) {
			boolean show_vector_field =     false; //true;
			boolean show_2d_correlations =  false; //true;
			boolean show_full_images =      false; //true;
			if (motion_scan == null) {
				System.out.println ("testCuasScanMotion(): Failed to read Vector Field from file, calculating");
				// readVectorField(String path)

				String dbg_title = imp_name;
//				double [][][] 
				corr2d = show_2d_correlations? new double [num_corr_samples][][] : null;
				motion_scan = getMotionScan(
						false, // batch_mode,     // boolean           batch_mode,
						clt_parameters, // CLTParameters     clt_parameters,
						cuasMotion,     // CuasMotion        cuasMotion,
						fpixels,        // float [][]        fpixels,
						start_frame,    // int               start_frame,
						corr_pairs,     // int               corr_pairs,
						corr_offset,    // int               corr_offset,
						corr_step,      // int               corr_step,
						smooth,         // boolean           smooth,
						fat_zero,       // double            fat_zero,
						cent_radius,    // double            cent_radius,
						n_recenter,     // int               n_recenter,
						-rstr,          // double            rstr,
						dbg_title,      // String            title,
						corr2d,         // double [][][]     corr2d, // null or [(fpixels.length - seq_length - start_frame) / corr_step)[][]
						debugLevel);    // int               debugLevel) {
				
				if (show_2d_correlations) { // 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)
					ShowDoubleFloatArrays.showArrays(
							dbg_2d_corrs,
							cuasMotion.tilesX * (corr_size + 1),
							cuasMotion.tilesY * (corr_size + 1),
							true,
							title_cor2d,          // "-corr2d"+"-"+frame0+"-"+frame1+"-"+corr_pairs,
							slice_titles);
				}
				
				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
			double [][][] targets_bad =  new double [motion_scan.length][motion_scan[0].length][]; // will combine good targets
			
			for (niter=0; niter < max_iter; niter++) {
				// 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_scan, // double [][][] scan0, // all tried for targets
						targets_bad, // double [][][] scan1) // good targets
						remain);
				printRemain(remain, "motion_scan wo bad", true);
				// filter remaining (no known bad ones)
				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);
				printRemain(remain, "filter5", true);
				motion_scan_filtered = applyFilter(
						motion_scan,          // double [][][] motion_scan,
						filter5);             // boolean [][] filter5)
				// remove know good (already processed)
				motion_scan_filtered = subtractMotionScans(
						motion_scan_filtered, // double [][][] scan0, // all tried for targets
						targets_good, // double [][][] scan1) // good targets
						remain);
				printRemain(remain, "filtered wo good", true);
				// Anything remains? we'll see agter extension
				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 =  printRemain(remain, "extended_scan", true);
				if ((debugLevel >-4) && (remain !=null)) { 
					System.out.println("testCuasScanMotion() Total new tiles to process for "+remain.length+" scene sequences is "+remained_total);
					for (int nscan = 0; nscan < remain.length; nscan++) {
						System.out.print(nscan+":"+remain[nscan]+", ");
					}
					System.out.println();
				}
				if (remained_total == 0) {
					break;
				}
				// there are some tiles left to try
				
				if (show_vector_field) {
					//title_vf_all					
					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
							title_vf_all+"-n"+niter,// String        title,
							false,                // boolean       nan_effective_strength,
							true,                 // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
					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
							title_vf+"-n"+niter,  // String        title,
							true,                 // boolean       nan_effective_strength,
							true,                 // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
					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
							title_vf_filtered+"-n"+niter,    // String        title,
							false,                // boolean       nan_effective_strength,
							true,                 // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
					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
							title_vf_extended+"-n"+niter, // String        title,
							false,                // boolean       nan_effective_strength,
							true,                 // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
					// show good and bad accumulated here too?
				}
				
				if (debugLevel > -4) {
					System.out.println("Starting render, iteration = "+niter);
				}

				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_step,        // 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");
				}
				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_step,        // final int           frame_step,
						half_accum_range, // final int           half_range,
						smooth_accum, // final boolean       smooth,
						corr_offset,      // final int           corr_offset, // interframe distance for correlation
						true);            // final boolean       batch_mode) {
				// show just fpixels_accumulated
				// move outside? compare to slice_titles known
				if (show_full_images) {
					ShowDoubleFloatArrays.showArrays(
							fpixels_accumulated,
							cuasMotion.gpu_max_width,
							cuasMotion.gpu_max_height, 
							true,
							title_accumulated+"-n"+niter, // "-corr2d"+"-"+frame0+"-"+frame1+"-"+corr_pairs,
							slice_titles); // titles_accum);
				}
				// replace center frames with the accumulated ones
				for (int nseq = 0; nseq < fpixels_accumulated.length; nseq++){
					fpixels_rendered[frame0 + nseq * corr_step] = fpixels_accumulated[nseq];
				}
				if (debugLevel > -4) {
					System.out.println("Accumulation DONE");
				}

				float [][][] fpixels_hyper = {fpixels,fpixels_rendered};
				String [] hyper_titles_top = {"Source","Rendered"}; 
				if (show_full_images) {
					ImagePlus imp_hyper = ShowDoubleFloatArrays.showArraysHyperstack(
							fpixels_hyper,            // float[][][] pixels, 
							cuasMotion.gpu_max_width, // int          width, 
							title_rendered_hyper+"-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
							true);                    // boolean      show)
				}
				//			double target_radius = cent_radius; // for now - make separate
				// target_max
				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,
					    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 (show_vector_field) {
					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
							title_targets_all+"-n"+niter,    // String        title,
							false,                // boolean       nan_effective_strength,
							true,                 // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
					showVectorFieldsSequenceLMA(
							coord_lma,            // double [][][] target_scene_sequence, // rename
							slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
							title_targets_all+"-LMA-n"+niter, // String        title,
							true,                 // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
				}
				int [] targets_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, //
						title_targets_filt+"-n"+niter,   // String        title_targets_filt,
						targets_remain,       // int []        targets_remain,
						cuasMotion.tilesX,    // int           tilesX)
						show_vector_field);   // boolean       show)
				remained_total = printRemain(targets_remain, "coord_data_filtered: Total new targets", true);
				if (remained_total == 0) {
					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, //
						title_targets_filt+"-n"+niter,   // String        title_targets_filt,
						targets_remain,       // int []        targets_remain,
						cuasMotion.tilesX,    // int           tilesX)
						show_vector_field);   // boolean       show)
				remained_total = printRemain(targets_remain, "coord_data_filtered: Total new targets", true);

				
				
				// add good targets to targets_good
				targets_good  = combineMotionScans(
						targets_good,         // double [][][] scan0,
						coord_data_filtered, // double [][][] scan1)
						remain);
				printRemain(remain, "targets_good", true);
				
				if (show_vector_field) {
					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
							title_targets_good+"-n"+niter,// String        title,
							false,                // boolean       nan_effective_strength,
							true,                 // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
				}
				
				
				double [][][] targets_bad_last  = subtractMotionScans(
						motion_scan_filtered, // double [][][] scan0, // all tried for targets
						coord_data_filtered, // double [][][] scan1) // good targets
						remain);
				printRemain(remain, "targets_bad_last", true);
				// add bad targets to targets_bad
				targets_bad  = combineMotionScans(
						targets_bad,         // double [][][] scan0,
						targets_bad_last,   // double [][][] scan1)
						remain); //
				printRemain(remain, "targets_bad", true);
				
				float  [][] fpixels_accumulated_filtered = getTargetImages(
						coord_data_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 (show_full_images) {
					ShowDoubleFloatArrays.showArrays(
							fpixels_accumulated_filtered,
							cuasMotion.gpu_max_width,
							cuasMotion.gpu_max_height, 
							true,
							title_acc_targets+"-n"+niter, // "-corr2d"+"-"+frame0+"-"+frame1+"-"+corr_pairs,
							slice_titles); // titles_accum);
				}
				if (debugLevel > -4) {
					System.out.println("Iteration "+niter+" DONE.");
				}

			} //for (niter=0; niter < max_iter; niter++)
			show_vector_field = true;
			show_full_images = true;
			// make good motion vectors from good targets
			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);
			printRemain(remain, "All done: motion_good", true);
			
			// make extended from all good
			extended_scan = extendMotionScan(
					motion_good, // 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)
			printRemain(remain, "targets_good", true);
			if (show_vector_field) {
				showVectorFieldsSequence(
						motion_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
						title_vf_good,// String        title,
						false,                // boolean       nan_effective_strength,
						true,                 // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				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
						title_targets_good,// String        title,
						false,                // boolean       nan_effective_strength,
						true,                 // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				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
						title_extended_good,// String        title,
						false,                // boolean       nan_effective_strength,
						true,                 // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
			}
			
			// Render for all good
			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_step,        // 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 all good DONE");
			}
			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_step,        // final int           frame_step,
					half_accum_range, // final int           half_range,
					smooth_accum, // final boolean       smooth,
					corr_offset,      // final int           corr_offset, // interframe distance for correlation
					true);            // final boolean       batch_mode) {
			if (show_full_images) {
				ShowDoubleFloatArrays.showArrays( // good
						fpixels_accumulated,
						cuasMotion.gpu_max_width,
						cuasMotion.gpu_max_height, 
						true,
						title_accumulated+"-n"+niter, // "-corr2d"+"-"+frame0+"-"+frame1+"-"+corr_pairs,
						slice_titles); // titles_accum);
			}
			// replace center frames with the accumulated ones
			for (int nseq = 0; nseq < fpixels_accumulated.length; nseq++){
				fpixels_rendered[frame0 + nseq * corr_step] = fpixels_accumulated[nseq];
			}
			if (debugLevel > -4) {
				System.out.println("Accumulation all good DONE");
			}

			float [][][] fpixels_hyper = {fpixels,fpixels_rendered};
			String [] hyper_titles_top = {"Source","Rendered"};
			if (show_full_images) {
				ImagePlus imp_hyper = ShowDoubleFloatArrays.showArraysHyperstack( // good
						fpixels_hyper,            // float[][][] pixels, 
						cuasMotion.gpu_max_width, // int          width, 
						title_rendered_hyper+"-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
						true);                    // boolean      show)
			}
			//			double target_radius = cent_radius; // for now - make separate
			// target_max
			double [][][][] coord_data2 = 	getAccumulatedCoordinates(
					motion_good, // 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,
				    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 (show_vector_field) {
				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
						title_targets_all+"-COMBO",    // String        title,
						false,                // boolean       nan_effective_strength,
						true,                 // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				showVectorFieldsSequenceLMA(
						coord_lma,            // double [][][] target_scene_sequence, // rename
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						title_targets_all+"-LMA-COMBO", // String        title,
						true,                 // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
			}
			int [] targets_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, //
					title_targets_filt+"-n"+niter,   // String        title_targets_filt,
					targets_remain,       // int []        targets_remain,
					cuasMotion.tilesX,   // int           tilesX)
					show_vector_field);   // boolean       show)
			int remained_total = printRemain(targets_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, //
					title_targets_filt+"-n"+niter,   // String        title_targets_filt,
					targets_remain,       // int []        targets_remain,
					cuasMotion.tilesX,    // int           tilesX)
					show_vector_field);   // boolean       show)
			remained_total = printRemain(targets_remain, "coord_data_filtered: Total new targets LMA", true);
			
			
			if (debugLevel > -4) {
				System.out.println("testCuasScanMotion() Total good targets for "+targets_remain.length+" scene sequences is "+remained_total);
			}
			
			if (debugLevel > -4) {
				System.out.println("All DONE");
			}
		} // 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];
		}
		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(
			double [][][] coord_data,
			double        target_strength,
			double [][]   target_frac,
			String []     slice_titles, //
			String        title_targets_filt,
			int []        targets_remain,
			int           tilesX,
			boolean       show) {
		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) {
		///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,
					true,                 // boolean       show,
					tilesX);   //  int           tilesX) {
		}
		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) {
		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 (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,
					true,                 // boolean       show,
					tilesX);   //  int           tilesX) {
		}
		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
										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        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); 
					// 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,
			int               start_frame,
			int               corr_pairs,
			int               corr_offset,
			int               corr_step,
			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_step;
		double [][][] motion_scan = new double [num_corr_samples][][];
		
		for (int nscan = 0; nscan < motion_scan.length; nscan++) {
			int frame0 = start_frame + corr_step * 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,
					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);
			}
		}
		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,
			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 = 0; dframe < frame_len; dframe++) {
			double weight = smooth ? Math.sin((dframe+0.5)/frame_len*Math.PI): 1.0;
			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;
	}
	
	
	

	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 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) {
		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;
		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);
			
			// TODO: multithreaded
			/*
			for (int i = 0; i < stackfp.length; i++) {
				stackfp[i] = (FloatProcessor) stackfp[i].resize(stackfp[i].getWidth() * 2, stackfp[i].getHeight()*2);
			}
			*/
			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);
//			imp.show();
		}
//		imp.show();
		/*
		ImageConverter imageConverter = new ImageConverter(imp);
		imageConverter.convertToRGB(); // very slow - need multitherading
		*/
//		ImageStack conv_stack = imp.getStack();
		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 width =  imp_scenes_pair[nstereo].getWidth();
//		final int height = imp_scenes_pair[nstereo].getHeight();
		final int posX=  width - scale * 119; // 521;
		final int posY= height + scale *   1;  // 513;
		final Font font = new Font("Monospaced", Font.PLAIN, scale2x? 24: 12);
		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;
															}
														}
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		imp.show();
		return imp;
	}
	
	public static String saveAsVideo(
			CLTParameters       clt_parameters,
			String              file_path,
			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;
		boolean remove_avi =         clt_parameters.imp.remove_avi;
		
		boolean dry_run = false;
		String avi_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;
		}
		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 0 -crf %d %s %s",
				avi_path, video_codec, 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;
	}
	
	
	
}
