package com.elphel.imagej.cuas;

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

import com.elphel.imagej.cameras.CLTParameters;
import com.elphel.imagej.common.DoubleGaussianBlur;
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.ims.UasLogReader;
import com.elphel.imagej.tileprocessor.Correlation2d;
import com.elphel.imagej.tileprocessor.ImageDtt;
import com.elphel.imagej.tileprocessor.IntersceneMatchParameters;
import com.elphel.imagej.tileprocessor.QuadCLT;
import com.elphel.imagej.tileprocessor.TDCorrTile;
import com.elphel.imagej.tileprocessor.TileNeibs;

import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.process.ColorProcessor;
import ij.process.FloatProcessor;
import ij.process.ImageConverter;
import ij.process.ImageProcessor;

public class CuasMotion {
	final static public  int MAX_ALT_TARGETS = 10; // convenient to be power of 10
	
	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;
	public static final int TARGET_MISMATCH_BEFORE =  5;
	public static final int TARGET_MISMATCH_AFTER =   6;
	final static public int TARGET_SCORE =            7;
	final static public int TARGET_SEQLEN =           8;
	final static public int TARGET_NTILE =            9;
	final static public int TARGET_LENGTH = TARGET_NTILE+1;
	
	final static public int IMPORTANCE_A =        0;
	final static public int IMPORTANCE_RMS =      1;
	final static public int IMPORTANCE_RMS_A =    2;
	final static public int IMPORTANCE_CENTER =   3;
	final static public int IMPORTANCE_MISMATCH = 4;
	final static public int IMPORTANCE_MATCH_LEN= 5;
	final static public int IMPORTANCE_LENGTH = IMPORTANCE_MATCH_LEN + 1;
	
	final static public int TOTALS_ALL =        0;
	final static public int TOTALS_UNDEFINED =  1;
	final static public int TOTALS_GOOD =       2;
	final static public int TOTALS_BAD =        3;
	final static public int TOTALS_LENGTH =     TOTALS_BAD+1;
	
	/*
			final double []     importance, // for now (each - squared?): [0] - Amplitude (A/A0), 1 - RMS (RMS0/RMS), 2 - RRMS((RMS/A0) / (RMS/A) 

	 */
	
	
	public static String RESOURCE_GRAPICS_DIR ="graphics";
	public static String ICON_TARGET_1X =      "TDbox_dashed21x21_bold.png";
	public static String ICON_TARGET_2X =      "TDbox_dashed43x43_2px.png";
	public static String [][] TARGET_ICONS = {
			{"TDbox_dashed21x21_bold.png","TDbox_dashed43x43_2px.png"}, // unknown
			{"TDbox21x21.png",            "TDbox43x43_2px.png"},        // selected 
			{"Circle21x21.png",           "Circle43x43_2px.png"},       // friend
			{"diamond21x17.png",          "diamond43x35_2px.png"}       // foe
	};
	public static String ICON_BLUE = "Circle63x63blue.png";

	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;
	private double []               sky_mask = null;
	
	public boolean slow_targets = false;
	
	public CuasMotion (
			CLTParameters     clt_parameters,
			QuadCLT           parentCLT,
			int               debugLevel) {
		this.debugLevel =     debugLevel;
		this.clt_parameters = clt_parameters;
		this.parentCLT =      parentCLT;  
		final double max_01_diff = 1E-3;
		
    	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);
		if (clt_parameters.imp.cuas_sky_path != null) {
			ImagePlus imp_mask = new ImagePlus(clt_parameters.imp.cuas_sky_path);
			if (imp_mask.getWidth() == 0) {
				System.out.println("CuasMotion(): Failed to read sky mask from "+clt_parameters.imp.cuas_sky_path+", not using mask.");
				clt_parameters.imp.cuas_sky_path = null;
			} else {
				int [] wh = new int[2];
				double [][] masks = ShowDoubleFloatArrays.readDoubleArray(imp_mask, 0, wh);
				if ((wh[0] != gpu_max_width) || (wh[1] != gpu_max_height)) {
					throw new IllegalArgumentException(String.format("Mask image dimensions (%d x %d) do not match those of the image (%d x %d)", wh[0], wh[1], gpu_max_width,gpu_max_height));
				}
				//sky_mask
				double offs_x = clt_parameters.imp.cuas_sky_offset[0];
				double offs_y = clt_parameters.imp.cuas_sky_offset[1];
				double []mask = masks[0];
				if ((offs_x==0) && (offs_y ==0)) {
					sky_mask = masks[0];
				} else { // linear interpolate
					sky_mask = new double [mask.length];
					int [] mask01 = new int [sky_mask.length];
					for (int i = 0; i < mask01.length; i++) {
						if (Math.abs(sky_mask[i]) < max_01_diff) {
							mask01[i] = 0;
						} else if (Math.abs(sky_mask[i] - 1.0) < max_01_diff) {
							mask01[i] = 2;
						} else {
							mask01[i] = 1;
						}
					}
					for (int py = 0; py< gpu_max_height; py++) {
						double my = py +offs_y;
						int imy0 = Math.min(gpu_max_height-1, Math.max(0, ((int) Math.floor(my))));
						int imy1 = Math.min(gpu_max_height-1, Math.max(0, ((int) Math.ceil(my))));
						for (int px = 0; px< gpu_max_width; px++) {
							int indx = px + gpu_max_width * py;
							double mx = px +offs_x;
							int imx0 = Math.min(gpu_max_width-1, Math.max(0, ((int) Math.floor(mx))));
							int imx1 = Math.min(gpu_max_width-1, Math.max(0, ((int) Math.ceil(mx))));
							int ind00 = imx0 + gpu_max_width * imy0; 
							int ind01 = imx1 + gpu_max_width * imy0; 
							int ind10 = imx0 + gpu_max_width * imy1; 
							int ind11 = imx1 + gpu_max_width * imy1; 
							if ((mask01[ind00] == 0) && (mask01[ind01] == 0) && (mask01[ind10] == 0) && (mask01[ind11] == 0)) {
								sky_mask[indx] = 0;
							} else 	if ((mask01[ind00] == 2) && (mask01[ind01] == 2) && (mask01[ind10] == 2) && (mask01[ind11] == 2)) {
								sky_mask[indx] = 1;
							} else {
								double kx = (imx1 > imx0) ? ((mx - imx0)/(imx1-imx0)) :0;
								double ky = (imy1 > imy0) ? ((my - imy0)/(imy1-imy0)) :0;
								sky_mask[indx] =
										(1-ky)* (1-kx) *mask[ind00] +
										(1-ky)* (  kx) *mask[ind01] +
										(  ky)* (1-kx) *mask[ind10] +
										(  ky)* (  kx) *mask[ind11];
							}
						}
					}
				}
			}
		}
		return;
	}
	
	
	
	
	public static boolean testCuasMotion(
			ImagePlus         imp_sel,
			CLTParameters     clt_parameters,
			QuadCLT           parentCLT,
			int               debugLevel) {
		int     framecent =         180;
		int     corr_offset =       clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =        clt_parameters.imp.cuas_corr_pairs;
		double  cuas_fat_zero =     clt_parameters.imp.cuas_fat_zero;
		double  cuas_cent_radius =  clt_parameters.imp.cuas_cent_radius;
		int     cuas_n_recenter =   clt_parameters.imp.cuas_n_recenter;
		double  cuas_rstr =         clt_parameters.imp.cuas_rstr;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
		boolean smooth =            clt_parameters.imp.cuas_smooth; // true;
		
		GenericJTabbedDialog gd = new GenericJTabbedDialog("Motion detect parameters");
		gd.addNumericField("Center frame",        framecent,        0,  5, "frame", "Center frame");
		gd.addNumericField("Number of pairs",     corr_pairs,       0,  3, "",      "The number of correlation pairs to accumulate.");
		gd.addNumericField("Pairs offset",        corr_offset,      0,  3, "",      "Offset between the correlation pairs");
		gd.addCheckbox    ("Smooth weights",      smooth, 			"Apply cosine weights when averaging a sequence of correlation pairs.");
		gd.addNumericField("Fat zero",            cuas_fat_zero,    7, 10, "",      "Fat zero for TD->PD conversion");
		gd.addNumericField("Center radius",       cuas_cent_radius, 3,  5, "pix",   "Center radius");
		gd.addNumericField("Number of recenters", cuas_n_recenter,  0,  5, "",      "Number of centroid re-center iterations");
		gd.addNumericField("Relative strength",   cuas_rstr,        3,  5, "x",     "Minimal relative strength of the 2D correlation");
		gd.showDialog();
		if (gd.wasCanceled())
			return false;
		framecent =           (int) gd.getNextNumber();
		corr_pairs =          (int) gd.getNextNumber();
		corr_offset =         (int) gd.getNextNumber();
		smooth =                    gd.getNextBoolean();
		cuas_fat_zero =             gd.getNextNumber();
		cuas_cent_radius =          gd.getNextNumber();
		cuas_n_recenter =     (int) gd.getNextNumber();
		cuas_rstr =            gd.getNextNumber();
		
		final boolean batch_mode = false;
		int frame0 = framecent - (corr_offset + corr_pairs)/2;
		int frame1 = framecent + (corr_offset - corr_pairs)/2;
		String suffix_param = "-"+framecent+"-"+corr_offset+"-"+corr_pairs;
		String dbg_suffix = imp_sel.getTitle()+suffix_param;
		
		if (parentCLT.getTileProcessor() == null) {
			parentCLT.setTiles (imp_sel, // set global tp.tilesX, tp.tilesY
					parentCLT.getNumSensors(), // tp.getNumSensors(),
					  clt_parameters,
					  QuadCLT.THREADS_MAX); // where to get it? Use instance member
		}
		
		CuasMotion cuasMotion = new CuasMotion (
				clt_parameters, // CLTParameters     clt_parameters,
				parentCLT, // QuadCLT           parentCLT,
				debugLevel); // int               debugLevel)
		
		int num_slices = imp_sel.getStack().getSize();
		float [][] fpixels = new float[num_slices][];
		for (int nslice = 0; nslice < fpixels.length; nslice++) {
			fpixels[nslice] = (float[]) imp_sel.getStack().getPixels(nslice+1);
		}
		int           corr_ra_step = 1;
		TDCorrTile [] tdCorrTiles = cuasMotion.correlatePairs(
				clt_parameters, // CLTParameters clt_parameters,
				fpixels,        // float [][]    fpixels,
				frame0,         // int           frame0,
				frame1,         // int           frame1,
				corr_pairs,      // int           frame_len,
				corr_ra_step,   // int           corr_ra_step,
				smooth,         // boolean       smooth, // use cosine mask
				true,           // batch_mode,     // final boolean batch_mode,
				dbg_suffix,     // final String  dbg_suffix, // for image_names
				debugLevel);    // int           debugLevel)
		// convert to pixel domain and display 
		double [][] corr_tiles_pd = cuasMotion.convertTDtoPD(
				tdCorrTiles,          // final TDCorrTile [] tiles,
				0xFE,                // final int           corr_type, // 0xFE
				cuas_fat_zero,       // final double        gpu_fat_zero,
				debugLevel);         // final int           debug_level

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

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

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

//	    double  lma_minr1 =      clt_parameters.imp.cuas_lma_minr1; // =     1.0;  // Maximal inner radius
	    
	    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
//	    double  lma_a2a =        clt_parameters.imp.cuas_lma_a2a; // =       0.7;  // Minimal ratio of the maximal pixel to the amplitude
	    
		boolean remove_isolated= clt_parameters.imp.cuas_isolated; // true;
	    
		double  mask_width =     clt_parameters.imp.cuas_mask_width;  // 9;
		double  mask_blur =      clt_parameters.imp.cuas_mask_blur;   // 3;
		boolean mask_round =     clt_parameters.imp.cuas_mask_round;  // false;
	    
		int     target_type =    clt_parameters.imp.cuas_target_type; //0; // 0 - unknown, 1 - known, 2 - friend, 3 - foe
		double  input_range =    clt_parameters.imp.cuas_input_range; // 5;
		boolean scale2x =        clt_parameters.imp.cuas_scale2x;      //true;
		
		boolean ra_background =  clt_parameters.imp.cuas_ra_background;   //true;

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

		boolean save_params = true;
		boolean test_lma = false;

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

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

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

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


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

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

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

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

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

	
	
	
	
	
	
	public static double [][][] cloneTargetsSequence(
			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 int [] getRemain(
			final double [][][] target_sequence,
			int []              num_all_in,
			int []              num_undef_in,
			int []              num_good_in,
			int []              num_bad_in) {
		final int num_seq =   target_sequence.length;
		final int num_tiles = target_sequence[0].length;
		final int [] num_all =   (num_all_in !=   null) ? num_all_in :   new int [num_seq];
		final int [] num_good =  (num_good_in !=  null) ? num_good_in :  new int [num_seq];
		final int [] num_bad =   (num_bad_in !=   null) ? num_bad_in :   new int [num_seq];
		final int [] num_undef = (num_undef_in != null) ? num_undef_in : new int [num_seq];
		Arrays.fill(num_all,   0);
		Arrays.fill(num_good,  0);
		Arrays.fill(num_bad,   0);
		Arrays.fill(num_undef, 0);
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							double [] ts = target_sequence[nSeq][ntile];
							if (ts != null) {
								num_all[nSeq]++;
								double ts_fail = ts[CuasMotionLMA.RSLT_FAIL];
								if (Double.isNaN(ts_fail)) {
									num_undef[nSeq]++;
								} else if (ts_fail == 0) {
									num_good[nSeq]++;
								} else {
									num_bad[nSeq]++;
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return getTotals(
				num_all,   // int [] num_all,
				num_undef, // int [] num_undef,
				num_good,  // int [] num_good,
				num_bad);  // int [] num_bad)
	}

	public static int [] getTotals(
			int []              num_all,
			int []              num_undef,
			int []              num_good,
			int []              num_bad) {
		int num_seq = 0;
		if (num_all != null) {
			num_seq = num_all.length;
		} else if (num_undef != null) {
			num_seq = num_undef.length;
		} else if (num_good != null) {
			num_seq = num_good.length;
		} else if (num_bad != null) {
			num_seq = num_bad.length;
		}
		if (num_seq == 0) {
			return null;
		} else {
			int [] totals = new int[TOTALS_LENGTH];
			for (int nseq = 0; nseq < num_seq; nseq++) {
				if (num_all != null) totals[TOTALS_ALL] +=       num_all[nseq];
				if (num_undef != null) totals[TOTALS_UNDEFINED] += num_undef[nseq];
				if (num_good != null) totals[TOTALS_GOOD] +=      num_good[nseq];
				if (num_bad!= null) totals[TOTALS_BAD] +=       num_bad[nseq];
			}
			return totals;
		}
	}

	public static int getTotal(int [] num) {
		int total = 0;
		if (num != null) {
			for (int n:num) {
				total+=n;
			}
		}
		return total;
	}
	
	
	public static int addNewResults(
			final double [][][] target_sequence, // will only process non-nulls here
			final double [][][] new_sequence,
			final int when_iter,
			final boolean overwrite) {
		final int num_seq =   target_sequence.length;
		final int num_tiles = target_sequence[0].length;
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		final AtomicInteger anew = 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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) if (target_sequence[nSeq][ntile] != null) {
							double [] tg =  target_sequence[nSeq][ntile];
							if (overwrite || Double.isNaN(tg[CuasMotionLMA.RSLT_FAIL])) { // only those not defined yet
								double [] ns = new_sequence[nSeq][ntile];
								if ((ns != null) && !Double.isNaN(ns[CuasMotionLMA.RSLT_FAIL])){ // only defined as good/bad
									for (int i = 0; i < tg.length; i++) {
										if (!Double.isNaN(ns[i])) {
											tg[i] = ns[i];
										}
									}
									tg[CuasMotionLMA.RSLT_WHEN] = when_iter;
									anew.getAndIncrement();
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return anew.get();
	}

	public static double [][][] subtractTargetSequence(
			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;
	}
	

	/**
	 * Add data from the motion vectors to reserved by CuasMotionLMA fields. Will only process tiles that have
	 * both target (LMA) data and motion vectors data.   
	 * @param targets  [frame sequence number][tile number]{target data array CuasMotionLMA.RSLT_LEN length}
	 * @param motion_vectors motion vectors
	 */
	public static void   addMotionVectors(
			final double [][][] targets,
			final double [][][] motion_vectors) {
		final int num_seq =   targets.length;
		final int num_tiles = targets[0].length;
		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()) {
						double [][] tg = targets[nSeq];
						double [][] mv = targets[nSeq];
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							double [] t = tg[ntile];
							if (t != null) {
								double [] v = mv[ntile];
								if (v!= null) {
									t[CuasMotionLMA.RSLT_VX] =    v[INDX_VX];
									t[CuasMotionLMA.RSLT_VY] =    v[INDX_VY];
									t[CuasMotionLMA.RSLT_VSTR] = v[INDX_STRENGTH];
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
	}
	
	
	
	public static double [][][] getAccumulatedCoordinates(
			final boolean       centered, // accum_data was centered, use target_sequence[][][CuasMotionLMA.RSLT_X], target_sequence[][][CuasMotionLMA.RSLT_Y] (if not NaN)
			final double [][][] target_sequence, // contains vector_fields data, // centers
			final float  [][]   accum_data,    // should be around 0, no low-freq
		    final double        lmax_fraction, // 0.7;     // Check if local maximum is separated from the surrounding by this fraction of the maximum value
		    final double        lmax_flt_neglim,   // -0.3;   // limit negative data to reduce ridge influence (>= to disable)        
	        final double        lmax_flt_hsigma,	 // 1.0 // HPF (~UM) subtract GB with this sigma from the data tile     
	        final double        lmax_flt_lsigma,   // 1.0 // LPF - GB result of the previous subtraction
	        final double        lmax_flt_scale,    // 5.0 // scale filtering result
	        final double []     sky_mask,
	        final double        sky_threshold,    // 0.9 // minimal value of the sky mask where target is possible
	        final double        lma_horizon,
		    final double        lmax_hack_ridge, // 0.45;    // Hack for ridges: remove horizontal streaks, where average(abs()) for the line exceeds fraction of abs max 0.32-good, 0.55 - bad
		    final double        lmax_radius,   // 3;       // look inside cuas_lmax_radius* 2 + 1 square for the local maximum isolation 
			final boolean       lmax_zero,     // true;    // zero all data outside this radius from the maximum
			final double        centroid_radius,
			final int           n_recenter,    //  re-center window around new maximum. 0 -no refines (single-pass)
			final int           tilesX,
			final boolean       no_border,
		// Moving target LMA	
			final double        lma_sigma,
			final double        wnd_pedestal,
			final double        lma_r0,
			final double        lma_ovrsht,
		// CUAS Motion LMA parameters
			final boolean       lma_fit_xy,		
			final boolean       lma_fit_a,						
			final boolean       lma_fit_c,
			final boolean       lma_fit_r,
			final boolean       lma_fit_k,			
			final double        lambda,
			final double        lambda_good,
			final double        lambda_bad,
			final double        lambda_max,
			final double        rms_diff,
			final int           num_iter,
			final float [][]    accum_debug,
			final int           debugLevel){
		if (debugLevel > 0 ) {
			System.out.println("getAccumulatedCoordinates(): "+((sky_mask == null)? "NOT ":"")+"using sky_mask");
		}
		final boolean dbg_hack_ridge = (debugLevel > 0); // true;
		final int tile2 = 2 * GPUTileProcessor.DTT_SIZE;
		final int lmax_iradius = (int) Math.floor(lmax_radius);
		final int num_seq = target_sequence.length; // same as accum_data.length
		final int num_tiles = target_sequence[0].length;
		final int num_pixels = accum_data[0].length;
		final int width = GPUTileProcessor.DTT_SIZE * tilesX;
		final int height = num_pixels / width; 
		final double [][][] targets_out = 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 int dbg_tile = -(31 + 45 * 80); //(38 + 45 * 80);
		final int dbg_seq = 13;
		final boolean use_filters = (lmax_flt_hsigma > 0) && (lmax_flt_lsigma > 0) && (lmax_flt_scale > 0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					double [] pix_tile = new double [tile2 * tile2];
					double [] pix_tile_filtered0 = use_filters ? new double [tile2 * tile2]:null;
					double [] pix_tile_filtered =  use_filters ? new double [tile2 * tile2]:null;
					boolean [] disabled = new boolean[pix_tile.length];
					TileNeibs tn = new TileNeibs(tile2,tile2);
					DoubleGaussianBlur gb = new DoubleGaussianBlur();
					CuasMotionLMA cuasMotionLMA = new CuasMotionLMA(
							tile2,      // int         width,
							lma_sigma, // double     sigma);
							wnd_pedestal);
					// may be faster if process only where vector_field[nseq][ntile] is not null
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						if (accum_debug != null) {
							accum_debug[nSeq] = new float [num_pixels];
							Arrays.fill(accum_debug[nSeq], Float.NaN);
						}
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if (target_sequence[nSeq][ntile] != null) {
								double [] target = target_sequence[nSeq][ntile].clone();
								if ((nSeq==dbg_seq) && (ntile == dbg_tile)) {
									System.out.println("getAccumulatedCoordinates(): nSeq="+nSeq+", ntile="+ntile);
								}
								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
								int horizon_y = (int) Math.ceil (lma_horizon)-py0;
								if (centered && !Double.isNaN(target[CuasMotionLMA.RSLT_Y])) {
									horizon_y = (int) Math.ceil (lma_horizon - target[CuasMotionLMA.RSLT_Y])-py0;
								}
								int y_mask_offs =  (centered && !Double.isNaN(target[CuasMotionLMA.RSLT_Y])) ? ((int) Math.round(target[CuasMotionLMA.RSLT_Y])):0;
								int x_mask_offs =  (centered && !Double.isNaN(target[CuasMotionLMA.RSLT_X])) ? ((int) Math.round(target[CuasMotionLMA.RSLT_X])):0;
								// copy 16x16 tile
								{
									int indx_dst = 0;
									for (int y = 0; y < tile2; y++) {
										int indx_src = px0 + (py0 +y) * width;
										if (y >=  horizon_y) {
											for (int x = 0; x < tile2; x++) {
												pix_tile[indx_dst++] = 0;
												indx_src++; // float-> double
											}
										} else {
											for (int x = 0; x < tile2; x++) {
												pix_tile[indx_dst++] = accum_data[nSeq][indx_src++]; // float-> double
											}
										}
									}
								}
								
								// find absolute maximum (before hacking and masking)
								int ntile_amax0 = TileNeibs.getAmaxTile(
										pix_tile); //double [] data)
								if (lmax_hack_ridge > 0) {
									Arrays.fill(disabled,false);
//									disabled = new boolean[pix_tile.length];
									double thresh = pix_tile[ntile_amax0] * lmax_hack_ridge;
									for (int row=0; row < tile2; row++) {
										double s = 0;
										int n = 0;
										for (int col = 0; col < tile2; col++) {
											double d = Math.abs(pix_tile[row*tile2+col]);
											if (!Double.isNaN(d)) {
												s += d;
												n++;
											}
										}
										s/= n;
										if (s > thresh) {
											for (int col = 0; col < tile2; col++) {
												int pindx = row*tile2+col;
												disabled[pindx] = true;
												pix_tile[pindx] = 0.0;
											}											
										}
									}
								}
								
								if (sky_mask != null) {
									for (int row = 0; row < tile2; row++) {
										int mask_y = Math.min(height-1, Math.max(0, py0 + y_mask_offs + row));
										for (int col = 0; col < tile2; col++) {
											int mask_x = Math.min(width-1, Math.max(0, px0 + x_mask_offs + col));
											int pindx = col+row*tile2;
											double mask_val = sky_mask[mask_x + mask_y * width];
											pix_tile[pindx] *= mask_val;
											if (mask_val < sky_threshold) {
												disabled[pindx] = true;  // disallow maximums in this area
											}
										}
									}
									if ((nSeq==dbg_seq) && (ntile == dbg_tile)) {
										System.out.println("getAccumulatedCoordinates(): nSeq="+nSeq+", ntile="+ntile+
												" py0="+py0+" px0="+px0+", y_mask_offs="+y_mask_offs+", x_mask_offs="+x_mask_offs);
										for (int row = 0; row < tile2; row++) {
											int mask_y = Math.min(height-1, Math.max(0, py0 + y_mask_offs + row));
											for (int col = 0; col < tile2; col++) {
												int mask_x = Math.min(width-1, Math.max(0, px0 + x_mask_offs + col));
												int pindx = col+row*tile2;
												double mask_val = sky_mask[mask_x + mask_y * width];
												if (col == 0) {
													System.out.println("row="+row+", mask_y="+mask_y+"col="+col+", mask_x="+mask_x+", mask_val="+mask_val+", pindx="+pindx);
												}
											}
										}
									}
								}
								// find absolute maximum (after "hacking" and masking
								int ntile_amax = TileNeibs.getAmaxTile(
										pix_tile); //double [] data)
								// filtering tile for better maximum selection
								if (pix_tile_filtered != null) {
									System.arraycopy(pix_tile, 0, pix_tile_filtered, 0, pix_tile_filtered.length);
								  	if (lmax_flt_neglim <0) {
										for (int i = 0; i < pix_tile_filtered.length; i++) {
											pix_tile_filtered[i] = Math.max(lmax_flt_neglim, pix_tile_filtered[i]);
										}
								  	}
									System.arraycopy(pix_tile_filtered, 0, pix_tile_filtered0,  0, pix_tile_filtered.length);
								  
									gb.blurDouble(
											pix_tile_filtered, //
											tile2,
											tile2,
											lmax_flt_hsigma,   // double sigmaX,
											lmax_flt_hsigma,   // double sigmaY,
											0.01);             // double accuracy)
									for (int i = 0; i < pix_tile_filtered.length; i++) {
										pix_tile_filtered[i] = pix_tile_filtered0[i] - pix_tile_filtered[i];
									}
									gb.blurDouble(
											pix_tile_filtered, //
											tile2,
											tile2,
											lmax_flt_lsigma,   // double sigmaX,
											lmax_flt_lsigma,   // double sigmaY,
											0.01);             // double accuracy)
									for (int i = 0; i < pix_tile_filtered.length; i++) {
										pix_tile_filtered[i] *= lmax_flt_scale;
									}
									ntile_amax = TileNeibs.getAmaxTile(
											pix_tile_filtered); //double [] data)

								}
								double [] pix_tile_centroid = (pix_tile_filtered != null) ? pix_tile_filtered : pix_tile;
								
								double amax_val = pix_tile_centroid[ntile_amax];
								double lmax_val = amax_val;
								int use_max = -1;
								if (!tn.isEdge(ntile_amax)) {
									boolean isolated =  tn.isMaxIsolated(
											pix_tile_centroid, // double [] data,
										ntile_amax, // int       ntile,
										lmax_fraction, // double    fraction,
										lmax_iradius); //int       radius)) {
									if (disabled[ntile_amax]) {
										if (dbg_hack_ridge && isolated) {
											System.out.println ("getAccumulatedCoordinates(): Removing ridge max for nSeq="+nSeq+", ntile="+ntile+", ntile_amax="+ntile_amax);
										}
										isolated = false;
									}
									if (isolated) {
										use_max = ntile_amax;
									}
								}
								boolean max_abs = (use_max >= 0); // maximum is absolute maximum
								
								if (use_max < 0) { // find alternative maximum
									int [] max_indices = tn.getLocalMaxes(
											pix_tile_centroid, //double [] data,
											true); // boolean   exclude_margins)
									double best_val = Double.NaN;
									for (int indx:max_indices) {
										boolean isolated = tn.isMaxIsolated(
												pix_tile_centroid,      // double [] data,
												indx,          // int       ntile,
												lmax_fraction, // double    fraction,
												lmax_iradius); //int       radius)) {
										if (disabled[indx]) {
											if (dbg_hack_ridge && isolated) {
												System.out.println ("getAccumulatedCoordinates(): Removing ridge max for nSeq="+nSeq+", ntile="+ntile+", indx="+indx);
											}
											isolated = false;
										}
										if (isolated) {
											double max_val = pix_tile_centroid[indx];
											if (!(best_val >= max_val)) {
												best_val =    max_val;
												use_max =  indx;
											}
										}
									}
									lmax_val = best_val;
								}
								
								
								
								// zero out outside the circle if maximum is not the absolute maximum or 
								if ((use_max >= 0) && (lmax_zero || !max_abs)) {
									double lmax_iradius2 = (int) Math.ceil(lmax_radius * lmax_radius);
									int x0 = use_max % tile2;
									int y0 = use_max / tile2;
									for (int y = 0; y < tile2; y++) {
										int dy = y-y0;
										int dy2 = dy*dy;
										for (int x = 0; x < tile2; x++) {
											int dx = x - x0;
											int r2 = dy2+dx*dx;
											int pindx = x + tile2 * y;
											if (r2 > lmax_iradius2) {
												pix_tile[pindx] = 0.0;
											} else {
												double rratio = Math.sqrt(r2)/lmax_iradius;
												pix_tile[pindx] *= Math.cos(0.5*Math.PI*rratio);
											}
										}
									}
								}
								
								// accum_debug will apply circular mask only if no filtering. With filtering it will contain filtered (HPF+LPF) data
								if (accum_debug != null) {
									int indx_src = 0;
									for (int y = 0; y < tile2; y++) {
										int indx_dst = px0 + (py0 +y) * width;
										for (int x = 0; x < tile2; x++) {
											accum_debug[nSeq][indx_dst++] = (float) pix_tile_centroid[indx_src++]; // double -> float
										}
									}
								}
								target[CuasMotionLMA.RSLT_CENT_MX] = 0.0;
								if (use_max >= 0){ // at least one candidate found 
									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,            //          fpn_mask,        // boolean [] fpn_mask,
											false,           // boolean    ignore_border, // only if fpn_mask != null - ignore tile if maximum touches fpn_mask
											no_border,       // boolean    exclude_margins,
											true,            // boolean    calc_fraction,
											use_max,         // int        imax,  // index of the maximum in data[]
											false);          // boolean   debug)
									if (mv != null) {
										target[CuasMotionLMA.RSLT_CENT_X] =  mv[0];
										target[CuasMotionLMA.RSLT_CENT_Y] =  mv[1];
										target[CuasMotionLMA.RSLT_CENT_MX] = mv[2];
										if (mv.length > 3) {
											target[CuasMotionLMA.RSLT_CENT_F] =  mv[3];
										}
										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,
												lmax_val,   // double     lmax_val,
												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
												nSeq,        // int    dbg_seq,
												ntile,       // int    dbg_tile,
												-1,          // int    dbg_index,
												debugLevel); // int    debug_level)
										target[CuasMotionLMA.RSLT_ITERS] = rslt; // will save -1 (failure) also
										if (rslt >= 0) {
											cuasMotionLMA.setResult(target);
											int col = GPUTileProcessor.DTT_SIZE + (int) Math.round(target[CuasMotionLMA.RSLT_X]);
											int row = GPUTileProcessor.DTT_SIZE + (int) Math.round(target[CuasMotionLMA.RSLT_Y]);
											int pindx = col+row*tile2;
											if (!tn.isInside(pindx)) {
												target[CuasMotionLMA.RSLT_ITERS] = -2; // outside of the 16x16 tiles -> LMA failed to converge
											} else if (disabled[pindx]) {
												target[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_HORIZON;
											} 
											if (centered && !Double.isNaN(target_sequence[nSeq][ntile][CuasMotionLMA.RSLT_X])) {
												double x0 = target_sequence[nSeq][ntile][CuasMotionLMA.RSLT_X];
												double y0 = target_sequence[nSeq][ntile][CuasMotionLMA.RSLT_Y];
												target[CuasMotionLMA.RSLT_CENT_X] += x0;
												target[CuasMotionLMA.RSLT_CENT_Y] += y0;
												target[CuasMotionLMA.RSLT_X] +=      x0;
												target[CuasMotionLMA.RSLT_Y] +=      y0;
											}
											if ((ntile == dbg_tile) && (nSeq == dbg_seq)) { // if ((nSeq==dbg_seq) && (ntile == dbg_tile)) {
												// px0, py0 - top/left corner of 16x16
												int ipx = (int) (px0 + GPUTileProcessor.DTT_SIZE + target[CuasMotionLMA.RSLT_X]);
												int ipy = (int) (py0 + GPUTileProcessor.DTT_SIZE + target[CuasMotionLMA.RSLT_Y]);
												int mask_x = Math.min(width-1,  Math.max(0, ipx));
												int mask_y = Math.min(height-1, Math.max(0, ipy));
												double mask_val = sky_mask[mask_x + mask_y * width];
												System.out.println("getAccumulatedCoordinates(): nSeq="+nSeq+", ntile="+ntile+
														", centered="+centered+", pindx="+pindx+
														", ipx="+ipx+", ipy="+ipy+
														", mask_x="+mask_x + ", mask_y="+mask_y+", mask_val = "+mask_val);
												System.out.println();
												for (int prow = 0; prow  < tile2; prow++) {
													for (int pcol=0; pcol<tile2; pcol++) {
														System.out.print(disabled[pcol+prow*tile2]?".":"+");
													}
													System.out.println();
												}
												
											}
										}
									}
								}
								targets_out[nSeq][ntile] = target;
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return targets_out;
	}
	
	

	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 [][][] target_sequence,
			boolean [][]  filter5){
		double [][][] filtered_scan = new double [target_sequence.length][target_sequence[0].length][];
		for (int nscan = 0; nscan < target_sequence.length; nscan++) {
			for (int t=0; t<target_sequence[nscan].length; t++) {
				if ((target_sequence[nscan][t] != null) && filter5[nscan][t]) {
					filtered_scan[nscan][t] = target_sequence[nscan][t].clone();
				}
			}
		}
		return filtered_scan;
	}

	public static double [][][] applyFilter(
			double [][][] target_sequence,
			int    [][]   filter5){
		double [][][] filtered_scan = new double [target_sequence.length][target_sequence[0].length][];
		for (int nscan = 0; nscan < target_sequence.length; nscan++) {
			for (int t=0; t<target_sequence[nscan].length; t++) {
				if ((target_sequence[nscan][t] != null) && (filter5[nscan][t] >=0)) {
					filtered_scan[nscan][t] = target_sequence[nscan][t].clone();
				}
			}
		}
		return filtered_scan;
	}
	
	public static void findStrongerNeighbor(// does not modify "when"
			final double [][][] target_sequence,
			final boolean [][]  filter5,
			final boolean       mark_failed,
			final int           tilesX) {
		final int range = 2;
		final int num_seq = target_sequence.length;
		final int num_tiles = target_sequence[0].length;
		final int tilesY = num_tiles/tilesX;
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) if ((target_sequence[nSeq][ntile] != null) &&
								(target_sequence[nSeq][ntile][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE)) { // &&
							if (filter5[nSeq][ntile]) {
								target_sequence[nSeq][ntile][CuasMotionLMA.RSLT_STRONGER] = Double.NaN;
							} else { // find first stronger tile index
								check_nebs: {
									for (int dy = -range; dy <= range; dy++) {	
										for (int dx = -range; dx <= range; dx++) {
											int indx = tn.getNeibIndex(ntile, dx, dy);
											if ((indx >= 0) && filter5[nSeq][indx]) {
												target_sequence[nSeq][ntile][CuasMotionLMA.RSLT_STRONGER] = indx;
												break check_nebs;
											}
										}
									}
									System.out.println("findStrongerNeighbor(): FIXME: no selected neighbor in 5x5 vicinity, but this is not selected either. nSeq="+
									nSeq+", ntile="+ntile);
									target_sequence[nSeq][ntile][CuasMotionLMA.RSLT_STRONGER] = -1; // not found
								}
								if (mark_failed) {
									target_sequence[nSeq][ntile][CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_NEIGHBOR;
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
	}
	
	/*
	public static void clearFailByStrongerNeighbor(// does not modify "when"
			final double [][][] target_sequence){
		final int num_seq = target_sequence.length;
		final int num_tiles = target_sequence[0].length;
		
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) if ((target_sequence[nSeq][ntile] != null) &&
								(target_sequence[nSeq][ntile][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NEIGHBOR)) {
							target_sequence[nSeq][ntile][CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_NONE;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
	}
	*/
	
	
	public static double [][][] applyFilter(
			final double [][][] target_sequence,
			final int           index,
			final double        threshold){
		final int num_seq =   target_sequence.length;
		final int num_tiles = target_sequence[0].length;
		
		final double [][][] filtered_targets = new double [target_sequence.length][target_sequence[0].length][];
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if ((target_sequence[nSeq][ntile] != null) && (target_sequence[nSeq][ntile][index] > threshold) ) {
								filtered_targets[nSeq][ntile] = target_sequence[nSeq][ntile].clone();
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return filtered_targets;
	}
	
	
	
	
	
	public static ImagePlus showTargetSequence(
			double [][][] target_sequence, // rename
			String []     titles, // all slices*frames titles or just slice titles or null
			String        title,
			boolean       show,
			int           tilesX) {
		int num_fields = CuasMotionLMA.LMA_TITLES.length;
		int num_scenes = target_sequence.length;
		int num_tiles = target_sequence[0].length;
		String [] frame_titles = new String [CuasMotionLMA.LMA_TITLES.length + 1];
		for (int i = 0; i < CuasMotionLMA.LMA_TITLES.length; i++) {
			frame_titles[i] = CuasMotionLMA.LMA_TITLES[i];
		}
		frame_titles[CuasMotionLMA.LMA_TITLES.length] = "Targets";
		double [][][] img_data = new double [num_fields+1][num_scenes][num_tiles];
		for (int nscene = 0; nscene < num_scenes; nscene++) {
			for (int nfield = 0; nfield < img_data.length; nfield++) {
				Arrays.fill(img_data[nfield][nscene], Double.NaN);
			}
			for (int ntile=0; ntile<target_sequence[nscene].length; ntile++) {
				double [] target = target_sequence[nscene][ntile];
				if (target != null) {
					for (int nfield = 0; nfield < num_fields; nfield++) {
						img_data[nfield][nscene][ntile] = target[nfield];
					}
					img_data[num_fields][nscene][ntile] = ((target[CuasMotionLMA.RSLT_FAIL]==CuasMotionLMA.FAIL_NONE) &&
							Double.isNaN(target[CuasMotionLMA.RSLT_STRONGER]))? 1:0;
				}
			}
		}
		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
				frame_titles, // 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;
	}
	
	
	// fills out additional fields in target_coords
	public static void getEffectiveStrengthLMA(
			final double [][][] target_sequence, // modifies certain fields (scores)
			final double        target_strength,
			final double [][]   target_frac, // pairs - strength, minimal fraction for that strength
			final double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
			final double        lma_arms, // =      0.06; // Maximal absolute RMS (should match one of cuas_lma_arms OR cuas_lma_rrms (0.484)
			final double        lma_rrms, // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
			final double        lma_mina, // =      1.0;  // Minimal A (amplitude)
			final double        lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
            final double        lma_minr1,// =      1.0;  // Minimal R1 (radius of positive peak) 
			final double        lma_mink, // =      0.0;  // Minimal K (overshoot) = 1.0
			final double        lma_maxk, // =      5.0;  // Minimal K (overshoot) = 3.0
			final double        lma_a2a,
			final double        max_mismatch, 
			final double        good_mismatch, //do not add to score if worse
			final boolean       fail_mismatch,
			final double        lma_horizon, // horizon as maximal pixel Y
			final int           tilesX	) {
		// if centroid maximum is safely good, ignore lma_a2a ratio
		final double SAFE_CENT_MX = 3 * target_strength; // Find a more elegant solution
		final int num_seq = target_sequence.length;
		final int num_tiles = target_sequence[0].length;
//		final double center_scale = 0.5*Math.PI/GPUTileProcessor.DTT_SIZE;
		final double center_scale = 0.25*Math.PI/GPUTileProcessor.DTT_SIZE;
		final int dbg_tile = -(34 + 34*80);
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if (ntile == dbg_tile) {
								System.out.println("filterTargetsLMA(): ntile = "+ntile);
							}

							double [] lma_rslts = target_sequence[nSeq][ntile];
							if (lma_rslts != null) {
								lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_NONE;
								double CENT_STR = lma_rslts[CuasMotionLMA.RSLT_CENT_MX];
								double CENT_FRAC= lma_rslts[CuasMotionLMA.RSLT_CENT_F];
								double A =        lma_rslts[CuasMotionLMA.RSLT_A];
								double RMSE =     lma_rslts[CuasMotionLMA.RSLT_RMS];
								double RMSE_A =   lma_rslts[CuasMotionLMA.RSLT_RMS_A];
								double R1 =       lma_rslts[CuasMotionLMA.RSLT_R1];
								double MAX2A =    lma_rslts[CuasMotionLMA.RSLT_MAX2A];
								double Y = (ntile/tilesX +0.5) * GPUTileProcessor.DTT_SIZE + lma_rslts[CuasMotionLMA.RSLT_Y];
								double MM_BEFORE = lma_rslts[CuasMotionLMA.RSLT_MISMATCH_BEFORE];
								double MM_AFTER =  lma_rslts[CuasMotionLMA.RSLT_MISMATCH_AFTER];
								double x = lma_rslts[CuasMotionLMA.RSLT_X];
								double y = lma_rslts[CuasMotionLMA.RSLT_Y];
								double cxy = 1.0;
								if ((Math.abs(x) > 0.5*GPUTileProcessor.DTT_SIZE) || (Math.abs(y) > 0.5*GPUTileProcessor.DTT_SIZE)) {
									cxy = 0.0;
								} else {
									double ax = Math.abs(x) - 0.25*GPUTileProcessor.DTT_SIZE;
									double ay = Math.abs(x) - 0.25*GPUTileProcessor.DTT_SIZE;
									if (ax > 0) {
										cxy *= Math.cos(ax * center_scale);
									}
									if (ay > 0) {
										cxy *= Math.cos(ay * center_scale);
									}
								}
								//	double cxy= Math.max(0,Math.cos(x * center_scale)) * Math.max(0,Math.cos(y * center_scale));
								boolean failed = true;
								try_failures: {
									// start with centroid tests
									if (CENT_STR < target_strength) {
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_CENT_STR;
										break try_failures; // cemtroid maximum is too weak
									}
									for (double [] frac_pair : target_frac) {
										if ((CENT_STR > frac_pair[0]) && (CENT_FRAC < frac_pair[1])) {
											lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_CENT_FRAC;
											break try_failures; // centroid fraction is too small
										}
									}
									if (lma_rslts[CuasMotionLMA.RSLT_ITERS] < 0) { // fitting has not failed
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_LMA;
										break try_failures; // LMA failed
									}

									if (A < lma_mina) {
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_A_LOW;
										break try_failures; // too weak
									}

									if (MAX2A < lma_a2a) {
										if (CENT_STR < SAFE_CENT_MX) {
											lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_ACENT;
											break try_failures; // ratio of maximal value to A is too low
										}
									}

									if (RMSE > lma_rms) {
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_RMSE;
										break try_failures; // too high RMSE regardless of A
									}

									if ((RMSE > lma_arms) && (RMSE_A > lma_rrms)) {
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_RMSE_R;
										break try_failures; // too high RMSE
									}

									if (lma_rslts[CuasMotionLMA.RSLT_R0] > lma_maxr) {
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_R0_HIGH;
										break try_failures; // Radius is too high
									}
									if (R1 < lma_minr1) {
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_R1_LOW;
										break try_failures; // Inner radius too small
									}
									if (lma_rslts[CuasMotionLMA.RSLT_K] < lma_mink) {
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_K_LOW;
										break try_failures; // K is too low
									}

									if (lma_rslts[CuasMotionLMA.RSLT_K] > lma_maxk) {
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_K_HIGH;
										break try_failures; // K is too high
									}
									if (fail_mismatch && !(MM_BEFORE <= max_mismatch) && !(MM_AFTER <= max_mismatch)) { // on both ends, handles NaN
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_MISMATCH;
										break try_failures; // Mismatch is too high
									}
									if ((lma_horizon >0) && (Y > lma_horizon)) {
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_HORIZON;
										break try_failures; // below horizon line
									}
									if (cxy==0) {
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_FAR;
										break try_failures; // below horizon line
									}
									failed = false; // all tests passed
								}
								
								double [] quality_factors = new double [IMPORTANCE_LENGTH];
								if (!failed) {
									lma_rslts[CuasMotionLMA.RSLT_FAIL] = 0;
									double    inv_rms =         1.0 / RMSE;
									double    inv_rms_lim =     1.0 / lma_arms;
									double    inv_rel_rms =     1.0 / RMSE_A; // A/RMSE;
									double    inv_rel_rms_lim = 1.0 / lma_rrms;

									quality_factors[IMPORTANCE_A] =     Math.max(0,(A-            lma_mina)/        lma_mina);
									quality_factors[IMPORTANCE_RMS] =   Math.max(0,(inv_rms -     inv_rms_lim)/     inv_rms_lim); // only >0 for small max-es
									quality_factors[IMPORTANCE_RMS_A] = Math.max(0,(inv_rel_rms - inv_rel_rms_lim)/ inv_rel_rms_lim); // only >0 for small max-es

									if (MM_BEFORE <= good_mismatch) {
										quality_factors[IMPORTANCE_MISMATCH] += Math.max(0,(good_mismatch - MM_BEFORE)/good_mismatch); // 0 .. 1
									}
									if (MM_AFTER <= good_mismatch) {
										quality_factors[IMPORTANCE_MISMATCH] += Math.max(0,(good_mismatch - MM_AFTER)/good_mismatch); // 0 .. 1
									}
									quality_factors[IMPORTANCE_CENTER] = cxy*cxy; 
								}
								// save quality factors
								lma_rslts[CuasMotionLMA.RSLT_QA] =      quality_factors[IMPORTANCE_A];
								lma_rslts[CuasMotionLMA.RSLT_QRMS] =    quality_factors[IMPORTANCE_RMS];
								lma_rslts[CuasMotionLMA.RSLT_QRMS_A] =  quality_factors[IMPORTANCE_RMS_A];
								lma_rslts[CuasMotionLMA.RSLT_QMATCH] =  quality_factors[IMPORTANCE_MISMATCH];
								lma_rslts[CuasMotionLMA.RSLT_QCENTER] = quality_factors[IMPORTANCE_CENTER];
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return;
	}
	
	
	public static int [] getScore(
			final double [][][] target_sequence, // modifies certain fields (scores)
			final double        importance_limit,
			final double        importance_power, // Raise each factor to this power before combining
			final double []     importance, // for now (each - squared?): [0] - Amplitude (A/A0), 1 - RMS (RMS0/RMS), 2 - RRMS((RMS/A0) / (RMS/A)
			final int           tilesX	) {
        double sw = 0;
        for (int i = 0; i < importance.length; i++) {
            sw += importance[i];
        }
        for (int i = 0; i < importance.length; i++) {
            importance[i]/=sw;
        }
		// if centroid maximum is safely good, ignore lma_a2a ratio
		final int num_seq = target_sequence.length;
		final int num_tiles = target_sequence[0].length;
		int [] remain = new int [num_seq];
		final int dbg_tile = -(34 + 34*80);
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if (ntile == dbg_tile) {
								System.out.println("filterTargetsLMA(): ntile = "+ntile);
							}
							double [] lma_rslts = target_sequence[nSeq][ntile];
							if (lma_rslts != null) {
								lma_rslts[CuasMotionLMA.RSLT_QSCORE] = 0; // CuasMotionLMA.FAIL_NONE;
								if (lma_rslts[CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) {
									double [] quality_factors = new double [IMPORTANCE_LENGTH];
									quality_factors[IMPORTANCE_A] =        lma_rslts[CuasMotionLMA.RSLT_QA];      
									quality_factors[IMPORTANCE_RMS] =      lma_rslts[CuasMotionLMA.RSLT_QRMS];    
									quality_factors[IMPORTANCE_RMS_A] =    lma_rslts[CuasMotionLMA.RSLT_QRMS_A];  
									quality_factors[IMPORTANCE_MISMATCH] = lma_rslts[CuasMotionLMA.RSLT_QMATCH];  
									quality_factors[IMPORTANCE_CENTER] =   lma_rslts[CuasMotionLMA.RSLT_QCENTER]; 
									for (int i = 0; i < importance.length; i++) {
										quality_factors[i] = Math.min(quality_factors[i], importance_limit);
									}								

									double quality = 0;
									for (int i = 0; i < importance.length; i++) {
										if (!Double.isNaN(quality_factors[i])) {
											double factor_contrib = Math.pow(quality_factors[i], importance_power);
											quality += importance[i] * factor_contrib;
										}
									}
									quality = Math.pow(quality, 1.0/importance_power);
									lma_rslts[CuasMotionLMA.RSLT_QSCORE] = quality;
									remain[nSeq]++;
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return remain;
	}
	
	
	public static double [][] getEffectiveStrengthMV( // calculate tiles effective strength by the motion vectors. Combine with the target LMA?
			final double [][][] targets_sequence,
			int                 niter, // save iteration number on failure if >=
			final int           tilesX,
			final double        min_score_mv,
			double              speed_min,		
			double              speed_pref,		
			double              speed_boost	){
		final int num_seq = targets_sequence.length;
		final int num_tiles = targets_sequence[0].length;
		final double [][] effective_strength = 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() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						double [][] targets = targets_sequence[nSeq];
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if (targets[ntile] != null) {
								double es =	getEffectiveStrengthMV(
										targets[ntile],  // double [] target,
										speed_min,  // double speed_min,		
										speed_pref, // double speed_pref,		
										speed_boost); //double speed_boost)
								targets[ntile][CuasMotionLMA.RSLT_MSCORE] = es;
								effective_strength[nSeq][ntile] = es;
								if ((niter >= 0) && !(es >= min_score_mv)) {
									targets[ntile][CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_MOTION;
									targets[ntile][CuasMotionLMA.RSLT_WHEN] = niter;
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return effective_strength;
	}
	
	public static boolean [][] filter5Targets( // should work for motion vectors and target coordinates
			final double [][][] target_sequence,
			final boolean       use_motion,     // true - use motion vectors confidence, false - use target confidence
			final boolean       select_new,     // true - use only untested tiles, false - use good tiles 
			final double        min_confidence, // 0 OK
			final double        lma_horizon, // target below horizon
			final int           tilesX,
			final int           range, // 1 or 2
			final int     []    remain,
			final int     []    passes, // debugging - number of passes required
			final int           debugLevel){
		final int conf_index = use_motion ? CuasMotionLMA.RSLT_MSCORE : CuasMotionLMA.RSLT_QSCORE; 
		final int num_seq = target_sequence.length;
		final int num_tiles = target_sequence[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);
		final int dbg_tile = -(38+45*80);
		final int dbg_seq0 = 13; 
		final int dbg_seq1 = 14; 
		final int ihorizon = (lma_horizon > 0) ?( (int) Math.ceil(lma_horizon/GPUTileProcessor.DTT_SIZE)) : -1; // tiles with tileY >= ihorizon are removed
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					TileNeibs tn = new TileNeibs(tilesX, tilesY);
					boolean [] prohibit = new boolean [num_tiles];
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						double [][] targets = target_sequence[nSeq];
						int num_total = 0;
						Arrays.fill(prohibit, false);
						for (int ntile = 0; ntile < num_tiles; ntile++) {
///							if ((targets[ntile] == null) || !(targets[ntile][conf_index] >= min_confidence) || (targets[ntile][CuasMotionLMA.RSLT_FAIL] > 0)) { // added failed
							if ((targets[ntile] == null) ||
									!(targets[ntile][conf_index] >= min_confidence) ||
									(select_new ? !Double.isNaN(targets[ntile][CuasMotionLMA.RSLT_FAIL]) : (targets[ntile][CuasMotionLMA.RSLT_FAIL] > 0))) { // defined: good or bad
								if ((ntile == dbg_tile) && (nSeq >= dbg_seq0) && (nSeq <= dbg_seq1)) {
									System.out.println("filter5Targets()):0: ntile="+ntile+", nSeq="+nSeq);
								}
								prohibit[ntile] = true;
							}
						}
						if ((ihorizon > 0) && (ihorizon < tilesY)) {
							Arrays.fill(prohibit, ihorizon*tilesX, num_tiles, true);
						}
						int num_this_pass = 0;
						int npass = 0;
						do {
							npass++;
							num_this_pass = 0;
							for (int tileY = 0; tileY < tilesY; tileY++) {
								for (int tileX = 0; tileX < tilesX; tileX++) {
									int ntile = tileX + tilesX * tileY;
									if ((ntile == dbg_tile) && (nSeq >= dbg_seq0) && (nSeq <= dbg_seq1)) {
										System.out.println("filter5Targets()):1: ntile="+ntile+", nSeq="+nSeq+", npass="+npass+", num_this_pass="+num_this_pass+", num_total="+num_total);
									}
//									if ((conf[ntile] > 0.0) && !prohibit[ntile]) {
									if (!prohibit[ntile]) { // targets[ntile][conf_index] >0 here by the prohibit preset
										boolean ismax = true;
										check_max:{
											double cval = targets[ntile][conf_index] - min_confidence;
											if (cval <= 0) {
												ismax = false;
												prohibit[ntile] = true; // not needed 
												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) && !prohibit[indx]) {
//														double val = conf[indx]- min_confidence;
														double val = targets[indx][conf_index]- min_confidence;
														if (val > cval) {
															ismax = false;
															break check_max;
														}
													}
												}
											}
										}
										if (ismax) {
											filter5[nSeq][ntile] = true; // ismax;
											// prohibit all around, including this one
											for (int dy = -range; dy <= range; dy++) {
												for (int dx = -range; dx <= range; dx++) {
													int indx = tn.getNeibIndex(ntile, dx, dy);
													if (indx >= 0) { // && ((filtered == null) || !filtered[nSeq][indx])){
														prohibit[indx] = true;
													}
												}
											}
											num_this_pass++;
										}
									}
								}
							}
							num_total+=num_this_pass;
						} while (num_this_pass > 0);
						
						if (remain != null) {
							remain[nSeq] = num_total;
						}
						if (passes != null) {
							passes[nSeq] = npass;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return filter5;
	}
	
	
	
	private static void setBadHorizon(
			final double [][][] target_sequence,
			final double        horizon_y,
			final int           tilesX){
		final int num_seq =   target_sequence.length;
		final int num_tiles = target_sequence[0].length;
		final int tilesY =    num_tiles/tilesX; 
		
		final int ihorizon = (horizon_y > 0) ?( (int) Math.ceil(horizon_y/GPUTileProcessor.DTT_SIZE)) : -1; // tiles with tileY >= ihorizon are removed
		if (ihorizon  > 0) {
			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()) {
							for (int tileY = ihorizon; tileY < tilesY; tileY++) {
								int ntile = tileY*tilesX;
								for (int tileX = 0; tileX < tilesX; tileX++) {
									target_sequence[nSeq][ntile++] = null;
								}
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		}
	}
	
	
	private static double getEffectiveStrengthMV(
			double [] target,
			double speed_min,		
			double speed_pref,		
			double speed_boost) {
		if (target == null ) {
			return 0.0;
		}
		double val = target[CuasMotionLMA.RSLT_VSTR];
		if (!Double.isNaN(target[CuasMotionLMA.RSLT_VFRAC])) {
			val *= target[CuasMotionLMA.RSLT_VFRAC];
		}
		if ((speed_min > 0) ||(speed_pref >0)) {
			double vx = target[CuasMotionLMA.RSLT_VX];
			double vy = target[CuasMotionLMA.RSLT_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;
	}
	
	
	// now will skip tiles that have stronger neighbors
	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 irange = range; irange > 0; irange--) { // to try 5x5, but make sure 3x3 are always as needed
			ai.set(0);
			final int frange = irange;
			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) &&
											Double.isNaN(ms[i][CuasMotionLMA.RSLT_STRONGER]) && // should not have stronger neighbor
											(Double.isNaN(ms[i][CuasMotionLMA.RSLT_FAIL]) || (ms[i][CuasMotionLMA.RSLT_FAIL]==CuasMotionLMA.FAIL_NONE));
								}
							}
							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 = -frange; dy <= frange; dy++) {
											for (int dx = -frange; dx <= frange; 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 [][][] getTargetsFromCorr2d(
			CLTParameters     clt_parameters,
			boolean           batch_mode,
			CuasMotion        cuasMotion,
			float [][]        fpixels, // may be running average
			int               start_frame,
			int               corr_pairs,
			int               corr_offset,
			int               corr_inc,
		    int               corr_ra_step, //    5;      // correlation step when using rolling average
			boolean           smooth,
			double            fat_zero,
			double            cent_radius,
			int               n_recenter,
			double            rstr,
			String            title,
			double [][][]     corr2d, // null or [(fpixels.length - seq_length - start_frame) / corr_step)[][]
			int               debugLevel){
		int seq_length = corr_offset + corr_pairs;
		int num_scenes = fpixels.length;
		int num_corr_samples = (num_scenes - seq_length - start_frame) / corr_inc;
		int num_tiles = cuasMotion.tilesX*cuasMotion.tilesY;
		double [][][] targets = new double [num_corr_samples][num_tiles][];
		IJ.showStatus("Performing Motion Scan");
  		IJ.showProgress(0.0);
		for (int nscan = 0; nscan < targets.length; nscan++) {
			int frame0 = start_frame + corr_inc * nscan;
			int frame1 = frame0 + corr_offset;
			int frame_cent = frame0 + seq_length/2; // debug only
			String suffix_param = "-"+frame_cent+"-"+corr_offset+"-"+corr_pairs;
			String dbg_suffix = (title != null) ? (title+suffix_param) : null;
			TDCorrTile [] tdCorrTiles = cuasMotion.correlatePairs(
					clt_parameters, // CLTParameters clt_parameters,
					fpixels,        // float [][]    fpixels,
					frame0,         // int           frame0,
					frame1,         // int           frame1,
					corr_pairs,     // int           frame_len,
					corr_ra_step,   // int           corr_ra_step,
					smooth,         // boolean       smooth, // use cosine mask
					true,           // batch_mode,     // final boolean batch_mode,
					dbg_suffix,     // final String  dbg_suffix, // for image_names
					debugLevel);    // int           debugLevel)
			// convert to pixel domain and display 
			double [][] corr_tiles_pd = cuasMotion.convertTDtoPD(
					tdCorrTiles,    // final TDCorrTile [] tiles,
					0xFE,           // final int           corr_type, // 0xFE
					fat_zero,       // final double        gpu_fat_zero,
					debugLevel);    // final int           debug_level

			double [][] vector_field = TDCorrTile.getMismatchVector( // full tiles in gpu (512*512)
					corr_tiles_pd,  // final double[][] tiles,
					rstr,           // double              rmax, 
					cent_radius,    // final double        centroid_radius, // 0 - all same weight, > 0 cosine(PI/2*sqrt(dx^2+dy^2)/rad)
					n_recenter,     // final int           n_recenter); //  re-center window around new maximum. 0 -no refines (single-pass)
					true);            // final boolean     calc_fraction	){ // calculate fraction inside center circle
			for (int ntile = 0; ntile < num_tiles; ntile++) if (vector_field[ntile] != null){
				double [] target =  CuasMotionLMA.getEmpty();
				target[CuasMotionLMA.RSLT_VX] =    vector_field[ntile][INDX_VX];
				target[CuasMotionLMA.RSLT_VY] =    vector_field[ntile][INDX_VY];
				target[CuasMotionLMA.RSLT_VSTR] =  vector_field[ntile][INDX_STRENGTH];
				target[CuasMotionLMA.RSLT_VFRAC] = vector_field[ntile][INDX_FRAC];
				targets[nscan][ntile] = target;
			}
			// for debugging: to see each 2d correlation
			if (corr2d != null) {
				corr2d[nscan] = corr_tiles_pd;
			}
			IJ.showProgress(1.0*(nscan+1) / targets.length);	
		}
		IJ.showProgress(1.0);	
		return targets;
		
		
	}
	
	
	/**
	 * Convert Transform-domain 2D correlation to phase correlation,
	 * inverse-transform it to pixel domain and return result as 
	 * sparse array of double[] tiles mapped in linescan order. Empty
	 * tiles are null. 
	 * @param tiles
	 * @param corr_type
	 * @param gpu_fat_zero
	 * @param debug_level
	 * @return sparse array in line-scan order. Each element either null or double[225]
	 */
	public double [][]  convertTDtoPD(
			final TDCorrTile [] tiles,
			final int           corr_type, // 0xFE
			final double        gpu_fat_zero,
			final int           debug_level
			) {
		return TDCorrTile.convertTDtoPD(
				gpuQuad,       // final GpuQuad       gpuQuad,
				tiles,         // final TDCorrTile [] tiles,
				corr_type,     // 0xFEfinal int           corr_type, // 0xFE
				gpu_fat_zero,  // final double        gpu_fat_zero,
				debug_level);  // final int           debug_level)
	}
	
	
	
	public TDCorrTile [] correlatePairs(
			CLTParameters clt_parameters,
			float [][]    fpixels,
			int           frame0,
			int           frame1,
			int           frame_len,
			int           corr_ra_step,
			boolean       smooth, // use cosine mask
			final boolean batch_mode,
			final String  dbg_suffix, // for image_names
			int           debugLevel) {
		TDCorrTile [] tdCorrTiles = new TDCorrTile[tilesX * tilesY];
		double sw = 0;
		for (int dframe = corr_ra_step/2; dframe < frame_len; dframe+= corr_ra_step) {
			double weight = smooth ? Math.sin((dframe+0.5)/frame_len*Math.PI): 1.0;
			if (((frame0+dframe) >= 0) && ((frame1+dframe) < fpixels.length)) {
				String dbg_n_suffix = (dbg_suffix != null)? (dbg_suffix+"-"+dframe) : null;
				TDCorrTile [] pairTiles = correlatePair(
						clt_parameters,         // CLTParameters clt_parameters,
						fpixels[frame0+dframe], // float []      fpixels_ref,
						fpixels[frame1+dframe], // float []      fpixels_img,
						batch_mode,             // final boolean batch_mode,
						dbg_n_suffix,           // final String  dbg_suffix, // for image_names
						debugLevel);            // int           debugLevel)
				TDCorrTile.accumulate (
						tdCorrTiles,            // final TDCorrTile [] dst,
						pairTiles,              // final TDCorrTile [] src,
						weight);                   // final double src_weight)
				sw+= weight;
			}
		}
		double scale = 1.0/sw;
		for (TDCorrTile tile:tdCorrTiles) if (tile !=null){
			tile.scale(scale);
		}
		return tdCorrTiles;
	}
	
	public void  accumulateMotionScene(
			CLTParameters     clt_parameters,
			final boolean     center,
			float []          fpixels,
			final double [][] target_sequence,
			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(
					target_sequence,       // final double [][] vector_field,
					offset_scale,          // final double      offset_scale,
					magnitude_scale,       // final double      magnitude_scale,
					CuasMotionLMA.RSLT_VX, //  int         index_vx, 
					center ? CuasMotionLMA.RSLT_X: -1,                    // final int         index_xc,
					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 targets [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,
			final boolean     center,
			float []          fkeyframe,   // if null, will assume it is already in the GPU
			final double [][] targets,
			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(
					targets,               // final double [][] vector_field,
					offset_scale,          // final double      offset_scale,
					0.0,                   // final double      magnitude_scale,
					CuasMotionLMA.RSLT_VX, //  int         index_vx, 
					-1,                    // final int         index_xc,
					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,
							false,              // final boolean     center,
							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       center,
			final boolean       zero_fill,
			final float [][]    fpixels,
			final double [][][] target_sequence,
			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 [target_sequence.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 < target_sequence.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( // offsets Vx, Vy 
									clt_parameters,        // CLTParameters     clt_parameters,
									center,                // final boolean     center,
									fpix,                  // float []          fpixels,
									target_sequence[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;
	}
	
	/**
	 * Calculate image x,y for each target half keyframe interval before the keyframe and half-interval after.
	 * The results are stored in yet empty fields of the target data 
	 * It is used to filter out stray targets, these coordinates for the consecutive frames should be close:
	 * "after" for the earlier keyframe and "before" for the later one.
	 * @param targets [keyframe_number][tile][] target positions of the targets relative to the tile center and motion vectors
	 * @param good_only exclude failed tiles
	 * @param interseq_scale multiply velocity vector to get pixel offset in the middle between the two keyframes
	 * @param tilesX number of tiles in a row   
	 * @param debugLevel  debug level
	 */
	public static void getHalfBeforeAfterPixXY(
			final double [][][] targets,
			final boolean       good_only,
			final double        interseq_scale,
			final int           tilesX,
			final int           debugLevel) {
		final int num_seq =   targets.length;
		final int num_tiles = targets[0].length;
		final int tileSize = GPUTileProcessor.DTT_SIZE;
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							double [] target = targets[nSeq][ntile];
							if (target != null) {
								if (!good_only || (target[CuasMotionLMA.RSLT_FAIL] == 0)) {
									int tileX = ntile % tilesX;
									int tileY = ntile / tilesX;
									double xc = tileSize * tileX + tileSize/2;  
									double yc = tileSize * tileY + tileSize/2;
									double xtk = xc + target[CuasMotionLMA.RSLT_X];  
									double ytk = yc + target[CuasMotionLMA.RSLT_Y];
									double dx = target[CuasMotionLMA.RSLT_VX] * interseq_scale;
									double dy = target[CuasMotionLMA.RSLT_VY] * interseq_scale;
									//									double [][] baxy = new double [2][2];
									//baxy[0][0]
									target[CuasMotionLMA.RSLT_BX] = xtk - dx; // before, x
									target[CuasMotionLMA.RSLT_BY] = ytk - dy; // before, y
									target[CuasMotionLMA.RSLT_AX] = xtk + dx; // after, x
									target[CuasMotionLMA.RSLT_AY] = ytk + dy; // after, y
									//									pix_xy[nSeq][ntile] = baxy;
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return;
	}
	
	
	
	
	/**
	 * Generate target images
	 * @param clt_parameters
	 * @param centered         target is rendered in the center of the selected tile
	 * @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 (centered to the tiles if center is true)
	 * @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 boolean       centered,
			final double        mask_width,
			final double        mask_blur,
			final boolean       round,
			final float  [][]   target_keyframes,
			final double [][][] target_sequence,
			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 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 < target_sequence.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_sequence[nseq].length; ntile++) {
				double[] target = target_sequence[nseq][ntile]; 
				if ((target !=null) && (target[CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE)){
					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( // uses targets that contain vx, vy
							clt_parameters, // CLTParameters     clt_parameters,
							// keep/velocity-shift regardless of if they are centered.
							false, // center,      // final boolean     center,
							keyframe,   // float []          fkeyframe,   // if null, will assume it is already in the GPU
							target_sequence[nseq], // final double [][] targets,
							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
					/// TODO: Fix center parameter when copying below !
					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 = target_sequence[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[CuasMotionLMA.RSLT_X];  
									double ytk = yc + target[CuasMotionLMA.RSLT_Y];
									double dx = target[CuasMotionLMA.RSLT_VX] * fdscene * velocity_scale;
									double dy = target[CuasMotionLMA.RSLT_VY] * fdscene * velocity_scale;
									targets_data[nTarget][TARGET_X] =               xtk + dx;
									targets_data[nTarget][TARGET_Y] =               ytk + dy;
									targets_data[nTarget][TARGET_VX] =              target[CuasMotionLMA.RSLT_VX];
									targets_data[nTarget][TARGET_VY] =              target[CuasMotionLMA.RSLT_VY];
									targets_data[nTarget][TARGET_STRENGTH] =        target[CuasMotionLMA.RSLT_A];
									targets_data[nTarget][TARGET_NTILE] =           ntile;
									targets_data[nTarget][TARGET_MISMATCH_BEFORE] = target[CuasMotionLMA.RSLT_MISMATCH_BEFORE];
									targets_data[nTarget][TARGET_MISMATCH_AFTER] =  target[CuasMotionLMA.RSLT_MISMATCH_AFTER];
									targets_data[nTarget][TARGET_SCORE] =           target[CuasMotionLMA.RSLT_QSCORE];
									targets_data[nTarget][TARGET_SEQLEN] =          target[CuasMotionLMA.RSLT_MATCH_LENGTH];
									
									int pxl = (int) Math.round(targets_data[nTarget][TARGET_X]) - offs;
									int pyt = (int) Math.round(targets_data[nTarget][TARGET_Y]) - offs;
									
									int pxl_targ = (int) Math.round(targets_data[nTarget][TARGET_X]) - offs;
									int pyt_targ = (int) Math.round(targets_data[nTarget][TARGET_Y]) - offs;
									if (centered) {
										pxl_targ = (int) Math.round(xc+dx) - offs;
										pyt_targ = (int) Math.round(yc+dy) - offs;
									}
									for (int ym = 0; ym < isize; ym++) {
										int py =    pyt + ym;
										int py_targ = pyt_targ + ym;
										if ((py >= 0) && (py < gpu_max_height)) {
											for (int xm = 0; xm < isize; xm++) {
												int px = pxl + xm;
												int px_targ = pxl_targ + xm;
												if ((px >= 0) && (px < gpu_max_width)) {
													int pix = py * gpu_max_width + px;
													int pix_targ = py_targ * gpu_max_width + px_targ;
													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_targ];
														} else {
															double a = mask[pix_mask];
															fpix_out[nscene][pix] = (float) ((1 - a) * fpix_out[nscene][pix] + a * frame[pix_targ]); 
														}
													}
												}
											}
										}
									}									
								}
							}
						};
					}		      
					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;
	}
	
	public static float [][] runningGaussian(
			final float [][] fpixels,
			final double     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 double sigma = 0.5* ra_length;
		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() {
					double [] temp_line = new double[width*num_scenes];
					DoubleGaussianBlur gb = new DoubleGaussianBlur();
					for (int nLine = ai.getAndIncrement(); nLine < height; nLine = ai.getAndIncrement()) {
						for (int nseq = 0; nseq < num_scenes; nseq++) {
							int src_index = nLine*width;
							int dst_index = nseq*width;
							for (int i = 0; i < width; i++) {
								temp_line[dst_index++] = fpixels[nseq][src_index++];
							}
						}
						gb.blur1Direction(
								temp_line,  // double [] pixels,
								width,      // int        width,
								num_scenes, // int       height,
								sigma,      // double     sigma,
								0.001,      // double   accuracy,
								false);     // boolean xDirection vertical along time line
						
						for (int nseq = 0; nseq < num_scenes; nseq++) {
							int dst_index = nLine*width;
							int src_index = nseq*width;
							for (int i = 0; i < width; i++) {
								out_pix[nseq][dst_index++] = (float)(temp_line[src_index++]);
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return out_pix;
	}

	public static float [][] runningGaussianNaN(
			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 double sigma = 0.5* ra_length;
		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()) {
						System.arraycopy(
								fpixels[nSeq],
								0,
								out_pix[nSeq],
								0,
								num_pixels);
						/*
						for (int npix = 0; npix < num_pixels; npix++) {
							out_pix[nSeq][npix] = fpixels[nSeq][npix];
						}
						*/
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					double [] temp_line = new double[width*num_scenes];
					boolean [] has_nans = new boolean[width];
					DoubleGaussianBlur gb = new DoubleGaussianBlur();
					for (int nLine = ai.getAndIncrement(); nLine < height; nLine = ai.getAndIncrement()) {
						Arrays.fill(has_nans, false);
						boolean has_some_nans = false; 
						for (int nseq = 0; nseq < num_scenes; nseq++) {
							int src_index = nLine*width;
							int dst_index = nseq*width;
							for (int x = 0; x < width; x++) {
								double dpix = out_pix[nseq][src_index++];
								temp_line[dst_index++] = dpix;
								boolean nan = Double.isNaN(dpix);
								has_nans[x] |= nan;
								has_some_nans |= nan;
							}
						}
						if (has_some_nans) {
							for (int x = 0; x <  width; x++) if (has_nans[x]){
								// duplicate first/last, interpolate middle
								if (Double.isNaN(temp_line[x])) {
									int i;
									for (i = 1; Double.isNaN(temp_line[i * width+x]) && (i < num_scenes); i++);
									temp_line[x] = temp_line[i * width+x];
								}
								if (Double.isNaN(temp_line[(num_scenes-1)*width + x])) {
									int i;
									for (i = num_scenes-1; Double.isNaN(temp_line[i*width+x]) && (i >=0); i--);
									temp_line[(num_scenes - 1) * width + x] = temp_line[i*width+x];
								}
								// interpolate remaining gaps
								int i;
								for (i=0; i < num_scenes; i++) {
									if (Double.isNaN(temp_line[i * width + x])) {
										int gap_start = i-1;
										for (i = i+1; Double.isNaN(temp_line[i * width + x]); i++); // last is not NaN, set above
										int gap_end = i;
										double v0 = temp_line[gap_start * width + x];
										double v1 = temp_line[gap_end * width +   x];
										for (int k = 1; k < (gap_end - gap_start); k++) {
											temp_line[(gap_start + k) * width + x] = v0 + (v1-v0) * k; 
										}
									}
								}
							}
						}
						gb.blur1Direction(
								temp_line,  // double [] pixels,
								width,      // int        width,
								num_scenes, // int       height,
								sigma,      // double     sigma,
								0.001,      // double   accuracy,
								false);     // boolean xDirection vertical along time line
						
						for (int nseq = 0; nseq < num_scenes; nseq++) {
							int dst_index = nLine*width;
							int src_index = nseq*width;
							for (int i = 0; i < width; i++) {
								out_pix[nseq][dst_index++] = (float)(temp_line[src_index++]);
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return out_pix;
	}
	
	
	
	
	/**
	 * Version of runningAverage) that tolerates NaN value. It modifies fpixels[][] replacing first/last scene values if they were NaNs 
	 * @param fpixels
	 * @param ra_length
	 * @param width
	 * @return
	 */
	public static float [][] runningAverageNaN(
			final float [][] fpixels,
			final int        ra_length,
			final int        width){
		final int num_scenes = fpixels.length;
		final int num_pixels = fpixels[0].length;
		final int height =  num_pixels/width;
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		final float [][] out_pix = new float [num_scenes][num_pixels];
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_scenes; nSeq = ai.getAndIncrement()) {
						Arrays.fill(out_pix[nSeq],Float.NaN);
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					float [] line_ra = new float[width];
					int [] line_ra_length = new int [width];
					for (int nLine = ai.getAndIncrement(); nLine < height; nLine = ai.getAndIncrement()) {
						Arrays.fill(line_ra, 0.0f);
						Arrays.fill(line_ra_length, 0);
						int pix0 = nLine* width;
						// fix first and last pixels if they are nulls;
						for (int x = 0; x < width; x++) {
							if (Float.isNaN(fpixels[0][pix0+x])) {
								int i;
								for (i = 1; Float.isNaN(fpixels[i][pix0+x]) && (i < num_scenes); i++);
								fpixels[0][pix0+x] = fpixels[i][pix0+x];
							}
							if (Float.isNaN(fpixels[num_scenes-1][pix0+x])) {
								int i;
								for (i = num_scenes-1; Float.isNaN(fpixels[i][pix0+x]) && (i >=0); i--);
								fpixels[num_scenes-1][pix0+x] = fpixels[i][pix0+x];
							}							
						}
						
						int head = 0;
						int tail = 0;
						for (; (head < num_scenes) || (tail < num_scenes);) {
							if (head < num_scenes) {
								float [] fpixels_head = fpixels[head];
								int pix = pix0; 
								for (int x = 0; x < width; x++) {
									float d = fpixels_head[pix++];
									if (!Float.isNaN(d)) {
										line_ra[x] += d;
										line_ra_length[x] ++;
									}
								}
								head++;
							}
							if ((tail < (head - ra_length)) || (tail >= (num_scenes - ra_length))) {
								float [] fpixels_tail = fpixels[tail];
								int pix = pix0; 
								for (int x = 0; x < width; x++) {
									float d = fpixels_tail[pix++];
									if (!Float.isNaN(d)) {
										line_ra[x] -= d;
										line_ra_length[x]--;
									}
								}
								tail++;
							}
							int avg_len = head - tail;
							int avg_pos = (head + tail) / 2;
							if ((avg_pos < num_scenes) && (((avg_len + ra_length) % 2 == 0 ) || (head == 1))) { // update output array
								int pix = pix0; 
								for (int x = 0; x < width; x++) {
									out_pix[avg_pos][pix++] = line_ra[x]/line_ra_length[x]; // avg_len;// should never be 0 as the first and the last are not NaN; 
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return out_pix;
	}
	
	public static float [][] removeOutliers(
			final float [][] fpixels,
			final float [][] fpixels_avg,
			double      threshold){
		final float fthreshold = (float) threshold;
		final int num_scenes = fpixels.length;
		final int num_pixels = fpixels[0].length;
		float [][] fpixels_filtered = new float [num_scenes][];
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_scenes; nSeq = ai.getAndIncrement()) {
						float [] fpix = fpixels[nSeq];
						float [] apix = fpixels_avg[nSeq];
						fpixels_filtered[nSeq]= fpix.clone();
						for (int pix = 0; pix < num_pixels; pix++) {
							if (Math.abs(fpix[pix] - apix[pix]) > fthreshold) {
								fpixels_filtered[nSeq][pix] = Float.NaN;
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		return fpixels;
	}
	
	public static float [][] subtract(
			float [][] fpixels,
			float [][] fpixels_avg){
		final int num_scenes = fpixels.length;
		final int num_pixels = fpixels[0].length;
		float [][] fpixels_diff = new float [num_scenes][num_pixels];
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_scenes; nSeq = ai.getAndIncrement()) {
						float [] fpix = fpixels[nSeq];
						float [] apix = fpixels_avg[nSeq];
						float [] dpix = fpixels_diff[nSeq];
						for (int pix = 0; pix < num_pixels; pix++) {
							dpix[pix] = fpix[pix] - apix[pix];
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		return fpixels_diff;
	}
	
	
	

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

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

	public static ImagePlus convertToRgbAnnotateTargets(
			CLTParameters       clt_parameters,
			final double        input_range, // 5
			final boolean       scale2x,
			final int           target_type, //  = 0; // 0 - unknown, 1 - known, 2 - friend, 3 - foe
			final int           known_type, // 2;  // Target location matching UAS flight log: 0; // 0 - unknown, 1 - known, 2 - friend, 3 - foe
			final double        known_err,  // 20; //  Maximal distance between the detected target and UAS log position (in raw image pixels);
			final float  [][]   fpixels,
			final double [][][] targets60hz,
			final boolean       change_color,
			final boolean       show_true_rng,// show true range (from the flight log)		
			final boolean       show_mismatch,
			final boolean       show_score,
			final boolean       show_uas,
			final int           frame0,
			final int           frame_step,
			final int           width_src,
			final String        title,
			final String []     titles,
			final UasLogReader  uasLogReader,			
			final int           debugLevel) {
		String uas_log_path = null;
		boolean  annotate_uas =  show_uas && (uasLogReader != null);
		if (annotate_uas) {
			String resource_name = ICON_BLUE;
			URL resourceUrl = CuasMotion.class.getClassLoader().getResource("graphics/"+resource_name);
			Path resourcePath = null;
			try {
				resourcePath = Paths.get(resourceUrl.toURI());
			} catch (URISyntaxException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			uas_log_path = resourcePath.toString();
			System.out.println("uas_log_path="+uas_log_path);
		}

		// preparation of the undetected target icon
		String undetected_resource_name = TARGET_ICONS[target_type][scale2x? 1 : 0];
		URL undetected_resourceUrl = CuasMotion.class.getClassLoader().getResource("graphics/"+undetected_resource_name);
		Path undetected_resourcePath = null;
		try {
			undetected_resourcePath = Paths.get(undetected_resourceUrl.toURI());
		} catch (URISyntaxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		String undetected_path = undetected_resourcePath.toString();
		ColorProcessor undetected_cp = null;
		try {
			undetected_cp = new ColorProcessor(ImageIO.read(new File(undetected_path)));
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		final int undetected_width = undetected_cp.getWidth(); 
		final int undetected_height = undetected_cp.getHeight(); 
		
		final int [] undetected_pixels = ((int []) undetected_cp.getPixels());
		final int [][] undetected_rgba = new int [undetected_pixels.length][4]; 
		for (int i = 0; i < undetected_rgba.length; i++) {
			for (int s = 0; s <4; s++) {
				undetected_rgba[i][s] = (undetected_pixels[i] >> (8 * s)) & 0xff;
			}
		}
		
		// preparation of the detected target icon
		String detected_resource_name = TARGET_ICONS[known_type][scale2x? 1 : 0];
		URL detected_resourceUrl = CuasMotion.class.getClassLoader().getResource("graphics/"+detected_resource_name);
		Path detected_resourcePath = null;
		try {
			detected_resourcePath = Paths.get(detected_resourceUrl.toURI());
		} catch (URISyntaxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		String detected_path = detected_resourcePath.toString();
		ColorProcessor detected_cp = null;
		try {
			detected_cp = new ColorProcessor(ImageIO.read(new File(detected_path)));
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		final int detected_width = detected_cp.getWidth(); 
		final int detected_height = detected_cp.getHeight(); 
		
		final int [] detected_pixels = ((int []) detected_cp.getPixels());
		final int [][] detected_rgba = new int [detected_pixels.length][4]; 
		for (int i = 0; i < detected_rgba.length; i++) {
			for (int s = 0; s <4; s++) {
				detected_rgba[i][s] = (detected_pixels[i] >> (8 * s)) & 0xff;
			}
		}

		
		
		final int scale = scale2x? 2 : 1;
		final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final boolean annotate_mono =             clt_parameters.imp.annotate_mono; 
		final boolean annotate_transparent_mono = clt_parameters.imp.annotate_transparent_mono;
		final Color annotate_color_mono =         clt_parameters.imp.annotate_color_mono;
		
		final boolean annotate =   clt_parameters.imp.cuas_annotate;
		final Color   text_color = clt_parameters.imp.cuas_text_color;
		final Color   selected_color = clt_parameters.imp.cuas_selected_color;
		final String  font_name =  clt_parameters.imp.cuas_font_name;
		final int     font_size =  clt_parameters.imp.cuas_font_size;
		final int     font_type =  clt_parameters.imp.cuas_font_type;
		final int     space_before_text = 2;
		
		
		
		ColorProcessor uaslog_cp = null;
		if (uas_log_path != null) {
			try {
				uaslog_cp = new ColorProcessor(ImageIO.read(new File(uas_log_path)));
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}
		final int uaslog_width = (uaslog_cp != null) ? uaslog_cp.getWidth() : 0; 
		final int uaslog_height = (uaslog_cp != null) ? uaslog_cp.getHeight() : 0; 
		final int [] uaslog_pixels = (uaslog_cp != null) ? ((int []) uaslog_cp.getPixels()) : null;
		final int [][] uaslog_rgba = (uaslog_cp != null) ? (new int [uaslog_pixels.length][4]) : null;
		if (uaslog_cp != null) {
			for (int i = 0; i < uaslog_rgba.length; i++) {
				for (int s = 0; s <4; s++) {
					uaslog_rgba[i][s] = (uaslog_pixels[i] >> (8 * s)) & 0xff;
				}
			}
		}
		if (uaslog_pixels != null) {
			System.out.println("uaslog_width="+uaslog_width+",uaslog_height="+uaslog_height);
		}

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

		
		
		final int width = imp.getProcessor().getWidth();
		final int height = imp.getProcessor().getHeight();
//		imp.show();
		final Color fcolor = annotate_color_mono;
		final ImageStack fstack_scenes = imp.getImageStack();
		final int posX=  width - scale * 119; // 521;
		final int posY= height + scale *   1;  // 513;
		final Font font = new Font("Monospaced", Font.PLAIN, scale * 12);
		final Font font_target = new Font(font_name, font_type, scale * font_size);
		final boolean target_text_transparent = true;
		final int nSlices = fstack_scenes.getSize();
		if (annotate_mono) {
			ai.set(0);
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						for (int nSlice = ai.getAndIncrement(); nSlice < nSlices; nSlice = ai.getAndIncrement()) {
							String scene_title = fstack_scenes.getSliceLabel(nSlice+1);
							ImageProcessor ip = fstack_scenes.getProcessor(nSlice+1);
							ip.setColor(fcolor); // Color.BLUE);
							ip.setFont(font);
							if (annotate_transparent_mono) {
								ip.drawString(scene_title, posX, posY); // transparent
							} else {
								ip.drawString(scene_title, posX, posY, Color.BLACK); // transparent
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		}
		ImageStack stack = imp.getStack();
		int [][] ipixels = new int [num_scenes][];
		for (int i = 0; i < num_scenes; i++) {
			ipixels[i] = (int[]) stack.getPixels(i+1);
		}
		// 		final int num_seq = targets60hz.length;
		final int half_step0 = -(frame_step+1)/2;
		final int half_step1 = frame_step + half_step0;
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						int frame_center = frame0 + nSeq * frame_step;
						for (int dscene = half_step0; dscene < half_step1; dscene ++) {
							int nscene = frame_center + dscene;
							if ((nscene >=0) && (nscene < num_scenes)) {
								double [] uas_pXpYDRange = uasLogReader.getUasPxPyDRange(titles[nscene]);
								if (uaslog_rgba != null) { // draw uas from log
									// get drone pixel coordinates
									int xc = (int) Math.round(scale * uas_pXpYDRange[0]);
									int yc = (int) Math.round(scale * uas_pXpYDRange[1]);
									int xl = xc - uaslog_width/2;
									int yt = yc - uaslog_height/2;
									if (nSeq < 0) { // disabled debug, keep 3 to use // 3) {
										System.out.println(String.format("uas_pXpYD=[%f,%f,%f]",uas_pXpYDRange[0],uas_pXpYDRange[1],uas_pXpYDRange[2]));
										System.out.println(String.format("xc=%d, yc=%d, xl=%d, yt=%d",xc,yc,xl,yt));
										System.out.println(String.format("A=%f, T=%f, R=%f",uasLogReader.getCameraATR()[0],uasLogReader.getCameraATR()[1],uasLogReader.getCameraATR()[1]));
										System.out.println(String.format("titles[nscene]=%s, startTimeStamp=%f",titles[nscene],uasLogReader.getStartTimestamp()));
									}
									for (int y = 0; y < uaslog_height; y++) {
										int py = yt + y;
										if ((py >= 0) && (py < height)) {
											for (int x = 0; x< uaslog_width; x++) {
												int px = xl +x;
												int dpix = x + y * uaslog_width;
												int ipix = px + py*width;
												if ((px >=0) && (px < width)) {
													int alpha = uaslog_rgba[dpix][3];
													if (alpha > 0) { // alpha
														int dp = uaslog_pixels[x + y * uaslog_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* uaslog_rgba[dpix][c]);
																if (rgb > 255) rgb = 255;
																new_pix |= (rgb << (8*c));
															}
															ipixels[nscene][ipix] = new_pix;
														}
													}
												}
											}
										}
									}
//								} else {
//									System.out.println("uaslog_rgba == null");
								}
								
								double [][] targets = targets60hz[nscene];
								if (targets != null) { // TODO: find why
									int matching_target = findMatchingTarget(
											targets,    // double [][] targets,
											uas_pXpYDRange,  // double [] uas_pXpYD,
											known_err); // double    max_dist)
									for (int ntarget = 0; ntarget < targets.length; ntarget++) {
//										if ((ntarget == matching_target) && (dscene == half_step0)) {
//											System.out.println("ntarget == matching_target="+ntarget+", nscene="+nscene+", nSeq="+nSeq);
//										}
										int xc = (int) Math.round(scale * targets[ntarget][TARGET_X]);
										int yc = (int) Math.round(scale * targets[ntarget][TARGET_Y]);
										int icon_width =             (ntarget == matching_target) ? detected_width : undetected_width;
										int icon_height =            (ntarget == matching_target) ? detected_height : undetected_height;
										int [][] target_rgba =       (ntarget == matching_target) ? detected_rgba : undetected_rgba;
										final int [] target_pixels = (ntarget == matching_target) ? detected_pixels : undetected_pixels;
										int xl = xc - icon_width/2;
										int yt = yc - icon_height/2;
										for (int y = 0; y < icon_height; y++) {
											int py = yt + y;
											if ((py >= 0) && (py < height)) {
												for (int x = 0; x< icon_width; x++) {
													int px = xl +x;
													int dpix = x + y * icon_width;
													int ipix = px + py*width;
													if ((px >=0) && (px < width)) {
														int alpha = target_rgba[dpix][3];
														if (alpha > 0) { // alpha
															int dp = target_pixels[x + y * icon_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* target_rgba[dpix][c]);
																	if (rgb > 255) rgb = 255;
																	new_pix |= (rgb << (8*c));
																}
																ipixels[nscene][ipix] = new_pix;
															}
														}
													}
												}
											}
										}
										if (annotate) {
											int text_left = xl + icon_width + space_before_text * scale;
											int text_top =  yt + scale * font_size; // text start from the bottom of the first line
											ImageProcessor ip = fstack_scenes.getProcessor(nscene+1);
											String txt = getTargetText(
													clt_parameters,    // CLTParameters       clt_parameters,
													targets[ntarget]); // double [] target);
///											int ntile = (int) targets[ntarget][TARGET_NTILE];
											
											// 	double [][] targets = targets60hz[nscene];
											if (show_true_rng && (ntarget == matching_target)) {
												// uas_pXpYDRange[3]
												txt += String.format("\nTRNG%3.0f",uas_pXpYDRange[3]);
											}

											if (show_mismatch) {
												txt += String.format("\nErr-%3.1f\nErr-%3.1f",
														targets[ntarget][TARGET_MISMATCH_BEFORE],targets[ntarget][TARGET_MISMATCH_AFTER]);
											}
											if (show_score) {
												txt += String.format("\nSEQ-%3.1f\n%5.3f",
														targets[ntarget][TARGET_SEQLEN],targets[ntarget][TARGET_SCORE]);
											}
											boolean sel_target = (ntarget == matching_target) && change_color;
											ip.setColor(sel_target ? selected_color: text_color);
											ip.setFont(font_target);
											if (target_text_transparent) {
												ip.drawString(txt, text_left, text_top); // transparent.
											} else {
												ip.drawString(txt, text_left, text_top, Color.BLACK); // on black background
											}
										}
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
//		imp.show();
		return imp;
	}
	
	
	private static int findMatchingTarget(
			double [][] targets,
			double [] uas_pXpYD,
			double    max_dist) {
		double max_dist2 = max_dist * max_dist;
		int best_index = -1;
		double best_dist2 = max_dist2; // Double.NaN;
		if ((targets == null) || (targets.length == 0)) {
			return best_index;
		}
		for (int ntarg = 0; ntarg < targets.length; ntarg++) {
			double dx = targets[ntarg][TARGET_X] -  uas_pXpYD[0];
			double dy = targets[ntarg][TARGET_Y] -  uas_pXpYD[1];
			double dist2 = dx*dx+dy*dy;
			if (!(dist2 > best_dist2)) {
				best_dist2 = dist2;
				best_index = ntarg;
			}
		}
		return best_index;
		
	}
	
	public static String getTargetText(
			CLTParameters       clt_parameters,
			double [] target) {
		double target_x = target[TARGET_X];
		double target_y = target[TARGET_Y];
		double target_vx = target[TARGET_VX];
		double target_vy = target[TARGET_VY];
		String omega = "\u03A9";
		double fps = 60.0;
		String number_format = "%3.0f";
		String omega_format =  "%3.1f";
		
		double   ifov =  clt_parameters.imp.cuas_ifov; // 0.05;  // degree per pixel
		int      px0 =   clt_parameters.imp.cuas_px0;  // 283;   // pixel with known azimuth 
		int      py0 =   clt_parameters.imp.cuas_py0;  // 386;   // pixel with known elevation
		double   az0 =   clt_parameters.imp.cuas_az0;  // 201.5; // degrees for cuas_px0;
		double   el0 =   clt_parameters.imp.cuas_el0;  // 0.0;   // degrees for cuas_px0;
		double az =  (target_x - px0)*ifov+az0;
		double el =  -(target_y - py0)*ifov+el0;
		double omega_az =  target_vx * ifov * fps;
		double omega_el = -target_vy * ifov * fps;
		while (az < 0) {
			az += 360;
		}
		while (az >= 360) {
			az -= 360;
		}
		while (el < -180) {
			el += 360;
		}
		while (el > 180) {
			el -= 360;
		}
		String txt = "";
		txt += " AZ  "+String.format(number_format,az)+"\n";
		txt += " EL "+ getSignedDouble(el,number_format)+"\n";
		txt += omega+"AZ "+getSignedDouble(omega_az,omega_format)+"\n";
		txt += omega+"EL "+getSignedDouble(omega_el,omega_format);
		return txt;
	}
	public static String getSignedDouble(
			double d,
			String format) {
		String s = (d > 0)? "+" : ( (d <0)? "-":" ");
		return s+ String.format(format, Math.abs(d));
	}
	
	
	public static String saveAsVideo(
			CLTParameters       clt_parameters,
			QuadCLT             scene,     // if not null - use it's model directory
			String              file_path, // if not null, use it regardless of scene
			ImagePlus           imp_color,
			int debugLevel) {
		double  video_fps =          clt_parameters.imp.video_fps;
		int     mode_avi =           clt_parameters.imp.mode_avi;
		int     avi_JPEG_quality =   clt_parameters.imp.avi_JPEG_quality; // 90;
	    boolean run_ffmpeg =         clt_parameters.imp.run_ffmpeg;
		String  video_ext =          clt_parameters.imp.video_ext;
		String  video_codec =        clt_parameters.imp.video_codec.toLowerCase();
		int     video_crf =          clt_parameters.imp.video_crf;
		double  video_bitrate_m =    clt_parameters.imp.video_bitrate_m;		
		boolean remove_avi =         clt_parameters.imp.remove_avi;
		boolean dry_run = false;
		String avi_path = null;
		
		if (file_path != null) {
			try {
				avi_path=QuadCLT.saveAVI(
						file_path, // String      file_path, // forces avi, removes .tif, tiff, and .avi
						dry_run, // !generate_mapped, // boolean     dry_run, 
						mode_avi,         // int         avi_mode,
						avi_JPEG_quality, // int         avi_JPEG_quality,
						video_fps,        // double      fps,
						imp_color); // imp_scenes_pair[nstereo]);      // ImagePlus   imp)
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			}
		} else {
			try {
				avi_path=scene.saveAVIInModelDirectory(
						dry_run,          // !generate_mapped, // boolean     dry_run, 
						null,             // String      suffix, // null - use title from the imp
						mode_avi,         // int         avi_mode,
						avi_JPEG_quality, // int         avi_JPEG_quality,
						video_fps,        // double      fps,
						imp_color);       // imp_scenes_pair[nstereo]);      // ImagePlus   imp)
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			}
		}
		if (avi_path == null) {
			return null;
		}
		if (!run_ffmpeg) {
			return avi_path; // webm not requested
		}
		String webm_path = avi_path.substring(0, avi_path.length()-4)+video_ext;
		String stereo_meta = "";
		
		String shellCommand = String.format("ffmpeg -y -i %s -c %s -b:v %fM -crf %d %s %s",
				avi_path, video_codec, video_bitrate_m, video_crf, stereo_meta, webm_path);
		Process p = null;
		if (!dry_run) {
			int exit_code = -1;
			try {
				p = Runtime.getRuntime().exec(shellCommand);
			} catch (IOException e) {
				System.out.println("Failed shell command: \""+shellCommand+"\"");
			}
			if (p != null) {
				try {
					p.waitFor();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				exit_code = p.exitValue();
			}
			System.out.println("Ran shell command: \""+shellCommand+"\" -> "+exit_code);
			// Check if webm file exists
			if ((exit_code != 0) || !(new File(webm_path)).exists()) {
				System.out.println("Failed to create : \""+webm_path+"\"");
				return avi_path;
			}
		} else {
			System.out.println("Simulated shell command: \""+shellCommand);

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

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

		int seq_length = corr_offset + corr_pairs;
		int corr_inc = half_step ? (corr_offset/2) : corr_offset;
		int num_corr_samples = (fpixels.length - seq_length - start_frame) / corr_inc;
		String [] slice_titles = new String [num_corr_samples];
		for (int nscan = 0; nscan < num_corr_samples; nscan++) {
			int frame_cent = start_frame + corr_inc * nscan + seq_length/2; // debug only
			slice_titles[nscan] =  scene_titles[frame_cent];
		}
		
		
		float [][] fpixels_tum = temporalUnsharpMask( // all good targets
				clt_parameters, // CLTParameters         clt_parameters,
				batch_mode,     // final boolean         batch_mode,
				parentCLT,      // QuadCLT               parentCLT,          //
				fpixels,        // final float [][]      fpixels,
				cuasMotion,     // final CuasMotion      cuasMotion,
				scene_titles,   // String []             scene_titles, // recreate slice_titles from scene titles?
//				slice_titles,   // String []             slice_titles,
				debugLevel);    // final int             debugLevel)

		double [][][] motion_sequence = prepareMotionBasedSequence( // all good targets
				clt_parameters, // CLTParameters         clt_parameters,
				batch_mode,     // final boolean         batch_mode,
				parentCLT,      // QuadCLT               parentCLT,          //
				//fpixels,        // final float [][]      fpixels,
				fpixels_tum,    // float [][]            fpixels_tum,
				cuasMotion,     // final CuasMotion      cuasMotion,
				scene_titles,   // String []             scene_titles, // recreate slice_titles from scene titles?
				slice_titles,   // String []             slice_titles,
				debugLevel);    // final int             debugLevel)
		
		double [][][] targets_good =   locateAndFreezeTargets( // all good targets
				clt_parameters, // CLTParameters         clt_parameters,
				batch_mode,     // final boolean         batch_mode,
				parentCLT,      // QuadCLT               parentCLT,          //
				fpixels,        // final float [][]      fpixels,
				fpixels_tum,    // float [][]            fpixels_tum,
				motion_sequence,// double [][][]         target_sequence,
				cuasMotion,     // final CuasMotion      cuasMotion,
				scene_titles,   // String []             scene_titles, // recreate slice_titles from scene titles?
				slice_titles,   // String []             slice_titles,
				debugLevel);    // final int             debugLevel)
		
		double [][][] targets_nonconflict = resolveTargetsConflicts(
				clt_parameters, // CLTParameters   clt_parameters,
				batch_mode,     // boolean         batch_mode,
				parentCLT,      // QuadCLT         parentCLT,          //
				fpixels,        // float [][]      fpixels,
				cuasMotion,     // CuasMotion      cuasMotion,
				targets_good,   // double [][][]   targets_good, // all targets, including conflicting
				slice_titles,   // String []             slice_titles,
				debugLevel);    // final int             debugLevel)
		
		boolean remove_isolated= clt_parameters.imp.cuas_isolated;		
		if (remove_isolated) {
			removeShortTargetSequences(
					clt_parameters,          // CLTParameters       clt_parameters,
					batch_mode,              // final boolean       batch_mode,
					parentCLT,               // QuadCLT             parentCLT,          //
					targets_nonconflict,     // final double [][][] target_sequence,
					cuasMotion,              // final CuasMotion    cuasMotion,
					slice_titles,            // String []           slice_titles,
					debugLevel);             // final int            debugLevel)
		}

		generateExtractFilterMovingTargets( // move parameters to clt_parameters
				clt_parameters,          // CLTParameters       clt_parameters,
				false, // final boolean       video_pass, // if clt_parameters.cuas_clean_video=true, video_pass=0 - output TIFFS, but no video. If video_pass==1 - only video with no debug
				batch_mode,              // final boolean       batch_mode,
				parentCLT,               // QuadCLT             parentCLT,          //
				// three arrays needed
				fpixels,                 // final float [][]    fpixels,
				targets_nonconflict,     // final double [][][] vf_sequence,  // center tiles (not extended), null /non-null only
				cuasMotion,              // final CuasMotion    cuasMotion,
				uasLogReader,            // UasLogReader        uasLogReader,	
				scene_titles,            // String []           scene_titles, // recreate slice_titles from scene titles?
				slice_titles,            // String []           slice_titles,
				debugLevel);             // final int            debugLevel)
	}	
	
	
	
	
	public static double [][][] locateAndFreezeTargets(
			CLTParameters         clt_parameters,
			final boolean         batch_mode,
			QuadCLT               parentCLT,          //
			float [][]            fpixels,
			float [][]            fpixels_tum,
			double [][][]         target_sequence,
			final CuasMotion      cuasMotion,
			String []             scene_titles, // recreate slice_titles from scene titles?
			String []             slice_titles,
			final int             debugLevel) {
		int     corr_offset =    clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =     clt_parameters.imp.cuas_corr_pairs;
		int     n_recenter =     clt_parameters.imp.cuas_n_recenter;
		double  speed_min =      clt_parameters.imp.cuas_speed_min;
		double  speed_pref =     clt_parameters.imp.cuas_speed_pref;
		double  speed_boost =    clt_parameters.imp.cuas_speed_boost;
		boolean smooth =         clt_parameters.imp.cuas_smooth; // true;
		boolean half_step =      clt_parameters.imp.cuas_half_step; // true;
		int     max_range =      clt_parameters.imp.cuas_max_range;
		int     num_cycles =     clt_parameters.imp.cuas_num_cycles;
	    double  lmax_fraction =  clt_parameters.imp.cuas_lmax_fraction;    // 0.7;     // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
	    double  lmax_flt_neglim= clt_parameters.imp.cuas_lmax_flt_neglim;  // -0.3;   // limit negative data to reduce ridge influence (make -10 to disable)        
	    double  lmax_flt_hsigma= clt_parameters.imp.cuas_lmax_flt_hsigma;  // 1.0 // HPF (~UM) subtract GB with this sigma from the data tile 
	    double  lmax_flt_lsigma= clt_parameters.imp.cuas_lmax_flt_lsigma;  // 1.0 // LPF - GB result of the previous subtraction
	    double  lmax_flt_scale=  clt_parameters.imp.cuas_lmax_flt_scale;   // 5.0 // scale filtering result
	    double  sky_threshold =  clt_parameters.imp.cuas_sky_threshold;     // 0.9;   // minimal value of the sky mask where target is possible
	    
	    double  lmax_hack_ridge= clt_parameters.imp.cuas_lmax_hack_ridge; // 0.45;    // Hack for ridges: remove horizontal streaks, where average(abs()) for the line exceeds fraction of abs max 0.32-good, 0.55 - bad
	    double  lmax_radius =    clt_parameters.imp.cuas_lmax_radius;   // 3.5;     // Look inside ((int)cuas_lmax_radius) * 2 + 1 square for the local maximum isolation
	    double   centered_radius=      clt_parameters.imp.cuas_centered_radius;      // 3.5; // in centered mode (target placed at [8,8] - zero out data outside peripheral areas, keep 0.5 at the radius 	
	    double   centered_blur =       clt_parameters.imp.cuas_centered_blur;        // 0.5; // relative transition radius range (for 0.5 the mask inside (1-0.5) radius will be 1.0, outside (1+0.5) radius will be 0
	    double   duplicate_tolerance = clt_parameters.imp.cuas_duplicate_tolerance ; // 2.0; // (pix) Remove weaker maximums closer than this to a stronger one   
	    
		boolean lmax_zero =      clt_parameters.imp.cuas_lmax_zero;     // true;    // zero all data outside this radius from the maximum
		
		double target_radius =   clt_parameters.imp.cuas_target_radius;
		double target_strength = clt_parameters.imp.cuas_target_strength;
		double [][] target_frac = new double [clt_parameters.imp.cuas_target_frac.length][2];
		boolean no_border=       clt_parameters.imp.cuas_no_border; // true;
	// Moving target LMA	
	    double  lma_sigma =      clt_parameters.imp.cuas_lma_sigma; // =     3.0;
	    double  wnd_pedestal =   clt_parameters.imp.cuas_wnd_pedestal; // =     3.0;
	    double  lma_r0 =         clt_parameters.imp.cuas_lma_r0; // =        3.0;  //maximum with with overshoot
	    double  lma_ovrsht =     clt_parameters.imp.cuas_lma_ovrsht; // =    2.0;
	// CUAS Motion LMA parameters
		boolean lma_fit_xy=      clt_parameters.imp.cuas_lma_fit_xy; // true;
		boolean lma_fit_a=       clt_parameters.imp.cuas_lma_fit_a; // true;
		boolean lma_fit_c=       clt_parameters.imp.cuas_lma_fit_c; // true;
		boolean lma_fit_r=       clt_parameters.imp.cuas_lma_fit_r; // true;
		boolean lma_fit_k=       clt_parameters.imp.cuas_lma_fit_k; // true;
	    double  lambda =         clt_parameters.imp.cuas_lambda; // =        0.1;
	    double  lambda_good =    clt_parameters.imp.cuas_lambda_good; // =   0.5;
	    double  lambda_bad =     clt_parameters.imp.cuas_lambda_bad; // =    8;
	    double  lambda_max =     clt_parameters.imp.cuas_lambda_max; // =  100;
	    double  rms_diff =       clt_parameters.imp.cuas_rms_diff; // =      0.001; // relative RMS improvement
	    int     num_iter =       clt_parameters.imp.cuas_num_iter; // =     20;
	// CUAS Motion LMA filter parameters
	    double  lma_rms =        clt_parameters.imp.cuas_lma_rms; // =       1.5;  // Maximal RMS, regardless of amplitude
	    double  lma_arms =       clt_parameters.imp.cuas_lma_arms; // =      0.06; // Maximal absolute RMS, sufficient for any amplitude
	    double  lma_rrms =       clt_parameters.imp.cuas_lma_rrms; // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
	    double  lma_mina =       clt_parameters.imp.cuas_lma_mina; // =      1.0;  // Minimal A (amplitude)
	    double  lma_pre_mina =   clt_parameters.imp.cuas_lma_pre_mina; // =      0.5;  // scale  cuas_lma_mina to filter initial candidates (if there are > one of them)
	    int     min_keep =       clt_parameters.imp.cuas_min_keep; // 2 // keep at least this number of candidates before using cuas_lma_pre_mina filter
	    
	    double  lma_maxr =       clt_parameters.imp.cuas_lma_maxr; // =      5.0;  // Maximal radius (>3.8)
	    double  lma_minr1 =      clt_parameters.imp.cuas_lma_minr1; // =     1.0;  // Maximal inner radius
	    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
	    double  lma_a2a =        clt_parameters.imp.cuas_lma_a2a; // =       0.7;  // Minimal ratio of the maximal pixel to the amplitude
	    double  max_mismatch =   clt_parameters.imp.cuas_max_mismatch;  // 2;
		double  good_mismatch =  clt_parameters.imp.cuas_good_mismatch; // 0.4;
		double  match_len_pwr = clt_parameters.imp.cuas_match_len_pwr;  // raise matching length to this power for calculating score
		
	    boolean fail_mismatch =  clt_parameters. imp.cuas_fail_mismatch;  // Fail on high mismatch early (when calculating scores);
	    
	    double  target_horizon=  clt_parameters.imp.cuas_horizon;	    
	// Handling overall target scores    
	    
		double min_score_mv =    clt_parameters.imp.cuas_score_mv;
//		double min_score_lma =   clt_parameters.imp.cuas_score_lma;
		double factor_lim =      clt_parameters.imp.cuas_factor_lim;
		double factor_pow =      clt_parameters.imp.cuas_factor_pow;
		double [] score_coeff =  clt_parameters.imp.cuas_score_coeff.clone();
	    
		double  input_range =    clt_parameters.imp.cuas_input_range; // 5;
		int     iter_show =      clt_parameters.imp.cuas_iter_show;       //1; // Maximal enhancement iteration to show intermediate result (0 - none) 
		boolean intermed_low =   clt_parameters.imp.cuas_intermed_low;    //true;
		boolean intermed_high =  clt_parameters.imp.cuas_intermed_high;   //true;
		boolean intermed_giga =  clt_parameters.imp.cuas_intermed_giga;   //false;
	    boolean center_targ =    clt_parameters.imp.cuas_center_targ;
	    
	
		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.");
			}
		}

		boolean test_multi = true;
		
		double filter_below_horizon=5;
		int start_frame = 0;
		int num_scenes = fpixels.length; // .getStack().getSize()- first_corr; // includes average
		int seq_length = corr_offset + corr_pairs;
		int corr_inc = half_step ? (corr_offset/2) : corr_offset;
		int num_corr_samples = (num_scenes - seq_length - start_frame) / corr_inc;
		final int     frame0 = start_frame + seq_length/2;
		final int     half_accum_range = corr_pairs/2;
		int [] filter5_remain = new int [num_corr_samples];
		int [] num_all =        new int [num_corr_samples];
		int [] num_undef =      new int [num_corr_samples];
		int [] num_good =       new int [num_corr_samples];
		int [] num_bad =        new int [num_corr_samples];
		int [] totals =         null; 
		int [] passes = new int [num_corr_samples]; // debugging filter5
		String model_prefix = parentCLT.getImageName()+getParametersSuffix(clt_parameters,null);
		for (int niter=0; niter < num_cycles; niter++) {
			boolean save_filtered_low =  intermed_low &&  (niter < iter_show);
			boolean save_filtered_high = intermed_high && (niter < iter_show);
			boolean save_filtered_giga = intermed_giga && (niter < iter_show);
			totals = getRemain(target_sequence, num_all, num_undef, num_good, num_bad);
//			if (debugLevel > -4) printStats ("Iteration "+niter+ " start", true,num_all, num_undef, num_good, num_bad);
			if (totals[TOTALS_UNDEFINED] == 0) {
				if (debugLevel > -4) System.out.println ("No undefined tiles left, breaking loop");
				break;
			}
			if (save_filtered_low) {
				ImagePlus imp_ms1 = showTargetSequence(
						target_sequence,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-MS1-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_ms1);
			}			

//	double [][] effective_strength = 
			getEffectiveStrengthMV( // calculate tiles effective strength by the motion vectors. Combine with the target LMA?
					target_sequence,   // final double [][][] motion_scan,
					niter,             // int                 niter, // save iteration number on failure if >=
					cuasMotion.tilesX, // final int           tilesX)
					0,                 // final double        min_score_mv,
					speed_min,         // double              speed_min,		
					speed_pref,        // double              speed_pref,		
					speed_boost);      // double              speed_boost);
			
			if (save_filtered_low) {
				ImagePlus imp_mv_strength = showTargetSequence(
						target_sequence,       // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-MV_STRENGTH-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_mv_strength);
			}
			
			boolean[][] filter5 = filter5Targets( // will ignore failed tiles
					target_sequence,    // final double [][][] target_sequence,
					true,               // final boolean       use_motion,     // true - use motion vectors confidence, false - use target confidence
					true,               // final boolean       select_new,     // true - use only untested tiles, false - use good tiles 
					min_score_mv,       // double              min_confidence,
					target_horizon,     // final double        lma_horizon, // target below horizon
					cuasMotion.tilesX,  // final int           tilesX,
					max_range,          // final int           range, // 1 or 2
					filter5_remain,     // final int     []    remain){
					passes,             // final int     []    passes, // debugging - numer of passes required
					debugLevel);        // final int           debugLevel)

			if (save_filtered_low) {
 				ImagePlus imp_filter5 = ShowDoubleFloatArrays.makeArrays(
 						filter5, // double[][] pixels,
 						cuasMotion.tilesX,
						cuasMotion.tilesY, 
						model_prefix+"-FILTER5_SELECTED-n"+niter,   // Selected to test 
						slice_titles);
				if (!batch_mode) {
					imp_filter5.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_filter5);  // ImagePlus   imp)

			}
			
			if (debugLevel > -4) printStatsLine("filter5 remain",true,filter5_remain);
			int filter5_total = getTotal(filter5_remain);
			if (filter5_total == 0) {
				if (debugLevel > -4) System.out.println ("No filter5 tiles left, breaking loop");
				break;
			}
			
			// was motion_scan_filtered
			double [][][] targets_nonoverlap = applyFilter( // motion vectors // will have nulls not top try 
					target_sequence,          // double [][][] motion_scan,
					filter5);                 // boolean [][] filter5)
			
			// Anything remains? we'll see after extension
			double [][][] extended_scan = extendMotionScan(
					targets_nonoverlap, // 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
					null); // remain);              // final int     []    remain)

			if (save_filtered_low) {
				
				// targets_nonoverlap will contain motion vectors used fro the next fpixels_accumulated
				ImagePlus imp_novl = showTargetSequence(
						targets_nonoverlap,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-SELECTED-TO-TRY-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_novl);

				ImagePlus imp_ext = showTargetSequence(
						extended_scan,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-EXTENDED-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_ext);
				// show good and bad accumulated here too?
			}
			
			// perform new accumulations of shifted non-confliocting tiles
			
			float [][] fpixels_accumulated = cuasMotion.shiftAndRenderAccumulate(
					clt_parameters,   // CLTParameters       clt_parameters,
					false,            // final boolean       center,
					false,            // final boolean       fill_zeros,
					fpixels_tum,      // final float [][]    fpixels,
					extended_scan,    // final double [][][] vector_field,
					frame0,           // final int           frame0,      // for vector_field[0]
					corr_inc,         // final int           frame_step,
					half_accum_range, // final int           half_range,
					smooth,           // final boolean       smooth,
					corr_offset,      // final int           corr_offset, // interframe distance for correlation
					true);            // final boolean       batch_mode) {
			if (save_filtered_high) {
				ImagePlus imp_acc = ShowDoubleFloatArrays.makeArrays(
						fpixels_accumulated, // double[][] pixels,
						cuasMotion.gpu_max_width,
						cuasMotion.gpu_max_height, 
						model_prefix+"-ACCUMULATED-n"+niter,   // String title,
						slice_titles);
				imp_acc.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_acc.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_acc);  // ImagePlus   imp)
			}
			
			
			// replace center frames with the accumulated ones
			if (debugLevel > -4) {
				System.out.println("Starting render, iteration = "+niter);
			}

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


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

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

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

				ImagePlus imp_hyper = ShowDoubleFloatArrays.showArraysHyperstack(
						fpixels_hyper,            // float[][][] pixels, 
						cuasMotion.gpu_max_width, // int          width, 
						model_prefix+"-OVERLAP-n"+niter,     // String       title, "time_derivs-rt"+diff_time_rt+"-rxy"+diff_time_rxy,
						scene_titles,             // String []    titles, // all slices*frames titles or just slice titles or null
						hyper_titles_top,         // String []    frame_titles, // frame titles or null
						!batch_mode);                    // boolean      show)
				imp_hyper.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				parentCLT.saveImagePlusInModelDirectory(imp_hyper);  // ImagePlus   imp)
			}
			// Calculate both centroid and LMA target parametes, return both
			// targets_new will contain motion vectors, centroid, and LMA results combined
			//save_filtered_high
			float [][] accum_debug = save_filtered_high? new float [num_corr_samples][]:null; //fpixels_accumulated.length]
			double [][][] targets_new = 	getAccumulatedCoordinates(
					false,                // final boolean       centered,
					targets_nonoverlap,   // final double [][][] vector_fields, // centers
					fpixels_accumulated,  // final double [][]   accum_data,   // should be around 0, no low-freq
					lmax_fraction,        // final double        cuas_lmax_fraction, // 0.7;     // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
					lmax_flt_neglim,      // final double        lmax_flt_neglim,   // -0.3;   // limit negative data to reduce ridge influence (make -10 to disable)
					lmax_flt_hsigma,	  // final double        lmax_flt_hsigma,	 // 1.0 // HPF (~UM) subtract GB with this sigma from the data tile     
					lmax_flt_lsigma,      // final double        lmax_flt_lsigma,   // 1.0 // LPF - GB result of the previous subtraction
					lmax_flt_scale,       // final double        lmax_flt_scale,    // 5.0 // scale filtering result
					cuasMotion.sky_mask,  // final double        sky_mask,
					sky_threshold,    // final double        sky_threshold,    // 0.9 // minimal value of the sky mask where target is possible
					target_horizon+filter_below_horizon,       // final double        lma_horizon,
					lmax_hack_ridge,      // final double        lmax_hack_ridge, // 0.45;    // Hack for ridges: remove horizontal streaks, where average(abs()) for the line exceeds fraction of abs max 0.32-good, 0.55 - bad
					lmax_radius,          // final int           cuas_lmax_radius,   // 3;       // look inside cuas_lmax_radius* 2 + 1 square for the local maximum isolation 
					lmax_zero,            // final boolean       cuas_lmax_zero,     // true;    // zero all data outside this radius from the maximum
					target_radius,        // final double        centroid_radius,
					n_recenter,           // final int           n_recenter, //  re-center window around new maximum. 0 -no refines (single-pass)
					cuasMotion.tilesX,    // final int           tilesX){
					no_border,            // final boolean       no_border,
					// Moving target LMA	
				    lma_sigma,            // final double        lma_sigma,
					wnd_pedestal,         // final double        wnd_pedestal,
				    lma_r0,               // final double        lma_r0,
				    lma_ovrsht,  // final double        lma_ovrsht,
				// CUAS Motion LMA parameters
				    lma_fit_xy,           // final boolean       lma_fit_xy,		
				    lma_fit_a,            // final boolean       lma_fit_a,						
				    lma_fit_c,            // final boolean       lma_fit_c,
				    lma_fit_r,            // final boolean       lma_fit_r,
				    lma_fit_k,            // final boolean       lma_fit_k,		
				    lambda,               // final double        lambda,
				    lambda_good,          // final double        lambda_good,
				    lambda_bad,           // final double        lambda_bad,
				    lambda_max,           // final double        lambda_max,
				    rms_diff,             // final double        rms_diff,
				    num_iter,             // final int           num_iter,
				    accum_debug,          // final float [][]    accum_debug,
				    debugLevel);          // final int           debugLevel);
			if (accum_debug != null) {
				ImagePlus imp_acc = ShowDoubleFloatArrays.makeArrays(
						accum_debug, // double[][] pixels,
						cuasMotion.gpu_max_width,
						cuasMotion.gpu_max_height, 
						model_prefix+"-ACCUMULATED_FILTERED-n"+niter,   // String title,
						slice_titles);
				imp_acc.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_acc.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_acc);  // ImagePlus   imp)
			}
			
			if (save_filtered_low) {
				ImagePlus imp_new = showTargetSequence(
						targets_new,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-NEW-TARGETS-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_new);  // ImagePlus   imp)
			}
			
			if (test_multi) {
				boolean       keep_failed = false; // keep failed targets
				double [][][][] targets_new_multi = 	getAccumulatedCoordinatesMulti(
						keep_failed,         // final boolean       keep_failed, // keep failed targets
						niter, // 	final int           when, // set if >=0 for failures
						false,                // final boolean       centered,
						targets_nonoverlap,   // final double [][][] vector_fields, // centers
						fpixels_accumulated,  // final double [][]   accum_data,   // should be around 0, no low-freq
						lmax_fraction,        // final double        cuas_lmax_fraction, // 0.7;     // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
						lmax_flt_neglim,      // final double        lmax_flt_neglim,   // -0.3;   // limit negative data to reduce ridge influence (make -10 to disable)
						lmax_flt_hsigma,	  // final double        lmax_flt_hsigma,	 // 1.0 // HPF (~UM) subtract GB with this sigma from the data tile     
						lmax_flt_lsigma,      // final double        lmax_flt_lsigma,   // 1.0 // LPF - GB result of the previous subtraction
						lmax_flt_scale,       // final double        lmax_flt_scale,    // 5.0 // scale filtering result
						cuasMotion.sky_mask,  // final double        sky_mask,
						sky_threshold,    // final double        sky_threshold,    // 0.9 // minimal value of the sky mask where target is possible
						target_horizon+filter_below_horizon,       // final double        lma_horizon,
						lmax_hack_ridge,      // final double        lmax_hack_ridge, // 0.45;    // Hack for ridges: remove horizontal streaks, where average(abs()) for the line exceeds fraction of abs max 0.32-good, 0.55 - bad
						lmax_radius,          // final int           cuas_lmax_radius,   // 3;       // look inside cuas_lmax_radius* 2 + 1 square for the local maximum isolation
						centered_radius,      // final double        centered_radius,
						centered_blur,        // final double        centered_radius_blur,
						duplicate_tolerance,  // final double        pix_tolerance, // 
//						lmax_zero,            // final boolean       cuas_lmax_zero,     // true;    // zero all data outside this radius from the maximum
						target_radius,        // final double        centroid_radius,
						n_recenter,           // final int           n_recenter, //  re-center window around new maximum. 0 -no refines (single-pass)
						cuasMotion.tilesX,    // final int           tilesX){
						no_border,            // final boolean       no_border,
						// Moving target LMA	
					    lma_sigma,            // final double        lma_sigma,
						wnd_pedestal,         // final double        wnd_pedestal,
					    lma_r0,               // final double        lma_r0,
					    lma_ovrsht,  // final double        lma_ovrsht,
					// CUAS Motion LMA parameters
					    lma_fit_xy,           // final boolean       lma_fit_xy,		
					    lma_fit_a,            // final boolean       lma_fit_a,						
					    lma_fit_c,            // final boolean       lma_fit_c,
					    lma_fit_r,            // final boolean       lma_fit_r,
					    lma_fit_k,            // final boolean       lma_fit_k,		
					    lambda,               // final double        lambda,
					    lambda_good,          // final double        lambda_good,
					    lambda_bad,           // final double        lambda_bad,
					    lambda_max,           // final double        lambda_max,
					    rms_diff,             // final double        rms_diff,
					    num_iter,             // final int           num_iter,0, // 			final double        pre_min_a,   // pre-filter minimal LMA-A (half finbal?)
					    lma_pre_mina * lma_mina,// final double      pre_min_a,   // 0.5 //scale  cuas_lma_mina to filter initial candidates (if there are > one of them)
					    min_keep,             // final int           min_keep, //  2; // keep at least this number of candidates before using cuas_lma_pre_mina filter
					    accum_debug,          // final float [][]    accum_debug,
					    debugLevel);          // final int           debugLevel);

				if (accum_debug != null) {
					ImagePlus imp_acc = ShowDoubleFloatArrays.makeArrays(
							accum_debug, // double[][] pixels,
							cuasMotion.gpu_max_width,
							cuasMotion.gpu_max_height, 
							model_prefix+"-ACCUMULATED_FILTERED-MULTI-n"+niter,   // String title,
							slice_titles);
					imp_acc.getProcessor().setMinAndMax(-input_range/2, input_range/2);
					if (!batch_mode) {
						imp_acc.show();
					}
					parentCLT.saveImagePlusInModelDirectory(imp_acc);  // ImagePlus   imp)
				}
				
				boolean         good_only=    true;
				boolean         show_empty =  true; // false; // show scenes with no (valid) targets
				if (save_filtered_low) {
					ImagePlus imp_new = showTargetSequence(
							targets_new_multi,    // final double [][][][] targets_multi,
							slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
							model_prefix+"-NEW-TARGETS-MULTI-n"+niter,// String        title,
							good_only,            // final boolean         good_only,
							show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
							!batch_mode,          // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
					parentCLT.saveImagePlusInModelDirectory(imp_new);  // ImagePlus   imp)
				}
			}

			double [][][] targets_new_centered = null;
			if (center_targ) {
				double [][][] extended_scan_centered = extendMotionScan(
						targets_new,          // 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
						null); // remain);              // final int     []    remain)

				if (save_filtered_low) {
					
					// targets_nonoverlap will contain motion vectors used fro the next fpixels_accumulated
					ImagePlus imp_novl = showTargetSequence(
							targets_new,          // double [][][] vector_fields_sequence,
							slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
							model_prefix+"-SELECTED-TO-TRY-CENTERED-n"+niter,// String        title,
							!batch_mode,          // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
					parentCLT.saveImagePlusInModelDirectory(imp_novl);

					ImagePlus imp_ext = showTargetSequence(
							extended_scan_centered,          // double [][][] vector_fields_sequence,
							slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
							model_prefix+"-EXTENDED-CENTERED-n"+niter,// String        title,
							!batch_mode,          // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
					parentCLT.saveImagePlusInModelDirectory(imp_ext);
					// show good and bad accumulated here too?
				}
				
				// Testing centered			
				float [][] fpixels_accumulated_centered = cuasMotion.shiftAndRenderAccumulate(
						clt_parameters,         // CLTParameters       clt_parameters,
						true,                   // final boolean       center,
						false,                  // final boolean       fill_zeros,
						fpixels_tum,            // final float [][]    fpixels,
						extended_scan_centered, // final double [][][] vector_field,
						frame0,                 // final int           frame0,      // for vector_field[0]
						corr_inc,               // final int           frame_step,
						half_accum_range,       // final int           half_range,
						smooth,                 // final boolean       smooth,
						corr_offset,            // final int           corr_offset, // interframe distance for correlation
						true);                  // final boolean       batch_mode) {
				if (save_filtered_high) {
					ImagePlus imp_acc = ShowDoubleFloatArrays.makeArrays(
							fpixels_accumulated_centered, // double[][] pixels,
							cuasMotion.gpu_max_width,
							cuasMotion.gpu_max_height, 
							model_prefix+"-ACCUMULATED_CENTERED-n"+niter,   // String title,
							slice_titles);
					imp_acc.getProcessor().setMinAndMax(-input_range/2, input_range/2);
					if (!batch_mode) {
						imp_acc.show();
					}
					parentCLT.saveImagePlusInModelDirectory(imp_acc);  // ImagePlus   imp)
				}
				
				targets_new_centered = 	getAccumulatedCoordinates(
						true,                 // final boolean       centered,
						targets_new,          // final double [][][] vector_fields, // centers
						fpixels_accumulated_centered,  // final double [][]   accum_data,   // should be around 0, no low-freq
						lmax_fraction,        // final double        cuas_lmax_fraction, // 0.7;     // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
						lmax_flt_neglim,      // final double        lmax_flt_neglim,   // -0.3;   // limit negative data to reduce ridge influence (make -10 to disable)
						lmax_flt_hsigma,	  // final double        lmax_flt_hsigma,	 // 1.0 // HPF (~UM) subtract GB with this sigma from the data tile     
						lmax_flt_lsigma,      // final double        lmax_flt_lsigma,   // 1.0 // LPF - GB result of the previous subtraction
						lmax_flt_scale,       // final double        lmax_flt_scale,    // 5.0 // scale filtering result
						cuasMotion.sky_mask,  // final double        sky_mask,
						sky_threshold,    // final double        sky_threshold,    // 0.9 // minimal value of the sky mask where target is possible
						target_horizon+filter_below_horizon,          // final double        lma_horizon,
						lmax_hack_ridge,      // final double        lmax_hack_ridge, // 0.45;    // Hack for ridges: remove horizontal streaks, where average(abs()) for the line exceeds fraction of abs max 0.32-good, 0.55 - bad
						lmax_radius,          // final int           cuas_lmax_radius,   // 3;       // look inside cuas_lmax_radius* 2 + 1 square for the local maximum isolation 
						lmax_zero,            // final boolean       cuas_lmax_zero,     // true;    // zero all data outside this radius from the maximum
						target_radius,        // final double        centroid_radius,
						n_recenter,           // final int           n_recenter, //  re-center window around new maximum. 0 -no refines (single-pass)
						cuasMotion.tilesX,    // final int           tilesX){
						no_border,            // final boolean       no_border,
						// Moving target LMA	
					    lma_sigma,            // final double        lma_sigma,
						wnd_pedestal,         // final double        wnd_pedestal,
					    lma_r0,               // final double        lma_r0,
					    lma_ovrsht,  // final double        lma_ovrsht,
					// CUAS Motion LMA parameters
					    lma_fit_xy,           // final boolean       lma_fit_xy,		
					    lma_fit_a,            // final boolean       lma_fit_a,						
					    lma_fit_c,            // final boolean       lma_fit_c,
					    lma_fit_r,            // final boolean       lma_fit_r,
					    lma_fit_k,            // final boolean       lma_fit_k,		
					    lambda,               // final double        lambda,
					    lambda_good,          // final double        lambda_good,
					    lambda_bad,           // final double        lambda_bad,
					    lambda_max,           // final double        lambda_max,
					    rms_diff,             // final double        rms_diff,
					    num_iter,             // final int           num_iter,
					    accum_debug,          // final float [][]    accum_debug,
					    debugLevel);          // final int           debugLevel);
				if (accum_debug != null) {
					ImagePlus imp_acc = ShowDoubleFloatArrays.makeArrays(
							accum_debug, // double[][] pixels,
							cuasMotion.gpu_max_width,
							cuasMotion.gpu_max_height, 
							model_prefix+"-ACCUMULATED_FILTERED_CENTERED-n"+niter,   // String title,
							slice_titles);
					imp_acc.getProcessor().setMinAndMax(-input_range/2, input_range/2);
					if (!batch_mode) {
						imp_acc.show();
					}
					parentCLT.saveImagePlusInModelDirectory(imp_acc);  // ImagePlus   imp)
				}

				
				if (save_filtered_low) {
					ImagePlus imp_new = showTargetSequence(
							targets_new_centered,          // double [][][] vector_fields_sequence,
							slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
							model_prefix+"-NEW-TARGETS_CENTERED-n"+niter,// String        title,
							!batch_mode,          // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
					parentCLT.saveImagePlusInModelDirectory(imp_new);  // ImagePlus   imp)
				}

				getEffectiveStrengthLMA(
						targets_new_centered,       // final double [][][] target_coords, // LMA
						target_strength,   // final double        target_strength,
						target_frac,       // final double [][]   target_frac, // pairs - strength, minimal fraction for that strength
						lma_rms,           // final double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
						lma_arms,          // final double        lma_arms, // =      0.06; // Maximal absolute RMS (should match one of cuas_lma_arms OR cuas_lma_rrms (0.484)
						lma_rrms,          // final double        lma_rrms, // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
						lma_mina,          // final double        lma_mina, // =      1.0;  // Minimal A (amplitude)
						lma_maxr,          // final double        lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
						lma_minr1,         // final double        lma_minr1,// =      1.0;  // Minimal R1 (radius of positive peak) 
						lma_mink,          // final double        lma_mink, // =      0.0;  // Minimal K (overshoot) = 1.0
						lma_maxk,          // final double        lma_maxk, // =      5.0;  // Minimal K (overshoot) = 3.0//			final double        lma_a2a, 
						lma_a2a,           // final double        lma_a2a, 
						0, // max_mismatch,      // final double        max_mismatch, apply only during final, when mismatch scores are calculated 
						good_mismatch,     // final double        good_mismatch, //do not add to score if worse
						fail_mismatch,     // final boolean       fail_mismatch,
						target_horizon,    // final double        lma_horizon, // horizon as maximal pixel Y
						cuasMotion.tilesX);            // final int         tilesX,
				int [] remain_es_centered = getScore(
						targets_new_centered, // final double [][][] target_sequence, // modifies certain fields (scores)
						factor_lim,        // final double        importance_limit,
						factor_pow,        // final double        importance_power, // Raise each factor to this power before combining
						score_coeff,       // final double []     importance, // for now (each - squared?): [0] - Amplitude (A/A0), 1 - RMS (RMS0/RMS), 2 - RRMS((RMS/A0) / (RMS/A)
						cuasMotion.tilesX);            // final int         tilesX,
				
				if (save_filtered_low) {
					ImagePlus imp_new_scores = showTargetSequence(
							targets_new_centered,          // double [][][] vector_fields_sequence,
							slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
							model_prefix+"-NEW-TARGETS_SCORES-n"+niter,// String        title,
							!batch_mode,          // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
					parentCLT.saveImagePlusInModelDirectory(imp_new_scores);  // ImagePlus   imp)
				}
				// overwrite
//				targets_new = targets_new_centered; 
				// TODO: Here combine new_centered with new - which is better?
				// Maybe use tougher limits for centered
			}
			
			
			
			getEffectiveStrengthLMA(
					targets_new,       // final double [][][] target_coords, // LMA
					target_strength,   // final double        target_strength,
					target_frac,       // final double [][]   target_frac, // pairs - strength, minimal fraction for that strength
					lma_rms,           // final double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
					lma_arms,          // final double        lma_arms, // =      0.06; // Maximal absolute RMS (should match one of cuas_lma_arms OR cuas_lma_rrms (0.484)
					lma_rrms,          // final double        lma_rrms, // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
					lma_mina,          // final double        lma_mina, // =      1.0;  // Minimal A (amplitude)
					lma_maxr,          // final double        lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
					lma_minr1,         // final double        lma_minr1,// =      1.0;  // Minimal R1 (radius of positive peak) 
					lma_mink,          // final double        lma_mink, // =      0.0;  // Minimal K (overshoot) = 1.0
					lma_maxk,          // final double        lma_maxk, // =      5.0;  // Minimal K (overshoot) = 3.0//			final double        lma_a2a, 
					lma_a2a,           // final double        lma_a2a, 
					max_mismatch,      // final double        max_mismatch, 
					good_mismatch,     // final double        good_mismatch, //do not add to score if worse
					fail_mismatch,     // final boolean       fail_mismatch,
					target_horizon,    // final double        lma_horizon, // horizon as maximal pixel Y
					cuasMotion.tilesX);            // final int         tilesX,
			int [] remain_es = getScore(
					targets_new, // final double [][][] target_sequence, // modifies certain fields (scores)
					factor_lim,        // final double        importance_limit,
					factor_pow,        // final double        importance_power, // Raise each factor to this power before combining
					score_coeff,       // final double []     importance, // for now (each - squared?): [0] - Amplitude (A/A0), 1 - RMS (RMS0/RMS), 2 - RRMS((RMS/A0) / (RMS/A)
					cuasMotion.tilesX);            // final int         tilesX,
			
			if (save_filtered_low) {
				ImagePlus imp_new_scores = showTargetSequence(
						targets_new,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-NEW-SCORES-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_new_scores);  // ImagePlus   imp)
			}
			
			/// re-render centered
//			boolean test_centered = true;

			// no filtering by scores here, it will be done only after the iteration when mismatch will be added
					
			int num_new = addNewResults(
					target_sequence, // final double [][][] target_sequence, // will only process non-nulls here
					targets_new,     // final double [][][] new_sequence,
					niter,           // final int when_iter,
					false);          // final boolean overwrite)
			if (debugLevel > -4) {
				System.out.println("Defined "+num_new+" tiles as good/bad");
			}
			if (save_filtered_low) {
				ImagePlus imp_added = showTargetSequence(
						target_sequence,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-ADDED_NEW-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_added);  // ImagePlus   imp)
			}
			
			if (targets_new_centered != null) {
				int num_new_centered = addNewResults(
						target_sequence,       // final double [][][] target_sequence, // will only process non-nulls here
						targets_new_centered,  // final double [][][] new_sequence,
						niter,                 // final int when_iter,
						true);                 // final boolean overwrite)
				if (debugLevel > -4) {
					System.out.println("Re-defined "+num_new_centered+" tiles from the centered targets");
				}
				num_new += num_new_centered;
				if (save_filtered_low) {
					ImagePlus imp_added_centered = showTargetSequence(
							target_sequence,          // double [][][] vector_fields_sequence,
							slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
							model_prefix+"-ADDED_NEW_CENTERED-n"+niter,// String        title,
							!batch_mode,          // boolean       show,
							cuasMotion.tilesX);   //  int           tilesX) {
					parentCLT.saveImagePlusInModelDirectory(imp_added_centered);  // ImagePlus   imp)
				}
			}
			if (num_new == 0) {
				if (debugLevel > -4) System.out.println ("Nothing added from last try, breaking the loop.\n");
				break;  // breaks here
			}
			totals = getRemain(target_sequence, num_all, num_undef, num_good, num_bad);
			if (debugLevel > -4) printStats ("After iteration "+niter, true, num_all, num_undef, num_good, num_bad);

			if (debugLevel > -4) {
				System.out.println("Iteration "+niter+" DONE.\n");
			}
		} //for (niter=0; niter < max_iter; niter++)
		
		if (intermed_low) { // targets_good now has
			ImagePlus imp_good = showTargetSequence(
					target_sequence,         // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-COMBINED",// String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_good);  // ImagePlus   imp)
		}
		return target_sequence; // contains all tiles, good or bad, their data and when they were defined
	}

	
	final static public double [][][] resolveTargetsConflicts(
			CLTParameters   clt_parameters,
			boolean         batch_mode,
			QuadCLT         parentCLT,          //
			float [][]      fpixels,
			CuasMotion      cuasMotion,
			double [][][]   target_sequence, // targets_good, // all targets, including conflicting
			String []       slice_titles,
			int             debugLevel){
		int     corr_offset =    clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =     clt_parameters.imp.cuas_corr_pairs;
		boolean half_step =      clt_parameters.imp.cuas_half_step; // true;
		int     max_range =      clt_parameters.imp.cuas_max_range;

		double target_strength = clt_parameters.imp.cuas_target_strength;
		double [][] target_frac = new double [clt_parameters.imp.cuas_target_frac.length][2];
	// Moving target LMA	
	// CUAS Motion LMA parameters
	// 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_minr1 =      clt_parameters.imp.cuas_lma_minr1; // =     1.0;  // Maximal inner radius
	    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
	    double  lma_a2a =        clt_parameters.imp.cuas_lma_a2a; // =       0.7;  // Minimal ratio of the maximal pixel to the amplitude
	    double  max_mismatch =   clt_parameters.imp.cuas_max_mismatch;  // 2;
		double  good_mismatch =  clt_parameters.imp.cuas_good_mismatch;  // 0.4;
//		double  match_len_pwr =  clt_parameters.imp.cuas_match_len_pwr;  // raise matching length to this power for calculating score
	    boolean fail_mismatch =  clt_parameters.imp.cuas_fail_mismatch;  // Fail on high mismatch early (when calculating scores);
	    double  target_horizon=  clt_parameters.imp.cuas_horizon;	    
	// Handling overall target scores    
	    
		double min_score_lma =   clt_parameters.imp.cuas_score_lma;
		double factor_lim =      clt_parameters.imp.cuas_factor_lim;
		double factor_pow =      clt_parameters.imp.cuas_factor_pow;
		double [] score_coeff =  clt_parameters.imp.cuas_score_coeff.clone();
	    
//	    boolean remove_isolated= clt_parameters.imp.cuas_isolated;
		boolean intermed_low =   clt_parameters.imp.cuas_intermed_low;    //true;
//	    boolean center_targ =    clt_parameters.imp.cuas_center_targ;
		boolean debug_more =     clt_parameters.imp.cuas_debug_more;
		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 model_prefix = parentCLT.getImageName()+getParametersSuffix(clt_parameters,null);
		int start_frame = 0;
		int num_scenes = fpixels.length; // .getStack().getSize()- first_corr; // includes average
		int seq_length = corr_offset + corr_pairs;
		int corr_inc = half_step ? (corr_offset/2) : corr_offset;
		int num_corr_samples = (num_scenes - seq_length - start_frame) / corr_inc;
		int [] filter5_remain = new int [num_corr_samples];
		int [] num_all =        new int [num_corr_samples];
		int [] num_undef =      new int [num_corr_samples];
		int [] num_good =       new int [num_corr_samples];
		int [] num_bad =        new int [num_corr_samples];
		int [] passes = new int [num_corr_samples]; // debugging filter5
		// there could be many conflicting tiles in targets_good. Calculate scores and then filter to keep the best ones that do not conflict
		// take into account before/after matching
		double interseq_scale =  0.5* corr_inc/corr_offset;  // multiply target velocity to get offset in the middle between the key frames
		// 		interseq_scale = (half_step ? 0.25 : 0.5) // ???
		
		if (debugLevel > -4) System.out.println("\nStarting resolution of conflicting tiles.");

		
		if (intermed_low) {
			ImagePlus imp_in = showTargetSequence(
					target_sequence,        // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-BEFORE_RESOLVE",// String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_in);
		}
		// Calculate image x,y for each target half keyframe interval before the keyframe and half-interval after.
		getHalfBeforeAfterPixXY(
				target_sequence,     // final double [][][] targets,
				true,                // final boolean       good_only,
				interseq_scale,      // , // final double        interseq_scale,
				cuasMotion.tilesX,   // final int           tilesX,
				debugLevel);         //	final int           debugLevel) {

		calculateMismatchBeforeAfter(
				target_sequence,     // final double [][][] targets,
				true,                // final boolean       good_only,
				cuasMotion.tilesX);  // final int           tilesX)
		if (intermed_low && debug_more) {
			ImagePlus imp_failures = showTargetSequence(
					target_sequence,        // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-MATCH_BA",// String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_failures);
		}
		
		getEffectiveStrengthLMA(
				target_sequence,      // final double [][][] target_coords, // LMA
				target_strength,   // final double        target_strength,
				target_frac,       // final double [][]   target_frac, // pairs - strength, minimal fraction for that strength
				lma_rms,           // final double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
				lma_arms,          // final double        lma_arms, // =      0.06; // Maximal absolute RMS (should match one of cuas_lma_arms OR cuas_lma_rrms (0.484)
				lma_rrms,          // final double        lma_rrms, // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
				lma_mina,          // final double        lma_mina, // =      1.0;  // Minimal A (amplitude)
				lma_maxr,          // final double        lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
				lma_minr1,         // final double        lma_minr1,// =      1.0;  // Minimal R1 (radius of positive peak) 
				lma_mink,          // final double        lma_mink, // =      0.0;  // Minimal K (overshoot) = 1.0
				lma_maxk,          // final double        lma_maxk, // =      5.0;  // Minimal K (overshoot) = 3.0
				lma_a2a,           // final double        lma_a2a,
				max_mismatch,      // final double        max_mismatch,
				good_mismatch,     // final double        good_mismatch, //do not add to score if worse
				fail_mismatch,     // final boolean       fail_mismatch,
				target_horizon,    // final double        lma_horizon, // target below horizon
				cuasMotion.tilesX); // final int           tilesX,
		
		int [] remain_es = getScore( // calculates composite sceres
				target_sequence, // final double [][][] target_sequence, // modifies certain fields (scores)
				factor_lim,        // final double        importance_limit,
				factor_pow,        // final double        importance_power, // Raise each factor to this power before combining
				score_coeff,       // final double []     importance, // for now (each - squared?): [0] - Amplitude (A/A0), 1 - RMS (RMS0/RMS), 2 - RRMS((RMS/A0) / (RMS/A)
				cuasMotion.tilesX);            // final int         tilesX,

		
		getRemain(target_sequence, num_all, num_undef, num_good, num_bad);
		if (debugLevel > -4) printStats ("Resolving, after matching before/after", true,num_all, num_undef, num_good, num_bad);
		if (intermed_low && debug_more) {
			ImagePlus imp_failures = showTargetSequence(
					target_sequence,        // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-SCORES_BA",// String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_failures);
		}
		
		// recalculate B/A and scores before selecting non-coflicting
		{
			// todo: Add stats good
			getRemain(target_sequence, num_all, num_undef, num_good, num_bad);
			if (debugLevel > -4) printStats ("Before re-mismatch", true, null, null, num_good, null);

			calculateMismatchBeforeAfter(
					target_sequence,     // final double [][][] targets,
					true,                // final boolean       good_only,
					cuasMotion.tilesX);  // final int           tilesX)
			if (intermed_low && debug_more) {
				ImagePlus imp_failures = showTargetSequence(
						target_sequence,        // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-MATCH_BA2",// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_failures);
			}
			getEffectiveStrengthLMA(
					target_sequence,      // final double [][][] target_coords, // LMA
					target_strength,   // final double        target_strength,
					target_frac,       // final double [][]   target_frac, // pairs - strength, minimal fraction for that strength
					lma_rms,           // final double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
					lma_arms,          // final double        lma_arms, // =      0.06; // Maximal absolute RMS (should match one of cuas_lma_arms OR cuas_lma_rrms (0.484)
					lma_rrms,          // final double        lma_rrms, // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
					lma_mina,          // final double        lma_mina, // =      1.0;  // Minimal A (amplitude)
					lma_maxr,          // final double        lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
					lma_minr1,         // final double        lma_minr1,// =      1.0;  // Minimal R1 (radius of positive peak) 
					lma_mink,          // final double        lma_mink, // =      0.0;  // Minimal K (overshoot) = 1.0
					lma_maxk,          // final double        lma_maxk, // =      5.0;  // Minimal K (overshoot) = 3.0
					lma_a2a,           // final double        lma_a2a,
					max_mismatch,      // final double        max_mismatch,
					good_mismatch,     // final double        good_mismatch, //do not add to score if worse
					fail_mismatch,     // final boolean       fail_mismatch,
					target_horizon,    // final double        lma_horizon, // target below horizon
					cuasMotion.tilesX); // final int           tilesX,

			int [] remain_es2 = getScore( // calculates composite sceres
					target_sequence, // final double [][][] target_sequence, // modifies certain fields (scores)
					factor_lim,        // final double        importance_limit,
					factor_pow,        // final double        importance_power, // Raise each factor to this power before combining
					score_coeff,       // final double []     importance, // for now (each - squared?): [0] - Amplitude (A/A0), 1 - RMS (RMS0/RMS), 2 - RRMS((RMS/A0) / (RMS/A)
					cuasMotion.tilesX);            // final int         tilesX,
			if (intermed_low && debug_more) {
				ImagePlus imp_failures = showTargetSequence(
						target_sequence,        // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-MATCH_BA3",// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_failures);
			}
			getRemain(target_sequence, num_all, num_undef, num_good, num_bad);
			if (debugLevel > -4) printStats ("After re-mismatch", true, null, null, num_good, null);
		}
		boolean[][] filter5 = filter5Targets(
				target_sequence,    // final double [][][] target_sequence,
				false,              // final boolean       use_motion,     // true - use motion vectors confidence, false - use target confidence
				false,              // final boolean       select_new,     // true - use only untested tiles, false - use good tiles 
				min_score_lma,      // min_mv_strength,    // double              min_confidence,
				target_horizon,     // final double        lma_horizon, // target below horizon
				cuasMotion.tilesX,  // final int           tilesX,
				max_range,          // final int           range, // 1 or 2
				filter5_remain,     // final int     []    remain){
				passes,             // final int     []    passes, // debugging - number of passes required
				debugLevel);        // final int           debugLevel)
		if (debugLevel > -4) printStatsLine("After filter5 remaining non-conflicting targets", true,filter5_remain);

		if (intermed_low) {
			ImagePlus imp_filter5_lma = ShowDoubleFloatArrays.makeArrays(
					filter5, // double[][] pixels,
					cuasMotion.tilesX,
					cuasMotion.tilesY, 
					model_prefix+"-FILTER5_FINAL",   // String title,
					slice_titles);
			if (!batch_mode) {
				imp_filter5_lma.show();
			}
			parentCLT.saveImagePlusInModelDirectory(imp_filter5_lma);
		}
		// apply filter5 to targets_good
		findStrongerNeighbor(// does not modify "when"
				target_sequence,      // final double [][][] target_sequence,
				filter5,              // final boolean [][] filter5)
				false,                // final boolean       mark_failed,
				cuasMotion.tilesX);   // final int           tilesX) {				
		// now failed by neighbors are marked as non-NaN (tile index or -1), not as failed

		if (intermed_low) { // save final 
			ImagePlus imp_good = showTargetSequence(
					target_sequence,         // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-GOOD-NONOVERLAP",// String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_good);  // ImagePlus   imp)
		}
		// remove singles here - no, just calculate 
		// Is it needed here?
		
		calculateMismatchBeforeAfter(
				target_sequence,        // final double [][][] targets,
				true, // false,                  // final boolean       good_only, ???? why it was false
				cuasMotion.tilesX);     // final int           tilesX)
		if (intermed_low) {
			ImagePlus imp_1 = showTargetSequence(
					target_sequence,        // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-MATCH",// String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_1);
		}
		return target_sequence; // non-overlap if consider marked stronger tiles
	}
	
	
	public static void removeShortTargetSequences(
			CLTParameters       clt_parameters,
			final boolean       batch_mode,
			QuadCLT             parentCLT,
			double [][][]       target_sequence,  // non-overlap only if consider marked stronger tiles
			CuasMotion          cuasMotion,
			String []           slice_titles,
			final int           debugLevel) {
		String model_prefix = parentCLT.getImageName()+getParametersSuffix(clt_parameters,null);


		final int     corr_offset =       clt_parameters.imp.cuas_corr_offset;
		final boolean half_step =         clt_parameters.imp.cuas_half_step; // true;
		final int corr_inc =             half_step ? (corr_offset/2) : corr_offset;
	    int min_seq=             clt_parameters.imp.cuas_min_seq;    // 3;     // minimal number of consecutive key frames for the same target
	    int min_seq_fl=          clt_parameters.imp.cuas_min_seq_fl; // 2;     // minimal number of consecutive key frames for the same target that includes first or last key frame
		double  max_mismatch =   clt_parameters.imp.cuas_max_mismatch;  // 2;
		double  good_mismatch =  clt_parameters.imp.cuas_good_mismatch;  // 0.4;
		boolean intermed_low =   clt_parameters.imp.cuas_intermed_low;    //true;
	    // for getEffectiveStrengthLMA(), may be moved out of this method to resolveTargetsConflicts()
		double target_strength = clt_parameters.imp.cuas_target_strength;
		double [][] target_frac = new double [clt_parameters.imp.cuas_target_frac.length][2];
	    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_minr1 =      clt_parameters.imp.cuas_lma_minr1; // =     1.0;  // Maximal inner radius
	    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
	    double  lma_a2a =        clt_parameters.imp.cuas_lma_a2a; // =       0.7;  // Minimal ratio of the maximal pixel to the amplitude
	    double  target_horizon=  clt_parameters.imp.cuas_horizon;	    
	    // end of for getEffectiveStrengthLMA(), may be moved out of this method to resolveTargetsConflicts()
	    // for getScore(), may be moved out of this method to resolveTargetsConflicts()
		double min_score_lma =   clt_parameters.imp.cuas_score_lma;
		double factor_lim =      clt_parameters.imp.cuas_factor_lim;
		double factor_pow =      clt_parameters.imp.cuas_factor_pow;
		double [] score_coeff =  clt_parameters.imp.cuas_score_coeff.clone();
		// Assuming non-conflicting tiles in the target_sequence[][][]
		double interseq_scale =  0.5* corr_inc/corr_offset;  // multiply target velocity to get offset in the middle between the key frames
		final int num_corr_samples = target_sequence.length;
		int [] filter5_remain = new int [num_corr_samples];		
		int [] num_all =        new int [num_corr_samples];
		int [] num_undef =      new int [num_corr_samples];
		int [] num_good =       new int [num_corr_samples];
		int [] num_bad =        new int [num_corr_samples];
		// Should be already calculated
		getHalfBeforeAfterPixXY( // fills fields in the target_sequence tiles
				target_sequence, // final double [][][] targets,
				true, // final boolean       good_only,
				interseq_scale,    // , // final double        interseq_scale,
				cuasMotion.tilesX, // final int           tilesX,
				debugLevel);       //	final int           debugLevel) {
		// should always be calculated, even if not removed - used for annotations
		calculateMismatchBeforeAfter( // fills fields in the target_sequence tiles
				target_sequence,    // final double [][][] targets,
				true,               // final boolean       good_only,
				cuasMotion.tilesX); // final int           tilesX)	
		
		if (intermed_low)  {
			ImagePlus imp_no_singles_lma= showTargetSequence(
					target_sequence,    // double [][][] target_scene_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-WITH_SINGLES",   // String        title,
					!batch_mode,    // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_no_singles_lma);  // ImagePlus   imp)
			if (debugLevel > -4) System.out.println();
		}
		// Should be already removed?
		getRemain(target_sequence, num_all, num_undef, num_good, num_bad);
		if (debugLevel > -4) printStats ("\ngenerateExtractFilterMovingTargets(): before isolated removal", true,num_all, num_undef, num_good, num_bad);

		filterIsolatedTargets(      // overwrites "good", does not change "when"
				target_sequence,    // final double [][][] targets,
				min_seq,            // final int           min_seq_length,   // minimal sequence length not including first/last keyframes (may include)
				min_seq_fl,         // final int           min_seq_length_fl,// < min_seq_length minimal sequence length that includes first/last keyframes
				max_mismatch,       // final double        max_mismatch, // if <=0, do not calculate  mismatch_ba and filter
				cuasMotion.tilesX); //  int           tilesX) {

		if (intermed_low)  {
			ImagePlus imp_no_singles_lma= showTargetSequence(
					target_sequence,    // double [][][] target_scene_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-NOSINGLES",   // String        title,
					!batch_mode,    // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_no_singles_lma);  // ImagePlus   imp)
			if (debugLevel > -4) System.out.println();
		}
		calculateMismatchBeforeAfter( // fills fields in the target_sequence tiles
				target_sequence,    // final double [][][] targets,
				true,               // final boolean       good_only,
				cuasMotion.tilesX); // final int           tilesX)	
		filterIsolatedTargets( // overwrites "good", does not change "when"
				target_sequence,      // final double [][][] targets,
				min_seq,              // final int           min_seq_length,   // minimal sequence length not including first/last keyframes (may include)
				min_seq_fl,           // final int           min_seq_length_fl,// < min_seq_length minimal sequence length that includes first/last keyframes
				max_mismatch,         // final double        max_mismatch, // if <=0, do not calculate  mismatch_ba and filter
				cuasMotion.tilesX);   //  int           tilesX) {

		if (intermed_low)  {
			ImagePlus imp_no_singles_lma= showTargetSequence(
					target_sequence,    // double [][][] target_scene_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-NOSINGLES2",   // String        title,
					!batch_mode,    // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_no_singles_lma);  // ImagePlus   imp)
			if (debugLevel > -4) System.out.println();
		}
		// re-calculate score after removing short sequences

		calculateMismatchBeforeAfter( // fills fields in the target_sequence tiles
				target_sequence,    // final double [][][] targets,
				true,               // final boolean       good_only,
				cuasMotion.tilesX); // final int           tilesX)	

		filterIsolatedTargets( // overwrites "good", does not change "when"
				target_sequence,      // final double [][][] targets,
				min_seq,              // final int           min_seq_length,   // minimal sequence length not including first/last keyframes (may include)
				min_seq_fl,           // final int           min_seq_length_fl,// < min_seq_length minimal sequence length that includes first/last keyframes
				max_mismatch,         // final double        max_mismatch, // if <=0, do not calculate  mismatch_ba and filter
				cuasMotion.tilesX);   //  int           tilesX) {
		if (intermed_low)  {
			ImagePlus imp_final_score = showTargetSequence(
					target_sequence,        // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-PRE-FINAL_SCORE",// String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_final_score);
		}
		
		getEffectiveStrengthLMA(
				target_sequence,      // final double [][][] target_coords, // LMA
				target_strength,   // final double        target_strength,
				target_frac,       // final double [][]   target_frac, // pairs - strength, minimal fraction for that strength
				lma_rms,           // final double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
				lma_arms,          // final double        lma_arms, // =      0.06; // Maximal absolute RMS (should match one of cuas_lma_arms OR cuas_lma_rrms (0.484)
				lma_rrms,          // final double        lma_rrms, // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
				lma_mina,          // final double        lma_mina, // =      1.0;  // Minimal A (amplitude)
				lma_maxr,          // final double        lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
				lma_minr1,         // final double        lma_minr1,// =      1.0;  // Minimal R1 (radius of positive peak) 
				lma_mink,          // final double        lma_mink, // =      0.0;  // Minimal K (overshoot) = 1.0
				lma_maxk,          // final double        lma_maxk, // =      5.0;  // Minimal K (overshoot) = 3.0
				lma_a2a,           // final double        lma_a2a,
				max_mismatch,      // final double        max_mismatch,
				good_mismatch,     // final double        good_mismatch, //do not add to score if worse
				false, // fail_mismatch,     // final boolean       fail_mismatch,
				target_horizon,    // final double        lma_horizon, // target below horizon
				cuasMotion.tilesX); // final int           tilesX,

		//			int [] remain_es2 = 
		getScore( // calculates composite sceres
				target_sequence, // final double [][][] target_sequence, // modifies certain fields (scores)
				factor_lim,        // final double        importance_limit,
				factor_pow,        // final double        importance_power, // Raise each factor to this power before combining
				score_coeff,       // final double []     importance, // for now (each - squared?): [0] - Amplitude (A/A0), 1 - RMS (RMS0/RMS), 2 - RRMS((RMS/A0) / (RMS/A)
				cuasMotion.tilesX);            // final int         tilesX,

		if (intermed_low)  {
			ImagePlus imp_final_score = showTargetSequence(
					target_sequence,        // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-FINAL_SCORE",// String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_final_score);
		}
		//					getRemain(target_sequence, num_all, num_undef, num_good, num_bad);
		getRemain(target_sequence, null, null, num_good, null);
		if (debugLevel > -4) printStats ("After final score", true, null, null, num_good, null);

		boolean[][] filter5 = filter5Targets(
				target_sequence,    // final double [][][] target_sequence,
				false,              // final boolean       use_motion,     // true - use motion vectors confidence, false - use target confidence
				false,              // final boolean       select_new,     // true - use only untested tiles, false - use good tiles 
				min_score_lma,      // min_mv_strength,    // double              min_confidence,
				target_horizon,     // final double        lma_horizon, // target below horizon
				cuasMotion.tilesX,  // final int           tilesX,
				2, // max_range,          // final int           range, // 1 or 2
				filter5_remain,     // final int     []    remain){
				null, // passes,             // final int     []    passes, // debugging - number of passes required
				debugLevel);        // final int           debugLevel)
		if (debugLevel > -4) printStatsLine("After filter5 remaining non-conflicting targets", true,filter5_remain);

		// apply filter5 to targets_good
		findStrongerNeighbor(// does not modify "when"
				target_sequence,      // final double [][][] target_sequence,
				filter5,              // final boolean [][] filter5)
				true,                // final boolean       mark_failed,
				cuasMotion.tilesX);   // final int           tilesX) {				
		// now failed by neighbors are marked as non-NaN (tile index or -1), not as failed
		getRemain(target_sequence, null, null, num_good, null);
		if (debugLevel > -4) printStats ("After removing by neighbors", true, null, null, num_good, null);



		if (intermed_low)  {
			ImagePlus imp_neibs = showTargetSequence(
					target_sequence,        // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-NO_CONFLICTS",// String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_neibs);
		}

		calculateMismatchBeforeAfter( // fills fields in the target_sequence tiles
				target_sequence,    // final double [][][] targets,
				true,               // final boolean       good_only,
				cuasMotion.tilesX); // final int           tilesX)	
		filterIsolatedTargets( // overwrites "good", does not change "when"
				target_sequence,      // final double [][][] targets,
				min_seq,              // final int           min_seq_length,   // minimal sequence length not including first/last keyframes (may include)
				min_seq_fl,           // final int           min_seq_length_fl,// < min_seq_length minimal sequence length that includes first/last keyframes
				max_mismatch,         // final double        max_mismatch, // if <=0, do not calculate  mismatch_ba and filter
				cuasMotion.tilesX);   //  int           tilesX) {

		if (intermed_low)  {
			ImagePlus imp_no_singles_lma= showTargetSequence(
					target_sequence,    // double [][][] target_scene_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-NOSINGLES3",   // String        title,
					!batch_mode,    // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_no_singles_lma);  // ImagePlus   imp)
			if (debugLevel > -4) System.out.println();
		}
		getRemain(target_sequence, num_all, num_undef, num_good, num_bad);
		if (debugLevel > -4) printStats ("generateExtractFilterMovingTargets(): after isolated removal", true,num_all, num_undef, num_good, num_bad);
	}
	
	

	public static void generateExtractFilterMovingTargets( // move parameters to clt_parameters
			CLTParameters       clt_parameters,
			final boolean       video_pass, // if clt_parameters.cuas_clean_video=true, video_pass=0 - output TIFFS, but no video. If video_pass==1 - only video with no debug
			final boolean       batch_mode,
			QuadCLT             parentCLT,          //
			final float [][]    fpixels,
			double [][][]       target_sequence,  // non-overlap only if consider marked stronger tiles
			CuasMotion          cuasMotion,
			UasLogReader        uasLogReader,	
			String []           scene_titles, // recreate slice_titles from scene titles?
			String []           slice_titles,
			final int           debugLevel) {
		String model_prefix = parentCLT.getImageName()+getParametersSuffixRslt(clt_parameters,null);
		boolean clean_video =    clt_parameters.imp.cuas_clean_video;      //true;// save video without any debug information for targets, output in TIFF files. False - same output for video and TIFFs

		final int     corr_offset =       clt_parameters.imp.cuas_corr_offset;
		final int     corr_pairs =        clt_parameters.imp.cuas_corr_pairs;
		
		final boolean half_step =         clt_parameters.imp.cuas_half_step; // true;
		final boolean smooth =            clt_parameters.imp.cuas_smooth; // true;
		final int corr_inc =             half_step ? (corr_offset/2) : corr_offset;
		final int     half_accum_range =  corr_pairs/2;
		
		final double  mask_width =        clt_parameters.imp.cuas_mask_width;  // 9;
		final double  mask_blur =         clt_parameters.imp.cuas_mask_blur;   // 3;
		final boolean mask_round =        clt_parameters.imp.cuas_mask_round;  // false;
	    
		int     target_type =    clt_parameters.imp.cuas_target_type; // 0;  // 0 - unknown, 1 - known, 2 - friend, 3 - foe
		int     known_type =     clt_parameters.imp.cuas_known_type;  // 2;  // Target location matching UAS flight log: 0; // 0 - unknown, 1 - known, 2 - friend, 3 - foe
		double  known_err =      clt_parameters.imp.cuas_known_err;   // 20; //  Maximal distance between the detected target and UAS log position (in raw image pixels);
		
		double  input_range =    clt_parameters.imp.cuas_input_range; // 5;
		boolean scale2x =        clt_parameters.imp.cuas_scale2x;      //true;
		boolean ra_background =  clt_parameters.imp.cuas_ra_background;   //true;
		
		boolean intermed_low =   clt_parameters.imp.cuas_intermed_low;    //true;
		boolean intermed_high =  clt_parameters.imp.cuas_intermed_high;   //true;
		
		boolean save_mono =      clt_parameters.imp.cuas_save_mono;       //true;
		boolean save_color =     clt_parameters.imp.cuas_save_color;      //true;
		boolean save_video =     clt_parameters.imp.cuas_save_video;      //true;
		boolean target_debug =   clt_parameters.imp.cuas_target_debug;    //true;
		boolean show_target_score = clt_parameters.imp.cuas_target_score;    //   false; // show target score and sequence length ijn the final video
		boolean annotate_uas =   clt_parameters.imp.cuas_annotate_uas;    //   false; // show circle around UAS position from the flight log
		boolean show_target_color = clt_parameters.imp.cuas_target_color;    //   false; // show target score and sequence length ijn the final video
		boolean show_true_rng =  clt_parameters.imp.cuas_show_true_rng;   // show true range (from the flight log)
		String clean_suffix = "";
		if (clean_video) {
			if (video_pass) {
				annotate_uas =      false;
				save_mono =         false;
				save_color =        false;
				target_debug =      false;
				show_target_score = false;
				show_target_color = false;
				clean_suffix =      "-CLEAN";
			} else {
				save_video = false;
			}
		}
		boolean cuas_gaussian_ra = clt_parameters.imp.cuas_gaussian_ra;  // use temporal Gaussian instead of running average
	    boolean center_targ =    false; // clt_parameters.imp.cuas_center_targ;
	    
		int start_frame = 0;
		int seq_length = corr_offset + corr_pairs;
		final int     frame0 = start_frame + seq_length/2;
		
		
		// create larger, 5x5 vector field for accumulation -> 3x3
		double [][][] extended_targets = extendMotionScan(
				target_sequence,     // 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
				null); // remain);              // final int     []    remain)
		if (intermed_low) {
			ImagePlus imp_extended= showTargetSequence(
					extended_targets,    // double [][][] target_scene_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-EXTENDED-TORENDER",   // String        title,
					!batch_mode,    // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_extended);
		}
				
		float [][] fpixels_accumulated5x5 = cuasMotion.shiftAndRenderAccumulate(
				clt_parameters,       // CLTParameters       clt_parameters,
				center_targ, // false,                // final boolean       center,
				true,                 // final boolean       fill_zeros,
				fpixels,              // final float [][]    fpixels,
				extended_targets, // final double [][][] vector_field,
				frame0,               // final int           frame0,      // for vector_field[0]
				corr_inc,             // final int           frame_step,
				half_accum_range,     // final int           half_range,
				smooth,               // final boolean       smooth,
				corr_offset,          // final int           corr_offset, // interframe distance for correlation
				true);                // final boolean       batch_mode) {
		// zero-fill undefined tiles
		
		
		ImagePlus imp_accumulated5x5 = ShowDoubleFloatArrays.makeArrays(
				fpixels_accumulated5x5, // float[][] pixels,
				cuasMotion.gpu_max_width, // int width,
				cuasMotion.gpu_max_height, // int height,
				model_prefix+"-ACCUMULATED"+(center_targ?"-CENTERED":""), //String title,
				slice_titles); //String [] titles)
		imp_accumulated5x5.getProcessor().setMinAndMax(-input_range/2, input_range/2);
		if (intermed_high) {
			if (!batch_mode) imp_accumulated5x5.show();
			parentCLT.saveImagePlusInModelDirectory(imp_accumulated5x5);            // ImagePlus   imp)
		}
		
		double  velocity_scale = 1.0/corr_offset;
		double [][][] targets60hz = new double [fpixels.length][][];
		
		
		
		float  [][]  background = fpixels;
		String ra_bg_suffix=(ra_background? ("-RABG"+corr_pairs):"");
		if (ra_background) {
			if (cuas_gaussian_ra) {
				background = runningGaussian(
						fpixels,                   // final float [][] fpixels,
						corr_pairs,                // final int        ra_length,
						cuasMotion.gpu_max_width); // final int        width)
			} else {
				background = runningAverage(
						fpixels,                   // final float [][] fpixels,
						corr_pairs,                // final int        ra_length,
						cuasMotion.gpu_max_width); // final int        width)
			}
		}
		
		///// Now expects targets_sequence !!!!!!!!!!
		float [][] replaced_targets = cuasMotion.shiftAndRenderTargets(
				clt_parameters,          // CLTParameters       clt_parameters,
				center_targ,             //	final boolean     center,
				mask_width,              // final double        mask_width,
				mask_blur,               // final double        mask_blur,
				mask_round,              // final boolean       round,
				fpixels_accumulated5x5,  // final float  [][]   target_keyframes,
				target_sequence,         // final double [][][] target_positions,
				background,              // final float  [][]   background, // background image
				frame0,                  // final int           frame0,
				corr_inc,                // final int           frame_step,
				velocity_scale,          // final double        velocity_scale, // 1.0/(disparity in frames)
				targets60hz,             // final double [][][] targets60hz, 
				batch_mode,              // final boolean       batch_mode,
				debugLevel);             // final int debugLevel)
		if (save_mono) {
			ImagePlus imp_replaced_targets = ShowDoubleFloatArrays.makeArrays(
					replaced_targets, // float[][] pixels,
					cuasMotion.gpu_max_width, // int width,
					cuasMotion.gpu_max_height, // int height,
					model_prefix+"-REPLACED-TARGETS"+(ra_background? "-RABG":""), //String title,
					scene_titles); //String [] titles)
			imp_replaced_targets.getProcessor().setMinAndMax(-input_range/2, input_range/2);			
			if (!batch_mode) {
				imp_replaced_targets.show();
			}
			parentCLT.saveImagePlusInModelDirectory(imp_replaced_targets);            // ImagePlus   imp)
		}
		
		ImagePlus imp_color = convertToRgbAnnotateTargets(
				clt_parameters,             // CLTParameters       clt_parameters,
				input_range,                // final double        input_range, // 5
				scale2x,                    // boolean       scale2x,
				target_type,                // final int target_type, //  = 0; // 0 - unknown, 1 - known, 2 - friend, 3 - foe
				known_type,                 // final int           known_type, // 2;  // Target location matching UAS flight log: 0; // 0 - unknown, 1 - known, 2 - friend, 3 - foe
				known_err,                  // final double        known_err,  // 20; //  Maximal distance between the detected target and UAS log position (in raw image pixels);
				replaced_targets,           // final float  [][]   fpixels,
				targets60hz,                // final double [][][] targets60hz,
				show_target_color,          // final boolean       change_color,
				show_true_rng,              // final boolean       show_true_rng,// show true range (from the flight log)		
				target_debug,               // final boolean       show_mismatch,
				show_target_score,          // final boolean       show_score,
				annotate_uas,               // final boolean       show_uas,
				frame0,                     // final int           frame0,
				corr_inc,                   // final int           frame_step,
				cuasMotion.gpu_max_width,   // final int           width,
				model_prefix+"-RGB"+ra_bg_suffix+clean_suffix, // String              title,
				scene_titles,               // String []           titles,
				uasLogReader,               // UasLogReader           uasLogReader,				
				debugLevel);                // final int           debugLevel) {
		// save tiff in model directory
		if (imp_color != null) {
			if (save_color) {
				if (!batch_mode) {
					imp_color.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_color);      // ImagePlus   imp)
			}
			// temporary, will use model directory
			if (save_video) {
				saveAsVideo( // will take name from the image title
						clt_parameters, // CLTParameters       clt_parameters,
						parentCLT,      // QuadCLT             scene,     // if not null - use it's model directory
						null, // video_path_tmp, // String              file_path,
						imp_color,      // ImagePlus           imp_color,
						debugLevel);    // int debugLevel) {
			}
		}
	}

	public static void filterIsolatedTargets( // overwrites "good", does not change "when"
			final double [][][] targets,
			final int           min_seq_length,   // minimal sequence length not including first/last keyframes (may include)
			final int           min_seq_length_fl,// < min_seq_length minimal sequence length that includes first/last keyframes
			final double        max_mismatch_in, // if <=0, do not calculate  mismatch_ba and filter
			final int           tilesX) {
		final int num_seq = targets.length;
		final int num_tiles = targets[0].length;
		final double       max_mismatch = (max_mismatch_in > 0) ? max_mismatch_in : Double.POSITIVE_INFINITY;
		final boolean [][] good_tiles = 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, num_tiles/tilesX); 
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) if ((targets[nSeq][ntile] != null)
								&& (targets[nSeq][ntile][CuasMotionLMA.RSLT_FAIL]==CuasMotionLMA.FAIL_NONE) && 
								(targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_DIRS]>0)) {
							double [] ba = {targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_BEFORE],targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_AFTER]};
							int [] dirs = decodeDirs(targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_DIRS]);
							for (int i = 0; i < ba.length; i++) {
								if ((dirs[i] < 0) || (ba[i] > max_mismatch)) {
									ba[i] = Double.NaN;
								}
							}
							boolean has_fl = (nSeq==0) || (nSeq == (num_seq-1));
							int seq_len = 1;
							if (Double.isNaN(ba[0]) && Double.isNaN(ba[1])) { // no connections either way
								continue;
							}
							int nseq1 = nSeq;
							int ntile1 = ntile;
							check_long_enough: {
								if ((seq_len >= min_seq_length) || (has_fl && (seq_len >= min_seq_length_fl))) {
									break check_long_enough;
								}
								// check after
								int ba_index = 1;// 0 - before, 1 - after
								// if required length is 0 or 1
								while ((nseq1 < num_seq)) {//  && (targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_AFTER] < max_mismatch) ) { // NaN will exit the loop
									if (    (targets[nseq1][ntile1] == null) ||
											(targets[nseq1][ntile1][CuasMotionLMA.RSLT_FAIL] != CuasMotionLMA.FAIL_NONE) ||
											!(targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_AFTER] <= max_mismatch)) {
										break;
									}

									
									int dir = decodeDirs(targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_DIRS])[ba_index]; // direction to the tile in the next/previous keyframe
									if (dir < 0) { // should not happen
										break;
									}
									ntile1 = tn.getNeibIndex(ntile1, dir);
									if (ntile1 < 0) { // should not happen
										break;
									}
									nseq1++;
									seq_len++;
									has_fl |= (nseq1==0) || (nseq1 == (num_seq-1));
									if ((seq_len >= min_seq_length) || (has_fl && (seq_len >= min_seq_length_fl))) {
										break check_long_enough;
									}
								}
								// check before
								nseq1 = nSeq;
								ntile1 = ntile;
								ba_index = 0;// 0 - before, 1 - after
								while ((nseq1 >= 0)) {// && (targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_BEFORE] < max_mismatch) ) { // NaN will exit the loop
									if (    (targets[nseq1][ntile1] == null) ||
											(targets[nseq1][ntile1][CuasMotionLMA.RSLT_FAIL]!=CuasMotionLMA.FAIL_NONE) ||
											!(targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_BEFORE] <= max_mismatch)) {
										break;
									}
									int dir = decodeDirs(targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_DIRS])[ba_index]; // direction to the tile in the next/previous keyframe
									if (dir < 0) {  // should not happen
										break;
									}
									ntile1 = tn.getNeibIndex(ntile1, dir);
									if (ntile1 < 0) { // should not happen
										break;
									}
									
									
									nseq1--;
									seq_len++;
									has_fl |= (nseq1==0) || (nseq1 == (num_seq-1));
									if ((seq_len >= min_seq_length) || (has_fl && (seq_len >= min_seq_length_fl))) {
										break check_long_enough;
									}
								}
								continue; // not long enough to match requirements - not a good target
							}
							good_tiles [nSeq][ntile] = true;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) if ((targets[nSeq][ntile] != null) && !good_tiles[nSeq][ntile]) {
							targets[nSeq][ntile][CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_MISMATCH;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return;
	}
	
	
	
	public static void filterIsolatedTargetsDebug( // overwrites "good", does not change "when"
			final double [][][] targets,
			final int           min_seq_length,   // minimal sequence length not including first/last keyframes (may include)
			final int           min_seq_length_fl,// < min_seq_length minimal sequence length that includes first/last keyframes
			final double        max_mismatch_in, // if <=0, do not calculate  mismatch_ba and filter
			final int           tilesX) {
		final int num_seq = targets.length;
		/*
		if (num_seq <0) {
			 filterIsolatedTargets( // overwrites "good", does not change "when"
					 targets, // final double [][][] targets,
					true, // boolean             remove_2seq,
					max_mismatch_in, // final double        max_mismatch, // if <=0, do not calculate  mismatch_ba and filter
					tilesX); // final int           tilesX)
			 return;
		}
		*/
		final int num_tiles = targets[0].length;
		final double       max_mismatch = (max_mismatch_in > 0) ? max_mismatch_in : Double.POSITIVE_INFINITY;
		final boolean [][] good_tiles = new boolean [num_seq][num_tiles];
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		final int dbg_tile = 38+46*80;
//		final int dbg_seq=14;
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					TileNeibs tn = new TileNeibs(tilesX, num_tiles/tilesX); 
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) if ((targets[nSeq][ntile] != null)
								&& (targets[nSeq][ntile][CuasMotionLMA.RSLT_FAIL]==CuasMotionLMA.FAIL_NONE) && 
								(targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_DIRS]>0)) {
							double [] ba = {targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_BEFORE],targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_AFTER]};
							int [] dirs = decodeDirs(targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_DIRS]);
							
							for (int i = 0; i < ba.length; i++) {
								if ((dirs[i] < 0) || (ba[i] > max_mismatch)) {
									ba[i] = Double.NaN;
								}
							}
							boolean has_fl = (nSeq==0) || (nSeq == (num_seq-1));
							int seq_len = 1;
							if (Double.isNaN(ba[0]) && Double.isNaN(ba[1])) { // no connections either way
								if (ntile== dbg_tile) { 
									System.out.println("filterIsolatedTargets():1: nSeq="+nSeq+", ntile="+ntile);
								}
								continue;
							}
							int nseq1 = nSeq;
							int ntile1 = ntile;
							check_long_enough: {
								if ((seq_len >= min_seq_length) || (has_fl && (seq_len >= min_seq_length_fl))) {
									if (ntile== dbg_tile) { 
										System.out.println("filterIsolatedTargets():2: nSeq="+nSeq+", ntile="+ntile);
									}
									break check_long_enough;
								}
								// check after
								int ba_index = 1;// 0 - before, 1 - after
								// if required length is 0 or 1
								while ((nseq1 < num_seq)) {//  && (targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_AFTER] < max_mismatch) ) { // NaN will exit the loop
									if (    (targets[nseq1][ntile1] == null) ||
											(targets[nseq1][ntile1][CuasMotionLMA.RSLT_FAIL] != CuasMotionLMA.FAIL_NONE) ||
											!(targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_AFTER] <= max_mismatch)) {
										if (ntile== dbg_tile) { //  && (nSeq==dbg_seq)) {
											if (targets[nseq1][ntile1] == null) {
												System.out.println("check after: nSeq="+nSeq+", ntile="+ntile+
											", targets["+nseq1+"]["+ntile1+"] == null, nSeq="+nSeq+", ntile="+ntile+", seq_len="+seq_len);
											} else if (!(targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_AFTER] <= max_mismatch)){
												System.out.println("check after:  nSeq="+nSeq+", ntile="+ntile+
														", targets["+nseq1+"]["+ntile1+"]["+
														CuasMotionLMA.RSLT_MISMATCH_BEFORE+"] = "+targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_BEFORE]+
														", ...["+CuasMotionLMA.RSLT_MISMATCH_AFTER+"] = "+targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_AFTER]+
														", ...["+CuasMotionLMA.RSLT_MISMATCH_DIRS+"] = "+targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_DIRS]+
														", seq_len="+seq_len);
											} else {
												System.out.println("check after: nSeq="+nSeq+", ntile="+ntile+
											", targets["+nseq1+"]["+ntile1+"] == null, nSeq="+nSeq+", ntile="+ntile+
														"targets[nseq1][ntile1][CuasMotionLMA.RSLT_FAIL]="+targets[nseq1][ntile1][CuasMotionLMA.RSLT_FAIL]+", seq_len="+seq_len);
											}
										}
										break;
									}

									
									int dir = decodeDirs(targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_DIRS])[ba_index]; // direction to the tile in the next/previous keyframe
									if (ntile== dbg_tile) { 
										System.out.println("filterIsolatedTargets():3: nSeq="+nSeq+", ntile="+ntile+", dir="+dir+", seq_len="+seq_len);	
									}
									if (dir < 0) {
										System.out.println("check after: nseq1="+nseq1+", ntile1="+ntile1+", dir="+dir+
												", targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_DIRS]="+
												targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_DIRS]+", seq_len="+seq_len);
										break;
									}
									
									ntile1 = tn.getNeibIndex(ntile1, dir);
									if (ntile1 < 0) {
										System.out.println("check after: nseq1="+nseq1+", ntile1="+ntile1+", seq_len="+seq_len);
										break;
									}
									
									nseq1++;
									seq_len++;
									has_fl |= (nseq1==0) || (nseq1 == (num_seq-1));
									if ((seq_len >= min_seq_length) || (has_fl && (seq_len >= min_seq_length_fl))) {
										System.out.println("+++1 nSeq="+nSeq+", ntile="+ntile+
												", nseq1="+nseq1+", ntile1="+ntile1+
												", seq_len="+seq_len+
												" has_fl="+has_fl+", min_seq_length="+min_seq_length+", min_seq_length_fl="+min_seq_length_fl);
										break check_long_enough;
									}
									if (ntile== dbg_tile) { 
										System.out.println("filterIsolatedTargets():4: nSeq="+nSeq+", ntile="+ntile+", dir="+dir+", seq_len="+seq_len);	
									}
									
								}
								// check before
								nseq1 = nSeq;
								ntile1 = ntile;
								ba_index = 0;// 0 - before, 1 - after
								while ((nseq1 >= 0)) {// && (targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_BEFORE] < max_mismatch) ) { // NaN will exit the loop
									if (    (targets[nseq1][ntile1] == null) ||
											(targets[nseq1][ntile1][CuasMotionLMA.RSLT_FAIL]!=CuasMotionLMA.FAIL_NONE) ||
											!(targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_BEFORE] <= max_mismatch)) {
										if (ntile== dbg_tile) {
											if (targets[nseq1][ntile1] == null) {
												System.out.println("check before: nSeq="+nSeq+", ntile="+ntile+
											", targets["+nseq1+"]["+ntile1+"] == null, nSeq="+nSeq+", ntile="+ntile+", seq_len="+seq_len);
											} else if (!(targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_BEFORE] <= max_mismatch)){
												System.out.println("check before: nSeq="+nSeq+", ntile="+ntile+
														", targets["+nseq1+"]["+ntile1+"]["+
														CuasMotionLMA.RSLT_MISMATCH_BEFORE+"] = "+targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_BEFORE]+
														", ...["+CuasMotionLMA.RSLT_MISMATCH_AFTER+"] = "+targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_AFTER]+
														", ...["+CuasMotionLMA.RSLT_MISMATCH_DIRS+"] = "+targets[nseq1][ntile1][CuasMotionLMA.RSLT_MISMATCH_DIRS]+
														", seq_len="+seq_len);
											} else {
												System.out.println("check before: nSeq="+nSeq+", ntile="+ntile+
														", targets["+nseq1+"]["+ntile1+"] == null, nSeq="+nSeq+", ntile="+ntile+
														"targets[nseq1][ntile1][CuasMotionLMA.RSLT_FAIL]="+targets[nseq1][ntile1][CuasMotionLMA.RSLT_FAIL]+", seq_len="+seq_len);
											}
										}
										break;
									}
									int dir = decodeDirs(targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_DIRS])[ba_index]; // direction to the tile in the next/previous keyframe
									if (ntile== dbg_tile) { 
										System.out.println("filterIsolatedTargets():5: nSeq="+nSeq+", ntile="+ntile+", dir="+dir+", seq_len="+seq_len);	
									}

									if (dir < 0) {
										System.out.println("check before: nseq1="+nseq1+", ntile1="+ntile1+", dir="+dir+
												", targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_DIRS]="+
												targets[nSeq][ntile][CuasMotionLMA.RSLT_MISMATCH_DIRS]+", seq_len="+seq_len);
										break;
									}
									ntile1 = tn.getNeibIndex(ntile1, dir);
									if (ntile1 < 0) {
										System.out.println("check before: nseq1="+nseq1+", ntile1="+ntile1+", seq_len="+seq_len);
										break;
									}
									
									
									nseq1--;
									seq_len++;
									has_fl |= (nseq1==0) || (nseq1 == (num_seq-1));
									if ((seq_len >= min_seq_length) || (has_fl && (seq_len >= min_seq_length_fl))) {
										System.out.println("+++2 nSeq="+nSeq+", ntile="+ntile+
												", nseq1="+nseq1+", ntile1="+ntile1+
												", seq_len="+seq_len+
												" has_fl="+has_fl+", min_seq_length="+min_seq_length+", min_seq_length_fl="+min_seq_length_fl);
										break check_long_enough;
									}
									if (ntile== dbg_tile) { 
										System.out.println("filterIsolatedTargets():6: nSeq="+nSeq+", ntile="+ntile+", dir="+dir+", seq_len="+seq_len);	
									}
								}
								if (ntile== dbg_tile) { 
									System.out.println("filterIsolatedTargets():7: nSeq="+nSeq+", ntile="+ntile+", seq_len="+seq_len);	
								}
								continue; // not long enough to match requirements - not a good target
							}
							/*
							System.out.println("*** nSeq="+nSeq+", ntile="+ntile+
									", nseq1="+nseq1+", ntile1="+ntile1+
									", seq_len="+seq_len+
									" has_fl="+has_fl+", min_seq_length="+min_seq_length+", min_seq_length_fl="+min_seq_length_fl);
									*/
							if (ntile== dbg_tile) { 
								System.out.println("filterIsolatedTargets():8: nSeq="+nSeq+", ntile="+ntile+", seq_len="+seq_len);	
							}
							good_tiles [nSeq][ntile] = true;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) if ((targets[nSeq][ntile] != null) && !good_tiles[nSeq][ntile]) {
							targets[nSeq][ntile][CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_MISMATCH;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return;
		
	}
	
	/**
	 * Fill traget fields for the best match error for the previous and next keyframe. Relies on the fields provided by
	 * getHalfBeforeAfterPixXY() 
	 * @param targets target data [keyframe index][ntile][]
	 * @param good_only exclude failed tiles
	 * @param tilesX number of tiles in a row (80 for Boson640)
	 */
	public static void calculateMismatchBeforeAfter(
			final double [][][] targets,
			final boolean       good_only,
			final int           tilesX) {
		final int num_seq = targets.length;
		final int num_tiles = targets[0].length;
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		final int dbg_seq = 26 - 1;
		final int dbg_tile = (38+45*tilesX);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					TileNeibs tn = new TileNeibs(tilesX, num_tiles/tilesX); 
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if ((nSeq==dbg_seq) && (ntile == dbg_tile)) {
								System.out.println("calculateMismatchBeforeAfter(): nSeq="+nSeq+", ntile="+ntile);
							}
							double [] target = targets[nSeq][ntile];
							if ((targets[nSeq][ntile] != null) && (!good_only || (target[CuasMotionLMA.RSLT_FAIL] == 0))) {
								double [] mm = {Double.NaN, Double.NaN};
								int [] mm_dirs = {-1,-1};
								if (target != null) {
									// should be no "continue" to always erase any previous values in these field by NaN
									boolean has_neib = true;
									look_for_neibs: {
										for (int dseq = -1; dseq <= 1; dseq+=2) {
											int nSeq1 = nSeq+dseq;
											if ((nSeq1 >= 0) && (nSeq1 < num_seq)) {
												for (int dir = 8; dir >= 0; dir--) {
													int tile1 = tn.getNeibIndex(ntile, dir);
													if ((tile1 >= 0) && (targets[nSeq1][tile1] != null) &&
															(!good_only || (targets[nSeq1][tile1][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE))) {
														break look_for_neibs;
													}
												}
											}
										}
										has_neib = false;
									}
									if (has_neib) {
										for (int dseq = -1; dseq <= 1; dseq+=2) {
											int indx = (dseq > 0) ? 1 : 0;
											int ba_indx_this =  CuasMotionLMA.RSLT_BX  + 2 * indx;
											int ba_indx_other = CuasMotionLMA.RSLT_BX  + 2 * (1 - indx);
											double [] this_ba = {target[ba_indx_this], target[ba_indx_this + 1]};	//{x,y}									
											int nSeq1 = nSeq+dseq;
											if ((nSeq1 >= 0) && (nSeq1 < num_seq)) {
												for (int dir = 8; dir >= 0; dir--) { // dir==8 is center tile - most likely
													int tile1 = tn.getNeibIndex(ntile, dir);
													if (tile1 >= 0) {
														double [] target_other = targets[nSeq1][tile1];
														if ((target_other != null)  &&
																(!good_only || (target_other[CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE)) ) {
															double [] other_ba = {target_other[ba_indx_other], target_other[ba_indx_other + 1]};
															double dx = this_ba[0] - other_ba[0];
															double dy = this_ba[1] - other_ba[1];
															double mismatch = Math.sqrt(dx*dx + dy*dy);
															if (!(mismatch > mm[indx])) { // tolerates NaN when not yet set
																mm[indx] = mismatch;
																mm_dirs[indx] = dir;
															}
														}
													}
												}
											}
										}
									}
									target[CuasMotionLMA.RSLT_MISMATCH_BEFORE] = mm[0];
									target[CuasMotionLMA.RSLT_MISMATCH_AFTER] =  mm[1];
									target[CuasMotionLMA.RSLT_MISMATCH_DIRS] =   encodeDirs(mm_dirs);
								}
							}
						} // for (int ntile = 0; ntile < num_tiles; ntile++)
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return; //  filtered_tiles;
	}

	/**
	 * Fill traget fields for the best match error for the previous and next keyframe. Relies on the fields provided by
	 * getHalfBeforeAfterPixXY() 
	 * @param targets target data [keyframe index][ntile][]
	 * @param good_only exclude failed tiles
	 * @param tilesX number of tiles in a row (80 for Boson640)
	 */
	
	public static void calculateMismatchBeforeAfter(
			final double [][][][]  targets_multi,
			final boolean          good_only,
			final int              tilesX) {
		final int num_seq = targets_multi.length;
		final int num_tiles = targets_multi[0].length;
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		final int dbg_seq = 26 - 1;
		final int dbg_tile = (38+45*tilesX);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					TileNeibs tn = new TileNeibs(tilesX, num_tiles/tilesX); 
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if ((nSeq==dbg_seq) && (ntile == dbg_tile)) {
								System.out.println("calculateMismatchBeforeAfter(): nSeq="+nSeq+", ntile="+ntile);
							}
							double [][] targets = targets_multi[nSeq][ntile];
							if (targets != null) { 
								for (int ntarg = 0; ntarg < targets.length; ntarg++) if (targets[ntarg] != null) {
									double [] target = targets[ntarg];
									if ((target != null) && (!good_only ||
											((target[CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) &&
													(target[CuasMotionLMA.RSLT_CENTERED] != CuasMotionLMA.CENTERED_USED)))) {
										double [] mm = {Double.NaN, Double.NaN};
										int [] mm_dirs = {-1,-1};
										int [] mm_alts = {-1,-1};
										//										if (target != null) {
										// should be no "continue" to always erase any previous values in these field by NaN
										boolean has_neib = true;
										look_for_neibs: {
											for (int dseq = -1; dseq <= 1; dseq+=2) {
												int nSeq1 = nSeq+dseq;
												if ((nSeq1 >= 0) && (nSeq1 < num_seq)) {
													for (int dir = 8; dir >= 0; dir--) {
														int tile1 = tn.getNeibIndex(ntile, dir);
														if ((tile1 >= 0) && (targets_multi[nSeq1][tile1] != null)) {
															for (int ntarg1 = 0; ntarg1 < targets_multi[nSeq1][tile1].length; ntarg1++) {
																double [] target1 = targets_multi[nSeq1][tile1][ntarg1];
																if  (!good_only || (
																		(target1[CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) &&
																		(target1[CuasMotionLMA.RSLT_CENTERED] != CuasMotionLMA.CENTERED_USED))) {
																	break look_for_neibs;
																}
															}
														}
													}
												}
											}
											has_neib = false;
										}
										if (has_neib) {
											for (int dseq = -1; dseq <= 1; dseq+=2) {
												int indx = (dseq > 0) ? 1 : 0;
												int ba_indx_this =  CuasMotionLMA.RSLT_BX  + 2 * indx;
												int ba_indx_other = CuasMotionLMA.RSLT_BX  + 2 * (1 - indx);
												double [] this_ba = {target[ba_indx_this], target[ba_indx_this + 1]};	//{x,y}									
												int nSeq1 = nSeq+dseq;
												if ((nSeq1 >= 0) && (nSeq1 < num_seq)) {
													for (int dir = 8; dir >= 0; dir--) { // dir==8 is center tile - most likely
														int tile1 = tn.getNeibIndex(ntile, dir);
														if (tile1 >= 0) {
															double [][] targets_other = targets_multi[nSeq1][tile1];
															if (targets_other != null) {
																for (int ntarg1 = 0; ntarg1 < targets_multi[nSeq1][tile1].length; ntarg1++) {
																	double [] target_other = targets_other[ntarg1];
																	if ((target_other != null)  &&
																			(!good_only || (target_other[CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE)) ) {
																		double [] other_ba = {target_other[ba_indx_other], target_other[ba_indx_other + 1]};
																		double dx = this_ba[0] - other_ba[0];
																		double dy = this_ba[1] - other_ba[1];
																		double mismatch = Math.sqrt(dx*dx + dy*dy);
																		if (!(mismatch > mm[indx])) { // tolerates NaN when not yet set
																			mm[indx] = mismatch;
																			mm_dirs[indx] = dir;
																			mm_alts[indx] = ntarg1;
																		}
																	}
																}
															}
														}
													}
												}
											}
										}
										target[CuasMotionLMA.RSLT_MISMATCH_BEFORE] = mm[0];
										target[CuasMotionLMA.RSLT_MISMATCH_AFTER] =  mm[1];
										target[CuasMotionLMA.RSLT_MISMATCH_DIRS] =   encodeDirs(mm_dirs,mm_alts);
									}
								}
							}
						} // for (int ntile = 0; ntile < num_tiles; ntile++)
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return; //  filtered_tiles;
	}
	
	
	
	
	private static double encodeDirs(int [] mm_dirs) {
		int rslt = (mm_dirs[0]+1) + 10 * (mm_dirs[1]+1);
		if (mm_dirs.length > 3) {
			rslt += 100 * (mm_dirs[2] + MAX_ALT_TARGETS * mm_dirs[3]) ;
		}
		return rslt;
	}

	private static double encodeDirs(int [] mm_dirs, int [] mm_alts) {
		int rslt = (mm_dirs[0]+1) + 10 * (mm_dirs[1]+1);
		rslt += 100 * (mm_alts[0] + MAX_ALT_TARGETS * mm_alts[1]) ;
		return rslt;
	}
	
	
	private static int [] decodeDirs(double ddirs) {
		int d = (int) Math.round(ddirs);
		int dirs =  d % 100;
		int layers = d / 100;
		return new int [] {(dirs % 10) -1, (dirs / 10) -1, layers % MAX_ALT_TARGETS, layers / MAX_ALT_TARGETS};
	}
	
	
	public static double [][][] filterByHorizon(
			final double [][][] target_sequence,
			final double        horizon,
			final int []        remain,
			final int           tilesX) {
		final int num_seq = target_sequence.length;
		final int num_tiles = target_sequence[0].length;
		final double [][][] filtered_tiles = new double [num_seq][num_tiles][];
		final int tileSize = GPUTileProcessor.DTT_SIZE;
		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()) {
						if (remain != null) {
							remain[nSeq] = 0;
						}

						for (int ntile = 0; ntile < num_tiles; ntile++) {
							double [] target_pos = target_sequence[nSeq][ntile];
							if (target_pos != null) { // should be always
								int tileY = ntile / tilesX;
								double yc = tileSize * tileY + tileSize/2;
								double ytk = yc + target_pos[CuasMotionLMA.RSLT_Y];
								if (ytk <= horizon) {
									filtered_tiles[nSeq][ntile] = target_pos;
									if (remain != null) {
										remain[nSeq]++;
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return filtered_tiles;
	}
	
	
	
	
	public static String getParametersSuffix(
			 CLTParameters       clt_parameters,	
			 String              prefix) {
		int     corr_offset =    clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =     clt_parameters.imp.cuas_corr_pairs;
		
		int     temporal_um =    clt_parameters.imp.cuas_temporal_um;
		double  tum_threshold =  clt_parameters.imp.cuas_tum_threshold;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
		
		int     precorr_ra =     clt_parameters.imp.cuas_precorr_ra;
		int     corr_ra_step =   clt_parameters.imp.cuas_corr_step;
		double  rstr =           clt_parameters.imp.cuas_rstr;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
	    double  lma_rms =        clt_parameters.imp.cuas_lma_rms; // =       1.5;  // Maximal RMS, regardless of amplitude
	    double  lma_arms =       clt_parameters.imp.cuas_lma_arms; // =      0.06; // Maximal absolute RMS, sufficient for any amplitude
	    double  lma_rrms =       clt_parameters.imp.cuas_lma_rrms; // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
	    double  lma_mina =       clt_parameters.imp.cuas_lma_mina; // =      1.0;  // Minimal A (amplitude)
	    double  fat_zero =       clt_parameters.imp.cuas_fat_zero;
	    double  lmax_fraction =  clt_parameters.imp.cuas_lmax_fraction; // 0.7;     // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
	    double  lmax_hack_ridge =  clt_parameters.imp.cuas_lmax_hack_ridge; // 0.45;    // Hack for ridges: remove horizontal streaks, where average(abs()) for the line exceeds fraction of abs max 0.32-good, 0.55 - bad
	    
	    double  lmax_flt_neglim= clt_parameters.imp.cuas_lmax_flt_neglim;   // -0.3;   // limit negative data to reduce ridge influence (make -10 to disable)        
	    double  lmax_flt_hsigma =  clt_parameters.imp.cuas_lmax_flt_hsigma; // 1.0 // HPF (~UM) subtract GB with this sigma from the data tile 
	    double  lmax_flt_lsigma =  clt_parameters.imp.cuas_lmax_flt_lsigma; // 1.0 // LPF - GB result of the previous subtraction
	    double  lmax_flt_scale =  clt_parameters.imp.cuas_lmax_flt_scale;   // 5.0 // scale filtering result
	    boolean center_targ =    clt_parameters.imp.cuas_center_targ;
		String ps = ((prefix != null)?prefix:"") + "-OFFS"+corr_offset+"-PAIRS"+corr_pairs+"-RSTR"+rstr+"-RMS"+lma_rms+
				"-SRMS"+lma_arms+"-RRMS"+lma_rrms+"-AMP"+lma_mina+"-FZ"+fat_zero+"-PCRA"+precorr_ra+"-PS"+corr_ra_step+
				"-TUM"+temporal_um+"-TT"+tum_threshold+"-MF"+lmax_fraction+
				"-HR"+lmax_hack_ridge+
				"-NL"+lmax_flt_neglim+"-FH"+lmax_flt_hsigma+"-FL"+lmax_flt_lsigma+"-FS"+lmax_flt_scale;
		if (center_targ) {
			ps+="-CENT";
		}
		return ps;
	}

	public static String getParametersSuffixRslt(
			 CLTParameters       clt_parameters,	
			 String              prefix) {
		int     corr_offset =    clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =     clt_parameters.imp.cuas_corr_pairs;
		
		int     temporal_um =    clt_parameters.imp.cuas_temporal_um;
		double  tum_threshold =  clt_parameters.imp.cuas_tum_threshold;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
		
		int     precorr_ra =     clt_parameters.imp.cuas_precorr_ra;
		int     corr_ra_step =   clt_parameters.imp.cuas_corr_step;
		double  rstr =           clt_parameters.imp.cuas_rstr;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
	    double  lma_rms =        clt_parameters.imp.cuas_lma_rms; // =       1.5;  // Maximal RMS, regardless of amplitude
	    double  lma_arms =       clt_parameters.imp.cuas_lma_arms; // =      0.06; // Maximal absolute RMS, sufficient for any amplitude
	    double  lma_rrms =       clt_parameters.imp.cuas_lma_rrms; // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
	    double  lma_mina =       clt_parameters.imp.cuas_lma_mina; // =      1.0;  // Minimal A (amplitude)
	    double  fat_zero =       clt_parameters.imp.cuas_fat_zero;
	    double  lmax_fraction =  clt_parameters.imp.cuas_lmax_fraction; // 0.7;     // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
	    double  lmax_hack_ridge= clt_parameters.imp.cuas_lmax_hack_ridge; // 0.45;    // Hack for ridges: remove horizontal streaks, where average(abs()) for the line exceeds fraction of abs max 0.32-good, 0.55 - bad
	    
	    double  lmax_flt_neglim= clt_parameters.imp.cuas_lmax_flt_neglim;   // -0.3;   // limit negative data to reduce ridge influence (make -10 to disable)        
	    double  lmax_flt_hsigma= clt_parameters.imp.cuas_lmax_flt_hsigma; // 1.0 // HPF (~UM) subtract GB with this sigma from the data tile 
	    double  lmax_flt_lsigma= clt_parameters.imp.cuas_lmax_flt_lsigma; // 1.0 // LPF - GB result of the previous subtraction
	    double  lmax_flt_scale = clt_parameters.imp.cuas_lmax_flt_scale;   // 5.0 // scale filtering result
//	    boolean center_targ =    clt_parameters.imp.cuas_center_targ;
	    
		double  min_score_lma =  clt_parameters.imp.cuas_score_lma;
		int     min_seq=         clt_parameters.imp.cuas_min_seq;    // 3;     // minimal number of consecutive key frames for the same target
		int     enough_seq=      clt_parameters.imp.cuas_enough_seq; // 5;     // minimal number of consecutive key frames for the same target
		double  seq_travel=      clt_parameters.imp.cuas_seq_travel; // 3.0;   // minimal diagonal of the bounding box that includes sequence to be considered "cuas_enough_seq". Filtering out atmospheric fluctuations
		
	    
		String ps = ((prefix != null)?prefix:"") + "-OFFS"+corr_offset+"-PAIRS"+corr_pairs+"-RSTR"+rstr+"-RMS"+lma_rms+
				"-SRMS"+lma_arms+"-RRMS"+lma_rrms+"-AMP"+lma_mina+"-FZ"+fat_zero+"-PCRA"+precorr_ra+"-PS"+corr_ra_step+
				"-TUM"+temporal_um+"-TT"+tum_threshold+"-MF"+lmax_fraction+
				"-HR"+lmax_hack_ridge+
				"-NL"+lmax_flt_neglim+"-FH"+lmax_flt_hsigma+"-FL"+lmax_flt_lsigma+"-FS"+lmax_flt_scale+
				"-MS"+min_score_lma+"-ML"+min_seq+"-SL"+enough_seq+"-MT"+seq_travel;
		
//		if (center_targ) {
//			ps+="-CENT";
//		}
		return ps;
	}
	
	
	
	
	// =============================== targets_multi ======================================
	
	
	// Uses 	final static public  int MAX_ALT_TARGETS = 10; // convenient to be power of 10

	public static double [][][][] getAccumulatedCoordinatesMulti(
			final boolean       keep_failed, // keep failed targets
			final int           when, // set if >=0 for failures
			final boolean       centered, // accum_data was centered, use target_sequence[][][CuasMotionLMA.RSLT_X], target_sequence[][][CuasMotionLMA.RSLT_Y] (if not NaN)
			final double [][][] probed_sequence, // contains vector_fields data, single target per tile
			final float  [][]   accum_data,    // should be around 0, no low-freq
		    final double        lmax_fraction, // 0.7;     // Check if local maximum is separated from the surrounding by this fraction of the maximum value
		    final double        lmax_flt_neglim,   // -0.3;   // limit negative data to reduce ridge influence (>= to disable)        
	        final double        lmax_flt_hsigma,	 // 1.0 // HPF (~UM) subtract GB with this sigma from the data tile     
	        final double        lmax_flt_lsigma,   // 1.0 // LPF - GB result of the previous subtraction
	        final double        lmax_flt_scale,    // 5.0 // scale filtering result
	        final double []     sky_mask,
	        final double        sky_threshold,    // 0.9 // minimal value of the sky mask where target is possible
	        final double        lma_horizon,
		    final double        lmax_hack_ridge, // 0.45;    // Hack for ridges: remove horizontal streaks, where average(abs()) for the line exceeds fraction of abs max 0.32-good, 0.55 - bad
		    final double        lmax_radius,   // 3;       // look inside cuas_lmax_radius* 2 + 1 square for the local maximum isolation
		    final double        centered_radius,
		    final double        centered_radius_blur, 
		    final double        pix_tolerance, // 
//			final boolean       lmax_zero,     // true;    // zero all data outside this radius from the maximum
			final double        centroid_radius,
			final int           n_recenter,    //  re-center window around new maximum. 0 -no refines (single-pass)
			final int           tilesX,
			final boolean       no_border,
		// Moving target LMA	
			final double        lma_sigma,
			final double        wnd_pedestal,
			final double        lma_r0,
			final double        lma_ovrsht,
		// CUAS Motion LMA parameters
			final boolean       lma_fit_xy,		
			final boolean       lma_fit_a,						
			final boolean       lma_fit_c,
			final boolean       lma_fit_r,
			final boolean       lma_fit_k,			
			final double        lambda,
			final double        lambda_good,
			final double        lambda_bad,
			final double        lambda_max,
			final double        rms_diff,
			final int           num_iter,
			final double        pre_min_a,   // pre-filter minimal LMA-A (half finbal?)
			final int           min_keep, //  2; // keep at least this number of candidates before using cuas_lma_pre_mina filter
			final float [][]    accum_debug,
			final int           debugLevel){
		// Will not set [RSLT_FAIL] = FAIL_NONE, but it is not final before
		// apply masking in centered mode
		// final int min_keep = 2; // Keep at least this number of (preliminary) best candidates
		final int tile1 = GPUTileProcessor.DTT_SIZE;
		final int tile2 = 2 * tile1;
		final double [] mask_centered = (centered && (centered_radius > 0)) ? new double [tile2* tile2]: null;
		final boolean [] disabled_centered = (mask_centered != null) ? (new boolean [mask_centered.length]) : null;
		if (mask_centered != null) {
			double lmax_inner_radius = centered_radius * (1.0-centered_radius_blur);
			double lmax_outer_radius = centered_radius * (1.0+centered_radius_blur);
			double lmax_inner_radius2 = lmax_inner_radius * lmax_inner_radius;
			double lmax_outer_radius2 = lmax_outer_radius * lmax_outer_radius;
			
			
			for (int y = 0; y < tile2; y++) {
				int dy = y - tile1;
				int dy2 = dy*dy;
				for (int x = 0; x < tile2; x++) {
					int dx = x - tile1;
					int r2 = dy2 + dx*dx;
					int indx = x + tile2 * y;
					if (r2 < lmax_inner_radius2) {
						mask_centered[indx] = 1.0;
						disabled_centered[indx] = false;
					} else if (r2 > lmax_outer_radius2) {
						mask_centered[indx] = 0.0;
						disabled_centered[indx] = true;
					} else {
						double r = Math.sqrt(r2);
						mask_centered[indx] = 0.5 * (1.0 + Math.cos(Math.PI*(r-lmax_inner_radius)/(lmax_outer_radius-lmax_inner_radius)));
						disabled_centered[indx] = false;
					}
				}
			}
		}
		
		final double pix_tolerance2= pix_tolerance * pix_tolerance;
		if (debugLevel > 0 ) {
			System.out.println("getAccumulatedCoordinates(): "+((sky_mask == null)? "NOT ":"")+"using sky_mask");
		}
		final boolean dbg_hack_ridge = (debugLevel > 0); // true;
		final int lmax_iradius = (int) Math.floor(lmax_radius);
		final int num_seq = probed_sequence.length; // same as accum_data.length
		final int num_tiles = probed_sequence[0].length;
		final int num_pixels = accum_data[0].length;
		final int width = GPUTileProcessor.DTT_SIZE * tilesX;
		final int height = num_pixels / width; 
		final double [][][][] targets_new = 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 int dbg_tile = -(38 + 45 * 80); //(38 + 45 * 80);
		final int dbg_seq = 13;
		final boolean use_filters = (lmax_flt_hsigma > 0) && (lmax_flt_lsigma > 0) && (lmax_flt_scale > 0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					double [] pix_tile = new double [tile2 * tile2];
					double [] pix_tile_filtered0 = use_filters ? new double [tile2 * tile2]:null;
					double [] pix_tile_filtered =  use_filters ? new double [tile2 * tile2]:null;
					boolean [] disabled = new boolean[pix_tile.length];
					TileNeibs tn = new TileNeibs(tile2,tile2);
					DoubleGaussianBlur gb = new DoubleGaussianBlur();
					CuasMotionLMA cuasMotionLMA = new CuasMotionLMA(
							tile2,      // int         width,
							lma_sigma, // double     sigma);
							wnd_pedestal);
					// may be faster if process only where vector_field[nseq][ntile] is not null
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						if (accum_debug != null) {
							accum_debug[nSeq] = new float [num_pixels];
							Arrays.fill(accum_debug[nSeq], Float.NaN);
						}
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if (probed_sequence[nSeq][ntile] != null) {
								double [] probed_target = probed_sequence[nSeq][ntile].clone();
								if ((nSeq==dbg_seq) && (ntile == dbg_tile)) {
									System.out.println("getAccumulatedCoordinatesMulti(): nSeq="+nSeq+", ntile="+ntile);
								}
								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
								int horizon_y = (int) Math.ceil (lma_horizon)-py0;
								if (centered && !Double.isNaN(probed_target[CuasMotionLMA.RSLT_Y])) {
									horizon_y = (int) Math.ceil (lma_horizon - probed_target[CuasMotionLMA.RSLT_Y])-py0;
								}
								int y_mask_offs =  (centered && !Double.isNaN(probed_target[CuasMotionLMA.RSLT_Y])) ? ((int) Math.round(probed_target[CuasMotionLMA.RSLT_Y])):0;
								int x_mask_offs =  (centered && !Double.isNaN(probed_target[CuasMotionLMA.RSLT_X])) ? ((int) Math.round(probed_target[CuasMotionLMA.RSLT_X])):0;
								// copy 16x16 tile
								{
									int indx_dst = 0;
									for (int y = 0; y < tile2; y++) {
										int indx_src = px0 + (py0 +y) * width;
										if (y >=  horizon_y) {
											for (int x = 0; x < tile2; x++) {
												pix_tile[indx_dst++] = 0;
												indx_src++; // float-> double
											}
										} else {
											for (int x = 0; x < tile2; x++) {
												pix_tile[indx_dst++] = accum_data[nSeq][indx_src++]; // float-> double
											}
										}
									}
								}
								
								
								
								// find absolute maximum (before hacking and masking)
								if (disabled_centered != null) {
									System.arraycopy(disabled_centered, 0, disabled, 0, disabled.length);
								} else {
									Arrays.fill(disabled,false);
								}
								int ntile_amax0 = TileNeibs.getAmaxTile(
										pix_tile); //double [] data)
								if (lmax_hack_ridge > 0) {
//									disabled = new boolean[pix_tile.length];
									double thresh = pix_tile[ntile_amax0] * lmax_hack_ridge;
									for (int row=0; row < tile2; row++) {
										double s = 0;
										int n = 0;
										for (int col = 0; col < tile2; col++) {
											double d = Math.abs(pix_tile[row*tile2+col]);
											if (!Double.isNaN(d)) {
												s += d;
												n++;
											}
										}
										s/= n;
										if (s > thresh) {
											for (int col = 0; col < tile2; col++) {
												int pindx = row*tile2+col;
												disabled[pindx] = true;
												pix_tile[pindx] = 0.0;
											}											
										}
									}
								}
								// in centered mode (and only in centered mode) mask out peripheral areas, keep only around the center
								if (mask_centered != null) {
									for (int i = 0; i < pix_tile.length; i++) {
										pix_tile[i] *= mask_centered[i];
									}
								}
								
								if (sky_mask != null) {
									for (int row = 0; row < tile2; row++) {
										int mask_y = Math.min(height-1, Math.max(0, py0 + y_mask_offs + row));
										for (int col = 0; col < tile2; col++) {
											int mask_x = Math.min(width-1, Math.max(0, px0 + x_mask_offs + col));
											int pindx = col+row*tile2;
											double mask_val = sky_mask[mask_x + mask_y * width];
											pix_tile[pindx] *= mask_val;
											if (mask_val < sky_threshold) {
												disabled[pindx] = true;  // disallow maximums in this area
											}
										}
									}
									if ((nSeq==dbg_seq) && (ntile == dbg_tile)) {
										System.out.println("getAccumulatedCoordinatesMulti(): nSeq="+nSeq+", ntile="+ntile+
												" py0="+py0+" px0="+px0+", y_mask_offs="+y_mask_offs+", x_mask_offs="+x_mask_offs);
										for (int row = 0; row < tile2; row++) {
											int mask_y = Math.min(height-1, Math.max(0, py0 + y_mask_offs + row));
											for (int col = 0; col < tile2; col++) {
												int mask_x = Math.min(width-1, Math.max(0, px0 + x_mask_offs + col));
												int pindx = col+row*tile2;
												double mask_val = sky_mask[mask_x + mask_y * width];
												if (col == 0) {
													System.out.println("row="+row+", mask_y="+mask_y+"col="+col+", mask_x="+mask_x+", mask_val="+mask_val+", pindx="+pindx);
												}
											}
										}
									}
								}
								// find absolute maximum (after "hacking" and masking
								int ntile_amax = TileNeibs.getAmaxTile(
										pix_tile); //double [] data)
								// filtering tile for better maximum selection
								if (pix_tile_filtered != null) {
									System.arraycopy(pix_tile, 0, pix_tile_filtered, 0, pix_tile_filtered.length);
								  	if (lmax_flt_neglim <0) {
										for (int i = 0; i < pix_tile_filtered.length; i++) {
											pix_tile_filtered[i] = Math.max(lmax_flt_neglim, pix_tile_filtered[i]);
										}
								  	}
									System.arraycopy(pix_tile_filtered, 0, pix_tile_filtered0,  0, pix_tile_filtered.length);
									gb.blurDouble(
											pix_tile_filtered, //
											tile2,
											tile2,
											lmax_flt_hsigma,   // double sigmaX,
											lmax_flt_hsigma,   // double sigmaY,
											0.01);             // double accuracy)
									for (int i = 0; i < pix_tile_filtered.length; i++) {
										pix_tile_filtered[i] = pix_tile_filtered0[i] - pix_tile_filtered[i];
									}
									gb.blurDouble(
											pix_tile_filtered, //
											tile2,
											tile2,
											lmax_flt_lsigma,   // double sigmaX,
											lmax_flt_lsigma,   // double sigmaY,
											0.01);             // double accuracy)
									for (int i = 0; i < pix_tile_filtered.length; i++) {
										pix_tile_filtered[i] *= lmax_flt_scale;
									}
									ntile_amax = TileNeibs.getAmaxTile(
											pix_tile_filtered); //double [] data)
								}
								
								
								
								double [] pix_tile_centroid = (pix_tile_filtered != null) ? pix_tile_filtered : pix_tile;
								// accum_debug will apply circular mask only if no filtering. With filtering it will contain filtered (HPF+LPF) data
								if (accum_debug != null) {
									int indx_src = 0;
									for (int y = 0; y < tile2; y++) {
										int indx_dst = px0 + (py0 +y) * width;
										for (int x = 0; x < tile2; x++) {
											accum_debug[nSeq][indx_dst++] = (float) pix_tile_centroid[indx_src++]; // double -> float
										}
									}
								}
								
								
								double amax_val = pix_tile_centroid[ntile_amax];
								double lmax_val = amax_val;

								int [] max_indices = tn.getLocalMaxes(
										pix_tile_centroid, //double [] data,
										true); // boolean   exclude_margins)
								ArrayList<Integer> max_candidate = new ArrayList<Integer>();

								for (int indx:max_indices) {
									boolean isolated = tn.isMaxIsolated(
											pix_tile_centroid,      // double [] data,
											indx,          // int       ntile,
											lmax_fraction, // double    fraction,
											lmax_iradius); //int       radius)) {
									if (disabled[indx]) {
										if (dbg_hack_ridge && isolated) {
											System.out.println ("getAccumulatedCoordinatesMulti(): Removing ridge max for nSeq="+nSeq+", ntile="+ntile+", indx="+indx);
										}
										isolated = false;
									}
									if (isolated) {
										max_candidate.add(indx);
//										double max_val = pix_tile_centroid[indx];
									}
								}
								
								if (!max_candidate.isEmpty()) {
									// Sort descending values at maximal pixels
									Collections.sort(max_candidate, new Comparator<Integer>() {
										@Override
										public int compare(Integer lhs, Integer rhs) { // descending
											return (pix_tile_centroid[lhs] > pix_tile_centroid[rhs]) ? -1 : ((pix_tile_centroid[lhs] < pix_tile_centroid[rhs]) ? 1 : 0) ;
										}
									});
									double [][] targets = new double [max_candidate.size()][];
									ArrayList<Integer> targets_list = new ArrayList<Integer>();
									for (int indx = 0; indx <  max_candidate.size(); indx++) {
										int use_max = max_candidate.get(indx);
										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,            //          fpn_mask,        // boolean [] fpn_mask,
												false,           // boolean    ignore_border, // only if fpn_mask != null - ignore tile if maximum touches fpn_mask
												no_border,       // boolean    exclude_margins,
												true,            // boolean    calc_fraction,
												use_max,         // int        imax,  // index of the maximum in data[]
												false);          // boolean   debug)
										if (mv != null) {
											double [] target = probed_target.clone(); // includes motion vectors
											target[CuasMotionLMA.RSLT_CENT_MX] = 0.0;
											target[CuasMotionLMA.RSLT_CENT_X] =  mv[0];
											target[CuasMotionLMA.RSLT_CENT_Y] =  mv[1];
											target[CuasMotionLMA.RSLT_CENT_MX] = mv[2];
											if (mv.length > 3) {
												target[CuasMotionLMA.RSLT_CENT_F] =  mv[3];
											}
											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,
													lmax_val,   // double     lmax_val,
													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
													nSeq,        // int    dbg_seq,
													ntile,       // int    dbg_tile,
													indx,        // int    dbg_index,
													debugLevel-3); // int    debug_level)
											target[CuasMotionLMA.RSLT_ITERS] = rslt; // will save -1 (failure) also
											if (rslt >= 0) {
												cuasMotionLMA.setResult(target);
												int col = GPUTileProcessor.DTT_SIZE + (int) Math.round(target[CuasMotionLMA.RSLT_X]);
												int row = GPUTileProcessor.DTT_SIZE + (int) Math.round(target[CuasMotionLMA.RSLT_Y]);
												int pindx = col+row*tile2;
												if (!tn.isInside(pindx)) {
													target[CuasMotionLMA.RSLT_ITERS] = -2; // outside of the 16x16 tiles -> LMA failed to converge
													target[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_LMA;
													if (when >=0) {
														target[CuasMotionLMA.RSLT_WHEN] = when;
													}
												} else if (disabled[pindx]) {
													target[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_HORIZON;
													if (when >=0) {
														target[CuasMotionLMA.RSLT_WHEN] = when;
													}
													// make it only if there are more than (1? good ones)
													// TODO: Improve. Apply filter after sortig results
												} else if ((target[CuasMotionLMA.RSLT_A] < pre_min_a) && (targets_list.size() >= min_keep)) {
													target[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_A_PRE;
													if (when >=0) {
														target[CuasMotionLMA.RSLT_WHEN] = when;
													}
												} 
												if (centered && !Double.isNaN(probed_sequence[nSeq][ntile][CuasMotionLMA.RSLT_X])) {
													double x0 = probed_sequence[nSeq][ntile][CuasMotionLMA.RSLT_X];
													double y0 = probed_sequence[nSeq][ntile][CuasMotionLMA.RSLT_Y];
													target[CuasMotionLMA.RSLT_CENT_X] += x0;
													target[CuasMotionLMA.RSLT_CENT_Y] += y0;
													target[CuasMotionLMA.RSLT_X] +=      x0;
													target[CuasMotionLMA.RSLT_Y] +=      y0;
												}
												if ((ntile == dbg_tile) && (nSeq == dbg_seq)) { // if ((nSeq==dbg_seq) && (ntile == dbg_tile)) {
													// px0, py0 - top/left corner of 16x16
													int ipx = (int) (px0 + GPUTileProcessor.DTT_SIZE + target[CuasMotionLMA.RSLT_X]);
													int ipy = (int) (py0 + GPUTileProcessor.DTT_SIZE + target[CuasMotionLMA.RSLT_Y]);
													int mask_x = Math.min(width-1,  Math.max(0, ipx));
													int mask_y = Math.min(height-1, Math.max(0, ipy));
													double mask_val = sky_mask[mask_x + mask_y * width];
													System.out.println("getAccumulatedCoordinatesMulti(): nSeq="+nSeq+", ntile="+ntile+
															", centered="+centered+", pindx="+pindx+
															", ipx="+ipx+", ipy="+ipy+
															", mask_x="+mask_x + ", mask_y="+mask_y+", mask_val = "+mask_val);
													System.out.println();
													for (int prow = 0; prow  < tile2; prow++) {
														for (int pcol=0; pcol<tile2; pcol++) {
															System.out.print(disabled[pcol+prow*tile2]?".":"+");
														}
														System.out.println();
													}

												}
											} else { // if (rslt >= 0) {
												target[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_LMA;
												if (when >=0) {
													target[CuasMotionLMA.RSLT_WHEN] = when;
												}
											}
											target[CuasMotionLMA.RSLT_CENTERED] = centered? 1.0 :0.0;
											if (keep_failed || Double.isNaN(target[CuasMotionLMA.RSLT_FAIL]) || (target[CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE )) {
												targets[indx] = target;
												targets_list.add(indx);
											}// keep_failed
										} // if (mv != null) {

									}
									// sort targets_list, descending LMA amplitude, failed after not failed
									// Sort descending values at maximal pixels
									Collections.sort(targets_list, new Comparator<Integer>() {
										@Override
										public int compare(Integer lhs, Integer rhs) { // descending
											if ((targets[lhs][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) && (targets[rhs][CuasMotionLMA.RSLT_FAIL] != CuasMotionLMA.FAIL_NONE)) {
												return -1;
											} else  if ((targets[lhs][CuasMotionLMA.RSLT_FAIL] != CuasMotionLMA.FAIL_NONE) && (targets[rhs][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE)) {
												return 1;
											}
											return (targets[lhs][CuasMotionLMA.RSLT_A]> targets[rhs][CuasMotionLMA.RSLT_A]) ? 
													-1 : ((targets[lhs][CuasMotionLMA.RSLT_A] < targets[rhs][CuasMotionLMA.RSLT_A]) ? 1 : 0);
										}
									});
									if (ntile == dbg_tile) {
										System.out.println("getAccumulatedCoordinatesMulti(): nSeq="+nSeq+", ntile="+ntile+", candidates:"+max_candidate.size()+", filtered:"+targets_list.size());
									}

									// Remove weaker duplicates if they converge to (almost) the same LMA x,y starting from the different pixels. 
									for (int indx2 = 1; indx2 < targets_list.size(); indx2++) {
										double tx2 = targets[targets_list.get(indx2)][CuasMotionLMA.RSLT_X];
										double ty2 = targets[targets_list.get(indx2)][CuasMotionLMA.RSLT_Y];
										for (int indx1 = 0; indx1 < indx2; indx1++) {
											double tx1 = targets[targets_list.get(indx1)][CuasMotionLMA.RSLT_X];
											double ty1 = targets[targets_list.get(indx1)][CuasMotionLMA.RSLT_Y];
											double dtx = tx2-tx1;
											double dty = ty2-ty1;
											double dt2 = dtx*dtx+dty*dty;
											if (dt2 < pix_tolerance2) {// indx2 is a duplicate, remove its reference from the list 
												targets_list.remove(indx2--); // indx2 will be incremented again at the end of the loop
												break; // inner cycle, continue with the for (int indx2 = 1; indx2 < targets_list.size(); indx2++)  cycle
											}
										}
									}

									// if too many - discard
									// Remove weaker duplicates if they converge to (almost) the same LMA x,y starting from the different pixels. 
									double [][] targets_sorted = new double [Math.min(targets_list.size(), MAX_ALT_TARGETS)][];
									for (int i = 0; i < targets_sorted.length; i++) {
										targets_sorted[i] = targets[targets_list.get(i)];
									}
									targets_new[nSeq][ntile] = targets_sorted; // will always put something, maybe just []
								}  else {// if (!max_candidate.isEmpty()) {
									targets_new[nSeq][ntile] = new double[0][];
								}// if (!max_candidate.isEmpty())  else
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return targets_new;
	}
	
	
	
	
	public static double [][][][] convertToMultiTarget(
			final double [][][] target_sequence) {
		final int num_seq = target_sequence.length;
		final int num_tiles = target_sequence[0].length;
		final double [][][][] target_multi = 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() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) if (target_sequence[nSeq][ntile] != null) {
							target_multi[nSeq][ntile] = new double[][] {target_sequence[nSeq][ntile]};
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return target_multi;
	}
	
	
	public static double [][][] convertFromMultiTarget(
			final double [][][][] target_multi,
			final double          minimal_score,
			final int             min_seq, // minimal sequence length
			final int             enough_seq, // good regardless of scores 
			final int []          remain){
		final int num_seq = target_multi.length;
		final int num_tiles = target_multi[0].length;
		final double [][][] target_sequence = new double[num_seq][num_tiles][];
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		System.out.println("convertFromMultiTarget(): minimal_score="+minimal_score+", min_seq="+min_seq+", enough_seq="+enough_seq);

		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()) {
						if (remain != null) {
							remain[nSeq] = 0;
						}
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							double [][] targets = target_multi[nSeq][ntile];
							if ((targets != null) && (targets.length > 0)) {
								if (targets[0][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) {
									if (targets[0][CuasMotionLMA.RSLT_MATCH_LENGTH] >=(enough_seq-1)) {
										target_sequence[nSeq][ntile] = targets[0];
										remain[nSeq]++;
									} else if ((targets[0][CuasMotionLMA.RSLT_QSCORE] >= minimal_score) && (targets[0][CuasMotionLMA.RSLT_MATCH_LENGTH] >= (min_seq-1))) {
										target_sequence[nSeq][ntile] = targets[0];
										if (remain != null) {
											remain[nSeq]++;
										}
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return target_sequence;
	}

	
	public static void sortMultiTargets(
			final double [][][][] target_multi,
			final boolean         trim_nulls) { // trim null targets
		final int num_seq = target_multi.length;
		final int num_tiles = target_multi[0].length;
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							double [][] targets = target_multi[nSeq][ntile];
							if ((targets != null) && (targets.length > 0)) { //&& (target_multi.length > 1)) {
								if (target_multi.length > 1) {
									Integer [] indices = new Integer[targets.length];
									for (int i = 0; i < indices.length; i++) {
										indices[i] = i;
									}
									Arrays.sort(indices, new Comparator<Integer>() { // decreasing weight
										@Override
										public int compare(Integer lhs, Integer rhs) { // descending score
											if      ((targets[lhs] != null) && (targets[rhs] == null)) return -1;
											else if ((targets[lhs] == null) && (targets[rhs] != null)) return  1;
											else if ((targets[lhs] == null) && (targets[rhs] == null)) return  0;
											// removed used from failed - they are ordered separately, all3 variants (centered, uncentered, used
											boolean fail_lhs = (targets[lhs] == null) ||
													(targets[lhs][CuasMotionLMA.RSLT_FAIL] != CuasMotionLMA.FAIL_NONE);
	//												 || (targets[lhs][CuasMotionLMA.RSLT_CENTERED] == CuasMotionLMA.CENTERED_USED); 
											boolean fail_rhs = (targets[rhs] == null) ||
													(targets[rhs][CuasMotionLMA.RSLT_FAIL] != CuasMotionLMA.FAIL_NONE);
//													 || (targets[rhs][CuasMotionLMA.RSLT_CENTERED] == CuasMotionLMA.CENTERED_USED); 
											if (!fail_lhs && fail_rhs) {
												return -1;
											} else if (fail_lhs && !fail_rhs) {
												return 1;
											}
											// centered are better than uncentered (before score) and used are the worst
											if (targets[lhs][CuasMotionLMA.RSLT_CENTERED] > targets[rhs][CuasMotionLMA.RSLT_CENTERED]) {
												return -1;
											} else if (targets[lhs][CuasMotionLMA.RSLT_CENTERED] < targets[rhs][CuasMotionLMA.RSLT_CENTERED]) {
												return 1;
											}
											return (targets[lhs][CuasMotionLMA.RSLT_QSCORE]> targets[rhs][CuasMotionLMA.RSLT_QSCORE]) ? 
													-1 : ((targets[lhs][CuasMotionLMA.RSLT_QSCORE] < targets[rhs][CuasMotionLMA.RSLT_QSCORE]) ? 1 : 0);
										}
									});
									double [][]  targets_orig = targets.clone(); // shallow
									for (int i = 0; i < targets.length; i++) {
										targets[i] = targets_orig[indices[i]];
									}
								}
								if (trim_nulls && (targets.length > 0) && (targets[targets.length-1] == null)) {
									int indx_null;
									for (indx_null = targets.length-1; (indx_null >=0) && (targets[indx_null] == null); indx_null--);
									double [][] targets_trimmed = new double[indx_null + 1][];
									for (int i = 0; i < targets_trimmed.length; i++) {
										targets_trimmed[i] = targets[i];
									}
									target_multi[nSeq][ntile] = targets_trimmed;
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return;
	}

	public static int [] removeWeakerDuplicates( // needs sortMultiTargets and after
			final double [][][][] target_multi,
			final double          pix_tolerance) {
		final double pix_tolerance2 = pix_tolerance * pix_tolerance;
		final int num_seq = target_multi.length;
		final int num_tiles = target_multi[0].length;
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		final AtomicInteger adup = new AtomicInteger(0);
		final AtomicInteger adup_good = 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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							double [][] targets = target_multi[nSeq][ntile];
							if (targets != null) {
								for (int indx2 = 1; indx2 < targets.length; indx2++) if (targets[indx2] != null) {
									double tx2 = targets[indx2][CuasMotionLMA.RSLT_X];
									double ty2 = targets[indx2][CuasMotionLMA.RSLT_Y];
									for (int indx1 = 0; indx1 < indx2; indx1++) if (targets[indx1] != null){
										double tx1 = targets[indx1][CuasMotionLMA.RSLT_X];
										double ty1 = targets[indx1][CuasMotionLMA.RSLT_Y];
										double dtx = tx2-tx1;
										double dty = ty2-ty1;
										double dt2 = dtx*dtx+dty*dty;
										if (dt2 < pix_tolerance2) {// indx2 is a duplicate, remove its reference from the list
											if ((targets[indx2][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) &&
													(targets[indx2][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE)) {
												adup_good.getAndIncrement();
											}
											targets[indx2] = null;
											adup.getAndIncrement();
											break; // for (int indx1 = 0; indx1 < indx2; indx1++) if (targets[indx1] != null){
										}
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return new int[] {adup.get(), adup_good.get()};
	}
	
	
	
	
	
	
	/**
	 * Calculate image x,y for each target half keyframe interval before the keyframe and half-interval after.
	 * The results are stored in yet empty fields of the target data 
	 * It is used to filter out stray targets, these coordinates for the consecutive frames should be close:
	 * "after" for the earlier keyframe and "before" for the later one.
	 * @param targets [keyframe_number][tile][ntarget][] target positions of the targets relative to the tile center and motion vectors
	 * @param good_only exclude failed tiles
	 * @param interseq_scale multiply velocity vector to get pixel offset in the middle between the two keyframes
	 * @param tilesX number of tiles in a row   
	 * @param debugLevel  debug level
	 */
	public static void getHalfBeforeAfterPixXY(
			final double [][][][] targets_multi,
			final boolean         good_only,
			final double          interseq_scale,
			final int             tilesX,
			final int             debugLevel) {
		final int num_seq =   targets_multi.length;
		final int num_tiles = targets_multi[0].length;
		final int tileSize = GPUTileProcessor.DTT_SIZE;
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							double [][] targets = targets_multi[nSeq][ntile];
							if (targets != null) {
								for (int ntarg = 0; ntarg < targets.length; ntarg++) {
									double [] target = targets[ntarg];
									if (target != null) {
										if (!good_only || (target[CuasMotionLMA.RSLT_FAIL] == 0)) {
											int tileX = ntile % tilesX;
											int tileY = ntile / tilesX;
											double xc = tileSize * tileX + tileSize/2;  
											double yc = tileSize * tileY + tileSize/2;
											double xtk = xc + target[CuasMotionLMA.RSLT_X];  
											double ytk = yc + target[CuasMotionLMA.RSLT_Y];
											double dx = target[CuasMotionLMA.RSLT_VX] * interseq_scale;
											double dy = target[CuasMotionLMA.RSLT_VY] * interseq_scale;
											target[CuasMotionLMA.RSLT_BX] = xtk - dx; // before, x
											target[CuasMotionLMA.RSLT_BY] = ytk - dy; // before, y
											target[CuasMotionLMA.RSLT_AX] = xtk + dx; // after, x
											target[CuasMotionLMA.RSLT_AY] = ytk + dy; // after, y
											//									pix_xy[nSeq][ntile] = baxy;
										}
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return;
	}
	
	/*
	 * Needs RSLT_BX, RSLT_BY, RSLT_AX, RSLT_AY to be known
	 */
	public static void calcMathingTargetsLengths( // calculate number of consecutive keyframes connected to each target
			final double [][][][] targets_multi,
			final double          max_mismatch,     // if <=0, do not calculate  mismatch_ba and filter
			final int             tilesX) {
		final double max_mismatch2 = max_mismatch * max_mismatch; 
		// Will only consider not-failed targets 
		final int num_seq = targets_multi.length;
		final int num_tiles = targets_multi[0].length;
		final int [][][][] len_ba = new int [2][num_seq][num_tiles][]; // first index is before/after
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		final int dbg_tile = -(32 + 44 * 80);
		if (dbg_tile >=0) {
			System.out.println("calcMathingTargetsLengths().0 max_mismatch="+max_mismatch+", max_mismatch2="+max_mismatch2);
		}
		for (int aba = 0; aba <=1; aba++) {// first pass - calculating connected keyframes before each current keyframe tile/target alternative
			final int ba = aba;
			for (int nseq = 0; nseq < num_seq; nseq++) {
				final boolean first = (nseq == 0); 
				final int fnseq = (ba == 0) ? nseq : (num_seq - nseq -1) ;
				final int fnseq_other = (ba == 0) ? (nseq - 1) : (num_seq - nseq); // not valid for first == true 
				ai.set(0);
				for (int ithread = 0; ithread < threads.length; ithread++) {
					threads[ithread] = new Thread() {
						public void run() {
							TileNeibs tn = new TileNeibs(tilesX, num_tiles/tilesX); 
							for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile= ai.getAndIncrement()) if (targets_multi[fnseq][nTile] != null){
								double [][] targets =targets_multi[fnseq][nTile];
								int [] lengths = new int [targets.length];
								if (nTile==dbg_tile) {
									System.out.println("calcMathingTargetsLengths().1 ba="+ba+", fnseq="+fnseq+", targets.length="+targets.length);
								}

								if (!first) { 
									for (int ntarg = 0; ntarg < targets.length; ntarg++) {
										double [] target = targets[ntarg];
										if ((target != null) && (target[CuasMotionLMA.RSLT_FAIL]==CuasMotionLMA.FAIL_NONE)){
											double this_x = target[CuasMotionLMA.RSLT_BX + 2 * ba]; // this target's "before" (when ba=0) or "after" (when ba=1) absolute X 
											double this_y = target[CuasMotionLMA.RSLT_BY + 2 * ba]; // this target's absolute Y
											// compare with every previous (/next) neighbor tiles/targets
											for (int dy = -1; dy <= 1; dy++) {
												for (int dx = -1; dx <= 1; dx++) {
													int ntile_other = tn.getNeibIndex(nTile, dx, dy);
													if (ntile_other >= 0) {
														double [][] targets_other = targets_multi[fnseq_other][ntile_other];
														if (targets_other != null) {
															for (int ntarg_other = 0; ntarg_other < targets_other.length; ntarg_other++) {
																double [] target_other = targets_other[ntarg_other];
																if ((target_other != null) && (target_other[CuasMotionLMA.RSLT_FAIL]==CuasMotionLMA.FAIL_NONE)){
																	if (len_ba[ba][fnseq_other][ntile_other][ntarg_other] > (lengths[ntarg] -1)) { // can potentially improve
																		double other_x = target_other[CuasMotionLMA.RSLT_BX + 2 * (1 - ba)];  // opposite direction
																		double other_y = target_other[CuasMotionLMA.RSLT_BY + 2 * (1 - ba)];  // opposite direction
																		double mismatch_x = other_x - this_x;
																		double mismatch_y = other_y - this_y;
																		double mismatch2 = mismatch_x * mismatch_x + mismatch_y * mismatch_y;
																		if (mismatch2 <= max_mismatch2) {
																			lengths[ntarg] = len_ba[ba][fnseq_other][ntile_other][ntarg_other] + 1;
																			if (nTile==dbg_tile) {
																				System.out.println("calcMathingTargetsLengths().2 ba="+ba+", fnseq = "+fnseq+", nTile="+nTile+", ntarg="+ntarg+
																						", fnseq_other="+fnseq_other+", ntile_other="+ntile_other+", ntarg_other="+ntarg_other+
																						", mismatch2="+mismatch2+
																						", lengths["+ntarg+"]="+lengths[ntarg]);
																			}
																		} else {
																			if (nTile==dbg_tile) {
																				System.out.println("calcMathingTargetsLengths().3 ba="+ba+", fnseq = "+fnseq+", nTile="+nTile+", ntarg="+ntarg+
																						", fnseq_other="+fnseq_other+", ntile_other="+ntile_other+", ntarg_other="+ntarg_other+
																						", mismatch2="+mismatch2+
																						", lengths["+ntarg+"]="+lengths[ntarg]+
																						", len_ba["+ba+"]["+fnseq_other+"]["+ntile_other+"]["+ntarg_other+"]=" + len_ba[ba][fnseq_other][ntile_other][ntarg_other]);
																			}
																		}
																	}  else {
																		if (nTile==dbg_tile) {
																			System.out.println("calcMathingTargetsLengths().3b ba="+ba+", fnseq = "+fnseq+", nTile="+nTile+", ntarg="+ntarg+
																					", fnseq_other="+fnseq_other+", ntile_other="+ntile_other+", ntarg_other="+ntarg_other+
																					", lengths["+ntarg+"]="+lengths[ntarg]+" FAILED");
																		}
																		
																	}
																	
																} else {
																	if (nTile==dbg_tile) {
																		System.out.println("calcMathingTargetsLengths().3a ba="+ba+", fnseq = "+fnseq+", nTile="+nTile+", ntarg="+ntarg+
																				", fnseq_other="+fnseq_other+", ntile_other="+ntile_other+", ntarg_other="+ntarg_other+
																				((target_other != null)? (", target_other[CuasMotionLMA.RSLT_FAIL]="+ target_other[CuasMotionLMA.RSLT_FAIL]) :", target_other == null")+
																				", len_ba["+ba+"]["+fnseq_other+"]["+ntile_other+"]["+ntarg_other+"]=" + len_ba[ba][fnseq_other][ntile_other][ntarg_other]+
																				", lengths["+ntarg+"]="+lengths[ntarg]);
																	}
																	
																}
															}	// for (int ntarg_other = 0; ntarg_other < targets_other.length; ntarg_other++) {													
														}
													}
												}
											}
										}
									}
								}
								len_ba[ba][fnseq][nTile] = lengths;
							}
						}
					};
				}		      
				ImageDtt.startAndJoin(threads);
			}
		}

		ai.set(0);
		// combine before/after into a single value
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							double [][] targets =targets_multi[nSeq][ntile];
							if (targets != null){
								for (int ntarg = 0; ntarg < targets.length; ntarg++) {
									double [] target = targets[ntarg];
									if ((target != null) && (target[CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE)) {
										target[CuasMotionLMA.RSLT_MATCH_LENGTH] = 0;
										for (int ba = 0; ba < len_ba.length; ba++) {
											if (len_ba[ba][nSeq][ntile] != null) {
												target[CuasMotionLMA.RSLT_MATCH_LENGTH]+= len_ba[ba][nSeq][ntile][ntarg];
											}
										}
										target[CuasMotionLMA.RSLT_BEFORE_LENGTH] = len_ba[0][nSeq][ntile][ntarg];
										target[CuasMotionLMA.RSLT_AFTER_LENGTH] =  len_ba[1][nSeq][ntile][ntarg];
										if (ntile == dbg_tile) {
											System.out.println("calcMathingTargetsLengths().4  nSeq = "+nSeq+", targets["+ntarg+"][CuasMotionLMA.RSLT_MATCH_LENGTH]="+
													target[CuasMotionLMA.RSLT_MATCH_LENGTH]);
										}
										
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return;
	}
	
	public static ImagePlus showTargetSequence(
			final double [][][][] targets_multi, //
			String []             scene_titles, // all slices*frames titles or just slice titles or null
			String                title,
			final boolean         no_bad_only,
			final boolean         show_empty, // show scenes with no (valid) targets
			boolean               show,
			final int             tilesX) {
		int num_fields = CuasMotionLMA.LMA_TITLES.length;
		int num_seq =    targets_multi.length;
		int num_tiles = targets_multi[0].length;
		final int [] max_num_alts = new int [num_seq];
		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()) {
						max_num_alts[nSeq] = 0;
						for (int ntile = 0; ntile < num_tiles; ntile++){
							int num_alts = 0;
							double [][] targets = targets_multi[nSeq][ntile];
							if (targets != null) {
								for (int ntarg = 0; ntarg < targets.length; ntarg++) {
									if ((targets[ntarg] != null) && (!no_bad_only || Double.isNaN(targets[ntarg][CuasMotionLMA.RSLT_FAIL]) || (targets[ntarg][CuasMotionLMA.RSLT_FAIL]==CuasMotionLMA.FAIL_NONE))) {
										num_alts++;
									}

								}
							}
							if (num_alts > max_num_alts[nSeq]) {
								max_num_alts[nSeq] = num_alts;
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		
		int total_slices = 0;
		for (int nseq = 0; nseq < num_seq; nseq++ ) {
			total_slices += max_num_alts[nseq];
			if (show_empty && (max_num_alts[nseq] == 0)) {
				total_slices++;
			}
		}
		String [] titles = new String[total_slices];
		final int [] slice_indices = new int [num_seq]; 
		int slice_index=0;
		for (int nseq = 0; nseq < num_seq; nseq++ ) {
			slice_indices[nseq] = slice_index;
			if (max_num_alts[nseq] > 0) {
				for (int i = 0; i < max_num_alts[nseq]; i++) {
					titles[slice_index++] = scene_titles[nseq]+"-"+nseq+":"+i;
				}
				
			} else if (show_empty) {
				titles[slice_index++] = scene_titles[nseq];
			}
		}		
		String [] frame_titles = new String [CuasMotionLMA.LMA_TITLES.length + 1];
		for (int i = 0; i < CuasMotionLMA.LMA_TITLES.length; i++) {
			frame_titles[i] = CuasMotionLMA.LMA_TITLES[i];
		}
		frame_titles[CuasMotionLMA.LMA_TITLES.length] = "Targets";
		final double [][][] img_data = new double [num_fields+1][total_slices][num_tiles];
		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 nslice = slice_indices[nSeq];
						int num_slices = max_num_alts[nSeq];
						if (show_empty && (num_slices == 0)) {
							num_slices++;
						}
						if (num_slices > 0) {
							for (int nfield = 0; nfield < num_fields; nfield++) {
								for (int num_alt = 0; num_alt < num_slices; num_alt++) {
									Arrays.fill(img_data[nfield][nslice+num_alt], Double.NaN);
								}
							}
							for (int ntile = 0; ntile < num_tiles; ntile++){
								int num_alt = 0;
								double [][] targets = targets_multi[nSeq][ntile];
								if (targets != null) {
									for (int ntarg = 0; ntarg < targets.length; ntarg++) {
										double [] target = targets[ntarg]; 
										if ((target != null) && (!no_bad_only|| Double.isNaN(target[CuasMotionLMA.RSLT_FAIL]) || (target[CuasMotionLMA.RSLT_FAIL]==CuasMotionLMA.FAIL_NONE))) {
											for (int nfield = 0; nfield < num_fields; nfield++) {
												img_data[nfield][nslice+num_alt][ntile] = target[nfield];
											}
											img_data[num_fields][nslice+num_alt][ntile] = ((target[CuasMotionLMA.RSLT_FAIL]==CuasMotionLMA.FAIL_NONE) &&
													Double.isNaN(target[CuasMotionLMA.RSLT_STRONGER]))? 1:0;
											num_alt++;
										}
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		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
				frame_titles,   // CuasMotionLMA.LMA_TITLES,  // String []    frame_titles, // frame titles or null
				show);          // boolean      show)
		return imp;
	}

	public static float [][] temporalUnsharpMask(
			CLTParameters         clt_parameters,
			final boolean         batch_mode,
			QuadCLT               parentCLT,          //
			float [][]            fpixels,
			final CuasMotion      cuasMotion,
			String []             scene_titles, // recreate slice_titles from scene titles?
//			String []             slice_titles,
			final int             debugLevel) {
		int     temporal_um =    clt_parameters.imp.cuas_temporal_um;
		double  tum_threshold =  clt_parameters.imp.cuas_tum_threshold;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
		double [][] target_frac = new double [clt_parameters.imp.cuas_target_frac.length][2];
		double  input_range =    clt_parameters.imp.cuas_input_range; // 5;
		boolean intermed_high =  clt_parameters.imp.cuas_intermed_high;   //true;
   		boolean cuas_gaussian_ra = clt_parameters.imp.cuas_gaussian_ra; // use gaussian temporal Gaussian instead of running average
		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.");
			}
		}
		boolean debug_tum= true; // false;
		String model_prefix = parentCLT.getImageName()+getParametersSuffix(clt_parameters,null)+(cuasMotion.slow_targets? "-SLOW":"-FAST");
		float [][] fpixels_tum = fpixels;
		if (temporal_um > 0) {
			if (intermed_high && debug_tum) {
				ImagePlus imp_src = ShowDoubleFloatArrays.makeArrays(
						fpixels,                     // float[][] pixels,
						cuasMotion.gpu_max_width,        // int width,
						cuasMotion.gpu_max_height,       // int height,
						model_prefix+"-SOURCE", //String title,
						scene_titles); //String [] titles)
				imp_src.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_src.show();
				}		
				parentCLT.saveImagePlusInModelDirectory(imp_src);            // ImagePlus   imp)
			}
			float [][] fpixels_ra;
			if (cuas_gaussian_ra) {
				fpixels_ra= runningGaussian(
						fpixels,                   // final float [][] fpixels,
						temporal_um,               // final int        ra_length,
						cuasMotion.gpu_max_width); // final int        width);
			} else {
				fpixels_ra= runningAverage(
						fpixels,                   // final float [][] fpixels,
						temporal_um,               // final int        ra_length,
						cuasMotion.gpu_max_width); // final int        width);
			}			
			if (intermed_high && debug_tum) {
				ImagePlus imp_src_ra = ShowDoubleFloatArrays.makeArrays(
						fpixels_ra,                     // float[][] pixels,
						cuasMotion.gpu_max_width,        // int width,
						cuasMotion.gpu_max_height,       // int height,
						model_prefix+(cuas_gaussian_ra?"-SOURCE-RG":"-SOURCE-RA")+temporal_um, //String title,
						scene_titles); //String [] titles)
				imp_src_ra.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_src_ra.show();
				}		
				parentCLT.saveImagePlusInModelDirectory(imp_src_ra);            // ImagePlus   imp)
			}
			
			if (tum_threshold > 0) {
				float [][] fpixels_nan = removeOutliers(
						fpixels,                // float [][] fpixels,
						fpixels_ra,             // float [][] fpixels_avg,
						tum_threshold);         // float      threshold)
				if (intermed_high && debug_tum) {
					ImagePlus imp_outliers = ShowDoubleFloatArrays.makeArrays(
							fpixels_nan,                     // float[][] pixels,
							cuasMotion.gpu_max_width,        // int width,
							cuasMotion.gpu_max_height,       // int height,
							model_prefix+"-SOURCE-OUTLIERS", //String title,
							scene_titles); //String [] titles)
					imp_outliers.getProcessor().setMinAndMax(-input_range/2, input_range/2);
					if (!batch_mode) {
						imp_outliers.show();
					}		
					parentCLT.saveImagePlusInModelDirectory(imp_outliers);            // ImagePlus   imp)
				}
				if (cuas_gaussian_ra) {
					fpixels_ra = runningGaussianNaN(
							fpixels_nan,               // final float [][] fpixels,
							temporal_um,               // final int        ra_length,
							cuasMotion.gpu_max_width); // final int        width);
				} else {
					fpixels_ra = runningAverageNaN(
							fpixels_nan,               // final float [][] fpixels,
							temporal_um,               // final int        ra_length,
							cuasMotion.gpu_max_width); // final int        width);
				}
				if (intermed_high && debug_tum) {
					ImagePlus imp_src_ra = ShowDoubleFloatArrays.makeArrays(
							fpixels_ra,                     // float[][] pixels,
							cuasMotion.gpu_max_width,        // int width,
							cuasMotion.gpu_max_height,       // int height,
							model_prefix+(cuas_gaussian_ra?"-SOURCE-RG":"-SOURCE-RA")+"-NOOUTLIERS"+temporal_um, //String title,
							scene_titles); //String [] titles)
					imp_src_ra.getProcessor().setMinAndMax(-input_range/2, input_range/2);
					if (!batch_mode) {
						imp_src_ra.show();
					}		
					parentCLT.saveImagePlusInModelDirectory(imp_src_ra);            // ImagePlus   imp)
				}

				// modify original fpixels
				fpixels_tum =  subtract(
						fpixels, // float [][] fpixels,
						fpixels_ra); // float [][] fpixels_avg)
				if (intermed_high) {
					ImagePlus imp_diff = ShowDoubleFloatArrays.makeArrays(
							fpixels_tum,                     // float[][] pixels,
							cuasMotion.gpu_max_width,        // int width,
							cuasMotion.gpu_max_height,       // int height,
							model_prefix+"-FPIXELS_TUM"+temporal_um, //String title,
							scene_titles); //String [] titles)
					imp_diff.getProcessor().setMinAndMax(-input_range/2, input_range/2);
					if (!batch_mode) {
						imp_diff.show();
					}		
					parentCLT.saveImagePlusInModelDirectory(imp_diff);            // ImagePlus   imp)
				}
			}
		}
		return fpixels_tum;
	}
	
	
	public static double [][][] prepareMotionBasedSequence(
			CLTParameters         clt_parameters,
			final boolean         batch_mode,
			QuadCLT               parentCLT,          //
//			float [][]            fpixels,
			float [][]            fpixels_tum,
			final CuasMotion      cuasMotion,
			String []             scene_titles, // recreate slice_titles from scene titles?
			String []             slice_titles,
			final int             debugLevel) {
		int     corr_offset =    clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =     clt_parameters.imp.cuas_corr_pairs;
		int     precorr_ra =     clt_parameters.imp.cuas_precorr_ra;
		int     corr_ra_step =   clt_parameters.imp.cuas_corr_step;
		double  fat_zero =       clt_parameters.imp.cuas_fat_zero;
		double  cent_radius =    clt_parameters.imp.cuas_cent_radius;
		int     n_recenter =     clt_parameters.imp.cuas_n_recenter;
		double  rstr =           clt_parameters.imp.cuas_rstr;// 0.003; //  clt_parameters.imp.rln_sngl_rstr; // FIXME: ADD
//		double  speed_min =      clt_parameters.imp.cuas_speed_min;
//		double  speed_pref =     clt_parameters.imp.cuas_speed_pref;
//		double  speed_boost =    clt_parameters.imp.cuas_speed_boost;
		boolean smooth =         clt_parameters.imp.cuas_smooth; // true;
		boolean half_step =      clt_parameters.imp.cuas_half_step; // true;
		double [][] target_frac = new double [clt_parameters.imp.cuas_target_frac.length][2];
	    double  target_horizon=  clt_parameters.imp.cuas_horizon;	    
		double  input_range =    clt_parameters.imp.cuas_input_range; // 5;
		boolean corr2d_save_show =  clt_parameters.imp.cuas_2d_save_show;    //true;
		boolean intermed_low =   clt_parameters.imp.cuas_intermed_low;    //true;
		boolean intermed_high =  clt_parameters.imp.cuas_intermed_high;   //true;
   		boolean cuas_gaussian_ra = clt_parameters.imp.cuas_gaussian_ra; // use gaussian temporal Gaussian instead of running average
		for (int i = 0; i < target_frac.length; i++) {
			if (clt_parameters.imp.cuas_target_frac[i].length >= 2) {
				target_frac[i][0] = clt_parameters.imp.cuas_target_frac[i][0];
				target_frac[i][1] = clt_parameters.imp.cuas_target_frac[i][1];
			} else {
				System.out.println("testCuasScanMotion(): wrong format for a pair of strength, fraction values.");
			}
		}
		int start_frame = 0;
		int num_scenes = fpixels_tum.length; // .length; // .getStack().getSize()- first_corr; // includes average
		int seq_length = corr_offset + corr_pairs;
		int corr_inc = half_step ? (corr_offset/2) : corr_offset;
		int num_corr_samples = (num_scenes - seq_length - start_frame) / corr_inc;
		int [] num_all =        new int [num_corr_samples];
		int [] num_undef =      new int [num_corr_samples];
		int [] num_good =       new int [num_corr_samples];
		int [] num_bad =        new int [num_corr_samples];
		String model_prefix = parentCLT.getImageName()+getParametersSuffix(clt_parameters,null)+(cuasMotion.slow_targets? "-SLOW":"-FAST");

		if (intermed_high) {
			ImagePlus imp_tum = ShowDoubleFloatArrays.makeArrays(
					fpixels_tum, // float[][] pixels,
					cuasMotion.gpu_max_width, // int width,
					cuasMotion.gpu_max_height, // int height,
					model_prefix+"-SOURCE", //String title,
					scene_titles); //String [] titles)
			imp_tum.getProcessor().setMinAndMax(-input_range/2, input_range/2);
			if (!batch_mode) {
				imp_tum.show();
			}		
			parentCLT.saveImagePlusInModelDirectory(imp_tum);            // ImagePlus   imp)
		}
		
		
		float [][] fpixels_ra = fpixels_tum; //fpixels;
		if (precorr_ra > 1) {
			if (cuas_gaussian_ra) {
				fpixels_ra = runningGaussian(
						fpixels_tum, // fpixels,                   // final float [][] fpixels,
						precorr_ra,                // final int        ra_length,
						cuasMotion.gpu_max_width); // final int        width)
			} else {
				fpixels_ra = runningAverage(
						fpixels_tum, // fpixels,                   // final float [][] fpixels,
						precorr_ra,                // final int        ra_length,
						cuasMotion.gpu_max_width); // final int        width)
			}
			if (intermed_high) {
				ImagePlus imp_ra = ShowDoubleFloatArrays.makeArrays(
						fpixels_ra, // float[][] pixels,
						cuasMotion.gpu_max_width, // int width,
						cuasMotion.gpu_max_height, // int height,
						model_prefix+(cuas_gaussian_ra?"-SOURCE-RG":"-SOURCE-RA")+precorr_ra, //String title,
						scene_titles); //String [] titles)
				imp_ra.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_ra.show();
				}		
				parentCLT.saveImagePlusInModelDirectory(imp_ra);            // ImagePlus   imp)
			}
		}
		double [][][] corr2d = corr2d_save_show? new double [num_corr_samples][][] : null;

		double [][][] target_sequence = 	getTargetsFromCorr2d(
				clt_parameters, // CLTParameters     clt_parameters,
				batch_mode,     // false,     // boolean           batch_mode,
				cuasMotion,     // CuasMotion        cuasMotion,
				fpixels_ra,     // float [][]        fpixels,
				start_frame,    // int               start_frame,
				corr_pairs,     // int               corr_pairs,
				corr_offset,    // int               corr_offset,
				corr_inc,       // int               corr_inc,
				corr_ra_step,   // int               corr_ra_step, //    5;      // correlation step when using rolling average
				smooth,         // boolean           smooth,
				fat_zero,       // double            fat_zero,
				cent_radius,    // double            cent_radius,
				n_recenter,     // int               n_recenter,
				-rstr,          // double            rstr,
				model_prefix,   // String            title,
				corr2d,         // double [][][]     corr2d, // null or [(fpixels.length - seq_length - start_frame) / corr_inc)[][]
				debugLevel);    // int               debugLevel) {
		
		setBadHorizon(
				target_sequence,      // final double [][][] target_sequence,
				target_horizon,       // final double        horizon_y,
				cuasMotion.tilesX);   // final int           tilesX)
		// After this target_sequence should not be null-ed, failure status should only change from NaN to 0/>0 values 
		
		if (intermed_low ) {
			ImagePlus imp_initial = showTargetSequence(
					target_sequence,          // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-INITIAL-MOTION",// String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_initial);
		}			
		

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

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

		// starting from the motion_scan[][][] - calculated or read from file. corr2d is curently unused, maybe we'll have to save restore
		getRemain(target_sequence, num_all, num_undef, num_good, num_bad);
		printStats ("initial", true,num_all, num_undef, null, null);
		if (intermed_low) {
				ImagePlus imp_sky_mask = ShowDoubleFloatArrays.makeArrays(
						cuasMotion.sky_mask, // double[][] pixels,
 						cuasMotion.gpu_max_width,
						cuasMotion.gpu_max_height, 
						model_prefix+"-SKY_MASK");   // Selected to test 
				if (!batch_mode) {
					imp_sky_mask.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_sky_mask);  // ImagePlus   imp)
		}
		
		return target_sequence;
	}
	
	public static double [][][][] locateAndFreezeTargetsMulti(
			CLTParameters         clt_parameters,
			final boolean         batch_mode,
			QuadCLT               parentCLT,          //
//			float [][]            fpixels,
			float [][]            fpixels_tum,
			double [][][]         motion_sequence, // starts with just motion-based, then adds  other fields (single target per tile)
			final CuasMotion      cuasMotion,
			String []             scene_titles, // recreate slice_titles from scene titles?
			String []             slice_titles,
			final int             debugLevel) {
		int     corr_offset =    clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =     clt_parameters.imp.cuas_corr_pairs;
		int     n_recenter =     clt_parameters.imp.cuas_n_recenter;
		double  speed_min =      clt_parameters.imp.cuas_speed_min;
		double  speed_pref =     clt_parameters.imp.cuas_speed_pref;
		double  speed_boost =    clt_parameters.imp.cuas_speed_boost;
		boolean smooth =         clt_parameters.imp.cuas_smooth; // true;
		boolean half_step =      clt_parameters.imp.cuas_half_step; // true;
		int     max_range =      clt_parameters.imp.cuas_max_range;
		int     num_cycles =     clt_parameters.imp.cuas_num_cycles;
	    double  lmax_fraction =  clt_parameters.imp.cuas_lmax_fraction;    // 0.7;     // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
	    double  lmax_flt_neglim= clt_parameters.imp.cuas_lmax_flt_neglim;  // -0.3;   // limit negative data to reduce ridge influence (make -10 to disable)        
	    double  lmax_flt_hsigma= clt_parameters.imp.cuas_lmax_flt_hsigma;  // 1.0 // HPF (~UM) subtract GB with this sigma from the data tile 
	    double  lmax_flt_lsigma= clt_parameters.imp.cuas_lmax_flt_lsigma;  // 1.0 // LPF - GB result of the previous subtraction
	    double  lmax_flt_scale=  clt_parameters.imp.cuas_lmax_flt_scale;   // 5.0 // scale filtering result
	    double  sky_threshold =  clt_parameters.imp.cuas_sky_threshold;     // 0.9;   // minimal value of the sky mask where target is possible
	    
	    double  lmax_hack_ridge= clt_parameters.imp.cuas_lmax_hack_ridge; // 0.45;    // Hack for ridges: remove horizontal streaks, where average(abs()) for the line exceeds fraction of abs max 0.32-good, 0.55 - bad
	    double  lmax_radius =    clt_parameters.imp.cuas_lmax_radius;   // 3.5;     // Look inside ((int)cuas_lmax_radius) * 2 + 1 square for the local maximum isolation 
//		boolean lmax_zero =      clt_parameters.imp.cuas_lmax_zero;     // true;    // zero all data outside this radius from the maximum
		
		double target_radius =   clt_parameters.imp.cuas_target_radius;
		double target_strength = clt_parameters.imp.cuas_target_strength;
		double [][] target_frac = new double [clt_parameters.imp.cuas_target_frac.length][2];
		boolean no_border=       clt_parameters.imp.cuas_no_border; // true;
	// Moving target LMA	
	    double  lma_sigma =      clt_parameters.imp.cuas_lma_sigma; // =     3.0;
	    double  wnd_pedestal =   clt_parameters.imp.cuas_wnd_pedestal; // =     3.0;
	    double  lma_r0 =         clt_parameters.imp.cuas_lma_r0; // =        3.0;  //maximum with with overshoot
	    double  lma_ovrsht =     clt_parameters.imp.cuas_lma_ovrsht; // =    2.0;
	// CUAS Motion LMA parameters
		boolean lma_fit_xy=      clt_parameters.imp.cuas_lma_fit_xy; // true;
		boolean lma_fit_a=       clt_parameters.imp.cuas_lma_fit_a; // true;
		boolean lma_fit_c=       clt_parameters.imp.cuas_lma_fit_c; // true;
		boolean lma_fit_r=       clt_parameters.imp.cuas_lma_fit_r; // true;
		boolean lma_fit_k=       clt_parameters.imp.cuas_lma_fit_k; // true;
	    double  lambda =         clt_parameters.imp.cuas_lambda; // =        0.1;
	    double  lambda_good =    clt_parameters.imp.cuas_lambda_good; // =   0.5;
	    double  lambda_bad =     clt_parameters.imp.cuas_lambda_bad; // =    8;
	    double  lambda_max =     clt_parameters.imp.cuas_lambda_max; // =  100;
	    double  rms_diff =       clt_parameters.imp.cuas_rms_diff; // =      0.001; // relative RMS improvement
	    int     num_iter =       clt_parameters.imp.cuas_num_iter; // =     20;
	// CUAS Motion LMA filter parameters
	    double  lma_rms =        clt_parameters.imp.cuas_lma_rms; // =       1.5;  // Maximal RMS, regardless of amplitude
	    double  lma_arms =       clt_parameters.imp.cuas_lma_arms; // =      0.06; // Maximal absolute RMS, sufficient for any amplitude
	    double  lma_rrms =       clt_parameters.imp.cuas_lma_rrms; // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
	    double  lma_mina =       clt_parameters.imp.cuas_lma_mina; // =      1.0;  // Minimal A (amplitude)
	    double  lma_pre_mina =   clt_parameters.imp.cuas_lma_pre_mina; // =      0.5;  // scale  cuas_lma_mina to filter initial candidates (if there are > one of them) 
	    int     min_keep =       clt_parameters.imp.cuas_min_keep; // 2 // keep at least this number of candidates before using cuas_lma_pre_mina filter
	    
	    //0.5 * lma_mina,// 			final double        pre_min_a
	    
	    double  lma_maxr =       clt_parameters.imp.cuas_lma_maxr; // =      5.0;  // Maximal radius (>3.8)
	    double  lma_minr1 =      clt_parameters.imp.cuas_lma_minr1; // =     1.0;  // Maximal inner radius
	    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
	    double  lma_a2a =        clt_parameters.imp.cuas_lma_a2a; // =       0.7;  // Minimal ratio of the maximal pixel to the amplitude
//	    double  max_mismatch =   clt_parameters.imp.cuas_max_mismatch;  // 2;
		double  good_mismatch =  clt_parameters.imp.cuas_good_mismatch;  // 0.4;
		double  match_len_pwr = clt_parameters.imp.cuas_match_len_pwr;  // raise matching length to this power for calculating score
	    boolean fail_mismatch =  clt_parameters. imp.cuas_fail_mismatch;  // Fail on high mismatch early (when calculating scores);
	    
	    double  target_horizon=  clt_parameters.imp.cuas_horizon;	    
	// Handling overall target scores    
	    
		double min_score_mv =    clt_parameters.imp.cuas_score_mv;
//		double min_score_lma =   clt_parameters.imp.cuas_score_lma;
		double factor_lim =      clt_parameters.imp.cuas_factor_lim;
		double factor_pow =      clt_parameters.imp.cuas_factor_pow;
		double [] score_coeff =  clt_parameters.imp.cuas_score_coeff.clone();
		double  slow_score =     clt_parameters.imp.cuas_slow_score;
		double  input_range =    clt_parameters.imp.cuas_input_range; // 5;
		int     iter_show =      clt_parameters.imp.cuas_iter_show;       //1; // Maximal enhancement iteration to show intermediate result (0 - none) 
		boolean intermed_low =   clt_parameters.imp.cuas_intermed_low;    //true;
		boolean intermed_high =  clt_parameters.imp.cuas_intermed_high;   //true;
//		boolean intermed_giga =  clt_parameters.imp.cuas_intermed_giga;   //false;
//	    boolean center_targ =    clt_parameters.imp.cuas_center_targ;
		double  centered_radius=      clt_parameters.imp.cuas_centered_radius;      // 3.5; // in centered mode (target placed at [8,8] - zero out data outside peripheral areas, keep 0.5 at the radius 	
		double  centered_blur =       clt_parameters.imp.cuas_centered_blur;        // 0.5; // relative transition radius range (for 0.5 the mask inside (1-0.5) radius will be 1.0, outside (1+0.5) radius will be 0
		double  duplicate_tolerance = clt_parameters.imp.cuas_duplicate_tolerance ; // 2.0; // (pix) Remove weaker maximums closer than this to a stronger one   		
		boolean debug_more =     clt_parameters.imp.cuas_debug_more;
	
		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.");
			}
		}
		
		double filter_below_horizon=5;
		int start_frame = 0;
		int num_scenes = fpixels_tum.length; // .getStack().getSize()- first_corr; // includes average
		int seq_length = corr_offset + corr_pairs;
		int corr_inc = half_step ? (corr_offset/2) : corr_offset;
		int num_corr_samples = (num_scenes - seq_length - start_frame) / corr_inc;
		final int     frame0 = start_frame + seq_length/2;
		final int     half_accum_range = corr_pairs/2;
		int [] filter5_remain = new int [num_corr_samples];
		int [] num_all =        new int [num_corr_samples];
		int [] num_undef =      new int [num_corr_samples];
		int [] num_good =       new int [num_corr_samples];
		int [] num_bad =        new int [num_corr_samples];
		int [] totals =         null; 
		int [] passes = new int [num_corr_samples]; // debugging filter5
		String model_prefix = parentCLT.getImageName()+getParametersSuffix(clt_parameters,null)+(cuasMotion.slow_targets? "-SLOW":"-FAST");

		final int num_seq = motion_sequence.length;
		final int num_tiles = motion_sequence[0].length;
		final double [][][][] target_sequence_multi = new double [num_seq][num_tiles][][]; 
		int niter=0;
		// first pass, using non-centered targets
		for (; niter < num_cycles; niter++) {
			boolean save_filtered_low =  intermed_low &&  (niter < iter_show);
			boolean save_filtered_high = intermed_high && (niter < iter_show);
			totals = getRemain(motion_sequence, target_sequence_multi, num_all, num_undef, num_good, num_bad);
			if (totals[TOTALS_UNDEFINED] == 0) {
				if (debugLevel > -4) System.out.println ("No undefined tiles left, breaking loop");
				break;
			}
			if (save_filtered_low && debug_more) {
				ImagePlus imp_ms1 = showTargetSequence(
						motion_sequence,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-MS1-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_ms1);
			}			

//	double [][] effective_strength = 
			// ==== does it need to be re-calculated ? =====
			getEffectiveStrengthMV( // calculate tiles effective strength by the motion vectors. Combine with the target LMA?
					motion_sequence,   // final double [][][] motion_scan,
					niter,             // int                 niter, // save iteration number on failure if >=
					cuasMotion.tilesX, // final int           tilesX)
					0,                 // final double        min_score_mv,
					speed_min,         // double              speed_min,		
					speed_pref,        // double              speed_pref,		
					speed_boost);      // double              speed_boost);
			
			if (save_filtered_low  && debug_more) {
				ImagePlus imp_mv_strength = showTargetSequence(
						motion_sequence,       // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-MV_STRENGTH-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_mv_strength);
			}
			
			int [][] filter5 = filter5Targets( // will ignore failed tiles
					motion_sequence,       // final double [][][] target_sequence,
					target_sequence_multi, // final double [][][][] target_sequence_multi,
					// if     use motion and select_new will only consider tiles (and compare motion scores in motion_sequence) that have nulls in target_sequence_multi[nseq][ntile]
					// if not use motion and select_new will only consider (and compare scores) tiles that have [RSLT_CENTERED] = 0.0
					// if not select_new (assumes not  use motion) will return best (and good) target indices
					true,               // final boolean       use_motion,     // true - use motion vectors confidence, false - use target confidence
					true,               // final boolean       select_new,     // true - use only untested tiles, false - use good tiles 
					min_score_mv,       // double              min_confidence,
					target_horizon,     // final double        lma_horizon, // target below horizon
					cuasMotion.tilesX,  // final int           tilesX,
					max_range,          // final int           range, // 1 or 2
					filter5_remain,     // final int     []    remain){
					passes,             // final int     []    passes, // debugging - numer of passes required
					debugLevel);        // final int           debugLevel)

			if (save_filtered_low) {
 				ImagePlus imp_filter5 = ShowDoubleFloatArrays.makeArrays(
 						filter5,
 						cuasMotion.tilesX,
						cuasMotion.tilesY, 
						model_prefix+"-FILTER5_SELECTED_MULTI-n"+niter,   // Selected to test 
						slice_titles);
				if (!batch_mode) {
					imp_filter5.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_filter5);  // ImagePlus   imp)

			}
			
			if (debugLevel > -4) printStatsLine("filter5 remain",true,filter5_remain);
			int filter5_total = getTotal(filter5_remain);
			if (filter5_total == 0) {
				if (debugLevel > -4) System.out.println ("No filter5 tiles left, breaking loop");
				break;
			}
			
			// was motion_scan_filtered
			double [][][] targets_nonoverlap = applyFilter( // motion vectors // will have nulls not top try 
					motion_sequence,          // double [][][] motion_scan,
					filter5);                 // boolean [][] filter5)
			// Anything remains? we'll see after extension
			double [][][] extended_scan = extendMotionScan(
					targets_nonoverlap, // 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
					null); // remain);              // final int     []    remain)

			if (save_filtered_low && debug_more) {
				
				// targets_nonoverlap will contain motion vectors used fro the next fpixels_accumulated
				ImagePlus imp_novl = showTargetSequence(
						targets_nonoverlap,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-SELECTED-TO-TRY-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_novl);

				ImagePlus imp_ext = showTargetSequence(
						extended_scan,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-EXTENDED-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_ext);
				// show good and bad accumulated here too?
			}
			
			// perform new accumulations of shifted non-confliocting tiles
			float [][] fpixels_accumulated = cuasMotion.shiftAndRenderAccumulate(
					clt_parameters,   // CLTParameters       clt_parameters,
					false,            // final boolean       center,
					false,            // final boolean       fill_zeros,
					fpixels_tum,      // final float [][]    fpixels,
					extended_scan,    // final double [][][] vector_field,
					frame0,           // final int           frame0,      // for vector_field[0]
					corr_inc,         // final int           frame_step,
					half_accum_range, // final int           half_range,
					smooth,           // final boolean       smooth,
					corr_offset,      // final int           corr_offset, // interframe distance for correlation
					true);            // final boolean       batch_mode) {
			if (save_filtered_high) {
				ImagePlus imp_acc = ShowDoubleFloatArrays.makeArrays(
						fpixels_accumulated, // double[][] pixels,
						cuasMotion.gpu_max_width,
						cuasMotion.gpu_max_height, 
						model_prefix+"-ACCUMULATED-n"+niter,   // String title,
						slice_titles);
				imp_acc.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_acc.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_acc);  // ImagePlus   imp)
			}
			
			// replace center frames with the accumulated ones
			if (debugLevel > -4) {
				System.out.println("Starting render, iteration = "+niter);
			}
			
			// show just fpixels_accumulated
			// move outside? compare to slice_titles known
			// Calculate both centroid and LMA target parametes, return both
			// targets_new will contain motion vectors, centroid, and LMA results combined
			//save_filtered_high
			float [][] accum_debug = save_filtered_high? new float [num_corr_samples][]:null; //fpixels_accumulated.length]
			boolean       keep_failed = false; // keep failed targets
			double [][][][] targets_new_multi = 	getAccumulatedCoordinatesMulti(
					keep_failed,         // final boolean       keep_failed, // keep failed targets
					niter, // 	final int           when, // set if >=0 for failures
					false,                // final boolean       centered,
					targets_nonoverlap,   // final double [][][] vector_fields, // centers
					fpixels_accumulated,  // final double [][]   accum_data,   // should be around 0, no low-freq
					lmax_fraction,        // final double        cuas_lmax_fraction, // 0.7;     // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
					lmax_flt_neglim,      // final double        lmax_flt_neglim,   // -0.3;   // limit negative data to reduce ridge influence (make -10 to disable)
					lmax_flt_hsigma,	  // final double        lmax_flt_hsigma,	 // 1.0 // HPF (~UM) subtract GB with this sigma from the data tile     
					lmax_flt_lsigma,      // final double        lmax_flt_lsigma,   // 1.0 // LPF - GB result of the previous subtraction
					lmax_flt_scale,       // final double        lmax_flt_scale,    // 5.0 // scale filtering result
					cuasMotion.sky_mask,  // final double        sky_mask,
					sky_threshold,    // final double        sky_threshold,    // 0.9 // minimal value of the sky mask where target is possible
					target_horizon+filter_below_horizon,       // final double        lma_horizon,
					lmax_hack_ridge,      // final double        lmax_hack_ridge, // 0.45;    // Hack for ridges: remove horizontal streaks, where average(abs()) for the line exceeds fraction of abs max 0.32-good, 0.55 - bad
					lmax_radius,          // final int           cuas_lmax_radius,   // 3;       // look inside cuas_lmax_radius* 2 + 1 square for the local maximum isolation
					centered_radius,      // final double        centered_radius,
					centered_blur,        // final double        centered_radius_blur,
					duplicate_tolerance,  // final double        pix_tolerance, // 
//					lmax_zero,            // final boolean       cuas_lmax_zero,     // true;    // zero all data outside this radius from the maximum
					target_radius,        // final double        centroid_radius,
					n_recenter,           // final int           n_recenter, //  re-center window around new maximum. 0 -no refines (single-pass)
					cuasMotion.tilesX,    // final int           tilesX){
					no_border,            // final boolean       no_border,
					// Moving target LMA	
				    lma_sigma,            // final double        lma_sigma,
					wnd_pedestal,         // final double        wnd_pedestal,
				    lma_r0,               // final double        lma_r0,
				    lma_ovrsht,  // final double        lma_ovrsht,
				// CUAS Motion LMA parameters
				    lma_fit_xy,           // final boolean       lma_fit_xy,		
				    lma_fit_a,            // final boolean       lma_fit_a,						
				    lma_fit_c,            // final boolean       lma_fit_c,
				    lma_fit_r,            // final boolean       lma_fit_r,
				    lma_fit_k,            // final boolean       lma_fit_k,		
				    lambda,               // final double        lambda,
				    lambda_good,          // final double        lambda_good,
				    lambda_bad,           // final double        lambda_bad,
				    lambda_max,           // final double        lambda_max,
				    rms_diff,             // final double        rms_diff,
				    num_iter,             // final int           num_iter,0, // 			final double        pre_min_a,   // pre-filter minimal LMA-A (half finbal?)
				    lma_pre_mina * lma_mina,// final double      pre_min_a,   // 0.5 //scale  cuas_lma_mina to filter initial candidates (if there are > one of them)
				    min_keep,             // final int           min_keep, //  2; // keep at least this number of candidates before using cuas_lma_pre_mina filter
				    accum_debug,          // final float [][]    accum_debug,
				    debugLevel);          // final int           debugLevel);
		
			if (accum_debug != null) {
				ImagePlus imp_acc = ShowDoubleFloatArrays.makeArrays(
						accum_debug, // double[][] pixels,
						cuasMotion.gpu_max_width,
						cuasMotion.gpu_max_height, 
						model_prefix+"-ACCUMULATED_FILTERED-MULTI-n"+niter,   // String title,
						slice_titles);
				imp_acc.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_acc.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_acc);  // ImagePlus   imp)
			}
			
			boolean         good_only=    false;
			boolean         show_empty =  true; // false; // show scenes with no (valid) targets
			if (save_filtered_low  && debug_more) {
				ImagePlus imp_new = showTargetSequence(
						targets_new_multi,    // final double [][][][] targets_multi,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-NEW-TARGETS-MULTI-n"+niter,// String        title,
						good_only,            // final boolean         good_only,
						show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_new);  // ImagePlus   imp)
			}
			
			// evaluate scores here to reduce the number of attempts for centered targets
			getEffectiveStrengthLMA(
					targets_new_multi, // final double [][][] target_coords, // LMA
					target_strength,   // final double        target_strength,
					target_frac,       // final double [][]   target_frac, // pairs - strength, minimal fraction for that strength
					lma_rms,           // final double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
					lma_arms,          // final double        lma_arms, // =      0.06; // Maximal absolute RMS (should match one of cuas_lma_arms OR cuas_lma_rrms (0.484)
					lma_rrms,          // final double        lma_rrms, // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
					lma_mina,          // final double        lma_mina, // =      1.0;  // Minimal A (amplitude)
					lma_maxr,          // final double        lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
					lma_minr1,         // final double        lma_minr1,// =      1.0;  // Minimal R1 (radius of positive peak) 
					lma_mink,          // final double        lma_mink, // =      0.0;  // Minimal K (overshoot) = 1.0
					lma_maxk,          // final double        lma_maxk, // =      5.0;  // Minimal K (overshoot) = 3.0//			final double        lma_a2a, 
					lma_a2a,           // final double        lma_a2a, 
					0, // max_mismatch,      // final double        max_mismatch, apply only during final, when mismatch scores are calculated 
					good_mismatch,     // final double        good_mismatch, //do not add to score if worse
					match_len_pwr,     // final double          match_len_pwr, // 0.5; // raise matching length to this power for calculating score
					fail_mismatch,     // final boolean       fail_mismatch,
					target_horizon,    // final double        lma_horizon, // horizon as maximal pixel Y
					cuasMotion.tilesX);            // final int         tilesX,
//			int [] remain_es_centered = 
			getScore(
					targets_new_multi, // final double [][][] target_sequence, // modifies certain fields (scores)
					factor_lim,        // final double        importance_limit,
					factor_pow,        // final double        importance_power, // Raise each factor to this power before combining
					score_coeff,       // final double []     importance, // for now (each - squared?): [0] - Amplitude (A/A0), 1 - RMS (RMS0/RMS), 2 - RRMS((RMS/A0) / (RMS/A)
					slow_score,        // final double          slow_score,           // multiply total score for targets detected in slow target mode
					cuasMotion.tilesX);            // final int         tilesX,
			
			if (save_filtered_low) {
				ImagePlus imp_new_scores = showTargetSequence(
						targets_new_multi,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-NEW-TARGETS_SCORES_MULTI-n"+niter,// String        title,
						good_only,            // final boolean         good_only,
						show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_new_scores);  // ImagePlus   imp)
			}

			int num_new = addNewResults(
					target_sequence_multi, // final double [][][] target_sequence, // will only process non-nulls here
					targets_new_multi,     // final double [][][] new_sequence,
					0, 			           // final int              num_best, // if >0, limit number of best results to add 
					niter);                // final int when_iter,
			
			if (debugLevel > -4) {
				System.out.println("Added "+num_new+" new tiles as good/bad");
			}
			if (save_filtered_low) {
				ImagePlus imp_new_scores = showTargetSequence(
						target_sequence_multi,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-ADDED_NEW_MULTI-n"+niter,// String        title,
						good_only,            // final boolean         good_only,
						show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_new_scores);  // ImagePlus   imp)
			}
			
			if (num_new == 0) {
				if (debugLevel > -4) System.out.println ("Nothing added from last try, breaking the loop.\n");
				break;  // breaks here
			}
			totals = getRemain(motion_sequence, target_sequence_multi, num_all, num_undef, num_good, num_bad);
			if (debugLevel > -4) printStats ("After iteration "+niter, true, num_all, num_undef, num_good, num_bad);
			if (debugLevel > -4) {
				System.out.println("Non-centered iteration "+niter+" DONE.\n");
			}
		} //for (niter=0; niter < max_iter; niter++)

		if (intermed_low) {
			ImagePlus imp_new_scores = showTargetSequence(
					target_sequence_multi,          // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-ROUNDE_ONE",// String        title,
					false, // good_only,            // final boolean         good_only,
					false, // show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_new_scores);  // ImagePlus   imp)
		}
		
		

		//===========================================================================
		if (debugLevel > -4) {
			System.out.println("\n========================== Starting centered iterations =============================.\n");
		}
		
		// second pass, using non-centered targets
		int niter_lim = niter+ num_cycles;
		int iter_show1 = iter_show + niter;
		for (; niter < niter_lim; niter++) {
			boolean         good_only=    false;
			boolean         show_empty =  true; // false; // show scenes with no (valid) targets
			
			boolean save_filtered_low =  intermed_low &&  (niter < iter_show1);
			boolean save_filtered_high = intermed_high && (niter < iter_show1);
//			totals = getRemain(motion_sequence, target_sequence_multi, num_all, num_undef, num_good, num_bad);
			totals = getRemain(target_sequence_multi, num_all, num_undef, num_good, num_bad);
			if (totals[TOTALS_UNDEFINED] == 0) {
				if (debugLevel > -4) System.out.println ("No undefined tiles left, breaking loop");
				break;
			}

			int [][] filter5 = filter5Targets( // will ignore failed tiles
					motion_sequence,       // final double [][][] target_sequence,
					target_sequence_multi, // final double [][][][] target_sequence_multi,
					// if     use motion and select_new will only consider tiles (and compare motion scores in motion_sequence) that have nulls in target_sequence_multi[nseq][ntile]
					// if not use motion and select_new will only consider (and compare scores) tiles that have [RSLT_CENTERED] = 0.0
					// if not select_new (assumes not  use motion) will return best (and good) target indices
					false,              // final boolean       use_motion,     // true - use motion vectors confidence, false - use target confidence
					true,               // final boolean       select_new,     // true - use only untested tiles, false - use good tiles 
					min_score_mv,       // double              min_confidence,
					target_horizon,     // final double        lma_horizon, // target below horizon
					cuasMotion.tilesX,  // final int           tilesX,
					max_range,          // final int           range, // 1 or 2
					filter5_remain,     // final int     []    remain){
					passes,             // final int     []    passes, // debugging - numer of passes required
					debugLevel);        // final int           debugLevel)

			if (save_filtered_low) {
				ImagePlus imp_filter5 = ShowDoubleFloatArrays.makeArrays(
						filter5,
						cuasMotion.tilesX,
						cuasMotion.tilesY, 
						model_prefix+"-FILTER5_SELECTED_MULTI_CENTERED-n"+niter,   // Selected to test 
						slice_titles);
				if (!batch_mode) {
					imp_filter5.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_filter5);  // ImagePlus   imp)
			}

			if (save_filtered_low  && debug_more) {
				ImagePlus imp_marked_used = showTargetSequence(
						target_sequence_multi,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-MARKED_USED-n"+niter,// String        title,
						good_only,            // final boolean         good_only,
						show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_marked_used);  // ImagePlus   imp)
			}
			
			
			if (debugLevel > -4) printStatsLine("filter5 remain",true,filter5_remain);
			int filter5_total = getTotal(filter5_remain);
			if (filter5_total == 0) {
				if (debugLevel > -4) System.out.println ("No filter5 non-centered tiles left, breaking loop");
				break;
			}
			// render and process in centered mode
			double [][][] targets_nonoverlap = applyFilter( // motion vectors // will have nulls not top try 
					motion_sequence,          // double [][][] motion_scan,
					filter5);                 // boolean [][] filter5)
			// Anything remains? we'll see after extension
			double [][][] extended_scan = extendMotionScan(
					targets_nonoverlap, // 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
					null); // remain);              // final int     []    remain)

			if (save_filtered_low  && debug_more) {
				// targets_nonoverlap will contain motion vectors used fro the next fpixels_accumulated
				ImagePlus imp_novl = showTargetSequence(
						targets_nonoverlap,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-SELECTED-TO-TRY-CENTERED-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_novl);

				ImagePlus imp_ext = showTargetSequence(
						extended_scan,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-EXTENDED-CENTERED-n"+niter,// String        title,
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_ext);
				// show good and bad accumulated here too?
			}

			// perform new accumulations of shifted non-confliocting tiles
			float [][] fpixels_accumulated = cuasMotion.shiftAndRenderAccumulate(
					clt_parameters,   // CLTParameters       clt_parameters,
					false,            // final boolean       center,
					false,            // final boolean       fill_zeros,
					fpixels_tum,      // final float [][]    fpixels,
					extended_scan,    // final double [][][] vector_field,
					frame0,           // final int           frame0,      // for vector_field[0]
					corr_inc,         // final int           frame_step,
					half_accum_range, // final int           half_range,
					smooth,           // final boolean       smooth,
					corr_offset,      // final int           corr_offset, // interframe distance for correlation
					true);            // final boolean       batch_mode) {
			if (save_filtered_high) {
				ImagePlus imp_acc = ShowDoubleFloatArrays.makeArrays(
						fpixels_accumulated, // double[][] pixels,
						cuasMotion.gpu_max_width,
						cuasMotion.gpu_max_height, 
						model_prefix+"-ACCUMULATED-CENTERED-n"+niter,   // String title,
						slice_titles);
				imp_acc.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_acc.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_acc);  // ImagePlus   imp)
			}

			// replace center frames with the accumulated ones
			if (debugLevel > -4) {
				System.out.println("Starting render, iteration = "+niter);
			}

			// show just fpixels_accumulated
			// move outside? compare to slice_titles known
			// Calculate both centroid and LMA target parametes, return both
			// targets_new will contain motion vectors, centroid, and LMA results combined
			//save_filtered_high
			float [][] accum_debug = save_filtered_high? new float [num_corr_samples][]:null; //fpixels_accumulated.length]
			boolean    keep_failed = false; // keep failed targets
			double [][][][] targets_new_multi = 	getAccumulatedCoordinatesMulti(
					keep_failed,          // final boolean       keep_failed, // keep failed targets
					niter,                // 	final int           when, // set if >=0 for failures
					true,                 // final boolean       centered,
					targets_nonoverlap,   // final double [][][] vector_fields, // centers
					fpixels_accumulated,  // final double [][]   accum_data,   // should be around 0, no low-freq
					lmax_fraction,        // final double        cuas_lmax_fraction, // 0.7;     // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
					lmax_flt_neglim,      // final double        lmax_flt_neglim,   // -0.3;   // limit negative data to reduce ridge influence (make -10 to disable)
					lmax_flt_hsigma,	  // final double        lmax_flt_hsigma,	 // 1.0 // HPF (~UM) subtract GB with this sigma from the data tile     
					lmax_flt_lsigma,      // final double        lmax_flt_lsigma,   // 1.0 // LPF - GB result of the previous subtraction
					lmax_flt_scale,       // final double        lmax_flt_scale,    // 5.0 // scale filtering result
					cuasMotion.sky_mask,  // final double        sky_mask,
					sky_threshold,        // final double        sky_threshold,    // 0.9 // minimal value of the sky mask where target is possible
					target_horizon+filter_below_horizon,       // final double        lma_horizon,
					lmax_hack_ridge,      // final double        lmax_hack_ridge, // 0.45;    // Hack for ridges: remove horizontal streaks, where average(abs()) for the line exceeds fraction of abs max 0.32-good, 0.55 - bad
					lmax_radius,          // final int           cuas_lmax_radius,   // 3;       // look inside cuas_lmax_radius* 2 + 1 square for the local maximum isolation
					lmax_radius,          // final double        centered_radius,
					0.5,                  // final double        centered_radius_blur, 
				    1.0,                  // final double        pix_tolerance, // 
					//					lmax_zero,            // final boolean       cuas_lmax_zero,     // true;    // zero all data outside this radius from the maximum
					target_radius,        // final double        centroid_radius,
					n_recenter,           // final int           n_recenter, //  re-center window around new maximum. 0 -no refines (single-pass)
					cuasMotion.tilesX,    // final int           tilesX){
					no_border,            // final boolean       no_border,
					// Moving target LMA	
					lma_sigma,            // final double        lma_sigma,
					wnd_pedestal,         // final double        wnd_pedestal,
					lma_r0,               // final double        lma_r0,
					lma_ovrsht,  // final double        lma_ovrsht,
					// CUAS Motion LMA parameters
					lma_fit_xy,           // final boolean       lma_fit_xy,		
					lma_fit_a,            // final boolean       lma_fit_a,						
					lma_fit_c,            // final boolean       lma_fit_c,
					lma_fit_r,            // final boolean       lma_fit_r,
					lma_fit_k,            // final boolean       lma_fit_k,		
					lambda,               // final double        lambda,
					lambda_good,          // final double        lambda_good,
					lambda_bad,           // final double        lambda_bad,
					lambda_max,           // final double        lambda_max,
					rms_diff,             // final double        rms_diff,
					num_iter,             // final int           num_iter,0, // 			final double        pre_min_a,   // pre-filter minimal LMA-A (half finbal?)
				    lma_pre_mina*lma_mina,// final double      pre_min_a,   // 0.5 //scale  cuas_lma_mina to filter initial candidates (if there are > one of them)
				    min_keep,             // final int           min_keep, //  2; // keep at least this number of candidates before using cuas_lma_pre_mina filter
					accum_debug,          // final float [][]    accum_debug,
					debugLevel);          // final int           debugLevel);

			if (accum_debug != null) {
				ImagePlus imp_acc = ShowDoubleFloatArrays.makeArrays(
						accum_debug, // double[][] pixels,
						cuasMotion.gpu_max_width,
						cuasMotion.gpu_max_height, 
						model_prefix+"-ACCUMULATED_FILTERED-CENTERED-MULTI-n"+niter,   // String title,
						slice_titles);
				imp_acc.getProcessor().setMinAndMax(-input_range/2, input_range/2);
				if (!batch_mode) {
					imp_acc.show();
				}
				parentCLT.saveImagePlusInModelDirectory(imp_acc);  // ImagePlus   imp)
			}

			// no need to to evaluate scores now - can be done all at once when all non-centered will be replaced by centered, removed (almost) duplicates
			// evaluating scores, but it is not needed
			// new results are sorted by decreasing amplitude, there should be only one - test it. If not still use only the first
			// continue niter for "when"
			// keep only one (best) result for centered getAccumulatedCoordinatesMulti()



			if (save_filtered_low   && debug_more) {
				ImagePlus imp_new = showTargetSequence(
						targets_new_multi,    // final double [][][][] targets_multi,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-NEW-TARGETS-CENTERED-MULTI-n"+niter,// String        title,
						good_only,            // final boolean         good_only,
						show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_new);  // ImagePlus   imp)
			}

			// evaluate scores here to reduce the number of attempts for centered targets
			getEffectiveStrengthLMA(
					targets_new_multi, // final double [][][] target_coords, // LMA
					target_strength,   // final double        target_strength,
					target_frac,       // final double [][]   target_frac, // pairs - strength, minimal fraction for that strength
					lma_rms,           // final double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
					lma_arms,          // final double        lma_arms, // =      0.06; // Maximal absolute RMS (should match one of cuas_lma_arms OR cuas_lma_rrms (0.484)
					lma_rrms,          // final double        lma_rrms, // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
					lma_mina,          // final double        lma_mina, // =      1.0;  // Minimal A (amplitude)
					lma_maxr,          // final double        lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
					lma_minr1,         // final double        lma_minr1,// =      1.0;  // Minimal R1 (radius of positive peak) 
					lma_mink,          // final double        lma_mink, // =      0.0;  // Minimal K (overshoot) = 1.0
					lma_maxk,          // final double        lma_maxk, // =      5.0;  // Minimal K (overshoot) = 3.0//			final double        lma_a2a, 
					lma_a2a,           // final double        lma_a2a, 
					0, // max_mismatch,      // final double        max_mismatch, apply only during final, when mismatch scores are calculated 
					good_mismatch,     // final double        good_mismatch, //do not add to score if worse
					match_len_pwr,     // final double          match_len_pwr, // 0.5; // raise matching length to this power for calculating score
					fail_mismatch,     // final boolean       fail_mismatch,
					target_horizon,    // final double        lma_horizon, // horizon as maximal pixel Y
					cuasMotion.tilesX);            // final int         tilesX,
			int [] remain_es_centered = getScore(
					targets_new_multi, // final double [][][] target_sequence, // modifies certain fields (scores)
					factor_lim,        // final double        importance_limit,
					factor_pow,        // final double        importance_power, // Raise each factor to this power before combining
					score_coeff,       // final double []     importance, // for now (each - squared?): [0] - Amplitude (A/A0), 1 - RMS (RMS0/RMS), 2 - RRMS((RMS/A0) / (RMS/A)
					slow_score,        // final double          slow_score,           // multiply total score for targets detected in slow target mode
					cuasMotion.tilesX);            // final int         tilesX,

			if (save_filtered_low) {
				ImagePlus imp_new_scores = showTargetSequence(
						targets_new_multi,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-NEW-TARGETS_CENTERED_SCORES_MULTI-n"+niter,// String        title,
						good_only,            // final boolean         good_only,
						show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_new_scores);  // ImagePlus   imp)
			}

			int num_new = addNewResults(
					target_sequence_multi, // final double [][][] target_sequence, // will only process non-nulls here
					targets_new_multi,     // final double [][][] new_sequence,
					// add just one best here?
					1, 			           // final int              num_best, // if >0, limit number of best results to add 
					niter);                // final int when_iter,

			if (debugLevel > -4) {
				System.out.println("Added "+num_new+" new tiles as good/bad at iteration "+niter+".");
			}
			if (save_filtered_low) {
				ImagePlus imp_new_scores = showTargetSequence(
						target_sequence_multi,          // double [][][] vector_fields_sequence,
						slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
						model_prefix+"-ADDED_NEW_CENTERED_MULTI-n"+niter,// String        title,
						good_only,            // final boolean         good_only,
						show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
						!batch_mode,          // boolean       show,
						cuasMotion.tilesX);   //  int           tilesX) {
				parentCLT.saveImagePlusInModelDirectory(imp_new_scores);  // ImagePlus   imp)
			}

			if (num_new == 0) {
				if (debugLevel > -4) System.out.println ("Nothing added from last try, breaking the loop.\n");
				break;  // breaks here
			}
			totals = getRemain(target_sequence_multi, num_all, num_undef, num_good, num_bad);
			if (debugLevel > -4) printStats ("After centered iteration "+niter, true, num_all, num_undef, num_good, num_bad);
			if (debugLevel > -4) {
				System.out.println("Centered iteration "+niter+" DONE.\n");
			}
		} //for (niter=0; niter < max_iter; niter++)
		boolean         good_only=    false;
		boolean         show_empty =  true; // false; // show scenes with no (valid) targets
		
		if (intermed_low) { // targets_good now has
			ImagePlus imp_good = showTargetSequence(
					target_sequence_multi,   // double [][][] vector_fields_sequence,
					slice_titles,            // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-ROUND_TWO",// String        title,
					good_only,               // final boolean         good_only,
					show_empty,              // final boolean         show_empty, // show scenes with no (valid) targets
					!batch_mode,             // boolean       show,
					cuasMotion.tilesX);      //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_good);  // ImagePlus   imp)
		}
		return target_sequence_multi; // target_sequence; // contains all tiles, good or bad, their data and when they were defined
	}
	
	public static int [][] filter5Targets( // should work for motion vectors and target coordinates returns index of selected target or -1 for unselected
			final double [][][]   motion_sequence, // may be null
			final double [][][][] target_sequence_multi, // not null
			// if     use motion and select_new will only consider tiles (and compare motion scores in motion_sequence) that have nulls in target_sequence_multi[nseq][ntile]
			// if not use motion and select_new will only consider (and compare scores) tiles that have [RSLT_CENTERED] = 0.0. After selection will mark those as 
			// if not select_new (assumes not  use motion) will return best (and good) target indices
			final boolean         use_motion_in,     // true - use motion vectors confidence, false - use target confidence
			final boolean         select_new_in,     // true - use only untested tiles, false - use good tiles 
			// when using good ones - will select first good one (best if correctly ordered)
			final double          min_confidence, // 0 OK
			final double          lma_horizon, // target below horizon
			final int             tilesX,
			final int             range, // 1 or 2
			final int     []      remain,
			final int     []      passes, // debugging - number of passes required
			final int             debugLevel){
		final boolean  use_motion = (motion_sequence != null) ? use_motion_in : false;
		final boolean  select_new = (motion_sequence != null) ? select_new_in : false;

		final int num_seq = target_sequence_multi.length;
		final int num_tiles = target_sequence_multi[0].length;
		final int tilesY = num_tiles/ tilesX;
		final int [][] filter5 = new int [num_seq][num_tiles];
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		final int dbg_tile = (debugLevel >0) ?(38+45*80) : -1;
		final int dbg_seq0 = -13; 
		final int dbg_seq1 = -14; 
		final int ihorizon = (lma_horizon > 0) ?( (int) Math.ceil(lma_horizon/GPUTileProcessor.DTT_SIZE)) : -1; // tiles with tileY >= ihorizon are removed
		if (dbg_tile >=0) {
			System.out.println("filter5Targets()):00: use_motion="+use_motion+", select_new="+select_new+", ihorizon="+ihorizon);
		}
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					TileNeibs tn = new TileNeibs(tilesX, tilesY);
					boolean [] prohibit = new boolean [num_tiles];
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						Arrays.fill(filter5[nSeq], -1);
						double [][] motion = (motion_sequence != null) ? motion_sequence[nSeq] : null;
						int num_total = 0;
						Arrays.fill(prohibit, false);
						// set initially prohibited tiles - ones not to consider when competing
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if ((motion != null) && ((motion[ntile] == null) || !(motion[ntile][CuasMotionLMA.RSLT_MSCORE] >= min_confidence))) { // defined: good or bad
								if ((ntile == dbg_tile) && (nSeq >= dbg_seq0) && (nSeq <= dbg_seq1)) {
									System.out.println("filter5Targets()):0: ntile="+ntile+", nSeq="+nSeq);
								}
								prohibit[ntile] = true;
							}  else if (select_new){
								if (use_motion ) { // && (target_sequence_multi[nSeq][ntile] != null)
									if (target_sequence_multi[nSeq][ntile] != null) {
										prohibit[ntile] = true;
									}
								} else { // !use_motion - find if it has at least one good with that have [RSLT_CENTERED] = 0.0
									boolean prohibit_this = true;
									double [][] targets = target_sequence_multi[nSeq][ntile]; 
									if (targets != null) {
										for (int i = 0; i < targets.length; i++) if (targets[i] != null) {
											if ((targets[i][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) && (targets[i][CuasMotionLMA.RSLT_CENTERED] == CuasMotionLMA.CENTERED_NO)) {
												prohibit_this = false; // still have some potential targets that were not tried in centered mode	
											}
										}
									}
									prohibit[ntile] |= prohibit_this;
								}

							} else { // valid motion (or motion_sequence==null), not select new - prohibit if no good targets for this tile
								boolean prohibit_this = true;
								double [][] targets = target_sequence_multi[nSeq][ntile]; 
								if (targets != null) {
									for (int i = 0; i < targets.length; i++)  if (targets[i] != null){
										if ((targets[i][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) &&
												(targets[i][CuasMotionLMA.RSLT_CENTERED] != CuasMotionLMA.CENTERED_USED)) {
											prohibit_this = false; // still have some potential targets that were not tried in centered mode
											break;
										}
									}
								}
								prohibit[ntile] |= prohibit_this;
							}
						}
						// is it still used?
						if ((ihorizon > 0) && (ihorizon < tilesY)) {
							Arrays.fill(prohibit, ihorizon*tilesX, num_tiles, true);
						}
						int num_this_pass = 0;
						int npass = 0;
						do {
							npass++;
							num_this_pass = 0;
							for (int tileY = 0; tileY < tilesY; tileY++) {
								for (int tileX = 0; tileX < tilesX; tileX++) {
									int ntile = tileX + tilesX * tileY;
									if ((ntile == dbg_tile) && (nSeq >= dbg_seq0) && (nSeq <= dbg_seq1)) {
										System.out.println("filter5Targets()):1: ntile="+ntile+", nSeq="+nSeq+", npass="+npass+", num_this_pass="+num_this_pass+", num_total="+num_total);
									}
									if (!prohibit[ntile]) { // targets[ntile][conf_index] >0 here by the prohibit preset
										boolean ismax = true;
										if (select_new) {
											if (use_motion) {
												check_max_motion:{
													double cval = motion[ntile][CuasMotionLMA.RSLT_MSCORE] - min_confidence;
													if (cval <= 0) {
														ismax = false;
														prohibit[ntile] = true; // not needed 
														break check_max_motion;
													}
													for (int dy = -range; dy <= range; dy++) {
														for (int dx = -range; dx <= range; dx++) {
															int indx = tn.getNeibIndex(ntile, dx, dy);
															if ((indx >= 0) && !prohibit[indx]) {
																double val = motion[indx][CuasMotionLMA.RSLT_MSCORE]- min_confidence;
																if (val > cval) {
																	ismax = false;
																	break check_max_motion;
																}
															}
														}
													}
													filter5[nSeq][ntile] = 0;
												}
											} else { // not using motion, only consider non-centered good tiles
												double [][] targets = target_sequence_multi[nSeq][ntile]; 
												if (targets !=  null) {
													// find the best non-centered good target in this tile
													int best_targ = -1;
													check_max_target:{
														for (int ntarg = 0; ntarg < targets.length; ntarg++) if ((targets[ntarg] != null) &&
																(targets[ntarg][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) &&
																(targets[ntarg][CuasMotionLMA.RSLT_CENTERED] == CuasMotionLMA.CENTERED_NO)) {
															double cval = targets[ntarg][CuasMotionLMA.RSLT_QSCORE];
															if ((cval > 0) && ((best_targ <0) || (cval > targets[best_targ][CuasMotionLMA.RSLT_QSCORE]))) {
																best_targ = ntarg;
															}
														}
														if (best_targ < 0) {
															ismax = false;
															prohibit[ntile] = true; // not needed
															break check_max_target; 
														}
														double cval = targets[best_targ][CuasMotionLMA.RSLT_QSCORE];
														for (int dy = -range; dy <= range; dy++) {
															for (int dx = -range; dx <= range; dx++) {
																int ntile1 = tn.getNeibIndex(ntile, dx, dy);
																if ((ntile1 >= 0) && !prohibit[ntile1]) {
																	targets =  target_sequence_multi[nSeq][ntile]; // other targets
																	if (targets != null) {
																		for (int ntarg = 0; ntarg < targets.length; ntarg++) if ((targets[ntarg] != null) &&
																				(targets[ntarg][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) &&
																				(targets[ntarg][CuasMotionLMA.RSLT_CENTERED] == CuasMotionLMA.CENTERED_NO)) {
																			double val = targets[ntarg][CuasMotionLMA.RSLT_QSCORE];
																			if (val > cval) {
																				ismax = false;
																				break check_max_target;
																			}
																		}

																	}
																}
															}
														}
														filter5[nSeq][ntile] = best_targ;
													} // check_max_target:{
												} else {
													prohibit[ntile] = true; // probably not needed
												}
											}
										} else {//if (select_new) find best good target (assuming non-motion)
											double [][] targets = target_sequence_multi[nSeq][ntile]; 
											if (targets !=  null) {
												// find the best good target in this tile ?
												int best_targ = -1;
												check_max_target_any:{
													for (int ntarg = 0; ntarg < targets.length; ntarg++) if ((targets[ntarg] != null) &&
															(targets[ntarg][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) &&
															(targets[ntarg][CuasMotionLMA.RSLT_CENTERED] != CuasMotionLMA.CENTERED_USED)) {
														double cval = targets[ntarg][CuasMotionLMA.RSLT_QSCORE];
														if ((cval > 0) && ((best_targ <0) || (cval > targets[best_targ][CuasMotionLMA.RSLT_QSCORE]))) {
															best_targ = ntarg;
														}
													}
													if (best_targ < 0) {
														ismax = false;
														prohibit[ntile] = true; // not needed
														break check_max_target_any; 
													}
													double cval = targets[best_targ][CuasMotionLMA.RSLT_QSCORE];
													for (int dy = -range; dy <= range; dy++) {
														for (int dx = -range; dx <= range; dx++) {
															int ntile1 = tn.getNeibIndex(ntile, dx, dy);
															if ((ntile1 >= 0) && !prohibit[ntile1]) {
																targets =  target_sequence_multi[nSeq][ntile1]; // other targets
																if (targets != null) {
																	for (int ntarg = 0; ntarg < targets.length; ntarg++) if ((targets[ntarg] != null) &&
																			(targets[ntarg][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) &&
																			(targets[ntarg][CuasMotionLMA.RSLT_CENTERED] != CuasMotionLMA.CENTERED_USED)) {
																		double val = targets[ntarg][CuasMotionLMA.RSLT_QSCORE];
																		if (val > cval) {
																			ismax = false;
																			break check_max_target_any;
																		}
																	}
																}
															}
														}
													}
													filter5[nSeq][ntile] = best_targ;
												} // check_max_target_any
											}
										} //if (select_new) else
										if (ismax) {
// 											filter5[nSeq][ntile] = true; // ismax; already set
											// prohibit all around, including this one
											for (int dy = -range; dy <= range; dy++) {
												for (int dx = -range; dx <= range; dx++) {
													int indx = tn.getNeibIndex(ntile, dx, dy);
													if (indx >= 0) { // && ((filtered == null) || !filtered[nSeq][indx])){
														prohibit[indx] = true;
													}
												}
											}
											num_this_pass++;
										}
									} // if (!prohibit[ntile]) { // targets[ntile][conf_index] >0 here by the prohibit preset
								} // for (int tileX = 0; tileX < tilesX; tileX++)
							} // for (int tileY = 0; tileY < tilesY; tileY++) {
							num_total+=num_this_pass;
						} while (num_this_pass > 0);
						if (remain != null) {
							remain[nSeq] = num_total;
						}
						if (passes != null) {
							passes[nSeq] = npass;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		if (select_new && !use_motion) {
			ai.set(0);
			// mark selected (non-centered) targets as "used"
			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()) {
							for (int ntile = 0; ntile < num_tiles; ntile++) { // if (target_sequence[nSeq][ntile] != null) {
								if (ntile == dbg_tile) {
									System.out.println("filter5Targets()):4: filter5["+nSeq+"]["+ntile+"]="+filter5[nSeq][ntile]);
								}

								if (filter5[nSeq][ntile] >= 0) {
									target_sequence_multi[nSeq][ntile][filter5[nSeq][ntile]][CuasMotionLMA.RSLT_CENTERED] = CuasMotionLMA.CENTERED_USED;
									if (ntile == dbg_tile) {
										System.out.println("target_sequence_multi["+nSeq+"]["+ntile+"]["+filter5[nSeq][ntile]+"]["+CuasMotionLMA.RSLT_CENTERED+"] ="+
									target_sequence_multi[nSeq][ntile][filter5[nSeq][ntile]][CuasMotionLMA.RSLT_CENTERED]);
									}
								}
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		}
		return filter5;
	}

	public static void getEffectiveStrengthLMA(
			final double [][][][] target_sequence_multi, // modifies certain fields (scores)
			final double          target_strength,
			final double [][]     target_frac, // pairs - strength, minimal fraction for that strength
			final double          lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
			final double          lma_arms, // =      0.06; // Maximal absolute RMS (should match one of cuas_lma_arms OR cuas_lma_rrms (0.484)
			final double          lma_rrms, // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
			final double          lma_mina, // =      1.0;  // Minimal A (amplitude)
			final double          lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
            final double          lma_minr1,// =      1.0;  // Minimal R1 (radius of positive peak) 
			final double          lma_mink, // =      0.0;  // Minimal K (overshoot) = 1.0
			final double          lma_maxk, // =      5.0;  // Minimal K (overshoot) = 3.0
			final double          lma_a2a,
			final double          max_mismatch, 
			final double          good_mismatch, // 0.4; // do not add to score if worse
			final double          match_len_pwr, // 0.5; // raise matching length to this power for calculating score
			final boolean         fail_mismatch,
			final double          lma_horizon, // horizon as maximal pixel Y
			final int             tilesX	) {
		// if centroid maximum is safely good, ignore lma_a2a ratio
		final double SAFE_CENT_MX = 3 * target_strength; // Find a more elegant solution
		final int num_seq = target_sequence_multi.length;
		final int num_tiles = target_sequence_multi[0].length;
//		final double center_scale = 0.5*Math.PI/GPUTileProcessor.DTT_SIZE;
		final double center_scale = 0.25*Math.PI/GPUTileProcessor.DTT_SIZE;
		final int dbg_tile = -(34 + 34*80);
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							if (ntile == dbg_tile) {
								System.out.println("filterTargetsLMA(): ntile = "+ntile);
							}
							double [][] targets = target_sequence_multi[nSeq][ntile];
							if (targets != null) {
								for (int ntarg = 0; ntarg < targets.length; ntarg++) if (targets[ntarg] != null){
									double [] lma_rslts = targets[ntarg];
									if (lma_rslts != null) {
										lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_NONE;
										double CENT_STR = lma_rslts[CuasMotionLMA.RSLT_CENT_MX];
										double CENT_FRAC= lma_rslts[CuasMotionLMA.RSLT_CENT_F];
										double A =        lma_rslts[CuasMotionLMA.RSLT_A];
										double RMSE =     lma_rslts[CuasMotionLMA.RSLT_RMS];
										double RMSE_A =   lma_rslts[CuasMotionLMA.RSLT_RMS_A];
										double R1 =       lma_rslts[CuasMotionLMA.RSLT_R1];
										double MAX2A =    lma_rslts[CuasMotionLMA.RSLT_MAX2A];
										double Y = (ntile/tilesX +0.5) * GPUTileProcessor.DTT_SIZE + lma_rslts[CuasMotionLMA.RSLT_Y];
										double MM_BEFORE = lma_rslts[CuasMotionLMA.RSLT_MISMATCH_BEFORE];
										double MM_AFTER =  lma_rslts[CuasMotionLMA.RSLT_MISMATCH_AFTER];
										double x = lma_rslts[CuasMotionLMA.RSLT_X];
										double y = lma_rslts[CuasMotionLMA.RSLT_Y];
										double cxy = 1.0;
										if ((Math.abs(x) > 0.5*GPUTileProcessor.DTT_SIZE) || (Math.abs(y) > 0.5*GPUTileProcessor.DTT_SIZE)) {
											cxy = 0.0;
										} else {
											double ax = Math.abs(x) - 0.25*GPUTileProcessor.DTT_SIZE;
											double ay = Math.abs(x) - 0.25*GPUTileProcessor.DTT_SIZE;
											if (ax > 0) {
												cxy *= Math.cos(ax * center_scale);
											}
											if (ay > 0) {
												cxy *= Math.cos(ay * center_scale);
											}
										}
										//	double cxy= Math.max(0,Math.cos(x * center_scale)) * Math.max(0,Math.cos(y * center_scale));
										boolean failed = true;
										try_failures: {
											// start with centroid tests
											if (CENT_STR < target_strength) {
												lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_CENT_STR;
												break try_failures; // cemtroid maximum is too weak
											}
											for (double [] frac_pair : target_frac) {
												if ((CENT_STR > frac_pair[0]) && (CENT_FRAC < frac_pair[1])) {
													lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_CENT_FRAC;
													break try_failures; // centroid fraction is too small
												}
											}
											if (lma_rslts[CuasMotionLMA.RSLT_ITERS] < 0) { // fitting has not failed
												lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_LMA;
												break try_failures; // LMA failed
											}

											if (A < lma_mina) {
												lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_A_LOW;
												break try_failures; // too weak
											}

											if (MAX2A < lma_a2a) {
												if (CENT_STR < SAFE_CENT_MX) {
													lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_ACENT;
													break try_failures; // ratio of maximal value to A is too low
												}
											}

											if (RMSE > lma_rms) {
												lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_RMSE;
												break try_failures; // too high RMSE regardless of A
											}

											if ((RMSE > lma_arms) && (RMSE_A > lma_rrms)) {
												lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_RMSE_R;
												break try_failures; // too high RMSE
											}

											if (lma_rslts[CuasMotionLMA.RSLT_R0] > lma_maxr) {
												lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_R0_HIGH;
												break try_failures; // Radius is too high
											}
											if (R1 < lma_minr1) {
												lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_R1_LOW;
												break try_failures; // Inner radius too small
											}
											if (lma_rslts[CuasMotionLMA.RSLT_K] < lma_mink) {
												lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_K_LOW;
												break try_failures; // K is too low
											}

											if (lma_rslts[CuasMotionLMA.RSLT_K] > lma_maxk) {
												lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_K_HIGH;
												break try_failures; // K is too high
											}
											if (fail_mismatch && !(MM_BEFORE <= max_mismatch) && !(MM_AFTER <= max_mismatch)) { // on both ends, handles NaN
												lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_MISMATCH;
												break try_failures; // Mismatch is too high
											}
											if ((lma_horizon >0) && (Y > lma_horizon)) {
												lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_HORIZON;
												break try_failures; // below horizon line
											}
											if (cxy==0) {
												lma_rslts[CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_FAR;
												break try_failures; // below horizon line
											}
											failed = false; // all tests passed
										}

										double [] quality_factors = new double [IMPORTANCE_LENGTH];
										if (!failed) {
											lma_rslts[CuasMotionLMA.RSLT_FAIL] = 0;
											double    inv_rms =         1.0 / RMSE;
											double    inv_rms_lim =     1.0 / lma_arms;
											double    inv_rel_rms =     1.0 / RMSE_A; // A/RMSE;
											double    inv_rel_rms_lim = 1.0 / lma_rrms;

											quality_factors[IMPORTANCE_A] =     Math.max(0,(A-            lma_mina)/        lma_mina);
											quality_factors[IMPORTANCE_RMS] =   Math.max(0,(inv_rms -     inv_rms_lim)/     inv_rms_lim); // only >0 for small max-es
											quality_factors[IMPORTANCE_RMS_A] = Math.max(0,(inv_rel_rms - inv_rel_rms_lim)/ inv_rel_rms_lim); // only >0 for small max-es

											if (MM_BEFORE <= good_mismatch) {
												quality_factors[IMPORTANCE_MISMATCH] += Math.max(0,(good_mismatch - MM_BEFORE)/good_mismatch); // 0 .. 1
											}
											if (MM_AFTER <= good_mismatch) {
												quality_factors[IMPORTANCE_MISMATCH] += Math.max(0,(good_mismatch - MM_AFTER)/good_mismatch); // 0 .. 1
											}
											quality_factors[IMPORTANCE_MATCH_LEN] = Math.pow(lma_rslts[CuasMotionLMA.RSLT_MATCH_LENGTH], match_len_pwr);// 1.0 if in every scene
											quality_factors[IMPORTANCE_CENTER] = cxy*cxy; 
										}
										// save quality factors
										lma_rslts[CuasMotionLMA.RSLT_QA] =         quality_factors[IMPORTANCE_A];
										lma_rslts[CuasMotionLMA.RSLT_QRMS] =       quality_factors[IMPORTANCE_RMS];
										lma_rslts[CuasMotionLMA.RSLT_QRMS_A] =     quality_factors[IMPORTANCE_RMS_A];
										lma_rslts[CuasMotionLMA.RSLT_QMATCH] =     quality_factors[IMPORTANCE_MISMATCH];
										lma_rslts[CuasMotionLMA.RSLT_QCENTER] =    quality_factors[IMPORTANCE_CENTER];
										lma_rslts[CuasMotionLMA.RSLT_QMATCH_LEN] = quality_factors[IMPORTANCE_MATCH_LEN];
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return;
	}
		
	public static int [] getScore(
			final double [][][][] target_sequence_multi,// modifies certain fields (scores)
			final double          importance_limit,
			final double          importance_power,     // Raise each factor to this power before combining
			final double []       importance,           // for now (each - squared?): [0] - Amplitude (A/A0), 1 - RMS (RMS0/RMS), 2 - RRMS((RMS/A0) / (RMS/A)
			final double          slow_score,           // multiply total score for targets detected in slow target mode
			final int             tilesX	) {
		double sw = 0;
		for (int i = 0; i < importance.length; i++) {
			sw += importance[i];
		}
		for (int i = 0; i < importance.length; i++) {
			importance[i]/=sw;
		}
		// if centroid maximum is safely good, ignore lma_a2a ratio
		final int num_seq = target_sequence_multi.length;
		final int num_tiles = target_sequence_multi[0].length;
		int [] remain = new int [num_seq];
		final int dbg_tile = -(34 + 34*80);
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++)  { // if (target_sequence_multi[nSeq][ntile] != null) {
							if (ntile == dbg_tile) {
								System.out.println("filterTargetsLMA(): ntile = "+ntile);
							}
							double [][] targets =target_sequence_multi[nSeq][ntile];
							if (targets != null ) {
								for (int ntarg = 0; ntarg < targets.length; ntarg++) {
									double [] lma_rslts = targets[ntarg];
									if (lma_rslts != null) {
										lma_rslts[CuasMotionLMA.RSLT_QSCORE] = 0; // CuasMotionLMA.FAIL_NONE;
										if (lma_rslts[CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) {
											double [] quality_factors = new double [IMPORTANCE_LENGTH];
											quality_factors[IMPORTANCE_A] =        lma_rslts[CuasMotionLMA.RSLT_QA];      
											quality_factors[IMPORTANCE_RMS] =      lma_rslts[CuasMotionLMA.RSLT_QRMS];    
											quality_factors[IMPORTANCE_RMS_A] =    lma_rslts[CuasMotionLMA.RSLT_QRMS_A];  
											quality_factors[IMPORTANCE_MISMATCH] = lma_rslts[CuasMotionLMA.RSLT_QMATCH];  
											quality_factors[IMPORTANCE_CENTER] =   lma_rslts[CuasMotionLMA.RSLT_QCENTER]; 
											quality_factors[IMPORTANCE_MATCH_LEN]= lma_rslts[CuasMotionLMA.RSLT_QMATCH_LEN]; 
											for (int i = 0; i < importance.length; i++) {
												if (Double.isNaN(quality_factors[i])) {
													quality_factors[i] = 0.0;
												}
												quality_factors[i] = Math.min(quality_factors[i], importance_limit);
											}								

											double quality = 0;
											for (int i = 0; i < importance.length; i++) {
												if (!Double.isNaN(quality_factors[i])) {
													double factor_contrib = Math.pow(quality_factors[i], importance_power);
													quality += importance[i] * factor_contrib;
												}
											}
											quality = Math.pow(quality, 1.0/importance_power);
											lma_rslts[CuasMotionLMA.RSLT_QSCORE] = quality;
											remain[nSeq]++;
										}
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return remain;
	}

	
	// add a separate filter to remove almost duplicates and reorder
	public static int addNewResults(
			final double [][][][] target_sequence_multi, // will only process non-nulls here
			final double [][][][] new_sequence_multi,
			final int             num_best, // if >0, limit number of best results to add 
			final int             when_iter) { // , 	final boolean overwrite) {
		final int num_seq =   target_sequence_multi.length;
		final int num_tiles = target_sequence_multi[0].length;
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		final AtomicInteger anew = 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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) { // if (target_sequence[nSeq][ntile] != null) {
							double [][] tg =  target_sequence_multi[nSeq][ntile];
							double [][] ns = new_sequence_multi[nSeq][ntile];
							if ((tg != null) || (ns != null)) {
								if (tg == null) tg = new double [0][];
								if (ns == null) ns = new double [0][];
								int new_len = ns.length;
								if (num_best > 0) {
									new_len = Math.min(new_len,  num_best);
								}
								int ct_len = tg.length + new_len;
								if (ct_len > 0) {
									double [][] ct = new double [ct_len][];
									for (int i = 0; i < tg.length; i++) {
										ct[i] = tg[i];
									}
									for (int i = 0; i < new_len; i++) {
										int idest = i + tg.length; 
										ct[idest] = ns[i].clone();
										if (!Double.isNaN(ct[idest][CuasMotionLMA.RSLT_FAIL]) && (when_iter >= 0)) {
											ct[idest][CuasMotionLMA.RSLT_WHEN] = when_iter;
										}
										anew.getAndIncrement();
									}
									target_sequence_multi[nSeq][ntile] = ct;
								} else { // need to put 0-length to mark tile as tested, not to retry again
									target_sequence_multi[nSeq][ntile] = new double[0][];
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return anew.get();
	}
	
	public static double [][][][] combineFastSlow(
			final double [][][][] targets_fast, // will only process non-nulls here
			final double [][][][] targets_slow,
			final double          scale_slow){    // if >0, limit number of best results to add 
		final int num_seq =   targets_fast.length;
		final int num_tiles = targets_fast[0].length;
		final double [][][][] targets_combo = 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() {
					for (int nSeq = ai.getAndIncrement(); nSeq < num_seq; nSeq = ai.getAndIncrement()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) { // if (target_sequence[nSeq][ntile] != null) {
							double [][] tf = targets_fast[nSeq][ntile];
							double [][] ts = targets_slow[nSeq][ntile];
							if ((tf != null) || (ts != null)) {
								if (tf == null) tf = new double [0][];
								if (ts == null) ts = new double [0][];
								double [][] tc = new double [tf.length+ts.length][];
								for (int i = 0; i < tf.length; i++) if (tf[i] != null){
									tc[i] = tf[i].clone();
									tc[i][CuasMotionLMA.RSLT_SLOW] = 0;
								}
								for (int i = 0; i < ts.length; i++) if (ts[i] != null){
									tc[i + tf.length] = ts[i].clone();
									tc[i + tf.length][CuasMotionLMA.RSLT_SLOW] = 1;
									tc[i + tf.length][CuasMotionLMA.RSLT_QSCORE] *= scale_slow;
								}
								targets_combo[nSeq][ntile] = tc;
							}							
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return targets_combo;
	}

	
	
	
	
	
	
	public static int [] getRemain(
			final double [][][][] target_sequence_multi,
			int []                num_all_in,
			int []                num_noncentered_in,
			int []                num_good_in,
			int []                num_bad_in) {
		final int num_seq =   target_sequence_multi.length;
		final int num_tiles = target_sequence_multi[0].length;
		final int [] num_all =         (num_all_in !=   null) ? num_all_in :   new int [num_seq];
		final int [] num_good =        (num_good_in !=  null) ? num_good_in :  new int [num_seq];
		final int [] num_bad =         (num_bad_in !=   null) ? num_bad_in :   new int [num_seq];
		final int [] num_noncentered = (num_noncentered_in != null) ? num_noncentered_in : new int [num_seq];
		Arrays.fill(num_all,   0);       // tiles that have good, bad or undefined targets
		Arrays.fill(num_good,  0);       // good centered targets (may be multiple per tile)
		Arrays.fill(num_bad,   0);       // bad centered targets (may be multiple per tile)
		Arrays.fill(num_noncentered, 0); // not centered (and not yet used for centering) targets
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) {
							double [][] tsm = target_sequence_multi[nSeq][ntile];
							if (tsm != null) {
								num_all[nSeq]++;
								for (int ntarg = 0; ntarg < tsm.length; ntarg++) if (tsm[ntarg] != null){
									double ts_fail = tsm[ntarg][CuasMotionLMA.RSLT_FAIL];
									int centered = (int)  tsm[ntarg][CuasMotionLMA.RSLT_CENTERED];
									if (centered != CuasMotionLMA.CENTERED_USED) {
										if ((centered == CuasMotionLMA.CENTERED_NO) && (ts_fail == CuasMotionLMA.FAIL_NONE)) { // only count good uncentered as undefined
											num_noncentered[nSeq]++;
										} else if (ts_fail == CuasMotionLMA.FAIL_NONE) {
											num_good[nSeq]++;
										} else {
											num_bad[nSeq]++;
										}
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return getTotals(
				num_all,   // int [] num_all,
				num_noncentered, // int [] num_undef,
				num_good,  // int [] num_good,
				num_bad);  // int [] num_bad)
	}
	
	
	public static int [] getRemain(
			final double [][][]   motion_sequence,
			final double [][][][] target_sequence_multi,
			int []                num_all_in,
			int []                num_undef_in,
			int []                num_good_in,
			int []                num_bad_in) {
		final int num_seq =   target_sequence_multi.length;
		final int num_tiles = target_sequence_multi[0].length;
		final int [] num_all =   (num_all_in !=   null) ? num_all_in :   new int [num_seq];
		final int [] num_good =  (num_good_in !=  null) ? num_good_in :  new int [num_seq];
		final int [] num_bad =   (num_bad_in !=   null) ? num_bad_in :   new int [num_seq];
		final int [] num_undef = (num_undef_in != null) ? num_undef_in : new int [num_seq];
		Arrays.fill(num_all,   0); // tiles that have good, bad or undefined targets
		Arrays.fill(num_good,  0); // good targets (may be multiple per tile)
		Arrays.fill(num_bad,   0); // bad targets (may be multiple per tile)
		Arrays.fill(num_undef, 0); // exist in motion_sequence, but not in target_sequence_multi
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++) if (motion_sequence[nSeq][ntile] != null){
							double [][] tsm = target_sequence_multi[nSeq][ntile];
							if (tsm != null) {
								num_all[nSeq]++;
								for (int ntarg = 0; ntarg < tsm.length; ntarg++) if (tsm[ntarg] != null){
									double ts_fail = tsm[ntarg][CuasMotionLMA.RSLT_FAIL];
									if (ts_fail == CuasMotionLMA.FAIL_NONE) {
										num_good[nSeq]++;
									} else {
										num_bad[nSeq]++;
									}
								}
							} else {
								num_undef[nSeq]++;
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return getTotals(
				num_all,   // int [] num_all,
				num_undef, // int [] num_undef,
				num_good,  // int [] num_good,
				num_bad);  // int [] num_bad)
	}

	public static void processMovingTargetsMulti(
			CLTParameters         clt_parameters,
			final boolean         batch_mode,
			QuadCLT               parentCLT,          //
			final float [][]      fpixels,
			UasLogReader          uasLogReader,			
			String []             scene_titles, // recreate slice_titles from scene titles?
			final int             debugLevel) {
		CuasMotion cuasMotion = new CuasMotion (
				clt_parameters, // CLTParameters     clt_parameters,
				parentCLT,      // QuadCLT           parentCLT,
				debugLevel);    // int               debugLevel)

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

		double  slow_ra =        clt_parameters.imp.cuas_slow_ra;
		double  slow_score =     clt_parameters.imp.cuas_slow_score;
		boolean clean_video =    clt_parameters.imp.cuas_clean_video;      //true;// save video without any debug information for targets, output in TIFF files. False - same output for video and TIFFs
		
		boolean  multi_targ =          clt_parameters.imp.cuas_multi_targ;           // true;  // Use multi-target for each tile mode 
		int seq_length = corr_offset + corr_pairs;
		int corr_inc = half_step ? (corr_offset/2) : corr_offset;
		int num_corr_samples = (fpixels.length - seq_length - start_frame) / corr_inc;
		String [] slice_titles = new String [num_corr_samples];
		for (int nscan = 0; nscan < num_corr_samples; nscan++) {
			int frame_cent = start_frame + corr_inc * nscan + seq_length/2; // debug only
			slice_titles[nscan] =  scene_titles[frame_cent];
		}
		double [][][] targets_nonconflict = null;
		// process fast targets		
		cuasMotion.slow_targets = false; // just for filenames
		float [][] fpixels_fast = temporalUnsharpMask( // all good targets
				clt_parameters, // CLTParameters         clt_parameters,
				batch_mode,     // final boolean         batch_mode,
				parentCLT,      // QuadCLT               parentCLT,          //
				fpixels,        // final float [][]      fpixels,
				cuasMotion,     // final CuasMotion      cuasMotion,
				scene_titles,   // String []             scene_titles, // recreate slice_titles from scene titles?
				debugLevel);    // final int             debugLevel)
		double [][][] motion_sequence_fast = prepareMotionBasedSequence( // all good targets
				clt_parameters, // CLTParameters         clt_parameters,
				batch_mode,     // final boolean         batch_mode,
				parentCLT,      // QuadCLT               parentCLT,          //
				fpixels_fast,    // float [][]            fpixels_tum,
				cuasMotion,     // final CuasMotion      cuasMotion,
				scene_titles,   // String []             scene_titles, // recreate slice_titles from scene titles?
				slice_titles,   // String []             slice_titles,
				debugLevel);    // final int             debugLevel)
		double [][][][] targets_fast =   locateAndFreezeTargetsMulti( // all good targets
				clt_parameters, // CLTParameters         clt_parameters,
				batch_mode,     // final boolean         batch_mode,
				parentCLT,      // QuadCLT               parentCLT,          //
				fpixels_fast,   // float [][]            fpixels_tum,
				motion_sequence_fast,// double [][][]         target_sequence,
				cuasMotion,     // final CuasMotion      cuasMotion,
				scene_titles,   // String []             scene_titles, // recreate slice_titles from scene titles?
				slice_titles,   // String []             slice_titles,
				debugLevel);    // final int             debugLevel)
		if (multi_targ) {
			// process slow targets		
			cuasMotion.slow_targets = true; // just for filenames
			float [][] fpixels_slow = runningGaussian(
					fpixels,                   // final float [][] fpixels,
					slow_ra,                   // final double     ra_length,
					cuasMotion.gpu_max_width); // final int        width)
			double [][][] motion_sequence_slow = prepareMotionBasedSequence( // all good targets
					clt_parameters, // CLTParameters         clt_parameters,
					batch_mode,     // final boolean         batch_mode,
					parentCLT,      // QuadCLT               parentCLT,          //
					fpixels_slow,    // float [][]            fpixels_tum,
					cuasMotion,     // final CuasMotion      cuasMotion,
					scene_titles,   // String []             scene_titles, // recreate slice_titles from scene titles?
					slice_titles,   // String []             slice_titles,
					debugLevel);    // final int             debugLevel)
			double [][][][] targets_slow =   locateAndFreezeTargetsMulti( // all good targets
					clt_parameters, // CLTParameters         clt_parameters,
					batch_mode,     // final boolean         batch_mode,
					parentCLT,      // QuadCLT               parentCLT,          //
					fpixels_slow,   // float [][]            fpixels_tum,
					motion_sequence_slow,// double [][][]         target_sequence,
					cuasMotion,     // final CuasMotion      cuasMotion,
					scene_titles,   // String []             scene_titles, // recreate slice_titles from scene titles?
					slice_titles,   // String []             slice_titles,
					debugLevel);    // final int             debugLevel)
			
			double [][][][] targets_good_multi = combineFastSlow(
					targets_fast,       // final double [][][][] targets_fast, // will only process non-nulls here
					targets_slow,       // final double [][][][] targets_slow,
					slow_score);        // final double          scale_slow){    // if >0, limit number of best results to add
			
			sortMultiTargets(
					targets_good_multi, // final double [][][][] target_multi,
					true);              // final boolean         trim_nulls) { // trim null targets

			targets_nonconflict = resolveTargetsConflictsMulti(
					clt_parameters, // CLTParameters         clt_parameters,
					batch_mode,     // boolean               batch_mode,
					parentCLT,      // QuadCLT               parentCLT,          //
					fpixels,        // float [][]            fpixels,
					cuasMotion,     // CuasMotion            cuasMotion,
					targets_good_multi,   // double [][][][] targets_good_multi, // all targets, including conflicting
					slice_titles,   // String []             slice_titles,
					debugLevel);    // final int             debugLevel)

		} else {

			double [][][] targets_good =   locateAndFreezeTargets( // all good targets
					clt_parameters, // CLTParameters         clt_parameters,
					batch_mode,     // final boolean         batch_mode,
					parentCLT,      // QuadCLT               parentCLT,          //
					fpixels,        // final float [][]      fpixels,
					fpixels_fast,    // float [][]            fpixels_tum,
					motion_sequence_fast,// double [][][]         target_sequence,
					cuasMotion,     // final CuasMotion      cuasMotion,
					scene_titles,   // String []             scene_titles, // recreate slice_titles from scene titles?
					slice_titles,   // String []             slice_titles,
					debugLevel);    // final int             debugLevel)

			targets_nonconflict = resolveTargetsConflicts(
					clt_parameters, // CLTParameters   clt_parameters,
					batch_mode,     // boolean         batch_mode,
					parentCLT,      // QuadCLT         parentCLT,          //
					fpixels,        // float [][]      fpixels,
					cuasMotion,     // CuasMotion      cuasMotion,
					targets_good,   // double [][][]   targets_good, // all targets, including conflicting
					slice_titles,   // String []             slice_titles,
					debugLevel);    // final int             debugLevel)

			boolean remove_isolated= clt_parameters.imp.cuas_isolated;		
			if (remove_isolated) {
				removeShortTargetSequences(
						clt_parameters,          // CLTParameters       clt_parameters,
						batch_mode,              // final boolean       batch_mode,
						parentCLT,               // QuadCLT             parentCLT,          //
						targets_nonconflict,     // final double [][][] target_sequence,
						cuasMotion,              // final CuasMotion    cuasMotion,
						slice_titles,            // String []           slice_titles,
						debugLevel);             // final int            debugLevel)
			}
		}
		generateExtractFilterMovingTargets( // move parameters to clt_parameters
				clt_parameters,          // CLTParameters       clt_parameters,
				false, // final boolean       video_pass, // if clt_parameters.cuas_clean_video=true, video_pass=0 - output TIFFS, but no video. If video_pass==1 - only video with no debug
				batch_mode,              // final boolean       batch_mode,
				parentCLT,               // QuadCLT             parentCLT,          //
				// three arrays needed
				fpixels,                 // final float [][]    fpixels,
				targets_nonconflict,     // final double [][][] vf_sequence,  // center tiles (not extended), null /non-null only
				cuasMotion,              // final CuasMotion    cuasMotion,
				uasLogReader,            // UasLogReader        uasLogReader,	
				scene_titles,            // String []           scene_titles, // recreate slice_titles from scene titles?
				slice_titles,            // String []           slice_titles,
				debugLevel);             // final int            debugLevel)
		if (clean_video) {
			generateExtractFilterMovingTargets( // move parameters to clt_parameters
					clt_parameters,          // CLTParameters       clt_parameters,
					true, // final boolean       video_pass, // if clt_parameters.cuas_clean_video=true, video_pass=0 - output TIFFS, but no video. If video_pass==1 - only video with no debug
					batch_mode,              // final boolean       batch_mode,
					parentCLT,               // QuadCLT             parentCLT,          //
					// three arrays needed
					fpixels,                 // final float [][]    fpixels,
					targets_nonconflict,     // final double [][][] vf_sequence,  // center tiles (not extended), null /non-null only
					cuasMotion,              // final CuasMotion    cuasMotion,
					uasLogReader,            // UasLogReader        uasLogReader,	
					scene_titles,            // String []           scene_titles, // recreate slice_titles from scene titles?
					slice_titles,            // String []           slice_titles,
					debugLevel);             // final int            debugLevel)
		}
	}	
	
	
	
	final static public double [][][] resolveTargetsConflictsMulti(
			CLTParameters     clt_parameters,
			boolean           batch_mode,
			QuadCLT           parentCLT,          //
			float [][]        fpixels,
			CuasMotion        cuasMotion,
			double [][][][]   targets_multi, // targets_good, // all targets, including conflicting
			String []         slice_titles,
			int               debugLevel){
		int     corr_offset =    clt_parameters.imp.cuas_corr_offset;
		int     corr_pairs =     clt_parameters.imp.cuas_corr_pairs;
		boolean half_step =      clt_parameters.imp.cuas_half_step; // true;
		int     max_range =      clt_parameters.imp.cuas_max_range;

		double target_strength = clt_parameters.imp.cuas_target_strength;
		double [][] target_frac = new double [clt_parameters.imp.cuas_target_frac.length][2];
	// Moving target LMA	
	// CUAS Motion LMA parameters
	// 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_minr1 =      clt_parameters.imp.cuas_lma_minr1; // =     1.0;  // Maximal inner radius
	    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
	    double  lma_a2a =        clt_parameters.imp.cuas_lma_a2a; // =       0.7;  // Minimal ratio of the maximal pixel to the amplitude
	    double  max_mismatch =   clt_parameters.imp.cuas_max_mismatch;   // 2;
		double  good_mismatch =  clt_parameters.imp.cuas_good_mismatch;  // 0.4;
		double  match_len_pwr =  clt_parameters.imp.cuas_match_len_pwr;   // raise matching length to this power for calculating score
	    boolean fail_mismatch =  clt_parameters.imp.cuas_fail_mismatch;  // Fail on high mismatch early (when calculating scores);
	    double  target_horizon=  clt_parameters.imp.cuas_horizon;	    
	// Handling overall target scores    
	    
		double min_score_lma =   clt_parameters.imp.cuas_score_lma;
		int min_seq=             clt_parameters.imp.cuas_min_seq;    // 3;     // minimal number of consecutive key frames for the same target
		int enough_seq=          clt_parameters.imp.cuas_enough_seq; // 5;     // minimal number of consecutive key frames for the same target
		double  seq_travel=      clt_parameters.imp.cuas_seq_travel; // 3.0;   // minimal diagonal of the bounding box that includes sequence to be considered "cuas_enough_seq". Filtering out atmospheric fluctuations
		double factor_lim =      clt_parameters.imp.cuas_factor_lim;
		double factor_pow =      clt_parameters.imp.cuas_factor_pow;
		double [] score_coeff =  clt_parameters.imp.cuas_score_coeff.clone();
		double  slow_score =     clt_parameters.imp.cuas_slow_score;
		boolean intermed_low =   clt_parameters.imp.cuas_intermed_low;    //true;
		boolean debug_more =     clt_parameters.imp.cuas_debug_more;
		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 model_prefix = parentCLT.getImageName()+getParametersSuffix(clt_parameters,null);
		int start_frame = 0;
		int num_scenes = fpixels.length; // .getStack().getSize()- first_corr; // includes average
		int seq_length = corr_offset + corr_pairs;
		int corr_inc = half_step ? (corr_offset/2) : corr_offset;
		int num_corr_samples = (num_scenes - seq_length - start_frame) / corr_inc;
		int [] filter5_remain = new int [num_corr_samples];
		int [] num_all =        new int [num_corr_samples];
		int [] num_undef =      new int [num_corr_samples];
		int [] num_good =       new int [num_corr_samples];
		int [] num_bad =        new int [num_corr_samples];
		int [] passes = new int [num_corr_samples]; // debugging filter5
		// there could be many conflicting tiles in targets_good. Calculate scores and then filter to keep the best ones that do not conflict
		// take into account before/after matching
		double interseq_scale =  0.5* corr_inc/corr_offset;  // multiply target velocity to get offset in the middle between the key frames
		// 		interseq_scale = (half_step ? 0.25 : 0.5) // ???
		
		if (debugLevel > -4) System.out.println("\nStarting resolution of conflicting tiles.");
		boolean         good_only=    false; // true;
		boolean         show_empty =  true; // false; // show scenes with no (valid) targets
		boolean         trim_nulls =  true;
		final double    duplicate_tolerance = clt_parameters.imp.cuas_duplicate_tolerance ; // 2.0; // (pix) Remove weaker maximums closer than this to a stronger one   		
		if (intermed_low && debug_more) {
			ImagePlus imp_in = showTargetSequence(
					targets_multi,        // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-BEFORE_RESOLVE_MULTI",// String        title,
					good_only,            // final boolean         good_only,
					show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_in);
		}
		
		sortMultiTargets(
				targets_multi,        // final double [][][][] target_multi,
				trim_nulls);          // final boolean         trim_nulls) { // trim null targets
		int [] num_duplicates = removeWeakerDuplicates( // needs sortMultiTargets and after
				targets_multi,        // final double [][][][] target_multi,
				duplicate_tolerance); // final double          pix_tolerance) 
		if (debugLevel > -4) System.out.println("Removed "+num_duplicates[1]+" good duplicate targets (total with bad - "+num_duplicates[0]+").");
		sortMultiTargets(
				targets_multi,        // final double [][][][] target_multi,
				trim_nulls);          // final boolean         trim_nulls) { // trim null targets
		
		if (intermed_low && debug_more) {
			ImagePlus imp_dup = showTargetSequence(
					targets_multi,        // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-AFTER_DUPLICATES",// String        title,
					good_only,            // final boolean         good_only,
					show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_dup);
		}
		
		// Calculate sequences lengths
		// Calculate image x,y for each target half keyframe interval before the keyframe and half-interval after.
		getHalfBeforeAfterPixXY(
				targets_multi,       // final double [][][][] targets_multi,
				true,                // final boolean       good_only,
				interseq_scale,      // , // final double        interseq_scale,
				cuasMotion.tilesX,   // final int           tilesX,
				debugLevel);         //	final int           debugLevel) {
		// still calculate BA in addition to lengths
		
		// TODO: untested yet
		
		calculateMismatchBeforeAfter(
				targets_multi,       // final double [][][][]  targets_multi,
				true, // final boolean          good_only,
				cuasMotion.tilesX);   //  int           tilesX) {		
		
		calcMathingTargetsLengths(  // calculate number of consecutive keyframes connected to each target
				targets_multi,       // final double [][][][] targets_multi,
				// which is better here?
				good_mismatch, // max_mismatch,        // final double          max_mismatch,     // if <=0, do not calculate  mismatch_ba and filter
				cuasMotion.tilesX);   //final int             tilesX)
		if (intermed_low && debug_more) {
			ImagePlus imp_lengths = showTargetSequence(
					targets_multi,        // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-MATCHING_LENGTHS",// String        title,
					good_only,            // final boolean         good_only,
					show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_lengths);
		}
		
		
		getEffectiveStrengthLMA(
				targets_multi,     // final double [][][][] target_sequence_multi, // modifies certain fields (scores)
				target_strength,   // final double        target_strength,
				target_frac,       // final double [][]   target_frac, // pairs - strength, minimal fraction for that strength
				lma_rms,           // final double        lma_rms, // =       1.5;  // Maximal RMS (should always match, regardless if A)
				lma_arms,          // final double        lma_arms, // =      0.06; // Maximal absolute RMS (should match one of cuas_lma_arms OR cuas_lma_rrms (0.484)
				lma_rrms,          // final double        lma_rrms, // =      0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
				lma_mina,          // final double        lma_mina, // =      1.0;  // Minimal A (amplitude)
				lma_maxr,          // final double        lma_maxr, // =      5.0;  // Minimal K (overshoot) = 3.0
				lma_minr1,         // final double        lma_minr1,// =      1.0;  // Minimal R1 (radius of positive peak) 
				lma_mink,          // final double        lma_mink, // =      0.0;  // Minimal K (overshoot) = 1.0
				lma_maxk,          // final double        lma_maxk, // =      5.0;  // Minimal K (overshoot) = 3.0
				lma_a2a,           // final double        lma_a2a,
				max_mismatch,      // final double        max_mismatch,
				good_mismatch,     // final double        good_mismatch, //do not add to score if worse
				match_len_pwr,     // final double          match_len_pwr, // 0.5; // raise matching length to this power for calculating score
				fail_mismatch,     // final boolean       fail_mismatch,
				target_horizon,    // final double        lma_horizon, // target below horizon
				cuasMotion.tilesX); // final int           tilesX,
		
//		int [] remain_es = 
				getScore( // calculates composite sceres
				targets_multi,     // final double [][][][] target_sequence_multi, // modifies certain fields (scores)
				factor_lim,        // final double        importance_limit,
				factor_pow,        // final double        importance_power, // Raise each factor to this power before combining
				score_coeff,       // final double []     importance, // for now (each - squared?): [0] - Amplitude (A/A0), 1 - RMS (RMS0/RMS), 2 - RRMS((RMS/A0) / (RMS/A)
				slow_score,        // final double          slow_score,           // multiply total score for targets detected in slow target mode
				cuasMotion.tilesX);            // final int         tilesX,

		getRemain(targets_multi, num_all, num_undef, num_good, num_bad);
		if (debugLevel > -4) printStats ("Resolving, after matching before/after", true,num_all, num_undef, num_good, num_bad);
		if (intermed_low  && debug_more) {
			ImagePlus imp_failures = showTargetSequence(
					targets_multi,        // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-SCORES_WITH_LENGTHS",// String        title,
					good_only,            // final boolean         good_only,
					show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_failures);
		}
		
		// no extra recalculations here as was in pre-Multi?
		// Use here the real threshold to remove weak
		int [][] filter5 = filter5Targets( // will ignore failed tiles
				null,               // final double [][][] target_sequence,
				targets_multi,      // final double [][][][] target_sequence_multi,
				// if     use motion and select_new will only consider tiles (and compare motion scores in motion_sequence) that have nulls in target_sequence_multi[nseq][ntile]
				// if not use motion and select_new will only consider (and compare scores) tiles that have [RSLT_CENTERED] = 0.0
				// if not select_new (assumes not  use motion) will return best (and good) target indices
				false,              // final boolean       use_motion,     // true - use motion vectors confidence, false - use target confidence
				false,              // final boolean       select_new,     // true - use only untested tiles, false - use good tiles 
				0, // min_score_lma,      // double              min_confidence,
				target_horizon,     // final double        lma_horizon, // target below horizon
				cuasMotion.tilesX,  // final int           tilesX,
				max_range,          // final int           range, // 1 or 2
				filter5_remain,     // final int     []    remain){
				passes,             // final int     []    passes, // debugging - numer of passes required
				1); // debugLevel);        // final int           debugLevel)
		if (debugLevel > -4) printStatsLine("After filter5 remaining non-conflicting targets", true,filter5_remain);

		if (intermed_low  && debug_more) {
			ImagePlus imp_filter5_lma = ShowDoubleFloatArrays.makeArrays(
					filter5, // double[][] pixels,
					cuasMotion.tilesX,
					cuasMotion.tilesY, 
					model_prefix+"-FILTER5_FINAL",   // String title,
					slice_titles);
			if (!batch_mode) {
				imp_filter5_lma.show();
			}
			parentCLT.saveImagePlusInModelDirectory(imp_filter5_lma);
		}
		
		// apply filter5 to targets_good
		findStrongerNeighbor(// does not modify "when"
				targets_multi,        // final double [][][][] target_multi,
				filter5,              // final boolean [][] filter5)
				true,                 // false,                // final boolean       mark_failed,
				cuasMotion.tilesX);   // final int           tilesX) {	
		if (intermed_low  && debug_more) {
			ImagePlus imp_neibs = showTargetSequence(
					targets_multi,         // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-STRONGER-NEIBS",// String        title,
					good_only,            // final boolean         good_only,
					show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_neibs);  // ImagePlus   imp)
		}
		
		
		
		// now failed by neighbors are marked as non-NaN (tile index or -1), not as failed
		// here failed by stronger neighbors are also removed (balance weights if needed)
		// maybe also needed to remove all under threshold here

		sortMultiTargets( // stronger neibs will be incorrect
				targets_multi, // final double [][][][] target_multi,
				true); // trim_nulls);   // final boolean         trim_nulls) { // trim null targets
		// final with all multi-target data
		if (intermed_low) { // save always 
			ImagePlus imp_good = showTargetSequence(
					targets_multi,         // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-TARGETS-MULTI",// String        title,
					good_only,            // final boolean         good_only,
					show_empty,           // final boolean         show_empty, // show scenes with no (valid) targets
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_good);  // ImagePlus   imp)
		}
		if (debugLevel > -4) System.out.println("Cutting below minimal score = "+min_score_lma);
		double [][][] target_single = convertFromMultiTarget(// single target per tile
				targets_multi,  // final double [][][][] target_multi) {
				min_score_lma,  //final double          minimal_score) {
				min_seq,        // final int             min_seq) { // minimal sequence length
				enough_seq, // final int             enough_seq, // good regardless of scores 
				num_good);      // final int []          remain){
		if (debugLevel > -4) printStatsLine("Final section of single targets per tile", true, num_good);

		if (intermed_low) { // save always
			ImagePlus imp_failures = showTargetSequence(
					target_single,        // double [][][] vector_fields_sequence,
					slice_titles,         // String []     titles, // all slices*frames titles or just slice titles or null
					model_prefix+"-TARGETS_SINGLE-FINAL",// String        title,
					!batch_mode,          // boolean       show,
					cuasMotion.tilesX);   //  int           tilesX) {
			parentCLT.saveImagePlusInModelDirectory(imp_failures);
		}
		return target_single; // targets_multi; // target_sequence; // non-overlap if consider marked stronger tiles
	}
		
	public static void findStrongerNeighbor(// does not modify "when"
			final double [][][][] target_multi,
			final int [][]        filter5,
			final boolean         mark_failed,
			final int             tilesX) {
		final int range = 2;
		final int num_seq = target_multi.length;
		final int num_tiles = target_multi[0].length;
		final int tilesY = num_tiles/tilesX;
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
	 // use filter5 values?
		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()) {
						for (int ntile = 0; ntile < num_tiles; ntile++)  { // && (target_sequence[nSeq][ntile][CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE)) { // &&
							double [][] targets = target_multi[nSeq][ntile];
							if (targets != null) {
								for (int ntarg = 0; ntarg < targets.length; ntarg++) if (targets[ntarg] != null){
									double [] target = targets[ntarg];
									if ((target != null) && (target[CuasMotionLMA.RSLT_FAIL] == CuasMotionLMA.FAIL_NONE) &&
											(target[CuasMotionLMA.RSLT_CENTERED] != CuasMotionLMA.CENTERED_USED)) {
										if (filter5[nSeq][ntile] >=0) {
											if (filter5[nSeq][ntile] == ntarg) {
												targets[ntarg][CuasMotionLMA.RSLT_STRONGER] = Double.NaN;
											} else {
												targets[ntarg][CuasMotionLMA.RSLT_STRONGER] = ntile + num_tiles * filter5[nSeq][ntile]; // encode tile and variant (ntarg)
											}
										} else { // find a better around
											check_nebs: {
												for (int dy = -range; dy <= range; dy++) {	
													for (int dx = -range; dx <= range; dx++) {
														int ntile1 = tn.getNeibIndex(ntile, dx, dy);
														if ((ntile1 >= 0) && (filter5[nSeq][ntile1] >=0)) {
															targets[ntarg][CuasMotionLMA.RSLT_STRONGER] = ntile1 + num_tiles * filter5[nSeq][ntile1]; // encode tile and variant (ntarg)
															break check_nebs;
														}
													}
												}
												System.out.println("findStrongerNeighbor(): FIXME: no selected neighbor in 5x5 vicinity, but this is not selected either. nSeq="+
														nSeq+", ntile="+ntile+", ntarg="+ntarg);
												throw new IllegalArgumentException("findStrongerNeighbor(): FIXME: no selected neighbor in 5x5 vicinity, but this is not selected either. nSeq="+
														nSeq+", ntile="+ntile+", ntarg="+ntarg);
											}
										}
										if (mark_failed && (filter5[nSeq][ntile] != ntarg)) {
											targets[ntarg][CuasMotionLMA.RSLT_FAIL] = CuasMotionLMA.FAIL_NEIGHBOR;
										}
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return;
	}
	
	
	
}
