package com.elphel.imagej.cuas;

import java.awt.Rectangle;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;

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

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"};


	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;
//		int     frame0cent =        210;
//		int     frame1cent =        frame0cent + clt_parameters.imp.cuas_corr_offset;
//		int     frame_len =         clt_parameters.imp.cuas_corr_pairs; // 40;
		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;
//		double  rln_neib_rstr =    clt_parameters.imp.rln_neib_rstr;
		
		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];
		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;
		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.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.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());

			data_dir =                  gd.getNextString();
			save_params =               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.");
					}
				}
			}

			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));
			}
			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;
			
			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";
			motion_scan_filtered = getVectorFieldHyper(vf_filtered_path); // 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) {
				System.out.println ("testCuasScanMotion(): Failed to read Vector Field from file, calculating");
				// readVectorField(String path)
				boolean show_vector_field =    true;
				boolean show_2d_correlations =  true;


				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) {
				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);
				motion_scan_filtered = applyFilter(
						motion_scan,          // double [][][] motion_scan,
						filter5);             // boolean [][] filter5)

				//			double [][][]
				extended_scan = extendMotionScan(
						motion_scan,       // 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)
				if ((debugLevel >-4) && (remain !=null)) {
					for (int nscan = 0; nscan < remain.length; nscan++) {
						System.out.print(nscan+":"+remain[nscan]+", ");
					}
					System.out.println();
				}
				
				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 (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,             // 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,             // 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,    // 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,          // String        title,
							false,                // boolean       nan_effective_strength,
							true,                 // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
				}
				if (debugLevel > -4) {
					System.out.println("scan DONE");
				}
			}

			if (debugLevel > -4) {
				System.out.println("Starting render");
			}
			
			int frame0 = start_frame + seq_length/2;
			
			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");
			}
			final int     half_accum_range = corr_pairs/2;
			final boolean smooth_accum = true;
			float [][] fpixels_accumulated = cuasMotion.shiftAndRenderAccumulate(
					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,
					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
			String [] titles_accum = new String [fpixels_accumulated.length];
			//scene_titles
			for (int nseq = 0; nseq < fpixels_accumulated.length; nseq++){
				titles_accum[nseq] = scene_titles[frame0 + nseq * corr_step];
			}
			
			ShowDoubleFloatArrays.showArrays(
					fpixels_accumulated,
					cuasMotion.gpu_max_width,
					cuasMotion.gpu_max_height, 
					true,
					title_accumulated, // "-corr2d"+"-"+frame0+"-"+frame1+"-"+corr_pairs,
					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"}; 
			ImagePlus imp_hyper = ShowDoubleFloatArrays.showArraysHyperstack(
					fpixels_hyper,            // float[][][] pixels, 
					cuasMotion.gpu_max_width, // int          width, 
					title_rendered_hyper,     // 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_data = 	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){
			// update parameters
			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,    // String        title,
					false,                // boolean       nan_effective_strength,
					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,   // String        title_targets_filt,
					targets_remain,       // int []        targets_remain,
					cuasMotion.tilesX);   // int           tilesX)
			
			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)
			
			ShowDoubleFloatArrays.showArrays(
					fpixels_accumulated_filtered,
					cuasMotion.gpu_max_width,
					cuasMotion.gpu_max_height, 
					true,
					title_acc_targets, // "-corr2d"+"-"+frame0+"-"+frame1+"-"+corr_pairs,
					titles_accum);

			/*
			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
					cuasMotion.tilesX,  // final int           tilesX,
					targets_remain);    // final int     []    remain) {
			double [][][] coord_data_filtered = applyFilter(
					coord_data,         // double [][][] motion_scan,
					good_targets);      // boolean [][] filter5)
			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,
					true,                 // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
*/					
			if (debugLevel > -4) {
				System.out.println("All DONE");
			}
		}
	}
	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) {
		
//		int [] targets_remain = new int [coord_data.length];
		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) {
		if (targets_remain !=null) {
			for (int nscan = 0; nscan < targets_remain.length; nscan++) {
				System.out.print(nscan+":"+targets_remain[nscan]+", ");
			}
			System.out.println();
		}
		double [][][] coord_data_filtered = applyFilter(
				coord_data,         // double [][][] motion_scan,
				good_targets);      // boolean [][] filter5)
		///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 [][][] 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 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; // 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)
										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 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 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 [][] 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){
		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;
	}
	
	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 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) {
//		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][];
		float [][] frames_accum = new float [vector_field.length][];
		
		Arrays.fill(nan_frame, Float.NaN);
//		Arrays.fill(result_frames, nan_frame);
		Arrays.fill(frames_accum, nan_frame);
//		int half_step = frame_step/2;
		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;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		
		return frames_accum;
	}
	
	
	
	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();
	}

	
	
}
