package com.elphel.imagej.tileprocessor;
/**
 **
 ** QuadCLTCPU - Process images with CLT-based methods (code specific to ImageJ plugin)
 ** Using CPU
 **
 ** Copyright (C) 2017-2020 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  QuadCLTCPU.java is free software: you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation, either version 3 of the License, or
 **  (at your option) any later version.
 **
 **  This program is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ** -----------------------------------------------------------------------------**
 **
 */

//import java.awt.Polygon;

import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
import org.apache.commons.math3.geometry.euclidean.threed.RotationOrder;
import java.awt.Rectangle;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.DoubleAccumulator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.elphel.imagej.calibration.CalibrationFileManagement;
import com.elphel.imagej.calibration.PixelMapping;
import com.elphel.imagej.cameras.CLTParameters;
import com.elphel.imagej.cameras.ColorProcParameters;
import com.elphel.imagej.cameras.EyesisCorrectionParameters;
import com.elphel.imagej.cameras.ThermalColor;
import com.elphel.imagej.common.DoubleGaussianBlur;
import com.elphel.imagej.common.GenericJTabbedDialog;
import com.elphel.imagej.common.LogTee;
import com.elphel.imagej.common.PolynomialApproximation;
import com.elphel.imagej.common.ShowDoubleFloatArrays;
import com.elphel.imagej.correction.CorrectionColorProc;
import com.elphel.imagej.correction.EyesisCorrections;
import com.elphel.imagej.correction.Eyesis_Correction;
import com.elphel.imagej.cuas.CorrectionFPN;
import com.elphel.imagej.cuas.Cuas;
import com.elphel.imagej.cuas.CuasData;
import com.elphel.imagej.gpu.GPUTileProcessor;
import com.elphel.imagej.gpu.GpuQuad;
import com.elphel.imagej.gpu.TpTask;
import com.elphel.imagej.ims.Did_gps_pos;
import com.elphel.imagej.ims.Did_ins_1;
import com.elphel.imagej.ims.Did_ins_2;
import com.elphel.imagej.ims.Did_pimu;
import com.elphel.imagej.ims.EventLogger;
import com.elphel.imagej.ims.Imx5;
import com.elphel.imagej.orthomosaic.OrthoMap;
import com.elphel.imagej.readers.ElphelTiffWriter;
import com.elphel.imagej.readers.ImagejJp4Tiff;
import com.elphel.imagej.x3d.export.TriMesh;
import com.elphel.imagej.x3d.export.WavefrontExport;
import com.elphel.imagej.x3d.export.X3dOutput;

import ij.CompositeImage;
import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.Prefs;
import ij.WindowManager;
//import ij.gui.Overlay;
import ij.io.FileSaver;
import ij.plugin.filter.AVI_Writer;
import ij.process.ColorProcessor;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;
import loci.formats.FormatException;


public class QuadCLTCPU {
	public static final String    IMU_CALIB_LOGS_SUFFIX =      "-IMU_CALIB.log";
	public static final String    IMU_CALIB_DETAILS_SUFFIX =   "-IMU_CALIB_DETAILS.csv";
	public static final String    IMU_CALIB_COMBO_SUFFIX =     "-IMU_CALIB_COMBO.csv";
	public static final String    IMU_CALIB_SUMMARY_SUFFIX =   "-IMU_CALIB_SUMMARY.csv";
	public static final String    IMU_CALIB_SUM_COMBO_SUFFIX = "-IMU_CALIB_SUMMARY_COMBO.csv";
	public static final String    ORIENTATION_LOGS_SUFFIX =    "-ORIENTATION.log";
	public static final String    INTERFRAME_SUFFIX =          "-INTERFRAME";
	public static final String    FIELD_CALIBRATION_SUFFIX =   "-FIELD_CALIBRATION";
	public static final String    SYSTEM_OUT_LOG_SUFFIX =      "-SYSTEM_OUT.log";
	public static final String    CONFIGURATION_EXTENSION =    ".corr-xml";
	
	public static final String [] DSI_SUFFIXES = {"-INTER-INTRA-LMA","-INTER-INTRA","-DSI_MAIN"};
	public static int             INDEX_INTER_LMA = 0;
	public static int             INDEX_INTER =     1;
	public static int             INDEX_DSI_MAIN =  2;
	public static final String [] FGBG_TITLES_ADJ = {"disparity","strength"};
//	public static final String [] FGBG_TITLES = {"disparity","strength", "rms","rms-split","fg-disp","fg-str","bg-disp","bg-str"};
	public static final String [] FGBG_TITLES_AUX = 
		{"disparity","strength", "rms","rms-split","fg-disp","fg-str","bg-disp","bg-str","aux-disp","aux-str"};
//	public static final enum      FGBG           {DISPARITY,  STRENGTH,   RMS,  RMS_SPLIT,  FG_DISP,  FG_STR,  BG_DISP,  BG_STR};
	public static final int       FGBG_DISPARITY = 0;
	public static final int       FGBG_STRENGTH =  1;
	public static final int       FGBG_RMS =       2;
	public static final int       FGBG_RMS_SPLIT = 3;
	public static final int       FGBG_FG_DISP =   4;
	public static final int       FGBG_FG_STR =    5;
	public static final int       FGBG_BG_DISP =   6;
	public static final int       FGBG_BG_STR =    7;
	public static final int       FGBG_AUX_DISP =  8; // AUX calculated disparity
	public static final int       FGBG_AUX_STR =   9; // AUX calculated strength

	public static final int       DSRBG_DISPARITY =     0;
	public static final int       DSRBG_STRENGTH =      1;
	public static final int       DSRBG_DISPARITY_LMA = 2;
	public static final int       DSRBG_RED =           3;
	public static final int       DSRBG_BLUE =          4;
	public static final int       DSRBG_GREEN =         5;
	public static final int       DSRBG_MONO =          3;
	
	public static final boolean   USE_PRE_2021 = false; // temporary
	public static final int       THREADS_MAX =       100;
	public static final double [] ZERO3 = {0.0,0.0,0.0};
	
	public static final String CENTER_DIR_SUFFIX =    "-CENTER";
	public static final String CENTER_CLT_SUFFIX =    "-CLT";
	public static final String CENTER_FPN_SUFFIX =    "-FPN";
	public static final String CENTER_ROWCOL_SUFFIX = "-ROWCOL";
	
	 // Scale estimated overlap before masking out folded pixels to account for rotations and distortions
	public static final double RELIABLE_SCALE =        1.2;

//	public GPUTileProcessor.GpuQuad gpuQuad =      null;

	static String []                                       fine_corr_coeff_names = {"A","B","C","D","E","F"};
	static String []                                       fine_corr_dir_names = {"X","Y"};
	public static String                                   PREFIX =     "EYESIS_DCT.";    // change later (first on save)
	public static String                                   PREFIX_AUX = "EYESIS_DCT_AUX."; // change later (first on save)
//	static int                                             QUAD =  4; // number of cameras

	public Properties                                      properties = null;
	public EyesisCorrections                               eyesisCorrections = null;
	public EyesisCorrectionParameters.CorrectionParameters correctionsParameters=null;
	double [][][][][][]                                    clt_kernels = null; // can be used to determine monochrome too?
	public GeometryCorrection                              geometryCorrection = null;
	double[] extrinsic_vect; //  = new double [GeometryCorrection.CORR_NAMES.length]; // extrinsic corrections (needed from properties, before geometryCorrection
	public int                                             extra_items = 8; // number of extra items saved with kernels (center offset (partial, full, derivatives)
	public ImagePlus                                       eyesisKernelImage = null;
	public long                                            startTime;     // start of batch processing
	public long                                            startSetTime;  // start of set processing
	public long                                            startStepTime; // start of step processing

	public double [][][]                                   fine_corr  = new double [4][2][6]; // per port, per x/y, set of 6 coefficient for fine geometric corrections

	public TileProcessor                                   tp = null;

	public String                                          image_name = null;
	public String                                          image_path = null;
	double []                                              gps_lla =    null;
	public double [][][]                                   image_data = null; // [channel][color][pixel]
	public double [][][]                                   image_data_alt = null; // [channel][color][pixel] If not null - will use instead of image_data
	public double [][]                                     image_center = null; // reference to a common sensor-combined center view image (set for center instance)
	
	public boolean                                         no_kernels = false; // debug feature
	public double [][][]                                   image_fpn = null;  // [channel][color][pixel] Shared by images in a series, subtracted during conditioning
	// and applied to image_data;
	public double [][][]                                   image_fpn_applied = null;  // [channel][color][pixel] // when applying different instance, the old will be unapplied
	public double [][]                                     image_row_avg = null; //[nsens][width] integrated by columns, lwir-only  (single-color)
	public double [][]                                     image_col_avg = null; //[nsens][height] integrated by rows, lwir-only (single-color)
	public double [][]                                     image_row_avg_applied = null; //[nsens][width] integrated by columns, lwir-only  (single-color)
	public double [][]                                     image_col_avg_applied = null; //[nsens][height] integrated by rows, lwir-only (single-color)

	
	boolean                                                new_image_data = false;
    boolean [][]                                           saturation_imp = null; // (near) saturated pixels or null
    boolean                                                is_aux = false;
    String                                                 photometric_scene = null;
    double  []                                             lwir_offsets = null; // per image subtracted values
    double []                                              lwir_scales =  null;  // per image scales
    double []                                              lwir_scales2 = null;  // per image quadratic scales
    boolean                                                photometric_updated = true; // first time - as if updated
    @Deprecated
    double                                                 lwir_offset =  Double.NaN; // average of lwir_offsets[]
    // hot and cold are calculated during autoranging (when generating 4 images for restored (added lwir_offset)
    // absolute temperatures to be used instead of colorProcParameters lwir_low and lwir_high if autoranging
    // is enabled
    double []                                              lwir_cold_hot = null;
//    int []                                                 woi_tops; // used to calculate scanline timing
// just for debugging with the use of intermediate image
    public double [][]                                     dsi = null; // DSI to be saved/restored in the model
    public double                                          disp_infinity_ref = 0; // disparity at infinity for this scene when it is a reference one. add when calculating INTER-INTRA (MAIN_DSI is already) 
    private boolean                                        dsi_from_combo = false;
    public double [][]                                     ds_from_main = null;
    public double [][]                                     dsrbg = null; // D, S, R,B,G
    
    //number of times orientation is (re) calculated: 0 - none, 1 - before accumulation, 2 - readjusted after accumulation
    public int                                             num_orient = 0; 
    //number of times scenes are accumulated: 0 - none, 1 - after first orientation, 2 - after second orientation
    public int                                             num_accum =  0;
    // IMS data
    public double      ims_offs =         Double.NaN; // IMS offset for which data was saved (positive - IMS lag is lower than images)
    public double      gmt_plus =         Double.NaN;
    public Did_ins_1   did_ins_1 =        null;
    public Did_ins_2   did_ins_2 =        null;
    public Did_pimu    did_pimu =         null;
    public Did_gps_pos did_gps1_pos =     null;
    public Did_gps_pos did_gps2_pos =     null;
    public Did_gps_pos did_gps1_ubx_pos = null;
    public String      ims_last_path =    null;
    //quat_corr is additional (to clt_parameters.imp.getImsMountATR()) per-reference frame correction
    public double []   quat_corr =        null; // correction for IMS camera frame to actual camera frame (for reference frames)
    public double []   enu_corr_metric =  null; // GNSS correction - add metrix offset to GNSS of the reference scene
   
    // latest (or any? first also?) scene in a sequence has a timestamp of the reference scene  
    public String      timestamp_reference = null;
    // only reference scene has a pair of first/last scene in a sequence
    public String      timestamp_first =     null;
    public String      timestamp_last =      null;
    public String      timestamp_quarter1 =  null;
    public String      timestamp_quarter3 =  null;
    public String      timestamp_index =     null; // timestamp of the scene with index of all reference scenes in this sequence
    public HashSet<String> ref_scenes =      null; // Set of timestamps of the reference scenes in this sequence    
    // combined clt for center view, only used in cuas mode
    public float []    center_clt =           null; // clt data (single accumulated channel)
    
    @Deprecated
    public float []    center_clt_weights=    null; // 1/64 of center_clt (tilesX*tilesY)
    @Deprecated
    public QuadCLT     center_parent =        null; // center_clt instance from cumulative previous segments
    @Deprecated
    public boolean     center_is_parent =     false; // when true - use image_path that discards current version
    public String      center_parent_dir =    null;
    public ImagePlus   imp_center_average =   null;
    public CorrectionFPN correctionFPN =      null;
    
    public GroundPlane ground_plane =         null;
    /*
    public HashSet<String> getRefScenes(){
    	return ref_scenes;
    }
    */
    public String [] getRefScenes() { // increasing timestamps
    	if (ref_scenes == null) {
    		return null;
    	}
    	String [] names = ref_scenes.toArray(new String[0]);
    	Arrays.sort(names);
    	return names;
    }
    
    public void resetRefScenes() {
    	ref_scenes = null;
    }
    
    public void addRefScene(String ts) {
    	if (ref_scenes == null) {
    		ref_scenes = new HashSet<String>();
    	}
    	ref_scenes.add(ts);
    }
    
    public GroundPlane getGroundPlane() {
    	return ground_plane;
    }
    
    public void setGroundPlane(GroundPlane gp) {
    	this.ground_plane = gp;
    }
    
    
    public int getWidth() {
    	return tp.getTilesX()*tp.getTileSize();
    }

    public int getHeight() {
    	return tp.getTilesY()*tp.getTileSize();
    }
    
    public boolean    dsiIsFromCombo() {
    	return dsi_from_combo;
    }
    
    public void setCorrectionFPN() {
    	correctionFPN = new CorrectionFPN(this);
    }
    
    public CorrectionFPN getCorrectionFPN() {
    	if (correctionFPN == null) {
    		correctionFPN = new CorrectionFPN(this);
    	}
    	return correctionFPN;
    }
    
    /**
     * Get disparity at infinity for this scene when it is a reference one. Add when calculating INTER-INTRA (MAIN_DSI is already corrected) 
     * @return disparity at infinity
     */
    public double getDispInfinityRef() {
    	return disp_infinity_ref;
    }

    /**
     * Set disparity at infinity for this scene when it is a reference one.
     * @param disp disparity at infinity value to set. Will be used when calculating INTER-INTRA
     */
    public void setDispInfinityRef(
    		double disp) {
    	disp_infinity_ref = disp;
    }
    
    /**
     * Increment disparity at infinity for this scene when it is a reference one.
     * @param disp disparity at infinity value to add to the previous one. Will be used when calculating INTER-INTRA
     */
    public void incDispInfinityRef(
    		double disp) {
    	disp_infinity_ref += disp;
    }
    
    public ImagePlus   getCenterAverage() {
    	return imp_center_average;
    }
    
    public void setCenterAverage(ImagePlus imp_center_average) {
    	this.imp_center_average = imp_center_average;
    }
    
    
    @Deprecated
    public boolean isCenterParent() {
    	return center_is_parent;
    }
    
    @Deprecated
    public QuadCLT getCenterParent() {
    	return center_parent;
    }
    @Deprecated
    public void setCenterParent(QuadCLT parent) {
    	center_parent = parent;
    	parent.center_is_parent = true;
    }
    @Deprecated
    public void setCenterParent() {
    	center_is_parent = true;
    }
    
    public void setCenterParentDir(String dir) {
    	center_parent_dir = dir;
    }
    
    public String getCenterParentDir() {
    	return center_parent_dir;
    }
    
    public boolean isPhotometricUpdated() {
    	return photometric_updated;
    }

    public boolean isPhotometricUpdatedAndReset() {
    	boolean changed = photometric_updated;
    	photometric_updated = false;
    	return changed;
    }
    
    public void setPhotometricUpdated(boolean updated) {
    	photometric_updated = updated;
    }
    
    public String getImagePath() {
    	return image_path;
    }

    public String getParentDir() {
    	return center_parent_dir;
    }
    
    public void setImagePath(String path) {
    	image_path = path;
    	
    }
    
    public boolean hasCenterClt() {
    	return (center_clt != null); //  && (center_clt_weights != null);
    }
    
    /*
    public void setCenterClt(
    		float [] clt,
    		int []   clt_num,
    		int      clt_width) {
    	center_clt =       clt;
    	center_clt_num =   clt_num;
    	center_clt_width = clt_width;
    }
    */

    public void setCenterClt(
    		float [] clt) {
    	center_clt =         clt;
    }
    
    
    public void setCenterClt( // not used
    		float [] clt,
    		float [] clt_weights) {
    	center_clt =         clt;
    	center_clt_weights = clt_weights;
    }

    public float [] getCenterClt() {
    	return center_clt;
    }
    @Deprecated
    public float [] getCenterCltWeights() {
    	return center_clt_weights;
    }
	
	public static String getCenterDirName(String ref_name) {
		return ref_name + CENTER_DIR_SUFFIX;
	}
	


	/**
	 * If full_path is provided: read parent center_CLT data.
	 * Then try to restore tuned center_CLT if it exists.
	 * If it exists, provide a link to the parent center_CLT instance.
	 * If it does not exist, create a new instance with center_clt copy
	 * and center_clt_weights = null, so weights will be initialized later
	 * @param ref_clt QuadCLT template
	 * @param full_path null or full path to parent directory
	 * @return center_CLT instance or null
	 */
	
	
	
	/**
	 * Read (try to read) this first, maybe parent is not needed (yet)
	 * @param clt_parameters
	 * @param ignore_this do not merge this sequence CLT data, only use parent's cumulative
	 * @param use_parent_dsi - use parent DSI even if this exists 
	 * @param ref_clt
	 * @param full_path
	 * @param single_first true: read single first, cumulative second, false - read cumulative first 
	 * @param debugLevel
	 * @return
	 */
	public static QuadCLT restoreCenterClt(
			CLTParameters   clt_parameters,
			boolean         ignore_this,
			boolean         use_parent_dsi,
			QuadCLT         ref_clt , // latest
			String          full_path, // if non-null, use it instead of ref_clt parent path or ""
			boolean         single_first, // true: read single first, cumulative second 
			double []       dbg_weights, // should be null or double [tilesX*tilesY] - will return debug weight ratio (new to cumul)
			int             debugLevel) {
		// 1) read single from model directory (only .cuas) if single_first only model dir?
		// 2) read cumulative from specified dir. If does not exist - try single from specified dir. If does not exist read old format. Read DSI from specified dir
		// If both local and parent exist - combine 
		// If rad old-style from parent - save it there as cumulative
		//What about DSI?
		if (debugLevel >-4) {
			System.out.println("Restoring CenterClt");
		}
		
		double tolerance = clt_parameters.imp.cuas_clt_threshold;
		double decay =  clt_parameters.imp.cuas_decay_average;
		if ((full_path != null) && full_path.endsWith(Prefs.getFileSeparator())) { // null
			full_path = full_path.substring(0, full_path.length()-1);
		}
		if (debugLevel >-4) {
			System.out.println("Restoring CenterClt, full_path="+full_path);
		}
		double [][] parent_dsi = null;
		int tilesX =     ref_clt.getTilesX(); 
		int num_colors = ref_clt.getNumColors();
		double dts =     ref_clt.getTimeStamp();
		CuasData cuasData = null;
		String center_name =  QuadCLT.getCenterDirName(ref_clt.getImageName());
		// allow creation, otherwise returns config
		String center_dir_path = ref_clt.setX3dDirectory(center_name); // setX3dDirectory(center_name); // gets /home/elphel/lwir16-proc/eagle_mountain/configs!
		QuadCLT center_CLT = null;
		File cdir = (center_dir_path!=null) ? new File(center_dir_path) : null;
		if (!ignore_this) {
			// Try to read this (created/tuned) center_CLT
			try_read_center: {
				if (center_dir_path == null) {
					System.out.println("restoreCenterClt(): directory does not exist: "+center_dir_path);
					break try_read_center;
				}
//				cdir = new File(center_dir_path);
				if (!cdir.exists() || !cdir.isDirectory()) {
					System.out.println("restoreCenterClt(): directory does not exist or is not a directory: "+center_dir_path);
					break try_read_center;
				}
				// maybe make num_colors=0 here to preven reading old style?
				cuasData = CuasData.getCuasData( // do not need cumulative here!
						false,           // boolean try_cumul,  // and save as such if only old style existed
						center_dir_path, // String  full_path,  // ends with /vxx
						num_colors,      // int     num_colors, // only used if restored from tiff, ignored for .cuas
						tilesX,          // int     width,      // only used if restored from tiff, ignored for .cuas
						dts,             // double  dts,        // time stamp as double, only used if restored from tiff, ignored for .cuas
						debugLevel);     // int     debugLevel)
				if (cuasData == null) {
					System.out.println("restoreCenterClt(): CuasData returned null for "+center_dir_path);
					break try_read_center;
				}

				center_CLT = new QuadCLT(ref_clt, center_name);
				center_CLT.setImagePath(cdir.getPath());

				// set center data
				if (center_CLT.restoreComboDSI(true) == null) { // does not use restored data?
					System.out.println("restoreCenterClt(): No DSI data in "+center_CLT.getImagePath());
					center_CLT = null; // will create a new one if parent exists
					break try_read_center;
				}
				dts =     center_CLT.getTimeStamp();
			}
		}
		CuasData localCuasData = cuasData;
		// Read parent (cumulative if possible) data if full_path is provided
		CuasData parentCuasData = null;
		if (center_CLT == null) {
			System.out.println("Gave up trying to read this CenterCLT, saved earlier");
		}
		if (full_path != null) { // if full path provided, but something went wrong with cuasData or dsi_parent - return null
			System.out.println("Trying to read parent CenterCLT");
			parentCuasData = CuasData.getCuasData( // need cumulative here!
					true,            // boolean try_cumul,  // and save as such if only old style existed
					full_path,       // String  full_path,  // ends with /vxx
					num_colors,      // int     num_colors, // only used if restored from tiff, ignored for .cuas
					tilesX,          // int     width,      // only used if restored from tiff, ignored for .cuas
					dts,             // double  dts,        // time stamp as double, only used if restored from tiff, ignored for .cuas
					debugLevel);     // int     debugLevel)
			if (parentCuasData == null) {
				System.out.println("restoreCenterClt(): parentCuasData returned null for "+full_path);
				return null;
			}
			// old code to restore combo_dsi
			int ver_sep = full_path.lastIndexOf(Prefs.getFileSeparator());
			int name_sep = full_path.lastIndexOf(Prefs.getFileSeparator(),ver_sep-1);
			String parent_name = full_path.substring(name_sep+1,ver_sep);
			File cdir_parent = new File(full_path);
			if (!cdir_parent.exists() || !cdir_parent.isDirectory()) {
				System.out.println("restoreCenterClt(): directory does not exist: "+full_path);
				return null;
			}
			String dsi_parent_path = full_path+Prefs.getFileSeparator()+parent_name+DSI_SUFFIXES[INDEX_INTER_LMA]+".tiff";
			parent_dsi = new double [OpticalFlow.COMBO_DSN_TITLES.length][];
			int num_slices = restoreDSI( // new method 06/03/2025, switched argument order
					parent_dsi, // double [][] dsi,
					dsi_parent_path,  // String file_path,
					true);     // boolean silent) 
			if (num_slices <= 0) {
				System.out.println("restoreCenterClt(): No parent DSI data in "+dsi_parent_path);
				return null;
			}
		}
		if ((center_CLT == null) && (full_path == null)) {
				System.out.println("restoreCenterClt(): parent directory is not provided, and this model data does not exist - bailing out.");
				return null;
		}

		
		
		if (use_parent_dsi || (center_CLT == null)) { // so parent parentCuasData and parent_dsi both exist
			if (cdir == null) {
				System.out.println("restoreCenterClt(): cdir == null - bailing out.");
				return null;
			}

			// Create new center_CLT
			if (center_CLT == null) {
				center_CLT = new QuadCLT(ref_clt, center_name);
				center_CLT.setImagePath(cdir.getPath()); // *********************		
				cdir.mkdirs();
				cuasData = parentCuasData; // will be collapsed in one place
			}
			center_CLT.setDSIFromCombo(parent_dsi); // set center DSI from parent
			String rslt_suffix = "-INTER-INTRA";
			if (!clt_parameters.correlate_lma){
				System.out.println("createCenterClt() BUG??? clt_parameters.correlate_lma="+clt_parameters.correlate_lma);
				clt_parameters.correlate_lma = true;
			}
			rslt_suffix += (clt_parameters.correlate_lma?"-LMA":"-NOLMA");
			center_CLT.saveDoubleArrayInModelDirectory( // error
					rslt_suffix,                  // String      suffix,
					OpticalFlow.COMBO_DSN_TITLES, // combo_dsn_titles_full, // null,          // String []   labels, // or null
					parent_dsi,             // double [][] data,
					center_CLT.getTilesX(),                       // int         width,
					center_CLT.getTilesY());                      // int         height)
			center_CLT.setDSIFromCombo(parent_dsi);
//			cuasData = parentCuasData; // will be collapsed in one place
		} else { // if (center_CLT == null) { 
			if (parentCuasData != null) { // both exist - combine
				boolean en_create = true;
//				double dts = center_CLT.getTimeStamp();
				// combine both
				cuasData= Cuas.combineAveragesNonCoherent(
						parentCuasData,  // CuasData          parentCuasData,
						cuasData,        // CuasData          thisCuasData,   // each tile list starts with those from the  parent_cuas
						en_create,       // final boolean     en_create, // create new variants
						tolerance,       // final double      tolerance, // NaN works as infinity
						decay,           // double            cuas_decay,    // seconds e times
						dts,             // final double      dts,            // only used for debug (when debug_title != null)
						dbg_weights,     // dbg_weights, // should be null or double [tilesX*tilesY] - will return debug weight ratio (new to cumul)  
						debugLevel);     // int debugLevel)
				// TODO: combine DSI too
			}
		}

		float [] fclt = cuasData.collapse( //  this or combined with the parent
				tolerance, // final double tolerance, // NaN works as infinity
				decay,     // final double decay,
				dts)[0];   // final double ts_now)
		center_CLT.setCenterClt(fclt); //	data used as a template for inter-scene correlation
		// convert fclt to image
		
		if (full_path != null) { // set/update parent directory where to look for cumulative data
			center_CLT.setCenterParentDir(full_path); // is not in cuasData, only in center_CLT instance
		}
		String centerBasePath = center_CLT.getImagePath()+Prefs.getFileSeparator()+center_CLT.getImageName();
		String centerSinglePath = centerBasePath + CuasData.getCuasSuffix();
		String centerCumulPath = centerBasePath + CuasData.getCuasCumulativeSuffix();
		File centerSingleFile = new File(centerSinglePath);
		File centerCumulFile = new File(centerCumulPath);
		if (!centerSingleFile.exists() || !centerCumulFile.exists()) {
			System.out.println(
					"restoreCenterClt(): WARNING reused center data in "+center_CLT.getImagePath()+
					", but serialized CuasData files are incomplete: single="+centerSingleFile.exists()+
					", cumulative="+centerCumulFile.exists()+
					". Backfilling from restored data.");
			try {
				if (!centerSingleFile.exists()) {
					CuasData singleCuasData = (localCuasData != null) ? localCuasData : cuasData;
					singleCuasData.writeCuasData(centerSinglePath);
					if (debugLevel > -3) {
						String source = (localCuasData != null) ? "local single-sequence data" : "effective restored data";
						System.out.println("restoreCenterClt(): wrote missing single-scene CuasData to "+centerSinglePath+
								" using "+source);
					}
				}
				if (!centerCumulFile.exists()) {
					cuasData.writeCuasData(centerCumulPath);
					if (debugLevel > -3) {
						System.out.println("restoreCenterClt(): wrote missing cumulative CuasData to "+centerCumulPath);
					}
				}
			} catch (IOException e) {
				System.out.println("restoreCenterClt(): ERROR backfilling serialized CuasData files for "+center_CLT.getImagePath());
				e.printStackTrace();
			}
		}

		// Load did_ins_2 from the IMS file saved by createCenterClt() (averaged LLA from sequence scenes).
		// Only reads the did_ins_2 entry; does not call EventLogger.
		if (center_CLT.did_ins_2 == null) {
			// Mirror saveIms() for physical scenes: file is above the version subdir.
			// center_CLT.getImagePath() == <center_dir>/v04, so getParent() == <center_dir>
			String center_scene_dir = new File(center_CLT.getImagePath()).getParent();
			String ims_path = center_scene_dir + Prefs.getFileSeparator() +
					center_CLT.getImageName() + ref_clt.correctionsParameters.imsSuffix;
			Properties ims_props = loadProperties(ims_path, null, true);
			if (ims_props != null) {
				Did_ins_2 center_did_ins_2 = new Did_ins_2("did_ins_2-", ims_props);
				if (center_did_ins_2.lla != null &&
						(center_did_ins_2.lla[0] != 0.0 || center_did_ins_2.lla[1] != 0.0)) {
					center_CLT.did_ins_2 = center_did_ins_2;
					if (debugLevel > -3) {
						System.out.println("restoreCenterClt(): restored center_CLT did_ins_2 LLA: lat=" +
								IJ.d2s(center_did_ins_2.lla[0], 6) +
								" lon=" + IJ.d2s(center_did_ins_2.lla[1], 6) +
								" alt=" + IJ.d2s(center_did_ins_2.lla[2], 1));
					}
				} else {
					System.out.println("restoreCenterClt(): WARNING: IMS file has no valid did_ins_2 LLA for " +
							center_CLT.getImageName() + " (file: " + ims_path + ")");
				}
			} else {
				if (debugLevel > -3) {
					System.out.println("restoreCenterClt(): no IMS file for center_CLT, did_ins_2 stays null (" + ims_path + ")");
				}
			}
		}

		return center_CLT;
	}

	
    public String getReferenceTimestamp() {
    	return timestamp_reference;
    }

    public double [] getQuatCorr() {
    	return quat_corr;
    }
    public void setQuatCorr(double[] quat) {
    	quat_corr = quat;
    }
    
    public double [] getENUCorrMetric() {
    	return enu_corr_metric;
    }
    public void setENUCorrMetric(double[] corr) {
    	enu_corr_metric = corr;
    }
    
    public String toString() {
    	return this.image_name;
    }
     
    // find best rotation between IMU XYZ and camera XYZ
    /**
     * Refine scene poses (now only linear) from currently adjusted poses
     * (adjusted using IMS orientations) and IMS positions (currently
     * only linear are used). Refined scene poses may be used as pull targets
     * with free orientations (and angular velocities for ERS).
     * Result poses are {0,0,0} for the reference frame.
     * 
     * Assuming correct IMU angular velocities and offset linear ones.   
     * 
     * @param clt_parameters configuration parameters
     * @param quadCLTs       scenes sequence (for timestamps)
     * @param xyzatr         current scenes [[x,y,z],[a,t,r]] in reference scene frame
     * @param ims_xyzatr     IMU-derived (integrated) scene position and orientation in reference
     *                       scene frame (only position is currently used)
     * @param ref_index      reference scene index 
     * @param early_index    earliest (lowest) scene index to use
     * @param last_index     last (highest) scene index to use
     * @return quaternion    componets 
     */
    
    public static double [] getRotationFromXYZATRCameraIms(
    		CLTParameters clt_parameters,
    		int           quat_lma_mode,
    		boolean       use3, // false - full4 quaternion (with scale), true - only q1,q2,q3
    		double        avg_height,
    		double        translation_weight,
    		QuadCLT[]     quadCLTs,
    		double [][][] xyzatr,
    		double [][][] ims_xyzatr,
    		int           ref_index,
    		int           early_index,
    		int           last_index,
			double []     rms, // null or double[5]; now [7]?
			double []     y_scale_p, // if not null will return y_scale
			int           debugLevel
    		) {
		if (quadCLTs[ref_index].getImageName().equals("1763233239_531146")) {
			System.out.println("getRotationFromXYZATRCameraIms(): quadCLTs[ref_index].getImageName().getImageName()=="+quadCLTs[ref_index].getImageName());
		}
//    	boolean use_scale_y = false;
//    	final boolean use3 = (quat_lma_mode == 3); // true;
		double lambda =            clt_parameters.imp.quat_lambda; // 0.1;
		double lambda_scale_good = clt_parameters.imp.quat_lambda_scale_good; // 0.5;
		double lambda_scale_bad =  clt_parameters.imp.quat_lambda_scale_bad; // 8.0;
		double lambda_max =        clt_parameters.imp.quat_lambda_max; // 100;
		double rms_diff =          clt_parameters.imp.quat_rms_diff; // 0.001;
		int    num_iter =          clt_parameters.imp.quat_num_iter; // 20;
		boolean last_run =         false;
		double  reg_w =            clt_parameters.imp.quat_reg_w; // 0.25;
		double  quat_max_ratio =   clt_parameters.imp.quat_max_ratio;
		double  quat_max_damping = clt_parameters.imp.quat_max_damping;
		boolean use_scale_y =      clt_parameters.imp.quat_scale_y;
		double []   quat0 = use3? new double[3] : new double [] {1.0, 0.0, 0.0, 0.0}; // identity
		QuaternionLma quaternionLma = new QuaternionLma();
		double [][][] vect_y = new double [quadCLTs.length][][]; // camera XYZATR
		double [][][] vect_x = new double [quadCLTs.length][][]; // IMS XYZATR
		double xyz_scale = 1.0 / (avg_height + 1.0); // 02/13/2026 - scaling XYZ components of vect_x and vect_y here, using weights 0..1
		for (int nscene = early_index; nscene <= last_index; nscene++) {
//			vect_x[nscene] = ims_xyzatr[nscene];
//			vect_y[nscene] = xyzatr[nscene];
			vect_x[nscene] =    new double [2][];
			vect_y[nscene] =    new double [2][];
			vect_x[nscene][1] = ims_xyzatr[nscene][1]; // rotations
			vect_y[nscene][1] = xyzatr    [nscene][1]; // rotations
			vect_x[nscene][0] = new double [3];
			vect_y[nscene][0] = new double [3];
			for (int i = 0; i < 3; i++) {
				vect_x[nscene][0][i] = ims_xyzatr[nscene][0][i] * xyz_scale;
				vect_y[nscene][0][i] = xyzatr    [nscene][0][i] * xyz_scale;
			}
		}
		// Is it needed now (below)? It scalesvect_y to have the same length as vect_x
		double [][][] vect_y_scaled;
		double y_scale = 1.0;
		if (use_scale_y) {
			y_scale = QuaternionLma.getLengthRatio(
					vect_x,      // double [][][] vect_x, // []{{x,y,z},{a,t,r}}
					vect_y,      // double [][][] vect_y, //  []{{x,y,z},{a,t,r}}
					null);       // new int [] {early_index, last_index}); // int []    first_last){ // null - all
			vect_y_scaled= QuaternionLma.scaleXYZ(
					1.0/y_scale, // double        s,
					vect_y,      // double [][][] vect_y, //  []{{x,y,z},{a,t,r}}
					null);       // new int [] {early_index, last_index}); // int []    first_last){ // null - all
		} else {
			vect_y_scaled = vect_y;
		}
		if (y_scale_p != null) {
			y_scale_p[0] = y_scale;
		}
		if ((quat_lma_mode == QuaternionLma.MODE_COMBO) || (quat_lma_mode == QuaternionLma.MODE_COMBO_LOCAL)) {
			quaternionLma.prepareLMA(
					quat_lma_mode,      // int           mode,
//					avg_height,         // double        avg_height,
					vect_x,             // double [][][] vect_x,
					vect_y_scaled,      // vect_y,             // double [][][] vect_y,
					null,               // double []     vect_w, all same weight
					0.0, // reg_w,              // double        reg_w,      // regularization weight [0..1)  
					quat0,              // double []     quat0,
					debugLevel);        // int           debug_level)
			double [] mn_mx_diag = quaternionLma.getMinMaxDiag(debugLevel);
			double max_to_min = Math.sqrt(mn_mx_diag[1]/mn_mx_diag[0]);
			if (debugLevel > -3) {
				System.out.println("max_to_min = "+max_to_min+", quat_max_ratio="+quat_max_ratio);
			}
//			if (mn_mx_diag[0]*quat_max_ratio*quat_max_ratio < mn_mx_diag[1]) {
			if (max_to_min > quat_max_ratio) {
				reg_w = quat_max_damping * (max_to_min - quat_max_ratio) / max_to_min;
				/*
				double a = mn_mx_diag[1]; // Math.sqrt(mn_mx_diag[1]);
				reg_w = a/(a + quat_max_ratio*quat_max_ratio/quat0.length);
				*/
				if (debugLevel > -3) {
					System.out.println("==== Calculated reg_w = "+reg_w);
				}
				quaternionLma.prepareLMA(
						quat_lma_mode,      // int           mode,
//						avg_height,         // double        avg_height,
						vect_x,             // double [][][] vect_x,
						vect_y_scaled,      // vect_y,             // double [][][] vect_y,
						null,               // double []     vect_w, all same weight
						reg_w,              // double        reg_w,      // regularization weight [0..1)  
						quat0,              // double []     quat0,
						debugLevel);        // int           debug_level)
			}			
			
		} else if ((quat_lma_mode == QuaternionLma.MODE_XYZQ) ||    // 1
				(quat_lma_mode == QuaternionLma.MODE_XYZQ_LOCAL) || // 3
				(quat_lma_mode == QuaternionLma.MODE_XYZ4Q3)) {     // 6
			quaternionLma.prepareLMA(
					quat_lma_mode,      // int           mode,
					vect_x,             // double [][][] vect_x,
					vect_y_scaled, // vect_y,             // double [][][] vect_y,
					null,               // double []     vect_w, all same weight
					translation_weight, // double        translation_weight, // 0.0 ... 1.0;
					0.0, // reg_w,              // double        reg_w, // regularization weight [0..1) weight of q0^2+q1^2+q3^2 -1  
					quat0,              // double []     quat0,
					debugLevel);        // int           debug_level)
			double [] mn_mx_diag = quaternionLma.getMinMaxDiag(debugLevel);
			double max_to_min = Math.sqrt(mn_mx_diag[1]/mn_mx_diag[0]);
			if (debugLevel > -3) {
				System.out.println("max_to_min = "+max_to_min+", quat_max_ratio="+quat_max_ratio);
			}
//			if (mn_mx_diag[0]*quat_max_ratio*quat_max_ratio < mn_mx_diag[1]) {
			if (max_to_min > quat_max_ratio) {
				reg_w = quat_max_damping * (max_to_min - quat_max_ratio) / max_to_min;
				/*
				double a = mn_mx_diag[1]; // Math.sqrt(mn_mx_diag[1]);
				reg_w = a/(a + quat_max_ratio*quat_max_ratio/quat0.length);
				*/
				if (debugLevel > -3) {
					System.out.println("==== Calculated reg_w = "+reg_w);
				}
				quaternionLma.prepareLMA(
						quat_lma_mode,      // int           mode,
						vect_x,             // double [][][] vect_x,
						vect_y_scaled, // vect_y,             // double [][][] vect_y,
						null,               // double []     vect_w, all same weight
						translation_weight, // double        translation_weight, // 0.0 ... 1.0;
						reg_w,              // double        reg_w, // regularization weight [0..1) weight of q0^2+q1^2+q3^2 -1  
						quat0,              // double []     quat0,
						debugLevel);        // int           debug_level)
			}
		} else {
			quaternionLma.prepareLMA(
					vect_x,        // quat_lma_xyz,     // double [][] vect_x,
					vect_y_scaled, // vect_y,             // double [][][] vect_y,
					null,          // double [][] vect_w, all same weight
					reg_w,         // double      reg_w, // regularization weight [0..1) weight of q0^2+q1^2+q3^2 -1  
					quat0,         // double []   quat0,
					debugLevel);   // int   debug_level)
		}
		
		int lma_result = quaternionLma.runLma( // <0 - failed, >=0 iteration number (1 - immediately)
				lambda,           // double lambda,           // 0.1
				lambda_scale_good,// double lambda_scale_good,// 0.5
				lambda_scale_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
				last_run,         // boolean last_run,
				debugLevel);     // int    debug_level)
		if (lma_result < 0) {
			return null;
		} else {
			if (rms != null) { // null or double[2];
				double [] last_rms = quaternionLma.getLastRms();
				rms[0] = last_rms[0];
				rms[1] = last_rms[1];
				if (rms.length >= 4) {
					double [] initial_rms = quaternionLma.getInitialRms();
					rms[2] = initial_rms[0];
					rms[3] = initial_rms[1];
					if (rms.length >= 5) {
						rms[4] = lma_result;
						if ((rms.length >= 6) && (last_rms.length > 2)){
							rms[5] = last_rms[2];
							if ((rms.length >= 7) && (last_rms.length > 3)){
								rms[6] = last_rms[3];
							}
						}
					}					
				}
			}
			// debug by outputting y_vector, fx
			
			
			
			return quaternionLma.getQuaternion();
		}
    }
    
    public static double [][][] rotateImsToCameraXYZ(
    		CLTParameters clt_parameters,
        	int           quat_lma_mode,
        	boolean       use3,// false - full4 quaternion (with scale), true - only q1,q2,q3
        	double        avg_height,
        	double        translation_weight,  // 1.0 - pure translation, 0.0 - pure rotation
    		QuadCLT[]     quadCLTs,
    		double [][][] xyzatr,
    		double [][][] ims_xyzatr,
    		int           ref_index,
    		int           early_index,
    		int           last_index,
			double []     rms, // null or double[5]; // now [7]?
			double []     quaternion, // null or double[4]
			double []     y_scale_p,          // double [] y_scale_p
			String        suffix,
			int           debugLevel) {
		if (quadCLTs[ref_index].getImageName().equals("1763233239_531146")) {
			System.out.println("quadCLTs[ref_index].getImageName().getImageName()=="+quadCLTs[ref_index].getImageName());
		}
//    	final boolean use3 = true; // false; // (quat_lma_mode == 3); // true;// extract from clt ?
    	boolean use_inv = false; //
    	double [] quat = getRotationFromXYZATRCameraIms(
    			clt_parameters,     // CLTParameters clt_parameters,
    			quat_lma_mode,      // int quat_lma_mode,
    			use3,               // boolean       use3, // false - full4 quaternion (with scale), true - only q1,q2,q3
    			avg_height,         // double        avg_height,    			
    			translation_weight, // double        translation_weight,
    			quadCLTs,           // QuadCLT[]     quadCLTs,
    			xyzatr,             // double [][][] xyzatr,
    			ims_xyzatr,         // double [][][] ims_xyzatr,
    			ref_index,          // int           ref_index,
    			early_index,        // int           early_index,
    			last_index,         // int           last_index,
    			rms,                // double []     rms, // null or double[5]; // now [7]?
    			y_scale_p,          //double []     y_scale_p, // if not null will return y_scale
    			debugLevel);        // int           debugLevel
    	if (quat == null) {
    		return null;
    	}
    	if (quaternion != null) {
    		System.arraycopy(quat, 0, quaternion, 0, 4);
    	}
    	Rotation rot = new Rotation(quat[0],quat[1],quat[2],quat[3], false); // no normalization - see if can be scaled
    	double [][][] rotated_xyzatr = new double [quadCLTs.length][][]; // orientation from camera, xyz from rotated IMS
    	double [] rotated_xyz = new double[3];
    	Rotation rot_invert = rot.revert();
    	for (int nscene = early_index; nscene <= last_index; nscene++) {
    		rot.applyTo(ims_xyzatr[nscene][0], rotated_xyz);
    		Rotation scene_atr = new Rotation(RotationOrder.YXZ, ErsCorrection.ROT_CONV,
    				xyzatr[nscene][1][0],    xyzatr[nscene][1][1],    xyzatr[nscene][1][2]);
    		Rotation ims_atr = new Rotation(RotationOrder.YXZ, ErsCorrection.ROT_CONV,
    				ims_xyzatr[nscene][1][0],    ims_xyzatr[nscene][1][1],    ims_xyzatr[nscene][1][2]);
    		Rotation rotation_atr,rotation_atr2;
    		if (use_inv) { // should not be used
        		rotation_atr = rot_invert.applyTo(ims_atr);
        		rotation_atr2 = rotation_atr.applyTo(rot); // applyInverseTo?
    		} else {
        		rotation_atr = rot.applyTo(ims_atr);
        		rotation_atr2 = rotation_atr.applyTo(rot_invert); // applyInverseTo?
    		}
    		rotated_xyzatr[nscene] = new double [][] {rotated_xyz.clone(),
    			rotation_atr2.getAngles(RotationOrder.YXZ, ErsCorrection.ROT_CONV)};
    	}
        if (suffix != null) {
    	    Path orient_path = Paths.get(quadCLTs[ref_index].getX3dDirectory(true)).
    	    		resolve(quadCLTs[ref_index].getImageName()+suffix); // IMU_CALIB_DETAILS_SUFFIX);
        	saveRotateImsDetails(
        			rot,                     // Rotation      rot,
        			early_index,             // int           early_index,
        			last_index,              // int           last_index,
        			xyzatr,                  // double [][][] xyzatr,
        			ims_xyzatr,              // double [][][] ims_xyzatr,
        			rotated_xyzatr,          // double [][][] rotated_xyzatr,
        			y_scale_p[0],            // double y_scale, // 
        			orient_path.toString()); // String        path) {
    		if (debugLevel > -3) {
    			System.out.println("Orientation details saved to "+orient_path.toString());
    		}
        }
        if (debugLevel > -1) {
			double []  angles = rot.getAngles(RotationOrder.YXZ, ErsCorrection.ROT_CONV);
			double []  degrees = new double[3];
			for (int i = 0; i < 3; i++) degrees[i]=angles[i]*180/Math.PI;
    		System.out.println("quat=["+quat[0]+", "+quat[1]+", "+quat[2]+", "+quat[3]+"]");
    		System.out.println("ATR(rad)=["+angles[0]+", "+angles[1]+", "+angles[2]+"]");
    		System.out.println("ATR(deg)=["+degrees[0]+", "+degrees[1]+", "+degrees[2]+"]");
    		System.out.println("y_scale= "+y_scale_p[0]);
    		
    		System.out.println(String.format("%3s"+
    				"\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s"+
    				"\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s"+
    				"\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s",
    				"N","X","Y","Z","A","T","R",
    				"PIMU-X","PIMU-Y","PIMU-Z","PIMU-A","PIMU-T","PIMU-R",
    				"ROT-X","ROT-Y","ROT-Z","ROT-A","ROT-T","ROT-R"));
        	for (int nscene = early_index; nscene <= last_index; nscene++) {
        		System.out.println(String.format("%3d"+
        				"\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f"+
        				"\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f"+
        				"\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f",
        				nscene,
        				xyzatr[nscene][0][0],xyzatr[nscene][0][1],xyzatr[nscene][0][2],
        				xyzatr[nscene][1][0],xyzatr[nscene][1][1],xyzatr[nscene][1][2],
        				ims_xyzatr[nscene][0][0],ims_xyzatr[nscene][0][1],ims_xyzatr[nscene][0][2],
        				ims_xyzatr[nscene][1][0],ims_xyzatr[nscene][1][1],ims_xyzatr[nscene][1][2],
        				rotated_xyzatr[nscene][0][0],rotated_xyzatr[nscene][0][1],rotated_xyzatr[nscene][0][2],
        				rotated_xyzatr[nscene][1][0],rotated_xyzatr[nscene][1][1],rotated_xyzatr[nscene][1][2]));
        	}    	
        }
    	return rotated_xyzatr;
    }
    
    
    
    
    
    public static void saveRotateImsDetails(
    		Rotation      rot,
    		int           early_index,
    		int           last_index,
    		double [][][] xyzatr,
    		double [][][] ims_xyzatr,
    		double [][][] rotated_xyzatr,
    		double        y_scale, //
    		String        path) {
		double []  angles = rot.getAngles(RotationOrder.YXZ, ErsCorrection.ROT_CONV);
		double []  degrees = new double[3];
		for (int i = 0; i < 3; i++) degrees[i]=angles[i]*180/Math.PI;
		StringBuffer sb = new StringBuffer();
		sb.append("quat\t"+rot.getQ0()+"\t"+rot.getQ1()+"\t"+rot.getQ2()+"\t"+rot.getQ3()+"\n");
		sb.append("ATR(rad)\t"+angles[0]+"\t"+angles[1]+"\t"+angles[2]+"\n");
		sb.append("ATR(deg)\t"+degrees[0]+"\t"+degrees[1]+"\t"+degrees[2]+"\n");
		sb.append("y_scale=\t"+y_scale+"\n");
		sb.append(
				"N\tX\tY\tZ\tA\tT\tR\t"+
				"PIMU-X\tPIMU-Y\tPIMU-Z\tPIMU-A\tPIMU-T\tPIMU-R\t"+
				"ROT-X\tROT-Y\tROT-Z\tROT-A\tROT-T\tROT-R\n");
    	for (int nscene = early_index; nscene <= last_index; nscene++) {
    		sb.append(String.format("%3d"+
    				"\t%f\t%f\t%f\t%f\t%f\t%f"+
    				"\t%f\t%f\t%f\t%f\t%f\t%f"+
    				"\t%f\t%f\t%f\t%f\t%f\t%f\n",
    				nscene,
    				xyzatr[nscene][0][0],xyzatr[nscene][0][1],xyzatr[nscene][0][2],
    				xyzatr[nscene][1][0],xyzatr[nscene][1][1],xyzatr[nscene][1][2],
    				ims_xyzatr[nscene][0][0],ims_xyzatr[nscene][0][1],ims_xyzatr[nscene][0][2],
    				ims_xyzatr[nscene][1][0],ims_xyzatr[nscene][1][1],ims_xyzatr[nscene][1][2],
    				rotated_xyzatr[nscene][0][0],rotated_xyzatr[nscene][0][1],rotated_xyzatr[nscene][0][2],
    				rotated_xyzatr[nscene][1][0],rotated_xyzatr[nscene][1][1],rotated_xyzatr[nscene][1][2]));
    	}
		CalibrationFileManagement.saveStringToFile (
				path,
				sb.toString());
    }
    
    
    public static double [] adjustImuOrient(
    		CLTParameters clt_parameters,     // CLTParameters clt_parameters,
    		boolean       orient_combo,
    		QuadCLT[]     quadCLTs,
    		int           ref_index,
    		int           earliest_scene,
    		int           last_index,
    		boolean       save_details,
    		int           debugLevel) {
		boolean orient_by_move =    clt_parameters.imp.orient_by_move; // use translation data to adjust IMU orientation
		boolean orient_by_rot =     clt_parameters.imp.orient_by_rot; // use rotation data to adjust IMU orientation
		if (!orient_by_move && !orient_by_rot) {
			System.out.println("Neither translation nor rotation data are enabled to adjust IMU orientation relative to the camera");
			return null;
		}
//		boolean orient_combo =     clt_parameters.imp.orient_combo; // use combined rotation+orientation for IMU/camera matching
		boolean apply_imu_orient =  clt_parameters.imp.apply_imu_orient; // apply  IMU misalignment to the camera if adjusted
		boolean adjust_gyro =       clt_parameters.imp.adjust_gyro; // adjust qyro omegas offsets
		boolean apply_gyro =        clt_parameters.imp.apply_gyro; // apply adjusted qyro omegas offsets
		boolean adjust_accl =       clt_parameters.imp.adjust_accl; // adjust IMU velocities scales
		boolean apply_accl =        clt_parameters.imp.apply_accl; // apply IMU velocities scales
		double  quat_max_change =   clt_parameters.imp.quat_max_change; // do not apply if any component of the result exceeds
		double  quat_min_lin =      clt_parameters.imp.quat_min_lin; // meters, minimal distance per axis to adjust IMS velocity scale
		boolean use3=               clt_parameters.imp.use3; // false; // false - full4 quaternion (with scale), true - only q1,q2,q3
		double  transl_cost =       clt_parameters.imp.wser_orient_transl; // AGL corrected, 0.5 - equal weights, 0.0 - rotation only, 1.0 - translation-only
		double [][][] pimu_xyzatr = QuadCLT.integratePIMU(
				clt_parameters, // final CLTParameters clt_parameters,
				quadCLTs,       // final QuadCLT[]     quadCLTs,
				null, // quat_corr,      // double []  quat_corr,
				ref_index,      // final int           ref_index,
				null,           // double [][][]       dxyzatr,
				earliest_scene, // final int           early_index,
				last_index,     //(quadCLTs.length -1) // int           last_index,
				debugLevel      // final int           debugLevel
	    		);
		double [][][] xyzatr = new double [quadCLTs.length][][];
		ErsCorrection ers_ref = quadCLTs[ref_index].getErsCorrection();
    	for (int nscene = earliest_scene; nscene <= last_index; nscene++) {
        	String ts = quadCLTs[nscene].getImageName();
        	xyzatr[nscene] = ers_ref.getSceneXYZATR(ts);
    	}
    	double [] rms = new double[7];
    	double [] quat = new double[4];
    	int quat_lma_mode = orient_combo?QuaternionLma.MODE_COMBO_LOCAL: QuaternionLma.MODE_XYZQ; // MODE_XYZ4Q3; // MODE_XYZQ; // MODE_XYZQ_LOCAL; // 4; // 3; //  2; // 1;
    	int debug_lev = debugLevel; // 3;
        double avg_z = quadCLTs[ref_index].getAverageZ(true); // in meters
        double  translation_agl =  1.0 / (avg_z + 1.0);
    	if (!orient_by_move) {
    		translation_agl = 0.0;
    	} else if (!orient_by_rot) {
    		translation_agl = 1.0;
    	}
    	// Apply transl_cost
    	double translation_weight = (translation_agl * transl_cost) / (translation_agl * transl_cost + (1-translation_agl)* (1-transl_cost));
    	/*
    	double  translation_weight =  1.0 / (avg_z + 1.0);
    	if (!orient_by_move) {
    		translation_weight = 0.0;
    	} else if (!orient_by_rot) {
    		translation_weight = 1.0;
    	}
    	*/
    	// boolean       orient_combo,
    	// boolean       save_details
    	if (debugLevel > -3) {
    		System.out.println("adjustImuOrient(): transl_cost="+transl_cost+", translation_agl="+translation_agl+", translation_weight="+translation_weight);
    	}
    	String        suffix = save_details ? (orient_combo ? IMU_CALIB_COMBO_SUFFIX: IMU_CALIB_DETAILS_SUFFIX) : null;
    	double [] y_scale_p = new double[1];
    	double [][][] rotated_xyzatr = QuadCLT.rotateImsToCameraXYZ(
    			clt_parameters,     // CLTParameters clt_parameters,
    			quat_lma_mode,      // int quat_lma_mode,
    			use3,               // boolean       use3,// false - full4 quaternion (with scale), true - only q1,q2,q3
    			avg_z,              // double        avg_height,
    			translation_weight, // double        translation_weight,
    			quadCLTs,           // QuadCLT[]     quadCLTs,
    			xyzatr,             // double [][][] xyzatr,
    			pimu_xyzatr,        // double [][][] ims_xyzatr,
    			ref_index,          // int           ref_index,
    			earliest_scene,     // int           early_index,
    			last_index,         // int           last_index,
    			rms,                // double []     rms, // null or double[5];
    			quat,               // double []     quaternion, // null or double[4]
    			y_scale_p,          // double []     y_scale_p,
    			suffix,             // String        suffix,
    			debug_lev);         // int           debugLevel
    	if (rotated_xyzatr != null) {
        	Rotation rot = new Rotation(quat[0],quat[1],quat[2],quat[3], false); // no normalization - see if can be scaled
        	double [] ims_mount_atr = clt_parameters.imp.getImsMountATR(); // converts to radians
        	double [] new_ims_mount_atr = Imx5.adjustMountAtrByQuat(
        			ims_mount_atr, // double [] ims_atr,
        			quat); // double [] corr_q)
        	/*
        	 saveStringInModelDirectory(
			sb.tiString, // String  string,
			IMU_CALIB_LOGS_SUFFIX); // String  suffix)
        	 */
        	StringBuffer sb = new StringBuffer();
        	sb.append(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime())+"\n");
    		sb.append("Applying correction to the IMS mount orientation:\n");
        	sb.append("getNumOrient()="+quadCLTs[ref_index].getNumOrient()+" of "+clt_parameters.imp.min_num_orient+"\n");
        	sb.append("getNumAccum()= "+quadCLTs[ref_index].getNumAccum()+ " of "+clt_parameters.imp.min_num_interscene+"\n");
        	sb.append("avg_z= "+avg_z+" m\n");
        	sb.append("translation_weight= "+translation_weight+"\n");
        	sb.append("quat_lma_mode= "+quat_lma_mode+"\n");
        	sb.append("y_scale=       "+y_scale_p[0]+"\n");
        	sb.append("RMSe="); //rms[0]+","+rms[1]+","+rms[2]+","+rms[3]+","+rms[4]);
        	for (int i = 0; i < rms.length-1; i++) sb.append(rms[i]+",");
        	sb.append(rms[rms.length-1]+"\n");
			double []  corr_angles = rot.getAngles(RotationOrder.YXZ, ErsCorrection.ROT_CONV);
			double []  corr_degrees = new double[3];
			double quat_scale = Math.sqrt(quat[0]*quat[0]+quat[1]*quat[1]+quat[2]*quat[2]+quat[3]*quat[3]);
			for (int i = 0; i < 3; i++) corr_degrees[i]=corr_angles[i]*180/Math.PI;
			double [] new_degrees = new double[3];
			for (int i = 0; i < 3; i++) new_degrees[i]=new_ims_mount_atr[i]*180/Math.PI;
			sb.append("quat=["+quat[0]+", "+quat[1]+", "+quat[2]+", "+quat[3]+"]");
			sb.append("scale="+quat_scale+"\n");
			double angle = Math.sqrt(corr_angles[0]*corr_angles[0] + corr_angles[1]*corr_angles[1] + corr_angles[2]*corr_angles[2]);
			sb.append("delta ATR(rad)=["+corr_angles[0]+", "+corr_angles[1]+", "+corr_angles[2]+"] ("+ angle+")\n");
			sb.append("delta ATR(deg)=["+corr_degrees[0]+", "+corr_degrees[1]+", "+corr_degrees[2]+"] ("+ angle*180/Math.PI+")\n");
			sb.append("  new ATR(rad)=["+new_ims_mount_atr[0]+", "+new_ims_mount_atr[1]+", "+new_ims_mount_atr[2]+"]\n");
			sb.append("  new ATR(deg)=["+new_degrees[0]+", "+new_degrees[1]+", "+new_degrees[2]+"]\n");
        	if (apply_imu_orient) {
        		for (int i = 0; i < new_ims_mount_atr.length; i++) {
        			if (Math.abs(new_ims_mount_atr[i]) > quat_max_change) {
        				apply_imu_orient = false;
        				sb.append("*** IMU mount angle ["+i+"]=="+new_ims_mount_atr[i]+
        						" exceeds the specified limit ("+quat_max_change+")\n");
        				sb.append("*** Orientation update is disabled.\n");
        			}
        		}
        	}        		
        	if (apply_imu_orient) { 
        		clt_parameters.imp.setImsMountATR(new_ims_mount_atr);
        		sb.append("*** IMU mount angles updated, need to save the main configuration file for persistent storage ***\n");
        	} else {
        		sb.append("*** IMU mount angles calculated, but not applied ***\n");
        	}
			double [] new_atr = clt_parameters.imp.getImsMountATR(); // converts to radians
			double [] degrees = new double[3];
			for (int i = 0; i < 3; i++) degrees[i]=new_atr[i]*180/Math.PI;
			sb.append(String.format(
					"Using ATR(rad)=[%9f, %9f, %9f]\n", new_atr[0], new_atr[1], new_atr[2]));
			sb.append(String.format(
					"Using ATR(deg)=[%9f, %9f, %9f]\n", degrees[0], degrees[1], degrees[2]));
			
        	double [] omega_corr = null;
        	if (adjust_gyro) {
        		omega_corr = getOmegaCorrections(
        				clt_parameters,     // CLTParameters clt_parameters,     // CLTParameters clt_parameters,
        				rotated_xyzatr,     // double [][][] rotated_xyzatr,
        				quadCLTs,           // QuadCLT[]     quadCLTs,
        				ref_index,          // int           ref_index,
        				earliest_scene,     // int           earliest_scene,
        				last_index,         // int           last_index,
        				debugLevel);        // int debugLevel
        		if (omega_corr != null) {
    				double [] used_omegas = clt_parameters.imp.get_pimu_offs()[1]; // returns in atr order
    				double [] new_omegas =  new double[3];
    				for (int i = 0; i < 3; i++) {
    					new_omegas[i] = used_omegas[i] - omega_corr[i];        					
    				}
    				sb.append(String.format(
    						"Used omegas (ATR, rad/s) = [%9f, %9f, %9f]\n",
    						used_omegas[0],used_omegas[1],used_omegas[2]));
    				sb.append(String.format(
    						"Diff.omegas (ATR, rad/s) = [%9f, %9f, %9f]\n",
    						omega_corr[0], omega_corr[1], omega_corr[2]));
    				sb.append(String.format(
    						"New  omegas (ATR, rad/s) = [%9f, %9f, %9f]\n",
    						new_omegas[0], new_omegas[1], new_omegas[2]));
        			if (apply_gyro) {
        				clt_parameters.imp.set_pimu_omegas(new_omegas);
        				sb.append(String.format(
        						"Applied new gyro omegas (ATR, rad/s) = [%9f, %9f, %9f]\n",
        						new_omegas[0], new_omegas[1], new_omegas[2]));
        				sb.append("*** Need to save the main configuration file ***\n");
        			} else {
        				sb.append(String.format(
        						"New gyro omegas (ATR, rad/s) are not applied = [%9f, %9f, %9f]\n",
        						new_omegas[0], new_omegas[1], new_omegas[2]));
        			}
        		} else {
        			sb.append("*** Adjustment of the gyro omegas failed ***\n");
        		}
        	}
        	double [] acc_corr = null;
        	if (adjust_accl) {
        		acc_corr = getVelocitiesCorrections( // > 1 when IMU is larger than camera
        				clt_parameters,     // CLTParameters clt_parameters,     // CLTParameters clt_parameters,
        				rotated_xyzatr,     // double [][][] rotated_xyzatr,
        				quadCLTs,           // QuadCLT[]     quadCLTs,
        				ref_index,          // int           ref_index,
        				earliest_scene,     // int           earliest_scene,
        				last_index,         // int           last_index,
        				quat_min_lin, // double        min_range,
        				debugLevel);        // int debugLevel
        		
        		if (acc_corr != null) {
    				double [] used_accl_corr = clt_parameters.imp.get_pimu_offs()[0];
    				double [] new_accl_corr =  used_accl_corr.clone();
    				int num_corr=0;
    				for (int i = 0; i < 3; i++) if (!Double.isNaN(acc_corr[i])){
    					new_accl_corr[i] = used_accl_corr[i] * acc_corr[i];
    					num_corr++;
    				}
    				sb.append(String.format(
    						"Used velocities scales = [%9f, %9f, %9f]\n",
    						used_accl_corr[0],used_accl_corr[1],used_accl_corr[2]));
    				sb.append(String.format(
    						"Diff.velocities scales = [%9f, %9f, %9f]\n",
    						acc_corr[0], acc_corr[1], acc_corr[2]));
    				sb.append(String.format(
    						"New  velocities scales = [%9f, %9f, %9f]\n",
    						new_accl_corr[0], new_accl_corr[1], new_accl_corr[2]));
        			if (apply_accl && (num_corr>0)) {
        				clt_parameters.imp.set_pimu_velocities_scales(new_accl_corr);
        				sb.append(String.format(
        						"Applied new velocities scales = [%9f, %9f, %9f]\n",
        						new_accl_corr[0], new_accl_corr[1], new_accl_corr[2]));
        				sb.append("*** Need to save the main configuration file ***\n");
        			} else {
        				sb.append(String.format(
        						"New velocities scales are not applied = [%9f, %9f, %9f]\n",
        						new_accl_corr[0], new_accl_corr[1], new_accl_corr[2]));
        			}
        		} else {
        			sb.append("*** Adjustment of the gyro omegas failed ***\n");
        		}
        		//
        	}
        	sb.append("------------------------\n\n");
    		quadCLTs[ref_index].appendStringInModelDirectory(sb.toString(),IMU_CALIB_LOGS_SUFFIX); // String  suffix)
        	if (debugLevel > -3) {
        		System.out.print(sb.toString());
        	}
    	} else {
    		System.out.println ("*** Failed to calculate IMS mount correction! ***");
    	}
		return quat;


    }
    
    public static double [] getVelocitiesCorrections(
    		CLTParameters clt_parameters,     // CLTParameters clt_parameters,
    		double [][][] rotated_xyzatr,
    		QuadCLT[]     quadCLTs,
    		int           ref_index,
    		int           early_index,
    		int           last_index,
    		double        min_range,
    		int           debugLevel
    		) {
		double [][][] xyzatr = new double [quadCLTs.length][][];
		ErsCorrection ers_ref = quadCLTs[ref_index].getErsCorrection();
    	double [] timestamps = new double [quadCLTs.length];
    	double [][][] data = new double[3][last_index-early_index+1][2];
//    	double ts_ref = quadCLTs[ref_index].getTimeStamp();
    	double [][] xyz_minmax = new double [3][2]; // zeros OK as it is for ref scene
    	for (int nscene = early_index; nscene <= last_index; nscene++) {
    		timestamps[nscene] = quadCLTs[nscene].getTimeStamp();
        	xyzatr[nscene] = ers_ref.getSceneXYZATR(quadCLTs[nscene].getImageName());
        	for (int i = 0; i < 3; i++) {
//        		double d = xyzatr[nscene][0][i] - rotated_xyzatr[nscene][0][i];
        		xyz_minmax[i][0] = Math.min(xyz_minmax[i][0], xyzatr[nscene][0][i]);
        		xyz_minmax[i][1] = Math.max(xyz_minmax[i][1], xyzatr[nscene][0][i]);
        	}
    	}
    	boolean [] dir_ok = new boolean[3];
    	for (int i = 0; i < 3; i++) {
    		dir_ok[i] = (xyz_minmax[i][1]-xyz_minmax[i][0]) > min_range;
    	}    	
    	for (int nscene = early_index; nscene <= last_index; nscene++) {
//    		double rel_ts = timestamps[nscene] - ts_ref;
    		for (int i = 0; i < 3; i++) if (dir_ok[i] ){
    			data[i][nscene-early_index][0] = xyzatr[nscene][0][i]; // for all xyz the same
    			data[i][nscene-early_index][1] = rotated_xyzatr[nscene][0][i];
    		}
    	}
    	PolynomialApproximation pa= new PolynomialApproximation(-1); // debugLevel);
    	double [][] coeffs = new double [3][2];
    	for (int i = 0; i < 3; i++)  {
    		if (dir_ok[i] ){
    			coeffs[i] = pa.polynomialApproximation1d(data[i], 1); // linear
    		} else {
    			coeffs[i] = new double [] {Double.NaN,Double.NaN};
    		}
    	}
       	if (debugLevel > -1) {
    		System.out.println("Velocity X correction: " +coeffs[0][1]+" ("+coeffs[0][0]+")");
    		System.out.println("Velocity Y correction: " +coeffs[1][1]+" ("+coeffs[1][0]+")");
    		System.out.println("Velocity Z correction: " +coeffs[2][1]+" ("+coeffs[2][0]+")");
    		if (debugLevel > 0) {
        		System.out.println(String.format("%3s"+
        				"\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s",
        				"N","X","Y","Z","IMU-X","IMU-Y","IMU-Z","lX","lY","lZ"));
            	for (int nscene = early_index; nscene <= last_index; nscene++) {
            		System.out.println(String.format("%3d"+
            				"\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f",
            				nscene,
            				xyzatr[nscene][0][0],
            				xyzatr[nscene][0][1],
            				xyzatr[nscene][0][2],
            				rotated_xyzatr[nscene][0][0],
            				rotated_xyzatr[nscene][0][1],
            				rotated_xyzatr[nscene][0][2],
            				coeffs[0][0]+coeffs[0][1] * xyzatr[nscene][0][0], 
            				coeffs[1][0]+coeffs[1][1] * xyzatr[nscene][0][1], 
            				coeffs[2][0]+coeffs[2][1] * xyzatr[nscene][0][2]));
            	}    			
    		}
    	}
    	return new double [] {coeffs[0][1],coeffs[1][1],coeffs[2][1]};
    }
    
    public static double [] getOmegaCorrections(
    		CLTParameters clt_parameters,     // CLTParameters clt_parameters,
    		double [][][] rotated_xyzatr,
    		QuadCLT[]     quadCLTs,
    		int           ref_index,
    		int           early_index,
    		int           last_index,
    		int           debugLevel
    		) {
		double [][][] xyzatr = new double [quadCLTs.length][][];
		ErsCorrection ers_ref = quadCLTs[ref_index].getErsCorrection();
    	double [] timestamps = new double [quadCLTs.length];
    	double [][][] data = new double[3][last_index-early_index+1][2];
    	double ts_ref = quadCLTs[ref_index].getTimeStamp();
    	for (int nscene = early_index; nscene <= last_index; nscene++) {
    		timestamps[nscene] = quadCLTs[nscene].getTimeStamp();
        	xyzatr[nscene] = ers_ref.getSceneXYZATR(quadCLTs[nscene].getImageName());
    		Rotation scene_atr = new Rotation(RotationOrder.YXZ, ErsCorrection.ROT_CONV,
    				xyzatr[nscene][1][0],    xyzatr[nscene][1][1],    xyzatr[nscene][1][2]);
    		Rotation imu_atr = new Rotation(RotationOrder.YXZ, ErsCorrection.ROT_CONV,
    				rotated_xyzatr[nscene][1][0],
    				rotated_xyzatr[nscene][1][1],
    				rotated_xyzatr[nscene][1][2]);
    		Rotation diff_rot = imu_atr.applyInverseTo(scene_atr); // left rotate IMU to match scene
    		double rel_ts = timestamps[nscene] - ts_ref;
    		double [] diff_atr = diff_rot.getAngles(RotationOrder.YXZ, ErsCorrection.ROT_CONV);
    		for (int i = 0; i < 3; i++) {
    			data[i][nscene-early_index][0] = rel_ts; // for all xyz the same
    			data[i][nscene-early_index][1] = diff_atr[i];
    		}
    	}
    	PolynomialApproximation pa= new PolynomialApproximation(-1); // debugLevel);
    	double [][] coeffs = new double [3][2];
    	for (int i = 0; i < 3; i++) {
    		coeffs[i] = pa.polynomialApproximation1d(data[i], 1); // linear
    	}
    	if (debugLevel > -1) {
    		System.out.println("Azimuth omega correction: " +coeffs[0][1]+" ("+coeffs[0][0]+")");
    		System.out.println("   Tilt omega correction: " +coeffs[1][1]+" ("+coeffs[1][0]+")");
    		System.out.println("   Roll omega correction: " +coeffs[2][1]+" ("+coeffs[2][0]+")");
    		if (debugLevel > 0) {
        		System.out.println(String.format("%3s"+
        				"\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s",
        				"N","rt","dA","dT","dR","lA","lT","lR"));
            	for (int nscene = early_index; nscene <= last_index; nscene++) {
            		System.out.println(String.format("%3d"+
            				"\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f",
            				nscene, data[0][nscene-early_index][0], // timestamps[nscene]-timestamps[ref_index],
            				data[0][nscene-early_index][1],
            				data[1][nscene-early_index][1],
            				data[2][nscene-early_index][1],
            				coeffs[0][0]+coeffs[0][1] * data[0][nscene-early_index][0], 
            				coeffs[1][0]+coeffs[1][1] * data[1][nscene-early_index][0], 
            				coeffs[2][0]+coeffs[2][1] * data[2][nscene-early_index][0]));
            	}    			
    		}
    	}
    	return new double [] {coeffs[0][1],coeffs[1][1],coeffs[2][1]};
    }
    
    public static double [][][] refineXYZFromIMU(
    		CLTParameters clt_parameters,
			boolean       common_scale_only,
			boolean       keepZ,
    		QuadCLT[]     quadCLTs,
    		double [][][] xyzatr,
    		double [][][] pimu_xyzatr, // if null - will be recalculated
    		int           ref_index,
    		int           early_index,
    		int           last_index,
    		int           debugLevel){
    	if (pimu_xyzatr == null) {
    		pimu_xyzatr = QuadCLT.integratePIMU( // recalculate to updated if calibration was changed
    				clt_parameters, // final CLTParameters clt_parameters,
    				quadCLTs,       // final QuadCLT[]     quadCLTs,
    				null, // quat_corr,      // double []  quat_corr,
    				ref_index,      // final int           ref_index,
    				null,           // double [][][]       dxyzatr,
    				early_index,    // final int           early_index,
    				last_index,     //(quadCLTs.length -1) // int           last_index,
    				debugLevel);    // final int           debugLevel

    				
    	}
    	double [][][] xyzatr_pull = new double [xyzatr.length][][];
    	double [] sxyz1= new double[3], sxyz2=new double[3];
    	for (int nscene = early_index; nscene <= last_index; nscene ++) {
    		for (int i = 0; i < 3; i++) {
    			double x = xyzatr[nscene][0][i];
    			double px = pimu_xyzatr[nscene][0][i]; // NULL pointer
    			sxyz1[i] += px*px;
    			sxyz2[i] += px* x;
    		}
    	}
    	double [] k = new double [3];
    	if (common_scale_only) {
    		double s1 = 0, s2 = 0;
    		for (int i = 0; i < 3; i++) {
    			s1+= sxyz1[i];
    			s2+= sxyz2[i];
    		}
    		for (int i = 0; i < 3; i++) {
    			k[i] = s2/s1;
    		}		
    	} else {
    		for (int i = 0; i < 3; i++) {
    			k[i] =  sxyz2[i]/ sxyz1[i];
    		}		
    	}

    	for (int nscene = early_index; nscene <= last_index; nscene ++) {
    		xyzatr_pull[nscene] = new double [][] {pimu_xyzatr[nscene][0].clone(), xyzatr[nscene][1].clone()};
    		for (int i = 0; i < 2; i++) {
    			xyzatr_pull[nscene][0][i] *= k[i];
    		}
    		if (keepZ) {
    			xyzatr_pull[nscene][0][2] = xyzatr[nscene][0][2];
    		} else {
    			xyzatr_pull[nscene][0][2] *= k[2];
    		}
    	}
    	if (debugLevel > -1) {
    		if (common_scale_only) {
    			System.out.println("refineXYZFromIMU(): Scaled IMU X,Y,Z by "+k[0]+"(common) to use as camera target values.");
    		} else {
    			System.out.println("refineXYZFromIMU(): Scaled IMU X,Y,Z by ["+k[0]+", "+k[1]+", "+k[2]+
    					"] to use as camera target values.");
    		}
    	}
    	if (debugLevel > 0) {
    		System.out.println(String.format("%3s"+
    				"\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s"+ //x,y,z, ix, iy, iz
    				"\t%9s\t%9s\t%9s", // px, py, pz
    				"N","X","Y","Z","iX","iY","iZ2",
    				"pX","pY","pZ"));

    		for (int nscene = early_index; nscene <= last_index; nscene ++) {
    			System.out.println(String.format("%3d"+
    					"\t%9.5f\t%9.5f\t%9.5f"+
    					"\t%9.5f\t%9.5f\t%9.5f"+
    					"\t%9.5f\t%9.5f\t%9.5f",
    					nscene,
    					xyzatr[nscene][0][0],xyzatr[nscene][0][1],xyzatr[nscene][0][2],
    					pimu_xyzatr[nscene][0][0],pimu_xyzatr[nscene][0][1],pimu_xyzatr[nscene][0][2],
    					xyzatr_pull[nscene][0][0],xyzatr_pull[nscene][0][1],xyzatr_pull[nscene][0][2]));
    		}
    	}
    	return xyzatr_pull;
    }    
    
    
    /**
     * Refine scene poses (now only linear) from currently adjusted poses
     * (adjusted using IMS orientations) and IMS velocities (currently
     * only linear are used). Refined scene poses may be used as pull targets
     * with free orientations (and angular velocities for ERS).
     * Result poses are {0,0,0} for the reference frame.
     * 
     * Assuming correct IMU angular velocities and offset linear ones.   
     * 
     * @param clt_parameters configuration parameters
     * @param quadCLTs       scenes sequence (for timestamps)
     * @param xyzatr         current scenes [[x,y,z],[a,t,r]] in reference scene frame
     * @param dxyzatr        IMU-provided linear and angular velocities in reference
     *                       scene frame
     * @param ref_index      reference scene index 
     * @param early_index    earliest (lowest) scene index to use
     * @param last_index     last (highest) scene index to use
     * @return refined array of scene poses [[x,y,z],[a,t,r]] (in reference scene frame),
     *                       same indices as input 
     */
    public static double [][][] refineFromImsVelocities(
    		CLTParameters clt_parameters,
    		QuadCLT[]     quadCLTs,
    		double [][][] xyzatr,
    		double [][][] dxyzatr,
    		int           ref_index,
    		int           early_index,
    		int           last_index
    		) {
    	boolean debug = true;
    	double [][] xyz_integ = new double [quadCLTs.length][3];
    	double [] tim = new double [quadCLTs.length];
    	double t00= quadCLTs[early_index].getTimeStamp();
    	double t0 = t00;
    	double s0 = 0, sx = 0, sx2 = 0;
    	double [] sxy = new double[3], sy = new double[3];
    	double [] vel_ref = new double[3], vel_ref_prev = null;
    	for (int nscene = early_index; nscene <= last_index; nscene++) {
    		// calculate inertial (reference frame) velocities
    		Rotation rot = new Rotation(RotationOrder.YXZ, ErsCorrection.ROT_CONV,
    				xyzatr[nscene][1][0],    xyzatr[nscene][1][1],    xyzatr[nscene][1][2]);
    		rot.applyTo(dxyzatr[nscene][0], vel_ref);
    		if (vel_ref_prev == null) {
    			vel_ref_prev = vel_ref;
    		}
    		double t = quadCLTs[nscene].getTimeStamp();
    		double x = t - t00; // from early_index
    		tim[nscene] = x;
    		s0 +=1.0;
    		sx += x;
    		sx2 += x*x;
    		double dt = (t-t0)/2;  // half from previous
    		t0 = t;
    		for (int i = 0; i < 3; i++) {
    			if (nscene == early_index) {
    				xyz_integ[nscene][i] = 0;
    			} else {
//    				xyz_integ[nscene][i] = xyz_integ[nscene - 1][i]+dt * (dxyzatr[nscene-1][0][i] + dxyzatr[nscene][0][i]);
    				xyz_integ[nscene][i] = xyz_integ[nscene - 1][i]+dt * (vel_ref_prev[i] + vel_ref[i]);
    				
    			}
    			double y = xyzatr[nscene][0][i] - xyz_integ[nscene][i];
    			sy[i] += y;
    			sxy[i] += x*y;
    		}
    		vel_ref_prev = vel_ref;
    	}
    	
    	double denom = sx2 * s0 - sx * sx;
    	double [] a = new double[3], b = new double[3], ref_offs = new double[3];
    	for (int i = 0; i < 3; i++) {
    		a[i] = (sxy[i] * s0 - sx * sy[i])/denom;
    		b[i] = (sy[i] * sx2 - sx * sxy[i])/denom;
    		ref_offs[i] = b[i] + a[i] *  tim[ref_index] + xyz_integ[ref_index][i];
    	}
    	double [][][] xyzatr_out = new double [quadCLTs.length][][];
    	for (int nscene = early_index; nscene <= last_index; nscene++) {
    		xyzatr_out[nscene] = new double[2][3];
    		for (int i = 0; i < 3; i++) {
    			xyzatr_out[nscene][0][i] = 	b[i] + a[i] *  tim[nscene] + xyz_integ[nscene][i] - ref_offs[i]; // 2 of 2
    			xyzatr_out[nscene][1][i] = xyzatr[nscene][1][i]; // for now - just copy old, maybe will add smth.
    		}
    	}
    	if (debug) {
    		System.out.println("refineFromImsVelocities():");
    		System.out.println("a= ["+a[0]+", "+a[1]+", "+a[2]+"]");
    		System.out.println("b= ["+b[0]+", "+b[1]+", "+b[2]+"]");
    		System.out.println("ref_offs=["+ref_offs[0]+", "+ref_offs[1]+", "+ref_offs[2]+"]");
    		System.out.println(String.format(
    				"%3s\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s\t\t%9s\t%9s\t%9s\t\t%9s\t%9s\t%9s\t\t%s\t%9s\t%9s\t\t%9s\t%9s\t%9s",
    				"N","T", "X","Y","Z","A","T","R","X-INT","Y-INT","Z-INT","X-err","Y-err","Z-err","X-lin","Y-lin","Z-lin",
    				"X-corr","Y-corr","Z-corr"));
        	for (int nscene = early_index; nscene <= last_index; nscene++) {
        		System.out.println(String.format(
        				"%3d\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t\t%9.5f\t%9.5f\t%9.5f\t\t%9.5f\t%9.5f\t%9.5f\t\t%9.5f\t%9.5f\t%9.5f\t\t%9.4f\t%9.4f\t%9.4f",
        				nscene,tim[nscene],
        				xyzatr[nscene][0][0],xyzatr[nscene][0][1],xyzatr[nscene][0][2],
        				xyzatr[nscene][1][0],xyzatr[nscene][1][1],xyzatr[nscene][1][2],
        				xyz_integ[nscene][0],xyz_integ[nscene][1],xyz_integ[nscene][2],
        				xyzatr[nscene][0][0]-xyz_integ[nscene][0],xyzatr[nscene][0][1]-xyz_integ[nscene][1],xyzatr[nscene][0][2]-xyz_integ[nscene][2],
//        				b[0]+a[0]*(tim[nscene]-tim[ref_index]), b[1]+a[1]*(tim[nscene]-tim[ref_index]), b[2]+a[2]*(tim[nscene]-tim[ref_index]),
        				b[0]+a[0]*tim[nscene], b[1]+a[1]*tim[nscene], b[2]+a[2]*tim[nscene],
        				xyzatr_out[nscene][0][0]-xyzatr[nscene][0][0],
        				xyzatr_out[nscene][0][1]-xyzatr[nscene][0][1],
        				xyzatr_out[nscene][0][2]-xyzatr[nscene][0][2]
        				));
        	}    	
    	}

    	return xyzatr_out;
    }

    /**
     * Refining scene poses by LPF filtering. Filters either translations or rotations
     * so it may use different widths (split clt_parameters.imp.avg_len)
     * repeat twice (feeding result to xyzatr) if both translation and rotation filtering
     * is needed.
     * @param clt_parameters configuration parameters
     * @param quadCLTs       scenes sequence (for timestamps)
     * @param xyzatr         current scenes [[x,y,z],[a,t,r]] in reference scene frame
     * @param avg_rlen       half-radius of averaging (cosine window)
     * @param filter_rot     false - filter translations (xyz), true - filter rotations (atr)
     * @param keep_rz        do not filter 3-rd component (Z or Roll) as they can be unambiguously
     *                       fitted 
     * @param ref_index      reference scene index 
     * @param early_index    earliest (lowest) scene index to use
     * @param last_index     last (highest) scene index to use
     * @return refined array of scene poses [[x,y,z],[a,t,r]] (in reference scene frame),
     *                       same indices as input 
     */
    public static double [][][] refineFromLPF(
    		CLTParameters clt_parameters,
    		QuadCLT[]     quadCLTs,
    		double [][][] xyzatr,
    		double        avg_rlen,// >= 1.0
    		boolean       filter_rot, // false - translation. Do twice for both
    		boolean       keep_rz,
    		int           ref_index,
    		int           early_index,
    		int           last_index
    		) {
//		int     avg_len	=                clt_parameters.imp.avg_len;
		int     avg_len	= (int) Math.floor(avg_rlen); //                clt_parameters.imp.avg_len;
    	double [][][] scenes_xyzatr_pull = new double [quadCLTs.length][][];
    	double [] avg_weights = new double [2*avg_len+1];
    	for (int i = 0; i < avg_weights.length; i++) {
    		avg_weights[i] = Math.cos(Math.PI * (i - avg_len) / (2 * avg_rlen)); //  + 1));
    	}
    	// it is incorrect for rotations - need to use quaternions!
		int num_comp = filter_rot ? 4: 3; 
    	for (int nscene = early_index; nscene <= last_index; nscene++) {
			double [][] inv_scene = ErsCorrection.invertXYZATR(xyzatr[nscene]);
			int n_pre =  nscene + avg_len;
			int n_post = nscene - avg_len;
			if (n_post < early_index) n_post = early_index;
			if (n_pre > last_index) n_pre = last_index;
			double s0=0, sx=0, sx2=0;
			double [] sy =  new double[num_comp], sxy = new double [num_comp];
			for (int n_other = n_post; n_other <= n_pre; n_other++) {
				int ix = n_other - nscene; //+/-i
				int i = ix + avg_len; // >=0
				double [][] other_xyzatr = xyzatr[n_other]; 
				double [][] other_rel = ErsCorrection.combineXYZATR(other_xyzatr,inv_scene);
				double [] other; 
				if (filter_rot) {
					other = other_rel[0];
				} else {
					Rotation rot = new Rotation(
		    				RotationOrder.YXZ,
		    				ErsCorrection.ROT_CONV,
		    				other_rel[1][0],
		    				other_rel[1][1],
		    				other_rel[1][2]);
					other = new double[] {
		    				rot.getQ0(),
		    				rot.getQ1(),
		    				rot.getQ2(),
		    				rot.getQ3()};
				}
				double w = avg_weights[i];
				s0 += w;
				sx += w * ix;
				sx2 += w * ix * ix;
				// averaging translations or rotations (quaternions)
				for (int k = 0; k < other.length; k++) {
					sy[k] += w * other[k];
					sxy[k] += w * other[k] * ix;
				}
			}
			double [] diff = new double[num_comp];
			double [][] diff_pull = new double[2][3];
			for (int k = 0; k < diff.length; k++) {
				diff[k]=(sy[k]*sx2 - sxy[k]*sx) / (s0*sx2 - sx*sx);
			}
			if (filter_rot) {
				Rotation rot = new Rotation(diff[0],diff[1],diff[2],diff[3],true);
				diff_pull[1]=rot.getAngles(RotationOrder.YXZ, ErsCorrection.ROT_CONV);
				// diff_pull[0] is {0,0,0}
			} else {
				diff_pull[0]=diff; // diff_pull[1] is {0,0,0}
			}
			// Only one component is non-zero 
			scenes_xyzatr_pull[nscene] = ErsCorrection.combineXYZATR(xyzatr[nscene],diff_pull);
			if (keep_rz) {
				scenes_xyzatr_pull[nscene][0][2] = 	xyzatr[nscene][0][2];
				scenes_xyzatr_pull[nscene][1][2] = 	xyzatr[nscene][1][2];
			}
    	}
    	return scenes_xyzatr_pull;
    }    
    
    
    /**
     * Get linear and angular velocities (camera frame) from
     * this scene IMS/IMU data
     * Omegas come from DID_PIMU, but linear velocities - from the DID_INS2 as DID_PIMU.vel
     * seems not to be preintegrated, but are accelerations, not velocities
     * 
     * Reorders [0][0] and [0][1], because A - around Y, T - around X, R - around Z
     * @param clt_parameters configuration parameters (including
     *        scales from IMS to linear for linear and angular velocities
     * @return linear and angular velocities in camera frame
     */
    protected double [][] getDxyzatrIms(
    		CLTParameters     clt_parameters,
    		double []  quat_corr) {
    	final double [][] pimu_offsets = clt_parameters.imp.get_pimu_offs();
    	double [] ims_ortho =     clt_parameters.imp.ims_ortho;
    	double [] ims_mount_atr = clt_parameters.imp.getImsMountATR(); // converts to radians
    	double [] quat_ims_cam = Imx5.quaternionImsToCam(
    			ims_mount_atr, // new double[] {0, 0.13, 0},
    			ims_ortho);
       	if (quat_corr != null) {
       		quat_ims_cam=Imx5.applyQuaternionToQuaternion(quat_corr, quat_ims_cam, false);
       	} 
       	if (did_ins_2 == null) {
       		System.out.println("getDxyzatrIms(): did_ins_2=null for "+getImageName());
       		return null;
       	}
    	double [] double_uvw =  did_ins_2.getUvw();
    	if ((double_uvw[0] == 0.0) && (double_uvw[1] == 0.0) && (double_uvw[2] == 0.0)) {
    		double_uvw =  did_ins_1.getUvw();
    	}
    	double [] omegas=new double[] {did_pimu.theta[0]/did_pimu.dt,
    			did_pimu.theta[1]/did_pimu.dt, did_pimu.theta[2]/did_pimu.dt};
    	double [] cam_dxyz = Imx5.applyQuaternionTo(quat_ims_cam, double_uvw, false);
    	double [] cam_datr = Imx5.applyQuaternionTo(quat_ims_cam, omegas, false);
    	// a (around Y),t (around X), r (around Z)
    	double [][] dxyzatr = new double [][] {cam_dxyz,{cam_datr[1],cam_datr[0],cam_datr[2]}};
    	if (pimu_offsets != null) { // TODO: apply offsets directly to omegas?
    		for (int i = 0; i < 3; i++) {
    			dxyzatr[0][i] /= pimu_offsets[0][i];
    			dxyzatr[1][i] -= pimu_offsets[1][i];
    		}
    	}
    	return dxyzatr;
    }

    /**
     * Get linear and angular velocities (camera frame) from
     * scenes' DID_PIMU data
     * @param clt_parameters configuration parameters (including
     *        scales from IMS to linear for linear and angular velocities
     * @param quadCLTs array of scenes with IMS data loaded
     * @param quat_corr quaternion correction from ims to the camera or null   
     * @return linear and angular velocities in camera frame for each scene that has IMS data
     */
    public static double [][][] getDxyzatrPIMU(
    		CLTParameters clt_parameters,
    		QuadCLT[]     quadCLTs,
    		double []  quat_corr){ //,
    	double [][][] dxyzatr = new double [quadCLTs.length][][];
    	for (int i = 0; i < quadCLTs.length; i++) if (quadCLTs[i] != null) {
    		dxyzatr[i] = quadCLTs[i].getDxyzatrIms(
    				clt_parameters, //
    				quat_corr); // double []  quat_corr,
    	}
    	return dxyzatr;
    }
    
    /**
     * Integrate position and orientation of the camera relative to the
     * (inertial) reference frame using IMU-derived local angular and 
     * linear velocities
     * @param clt_parameters configuration parameters
     * @param quadCLTs       scenes sequence
     * @param quat_corr      quaternion camera-to-ims correction or null
     * @param ref_index      reference scene index 
     * @param dxyzatr        local linear and angular velocities
     * @param early_index    earliest (lowest) scene index in quadCLTs to use
     * @param last_index     last (highest) scene index to use
     * @return  per-scene {{X,Y,Z},{A,T,R}} relative to the reference frame
     */
    public static double [][][] integratePIMU(
    		final CLTParameters clt_parameters,
    		final QuadCLT[]     quadCLTs,
    		double []           quat_corr,
    		final int           ref_index,
    		double [][][]       dxyzatr,
    		final int           early_index,
    		final int           last_index,
    		final int           debugLevel){
    	boolean renormalize = true;
    	double [] timestamps = new double [quadCLTs.length];
    	for (int nscene = early_index; nscene <= last_index; nscene++) {
    		timestamps[nscene] = quadCLTs[nscene].getTimeStamp();
    	}
    	if (dxyzatr == null) {
    		dxyzatr = getDxyzatrPIMU(
    				clt_parameters,
    				quadCLTs,
    				quat_corr); // double []  quat_corr,

    	}
    	double [][][] xyzatr = new double [quadCLTs.length][][];
    	xyzatr[ref_index] = new double [2][3];
    	double [] vel_local_half = new double[3];
    	double [][] vel_inertial_half = new double[2][3];
    	
    	for (int dir = -1; dir <= 1; dir += 2) {
    		double [][] vel_prev = dxyzatr[ref_index]; // for reference vel local == vel inertial 
    		
    		int prev_scene = ref_index;
    		Rotation rot = new Rotation(1.0, 0.0, 0.0,0.0, false);
    		for (int nscene = ref_index + dir; (nscene >= early_index) && (nscene <= last_index); nscene += dir) {
    			xyzatr[nscene] = new double[2][3];
    			double dt_half = 0.5* (timestamps[nscene]-timestamps[prev_scene]);
    			double [][] vel_this = dxyzatr[nscene];
    			// quaternion_integrate
    			// https://math.stackexchange.com/questions/1693067/differences-between-quaternion-integration-methods
    			// https://math.stackexchange.com/questions/773902/integrating-body-angular-velocity
    			// https://stackoverflow.com/questions/46908345/integrate-angular-velocity-as-quaternion-rotation
    			// https://stackoverflow.com/questions/24197182/efficient-quaternion-angular-velocity
    			double [] omega= new double[3]; // average omega
    			double omega_l2 = 0;
    			for (int i = 0; i < 3; i++) { 
    				omega[i] = 0.5 * (vel_this[1][i] + vel_prev[1][i]);
    				omega_l2 += omega[i]*omega[i]; 
    			}
    			double omega_l = Math.sqrt(omega_l2);
    			double a = Math.cos(omega_l * dt_half);
    			double b = Math.sin(omega_l * dt_half);
    			double [] v = new double[3];
    			for (int i = 0; i < 3; i++) {
    				v[i] = b * omega[i] / omega_l;
    			}
    			// Swap A and T !
    			Rotation qr = new Rotation(a, v[1], v[0], v[2], true);
    			// swap qr & rot?
    			Rotation qn = qr.applyTo(rot);
//    			Rotation qn = rot.applyTo(qr); inverted order
    			if (renormalize ) {
    				qn= new Rotation(qn.getQ0(),qn.getQ1(),qn.getQ2(),qn.getQ3(),true);
    			}
    			xyzatr[nscene][1]= qn.getAngles(RotationOrder.YXZ, ErsCorrection.ROT_CONV);
    			// linear integrate
    			for (int i = 0; i < 3; i++) {
    				vel_local_half[i] = 0.5 * (vel_prev[0][i] + vel_this[0][i]);
    			}
    			rot.applyTo(vel_local_half, vel_inertial_half[0]);
    			qn.applyTo(vel_local_half, vel_inertial_half[1]);
    			for (int i = 0; i < 3; i++) {
    				xyzatr[nscene][0][i] = xyzatr[prev_scene][0][i] + dt_half*(vel_inertial_half[0][i] + vel_inertial_half[1][i]);
    			}
    			rot = qn;
    			vel_prev = vel_this;
    			prev_scene = nscene;
    		}
    	}
    	if (debugLevel>0) { // null pointer at com.elphel.imagej.tileprocessor.QuadCLTCPU.integratePIMU(QuadCLTCPU.java:1676)
    		System.out.println(String.format(
    				"%3s\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s",
    				"N","X","Y","Z","A","T","R"));
        	for (int nscene = 0; nscene < quadCLTs.length; nscene++) if ((quadCLTs[nscene] != null) &&  (xyzatr[nscene] != null)){
           		System.out.println(String.format(
        				"%3d\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f",
        				nscene,
        				xyzatr[nscene][0][0],xyzatr[nscene][0][1],xyzatr[nscene][0][2],
        				xyzatr[nscene][1][0],xyzatr[nscene][1][1],xyzatr[nscene][1][2]
        				));
        	}    		
    	}    	
    	return xyzatr;
    }

    /**
     * Get offset and rotation of each scene from the sequence relative to this
     * scene using IMS data. Linear offsets may be too big for the fix, so they
     * need running correction from the last matched scene, scenes should start from
     * the closest to this one and move gradually farther away.
     * @param clt_parameters Configuration parameters, including IMS to camera rotation
     * @param quadCLTs   array of scenes
     * @param quat_corr  optional quaternion correcting IMS orientation (when known) or null
     * @param debugLevel debug level
     * @return per-scene array of [[x,y,z],[a,t,r]] in camera frame for initial fitting.
     * Orientation may be later used to pull LMA. Result for reference (this) scene may
     * be small values, but not exactly zeros.
     */
    public double [][][] getXyzatrIms(CLTParameters clt_parameters,
    		QuadCLT[]  quadCLTs,
    		double []  quat_corr, // only applies to rotations - verify!
    		int        debugLevel){
    	double [] ims_ortho =     clt_parameters.imp.ims_ortho;
    	double [] ims_mount_atr = clt_parameters.imp.getImsMountATR(); // converts to radians
    	double [][][] scenes_xyzatr = new double [quadCLTs.length][][];
    	//		scenes_xyzatr[ref_index] =         new double[2][3]; // all zeros
    	Did_ins_2   d2_ref = did_ins_2;
    	if (d2_ref == null) {
    		d2_ref=quadCLTs[quadCLTs.length-1].did_ins_2; 
    	}
    	// apply correction to orientation - better not to prevent extra loops.
    	// And it only applies to rotations
    	double [] cam_quat_ref_enu =Imx5.quaternionImsToCam( // camera absolute orientation
    			d2_ref.getQEnu(), // current IMS absolute orientation relative to ENU world frame
    			ims_mount_atr,    // camera to IMS orientation 
    			ims_ortho);       // full 90-degree camera to imas orientation
    	if (quat_corr != null) {
    		cam_quat_ref_enu=Imx5.applyQuaternionToQuaternion( // corrected camera absolute orientation: correction.applyTo(original)
    				quat_corr,
    				cam_quat_ref_enu,
    				false);
    		if (debugLevel > -3) {
    			System.out.println("getXyzatrIms(): Applying attitude correction from quaternion ["+
    					quat_corr[0]+", "+quat_corr[1]+", "+quat_corr[2]+", "+quat_corr[3]+"]");
    		}
    	}
    	double [] ref_abs_atr_enu = Imx5.quatToCamAtr(cam_quat_ref_enu); // absolute orientation ATR
    	double [][] ims_ref_xyzatr_enu = {ZERO3, ref_abs_atr_enu};
    	for (int nscene = 0; nscene < quadCLTs.length; nscene++) if (quadCLTs[nscene] != null) {
    		QuadCLT scene = quadCLTs[nscene];
    		Did_ins_2 d2 = scene.did_ins_2;
    		// enu is East, North, Up from the reference scene to current scene
    		double [] enu = Imx5.enuFromLla (d2.lla, d2_ref.lla); // East-North-Up May have inacurate altitude 
    		double [] cam_quat_enu =Imx5.quaternionImsToCam( //per scene rotation from enu (from lla) to camera 
    				d2.getQEnu(),
    				ims_mount_atr,
    				ims_ortho);
    		if (quat_corr != null) { // with correction if exists
    			cam_quat_enu=Imx5.applyQuaternionToQuaternion(
    					quat_corr,
    					cam_quat_enu,
    					false);
    		}			
    		double [] cam_xyz_enu = Imx5.applyQuaternionTo( // offset from reference in reference frame
    				// Offset in the reference scene frame, not in the current scene
    				cam_quat_ref_enu, // cam_quat_enu, Offset in reference scene frame | absolute corrected reference scene orientaion
    				enu, // absolute offset (East, North, Up) in meters
    				false);
    		double [] scene_abs_atr_enu = Imx5.quatToCamAtr(cam_quat_enu); // absolute camera orientation in ATR
    		if (debugLevel>0) {
    			System.out.println(nscene+"\t"+scene.getImageName()+"\t"+cam_quat_enu[0]+"\t"+cam_quat_enu[1]+"\t"+cam_quat_enu[2]+"\t"+cam_quat_enu[3]+
    					"\t"+d2.qn2b[0]+"\t"+d2.qn2b[1]+"\t"+d2.qn2b[2]+"\t"+d2.qn2b[3]);
    		}
    		double [][] ims_scene_xyzatr_enu = {cam_xyz_enu, scene_abs_atr_enu }; // try with xyz?
    		// set initial approximation from IMS, subtract reference XYZATR
    		// predicted by IMU from the reference scene
    		double [][] pose_ims = ErsCorrection.combineXYZATR(
    				ims_scene_xyzatr_enu,
    				ErsCorrection.invertXYZATR(ims_ref_xyzatr_enu)); // {{0,0,0},{ref_absolute_orientation}}
    		scenes_xyzatr[nscene] = pose_ims;
    	}
    	if (debugLevel>0) {
    		System.out.println(String.format(
    				"%3s\t%9s\t%9s\t%9s\t%9s\t%9s\t%9s",
    				"N","X","Y","Z","A","T","R"));
        	for (int nscene = 0; nscene < quadCLTs.length; nscene++) if (quadCLTs[nscene] != null) {
           		System.out.println(String.format(
        				"%3d\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f\t%9.5f",
        				nscene,
        				scenes_xyzatr[nscene][0][0],scenes_xyzatr[nscene][0][1],scenes_xyzatr[nscene][0][2],
        				scenes_xyzatr[nscene][1][0],scenes_xyzatr[nscene][1][1],scenes_xyzatr[nscene][1][2]
        				));
        	}    		
    	}
    	return scenes_xyzatr;
    }

    public boolean hasIns() {
    	return (did_ins_2 != null);
    }

    public double [] getLla() {
    	return did_ins_2.lla;
    }

    public double [] getQEnu() {
    	return did_ins_2.getQEnu();
    }
    
    
    public LocalDateTime getLocalDateTime() {
    	return did_ins_2.getLocalDateTime();
    }
    public static double [][] scaleDtToErs(
    		CLTParameters clt_parameters,
    		double [][] cam_dxyzatr){
    	double [][] dbg_scale_dt = {clt_parameters.ilp.ilma_scale_xyz, clt_parameters.ilp.ilma_scale_atr};
    	double [][] scaled_dxyzatr = new double[2][3];
    	for (int i = 0; i < scaled_dxyzatr.length; i++) {
    		for (int j = 0; j < scaled_dxyzatr[i].length; j++) {
    			scaled_dxyzatr[i][j] = cam_dxyzatr[i][j]* dbg_scale_dt[i][j];
    		}
    	}
    	return scaled_dxyzatr;
    }

    public static double [][] scaleDtFromErs(
    		CLTParameters clt_parameters,
    		double [][] scaled_dxyzatr){
    	double [][] dbg_scale_dt = {clt_parameters.ilp.ilma_scale_xyz, clt_parameters.ilp.ilma_scale_atr};
    	double [][] cam_dxyzatr = new double[2][3];
    	for (int i = 0; i < scaled_dxyzatr.length; i++) {
    		for (int j = 0; j < scaled_dxyzatr[i].length; j++) {
    			cam_dxyzatr[i][j] = scaled_dxyzatr[i][j] / dbg_scale_dt[i][j];
    		}
    	}
    	return cam_dxyzatr;
    }


    /**
     * If this scene has a reference to one of the scenes array, return its index in the array
     * otherwise return -1 if there is no pointer to reference scene and -2 if pointer is not
     * within the provided array. OK to call with scenes==null, results can be -1 (no link) and -2
     * @param scenes array of scenes (QuadCLTCPU instances)
     * @return index of the reference scene in the provided scenes array or negative on error
     */
    public int getReferenceIndex(QuadCLTCPU [] scenes) {
    	// saving existing data by providing a self-reference (is it needed)
 //   	if ((timestamp_reference == null) && (timestamp_first != null) && (timestamp_last != null)) {
 //   		timestamp_reference = getImageName(); // remove ? OF5126:	int center_index =quadCLTs[last_index].getReferenceIndex(new QuadCLT[] {quadCLTs[last_index]}); // null); self, may return 0

 //   	}
    	if (timestamp_reference != null) {
    		if (scenes != null) {
    			for (int i = 0; i < scenes.length; i++) if (scenes[i] != null){
    				if (timestamp_reference.equals(scenes[i].getImageName())) {
    					return i;
    				}
    			}
    		}
    		return -2; // timestamp_reference defined
    	}
    	return -1; // timestamp_reference is not defined
    }

    /**
     * If this scene is a reference scene, then return array of {first_index, last_index}.
     * If this scene is not a reference - return null.
     * @param scenes array of scenes (QuadCLTCPU instances)
     * @return a pair of {first_index, last_index, this_index}
     */
    public int [] getFirstLastIndex(QuadCLTCPU [] scenes) { // should be ordered accending ts
    	int [] fl = null;
    	if ((timestamp_first != null) && (timestamp_last != null)) {
    		fl = new int [] {-1,-1,-1};
    		if (scenes != null) {
    			int i = 0;
    			for (; i < scenes.length; i++) if (scenes[i] != null) {
    				if (timestamp_first.equals(scenes[i].getImageName())) {
    					fl[0] = i;
    					break;
    				}
    			}
    			for (; i < scenes.length; i++) if (scenes[i] != null) {
    				if (this.getImageName().equals(scenes[i].getImageName())) {
    					fl[2] = i; // reference index
    					break;
    				}
    			}
    			for (; i < scenes.length; i++) if (scenes[i] != null) {
    				if (timestamp_last.equals(scenes[i].getImageName())) {
    					fl[1] = i;
    					break;
    				}
    			}
    		}
    	}
    	return fl;
    }

    public int getRefIndex(String [] scene_names) { // should be ordered accending ts
    	String ts = timestamp_reference;
		if (scene_names != null) {
			for (int i=0; i < scene_names.length; i++) if (scene_names[i] != null) {
				if (ts.equals(scene_names[i])) {
					return i;
				}
			}
		}
    	return -1;
    }
    
    
    
    public int [] getFirstLastIndex(String [] scene_names) { // should be ordered accending ts
    	int [] fl = null;
    	if ((timestamp_first != null) && (timestamp_last != null)) {
    		fl = new int [] {-1,-1};
    		if (scene_names != null) {
    			int i = 0;
    			for (; i < scene_names.length; i++) if (scene_names[i] != null) {
    				if (timestamp_first.equals(scene_names[i])) {
    					fl[0] = i;
    					break;
    				}
    			}
    			for (; i < scene_names.length; i++) if (scene_names[i] != null) {
    				if (timestamp_last.equals(scene_names[i])) {
    					fl[1] = i;
    					break;
    				}
    			}
    		}
    	}
    	return fl;
    }
    
    
    
    
    /**
     * Set reference scene pointer by a reference scene timestamp.
     * @param ts reference scene pointer as String
     */
    public void setRefPointer(String ts) {
    	timestamp_reference = ts;
    }

    /**
     * Set reference scene pointer by a reference scene instance.
     * @param scene reference scene instance
     */
    public void setRefPointer(QuadCLTCPU scene) { // not used!
    	setRefPointer(scene.getImageName());
    }

    /**
     * Set first, last scene pointers by timestamps.
     * @param ts0 first scene pointer as String
     * @param ts1 last scene pointer as String
     */
    public void setFirstLastPointers(String ts0,String ts1) {
    	timestamp_first = ts0;
    	timestamp_last = ts1;
    }
    public String[] getFirstLastTimestamps() {
    	return new String [] {timestamp_first,timestamp_last};
    }

    /**
     * Set first, last scene pointers by instances.
     * @param scene0 first scene instance
     * @param scene1 last scene instance
     */
    public void setFirstLastPointers(QuadCLTCPU scene0,QuadCLTCPU scene1) {
    	setFirstLastPointers(
    			scene0.getImageName(),
    			scene1.getImageName());
    }
    /**
     * Get timestamp of the scene with the set of all reference scenes in this sequence
     * @return timestamp of the index scene
     */
    public String getTimestampIndex() {
    	return timestamp_index;
    }
    /**
     * Set timestamp of the scene with the set of all reference scenes in this series
     * @param timestamp
     */
    public void setTimestampIndex(String timestamp) {
    	timestamp_index = timestamp;
    }
    
    
    
    public void inc_orient()        {num_orient++;}
    public void inc_accum()         {num_accum++;}
    public void set_orient(int num) {num_orient = num;}
    public void set_accum (int num) {num_accum = num;}
    public int  getNumOrient()      {
    	return num_orient;}
    public int  getNumAccum()       {
    	return num_accum;}

    public int getEarliestScene(
    		QuadCLT []     scenes) {
    	String ts_ref = getImageName();
    	ErsCorrection ers_reference = getErsCorrection();
    	for (int nscene = scenes.length - 1; nscene >= 0; nscene--) {
    		if (scenes[nscene] == null) {
    			return nscene + 1;
    		}
    		String ts = scenes[nscene].getImageName();
    		if (!ts.equals(ts_ref)) {
    			if (ers_reference.getScene(ts) == null) {
    				return nscene + 1;
    			}
    			double []   scene_xyz = ers_reference.getSceneXYZ(ts);
    			double []   scene_atr = ers_reference.getSceneATR(ts);
    			if ((scene_xyz == null) || (scene_atr == null)){
    				return nscene + 1;
    			}
    		}
    	}
    	return 0;

    }

    public double getAverageZ(boolean use_lma) {
    	double [][] dls = getDLS();
    	int min_defined = 100;
    	if (dls==null) {
    		return Double.NaN;
    	}
    	double [][] ds = new double [][] {dls[use_lma?1:0], dls[2]};
    	double sw=0, swd=0;
    	int num_defined = 0;
    	for (int i = 0; i < ds[0].length; i++) if (!Double.isNaN(ds[0][i])){ // java.lang.NullPointerException
    		sw +=   ds[1][i];
    		swd +=  ds[0][i] * ds[1][i];
    		num_defined++;
    	}
    	if ((num_defined < min_defined) && use_lma) {
    		return getAverageZ(false);
    	}
    	double disp_avg = swd/sw;
    	// Using getDispInfinityRef() - defined only for reference scenes
    	double z_avg = getGeometryCorrection().getZFromDisparity(disp_avg - getDispInfinityRef());
    	return z_avg;
    }

    /**
     * Estimate average pixel offset between 2 scenes for avoiding FPN. This scene
     * should have DSI to estimate average distance 
     * Use estimateAverageShift() instead - much more accurate. This one underestimates
     * up to twice (not understood)
     * 
     * @param xyzatr0 - first  scene [[x,y,z],[a,t,r]] 
     * @param xyzatr1 - second scene [[x,y,z],[a,t,r]]
     * @param use_lma
     * @return
     */
    @Deprecated
    public double estimatePixelShift(
    		double [][] xyzatr0,
    		double [][] xyzatr1,
    		boolean use_lma
    		) {
    	double z = getAverageZ(use_lma);
    	if (Double.isNaN(z)) {
    		System.out.println("estimatePixelShift(): failed estimating distance - no DSI or too few DSI tiles.");
    		return Double.NaN;
    	}
    	double aztl_pix_per_rad = 1.0/getErsCorrection().getCorrVector().getTiltAzPerPixel();
    	double roll_pix_per_rad = 1.0/getErsCorrection().getCorrVector().getRollPixel();
    	double [][] dxyzatr = new double [2][3];
    	for (int i = 0; i<2;i++) for (int j=0; j < 3; j++) {
    		dxyzatr[i][j] = xyzatr1[i][j]-xyzatr0[i][j];
    	}
    	double eff_az = dxyzatr[1][0] + dxyzatr[0][0] / z;
    	double eff_tl = dxyzatr[1][1] + dxyzatr[0][1] / z;
    	double pix_x = -eff_az*aztl_pix_per_rad;
    	double pix_y =  eff_tl*aztl_pix_per_rad;
    	double pix_roll = dxyzatr[1][2] * roll_pix_per_rad;
    	double pix_zoom = (0.25*getErsCorrection().getSensorWH()[0])* dxyzatr[0][2]/z;
    	double est_pix = Math.sqrt(pix_x*pix_x + pix_y*pix_y + pix_roll*pix_roll + pix_zoom*pix_zoom);
    	return est_pix;
    }

    /**
     * Estimate average pixel offset between 2 scenes for avoiding FPN. This scene
     * should have DSI to estimate average distance.
     * Difference with rectilinear is small so both are OK.
     * @param xyzatr0 first scene pose (double[3][2]) for reference scene
     * @param xyzatr1 second scene pose
     * @param average_z average distance (altitude AGL for downward images) in meters
     * @param use_rot when false - compare only the center, when true - average 4 samples
     *                at 25% of width/height. May be needed for pure rotation (no center
     *                shift), but may fail if interscene shift exceeds 25%. So try first
     *                for center and use 4 samples only if center does not move.
     * @param rectilinear Ignore lens distortions
     * @return interscene shift in pixels
     */
    public double estimateAverageShift(
    		double [][] xyzatr0,
    		double [][] xyzatr1,
    		double average_z,
    		boolean use_rot,
    		boolean rectilinear
    		) {
    	double [][] xyzatr0_a = new double [][] {xyzatr0[0].clone(),xyzatr0[1].clone()}; // debugging
    	double [][] xyzatr1_a = new double [][] {xyzatr1[0].clone(),xyzatr1[1].clone()};
    	ErsCorrection ers = getErsCorrection();
    	double disp_avg = ers.getDisparityFromZ(average_z);
    	int [] wh = ers.getSensorWH();
    	double [][] xy_pairs = {{0.5*wh[0], 0.5*wh[1]}}; 
    	if  (use_rot) {
    		xy_pairs = new double [][] {
    			{0.25*wh[0],0.25*wh[1]},
    			{0.75*wh[0],0.25*wh[1]},
    			{0.25*wh[0],0.75*wh[1]},
    			{0.75*wh[0],0.75*wh[1]}};
    	}
    	double s2 = 0.0, s0=0.0;
    	for (double [] xy:xy_pairs) if (xy != null){
    		double [] pXpYD = ers.getImageCoordinatesERS(
    				null, // QuadCLT cameraQuadCLT, // camera station that got image to be to be matched 
    				xy[0], // double px,                // pixel coordinate X in the reference view
    				xy[1], // double py,                // pixel coordinate Y in the reference view
    				disp_avg, // double disparity,         // this reference disparity 
    				!rectilinear, // boolean distortedView,    // This camera view is distorted (diff.rect), false - rectilinear
    				xyzatr0_a[0], // double [] reference_xyz,  // this view position in world coordinates (typically zero3)
    				xyzatr0_a[1], // double [] reference_atr,  // this view orientation relative to world frame  (typically zero3)
    				!rectilinear, // boolean distortedCamera,  // camera view is distorted (false - rectilinear)
    				xyzatr1_a[0], // double [] camera_xyz,     // camera center in world coordinates
    				xyzatr1_a[1], // double [] camera_atr,     // camera orientation relative to world frame
    				OpticalFlow.LINE_ERR); // double    line_err);       // threshold error in scan lines (1.0)
    		if (pXpYD != null) {
    			double dx = pXpYD[0]-xy[0]; // null pointer
    			double dy = pXpYD[1]-xy[1];
    			s2 += dx*dx+dy*dy;
    			s0+=1.0;
//				System.out.println(String.format("estimateAverageShift(): dx=%8.4f, dy=%8.4f",dx, dy));				

    		} else {
    			continue;
    		}
    	}
    	double offs_avg = Math.sqrt(s2/s0);
    	return offs_avg;
    }

    public double [] estimateCenterShiftXY(
    		double [][] xyzatr0,
    		double [][] xyzatr1,
    		double average_z,
    		boolean rectilinear
    		) {
    	double [][] xyzatr0_a = new double [][] {xyzatr0[0].clone(),xyzatr0[1].clone()}; // debugging
    	double [][] xyzatr1_a = new double [][] {xyzatr1[0].clone(),xyzatr1[1].clone()};
    	ErsCorrection ers = getErsCorrection();
    	double disp_avg = ers.getDisparityFromZ(average_z);
    	int [] wh = ers.getSensorWH();
    	double [] xy = {0.5*wh[0], 0.5*wh[1]}; 
    	double [] pXpYD = ers.getImageCoordinatesERS(
    			null, // QuadCLT cameraQuadCLT, // camera station that got image to be to be matched 
    			xy[0], // double px,                // pixel coordinate X in the reference view
    			xy[1], // double py,                // pixel coordinate Y in the reference view
    			disp_avg, // double disparity,         // this reference disparity 
    			!rectilinear, // boolean distortedView,    // This camera view is distorted (diff.rect), false - rectilinear
    			xyzatr0_a[0], // double [] reference_xyz,  // this view position in world coordinates (typically zero3)
    			xyzatr0_a[1], // double [] reference_atr,  // this view orientation relative to world frame  (typically zero3)
    			!rectilinear, // boolean distortedCamera,  // camera view is distorted (false - rectilinear)
    			xyzatr1_a[0], // double [] camera_xyz,     // camera center in world coordinates
    			xyzatr1_a[1], // double [] camera_atr,     // camera orientation relative to world frame
    			OpticalFlow.LINE_ERR); // double    line_err);       // threshold error in scan lines (1.0)
  		if (pXpYD == null) {
  			System.out.println("estimateCenterShiftXY(): pXpYD== null");
  			System.out.println("estimateCenterShiftXY(): pXpYD== null");
  		}
    	
    	if (pXpYD == null) {
    		return null;
    	} else {
//    		return new double [] {pXpYD[0]-xy[0],pXpYD[1]-xy[1]};
    		return new double [] {xy[0]-pXpYD[0],xy[1]-pXpYD[1]};
    	}
    }
    public boolean []  maskByOverlap(
    		boolean [] reliable_ref_tiles,
    		double []  offset) {
    	if (reliable_ref_tiles == null) {
    		return null;
    	}
    	boolean [] relable_scene_tiles= reliable_ref_tiles.clone();
    	int tile_size = getTileSize(); 
    	int tilesX = getTilesX();
    	int tilesY = getTilesY();
    	int width = getWidth();
    	int height = getHeight();
    	double s = 0.5*(RELIABLE_SCALE-1); // > 0
    	int xl = (int) Math.round((offset[0] - s*    width)/tile_size);  
    	int xr = (int) Math.round((offset[0] + (s+1)*width)/tile_size);  
    	int yt = (int) Math.round((offset[1] - s*    height)/tile_size);  
    	int yb = (int) Math.round((offset[1] + (s+1)*height)/tile_size);
    	int x0 = Math.max(0, Math.min(tilesX, xl));
    	int x1 = Math.max(0, Math.min(tilesX, xr));
    	int y0 = Math.max(0, Math.min(tilesY, yt));
    	int y1 = Math.max(0, Math.min(tilesY, yb));
    	boolean clipped = (x0 != xl) || (x1 != xr) || (y0 != yt) || (y1 != yb);
    	if ((x0 >= x1) || (y0 >= y1)) {
    		System.out.println(
    				"maskByOverlap(): no overlap after clipping, offset=(" + offset[0] + ", " + offset[1] +
    				"), raw x=[" + xl + ", " + xr + "), y=[" + yt + ", " + yb +
    				"), clipped x=[" + x0 + ", " + x1 + "), y=[" + y0 + ", " + y1 +
    				"), tiles=" + tilesX + "x" + tilesY + ", image=" + width + "x" + height);
    		Arrays.fill(relable_scene_tiles, false);
    		return relable_scene_tiles;
    	}
    	if (clipped) {
    		System.out.println(
    				"maskByOverlap(): clipped overlap bounds, offset=(" + offset[0] + ", " + offset[1] +
    				"), raw x=[" + xl + ", " + xr + "), y=[" + yt + ", " + yb +
    				"), clipped x=[" + x0 + ", " + x1 + "), y=[" + y0 + ", " + y1 +
    				"), tiles=" + tilesX + "x" + tilesY + ", image=" + width + "x" + height);
    	}
    	if (y0 > 0)  {
    		Arrays.fill(relable_scene_tiles, 0, Math.min(relable_scene_tiles.length, tilesX * y0), false);
    	}
    	if (y1 < tilesY) {
    		Arrays.fill(relable_scene_tiles, Math.min(relable_scene_tiles.length, tilesX * y1), relable_scene_tiles.length, false);
    	}
    	for (int i = 0; i < tilesY; i++) {
    		int rowStart = tilesX * i;
    		if (rowStart >= relable_scene_tiles.length) {
    			break;
    		}
    		int rowEnd = Math.min(relable_scene_tiles.length, tilesX * (i + 1));
    		if (x0 > 0) {
    			Arrays.fill(relable_scene_tiles, rowStart, Math.min(rowEnd, rowStart + x0), false);
    		}
    		if (x1 < tilesX) {
    			Arrays.fill(relable_scene_tiles, Math.min(rowEnd, rowStart + x1), rowEnd, false);
    		}
    	}
    	return relable_scene_tiles;
    }

    public double [][] getGround(
    		CLTParameters clt_parameters,
    		boolean       use_lma,
    		boolean       use_parallel_proj,
    		double        disparity_offset,    		
    		double        discard_low,    // fraction of all pixels
    		double        discard_high,   // fraction of all pixels
    		double        discard_adisp,  // discard above/below this fraction of average height 
    		double        discard_rdisp,  // discard above/below this fraction of average height
    		double        pix_size,       // in meters
    		int           max_image_width,// increase pixel size as a power of 2 until image fits
    		double []     x0y0,           // top level offset in meters
    		int []        whs,            // initialize to int[3] to return {width, height, scale reduction}
    		int debug_level
    		) {
    	boolean ims_use =         clt_parameters.imp.ims_use;
    	if (!ims_use || (did_ins_2 == null)) {
    		System.out.println("getGroundIns(): no INS data available.");
    		ims_use=false;
    	}
    	if (ims_use) {
    		return getGroundIms(
    				clt_parameters,
    				use_lma,
    				use_parallel_proj, // boolean       use_parallel_proj,    	    		
    				disparity_offset,    		
    				discard_low,    // fraction of all pixels
    				discard_high,   // fraction of all pixels
    				discard_adisp,  // discard above/below this fraction of average height 
    				discard_rdisp,  // discard above/below this fraction of average height
    				pix_size,       // in meters
    				max_image_width,// increase pixel size as a power of 2 until image fits
    				x0y0,        // initialize to double[2] to return width, height
    				whs,            // initialize to int[3] to return {width, height, scale reduction}
    				debug_level);
    	} else {
    		return getGroundNoIms(
    				clt_parameters,
    				use_lma,
    				use_parallel_proj, // boolean       use_parallel_proj,    	    		
    				disparity_offset,    		
    				discard_low,    // fraction of all pixels
    				discard_high,   // fraction of all pixels
    				discard_adisp,  // discard above/below this fraction of average height 
    				discard_rdisp,  // discard above/below this fraction of average height
    				pix_size,       // in meters
    				max_image_width,// increase pixel size as a power of 2 until image fits
    				x0y0,        // initialize to double[2] to return width, height
    				whs,            // initialize to int[3] to return {width, height, scale reduction}
    				debug_level);
    	}
    }
    
    public void setSmoothGround(
    		CLTParameters clt_parameters,
    		double []     smooth_ground,
    		int           debugLevel) {
    	double [][] combo_dsn_final = restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky    	
    	combo_dsn_final[OpticalFlow.COMBO_DSN_INDX_GROUND] = smooth_ground;
		String rslt_suffix = "-INTER-INTRA";
		rslt_suffix += (clt_parameters.correlate_lma?"-LMA":"-NOLMA");
    	final int tilesX =   tp.getTilesX();	
    	final int tilesY =   tp.getTilesY();	
		saveDoubleArrayInModelDirectory( // error
				rslt_suffix,           // String      suffix,
				OpticalFlow.COMBO_DSN_TITLES, // combo_dsn_titles_full, // null,          // String []   labels, // or null
				combo_dsn_final,       // dbg_data,         // double [][] data,
				tilesX,                // int         width,
				tilesY);               // int         height)
    }

    public void setTerrain(
    		CLTParameters clt_parameters,
    		double []     terrain,
    		int           debugLevel) {
    	double [][] combo_dsn_final = restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky    	
    	combo_dsn_final[OpticalFlow.COMBO_DSN_INDX_TERRAIN] = terrain;
		String rslt_suffix = "-INTER-INTRA";
		rslt_suffix += (clt_parameters.correlate_lma?"-LMA":"-NOLMA");
    	final int tilesX =   tp.getTilesX();	
    	final int tilesY =   tp.getTilesY();	
		saveDoubleArrayInModelDirectory( // prints "saveDoubleArrayInModelDirectory(): saved ..."
				rslt_suffix,           // String      suffix,
				OpticalFlow.COMBO_DSN_TITLES, // combo_dsn_titles_full, // null,          // String []   labels, // or null
				combo_dsn_final,       // dbg_data,         // double [][] data,
				tilesX,                // int         width,
				tilesY);               // int         height)
    }
    
    
    
    public double [] getFlatGround(
    		double [] disparity,
			double    rmse_above, // from average
			double    rmse_below, // from average, // positive value
			int       num_refine,
			double    frac_above,
			double    frac_below,
			double [] plane, // null or double [3] - will return {tiltX,tiltY,average}
			String    debug_title) {
    	final double [] plane_disparity = new double [disparity.length];
		final int     num_bins = 1024;
    	final int tilesX =   tp.getTilesX();	
    	final int tilesY =   tp.getTilesY();
    	final double [] center_xy= {0.5*tilesX, 0.5*tilesY};
    	final double [] stdp = new double[1];
		double avg_all = OrthoMap.getMaskedAverage (
				disparity, // final double [] data,
				null, // final boolean [] mask)
				stdp); // final double []  stdp);
		double initial_above = rmse_above*stdp[0];
		double initial_below = rmse_below*stdp[0];
		double abs_high= avg_all + initial_below;
		double abs_low=  avg_all - rmse_below*stdp[0];
		boolean [] mask = 	OrthoMap.removeAbsoluteLowHigh (
				disparity,     // final double [] data,
				null,     // final boolean [] mask,
				abs_high, // final double threshold_high,
				abs_low); // final double threshold_low),
		double [] plane_tiles = {0,0,avg_all,center_xy[0], center_xy[1]}; // 5 elements here
		for (int ntry = 0; ntry < num_refine; ntry++) {
			if (ntry > 0) {
				// remove relatives, start from new mask
				mask = OrthoMap.removeRelativeLowHigh (
						disparity,       // final double [] data,
						null,       // final boolean [] mask_in,
						initial_above,   //  final double abs_high,
						-initial_below,    // final double abs_low,
						frac_above, // final double rhigh,
						frac_below, // final double rlow,
						plane_tiles,  // final double []  ground_plane, // tiltx,tilty, offs, x0(pix), y0(pix) or null
						tilesX,      // final int     width, // only used with ground_plane != null;
						num_bins);  // final int    num_bins) 				
				
			}
			plane_tiles=  OrthoMap.getPlane(
					disparity,     // final double []   data,
					mask ,         // final boolean [] mask,
					null,          // final double []  weight,
					tilesX,        // final int        width,
					center_xy);    // final double []  xy0)
		}
		if (plane != null) {
			plane[0] = plane_tiles[0];
			plane[1] = plane_tiles[1];
			plane[2] = plane_tiles[2];
		}
    	
		for (int nTile = 0; nTile < disparity.length; nTile++) {
			int tileX = nTile % tilesX;
			int tileY = nTile / tilesX;
			double x = tileX - plane_tiles[3];
			double y = tileY - plane_tiles[4];
			plane_disparity[nTile] = plane_tiles[2]+ x *plane_tiles[0] +y * plane_tiles[1];
		}
		if (debug_title != null) {
			String [] dbg_titles = {"disparity", "plane", "diff"};
			double [][] dbg_img = new double [dbg_titles.length][disparity.length];
			dbg_img[0] = disparity;
			dbg_img[1] = plane_disparity;
			for (int i = 0; i < disparity.length; i++) {
				dbg_img[2][i] = disparity[i] - plane_disparity[i];
			}
    		ShowDoubleFloatArrays.showArrays(
    				dbg_img,
    				tilesX,
    				tilesY,
    				true,
    				debug_title,
    				dbg_titles);
		}
    	return plane_disparity;
    	
    }
    
    
    
    public double [] getSmoothGround(
    		CLTParameters clt_parameters,
    		boolean       sfm_mode,
    		int           debugLevel) {
    	boolean       use_lma =         clt_parameters.gsmth_use_lma;    
    	double        discard_low =     clt_parameters.gsmth_discard_low;
    	double        discard_high =    clt_parameters.gsmth_discard_high;
    	int           stile_hstep =     clt_parameters.gsmth_stile_hstep;
    	double        stile_radius =    clt_parameters.gsmth_stile_radius; 
    	double        sigma =           clt_parameters.gsmth_sigma;
    	int           radius_levels =   clt_parameters.gsmth_radius_levels;
    	int           min_non_empty =   clt_parameters.gsmth_min_non_empty;
    	int           border_start_lev= clt_parameters.gsmth_border_level;
    	int           gsmth_sfm_lev =   clt_parameters.gsmth_sfm_lev;
    	if (sfm_mode) {
    		double dmin_num = (int) Math.round(min_non_empty);
    		for (int i = 0; i < gsmth_sfm_lev; i++) {
    			stile_hstep /= 2;
    			stile_radius /= 2;
    			sigma /= 2;
    			radius_levels += 1;
    			dmin_num *= 0.8;
    			// border_start_lev - keep?
    		}
    		min_non_empty = (int) Math.round(dmin_num);
    	}
//    	debugLevel = 3;
    	return getSmoothGround(
    			clt_parameters, // final CLTParameters clt_parameters,
    			use_lma,        // final boolean       use_lma, // false, or there will be too many gaps?
    			discard_low,    // final double        discard_low,    // fraction of all pixels
    			discard_high,   // final double        discard_high,   // fraction of all pixels
    			stile_hstep,    // final int           stile_hstep,
    			stile_radius,   // final double        stile_radius,
    			sigma,          // final double        sigma,
    			radius_levels,  // final int           radius_levels,
    			min_non_empty,  // final int           min_non_empty,  // minimal number of non-empty tiles for fitting planes
    			border_start_lev, // final int           border_start_lev0,
    			debugLevel);    // final int           debugLevel);
    	
    }
    
    public double [] getSmoothGround(
    		final CLTParameters clt_parameters,
    		final boolean       use_lma, // false, or there will be too many gaps?
    		final double        discard_low,    // fraction of all pixels
    		final double        discard_high,   // fraction of all pixels
    		final int           stile_hstep,
    		final double        stile_radius,
    		final double        sigma,
    		final int           radius_levels,
    		final int           min_non_empty,  // minimal number of non-empty tiles for fitting planes
    		final int           border_start_lev0,
    		final int           debugLevel) {
    	final int border_start_lev = Math.min(border_start_lev0,radius_levels-1); 
    	final int stile_step = 2 * stile_hstep;
    	final int num_bins = 1000;
    	final double normal_damping = 0.000001; // 0.001; // pull to horizontal if not enough data 
    	final double hist_rlow =    0.5;
    	final double hist_rhigh =   2.0;
    	// allow strong/high, but not near the edges
    	final int tilesX =   tp.getTilesX();	
    	final int tilesY =   tp.getTilesY();	
    	double [] smooth_disparity = new double [tilesX*tilesY];
    	int stilesX = (tilesX-1)/stile_step + 1;
    	int stilesY = (tilesY-1)/stile_step + 1;
    	final double [][] stiles_coeff = new double [stilesX*stilesY][3];
    	final double [][][] wnd= new double[radius_levels][][]; // ][irad][irad];
    	final double [] rad = new double [radius_levels];
    	final int [] irada = new int [radius_levels];
    	
    	final double [][] dbg2_img= (debugLevel > 0) ? new double [2][tilesX*tilesY]: null;
    	for (int nlev = 0; nlev < radius_levels; nlev++ ) {
    		if (nlev==0) {
    			rad[nlev] = stile_radius;
    		} else {
    			rad[nlev] = rad[nlev - 1] * 2;
    		}
    		irada[nlev] = (int) Math.ceil(rad[nlev]);
    		wnd[nlev]= new double [irada[nlev]][irada[nlev]];
    		double ir2 =  irada[nlev]*irada[nlev];
    		for (int i = 0; i < irada[nlev]; i++) {
    			for (int j = 0; j < irada[nlev]; j++) {
    				double r2 = (i*i + j * j)/ir2;
    				if (r2 <= 1.0) {
    					wnd[nlev][i][j] = Math.cos(Math.PI*Math.sqrt(r2)/2);
    				}
    			}
    		}
    	}
    	final double [][] dls = getDLS();
    	if (dls==null) {
    		return null;
    	}
    	final double [][] ds = new double [][] {dls[use_lma?1:0].clone(), dls[2]};
    	double sw=0, swd=0;
    	for (int i = 0; i < ds[0].length; i++) if (!Double.isNaN(ds[0][i])){
    		sw +=   ds[1][i];
    		swd +=  ds[0][i] * ds[1][i];
    	}
    	double disp_avg = swd/sw;
    	final double hist_low =  hist_rlow *  disp_avg;
    	final double hist_high = hist_rhigh * disp_avg;
    	double k = num_bins / (hist_high - hist_low);
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		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);
			    	PolynomialApproximation pa = new PolynomialApproximation();
			    	double [] damping = new double [] {normal_damping, normal_damping};
					double [] hist = new double [num_bins]; 
					for (int nstile = ai.getAndIncrement(); nstile < stiles_coeff.length; nstile = ai.getAndIncrement()) {
						int stileX = nstile % stilesX;
						int stileY = nstile / stilesX;
						int nlev = 0;
						if ((stileX == 0) || (stileY==0) || (stileX == (stilesX-1)) || (stileY == (stilesY-1))) {
							nlev = border_start_lev; // start from the second level
						}
						int num_non_empty;							// fill data arrays and the histogram
						int tileXC = stile_hstep + stile_step * stileX; // center tiles
						int tileYC = stile_hstep + stile_step * stileY;
						int ntileC = tileXC + tilesX * tileYC; 
				    	int numgood = 0;
						int irad=0;
						int idia=0;
						double [] stile_d=null;
						double [] stile_w=null;
						for (; nlev < radius_levels; nlev++) {
							irad = irada[nlev];
							idia = 2 * irad - 1;
							stile_d = new double [idia*idia];
							stile_w = new double [idia*idia];
//							Arrays.fill(stile_w,  0.0); // not all elements may be filled
							Arrays.fill(hist,     0.0);
							double sw=0; // , swd=0;
							num_non_empty = 0;							// fill data arrays and the histogram
							// later make to use lma first, then non-lma if nothing exists. Or increase size? (use several ones?
							for (int i = 0; i < idia; i++) {
								int dy = i-irad+1;
								int ady = Math.abs(dy);
								for (int j = 0; j < idia; j++) {
									int dx = j-irad+1;
									int adx = Math.abs(dx);
									int tindx = tn.getNeibIndex(ntileC, dx, dy);
									if (tindx >= 0) {
										int ltile = i * idia + j;
										double d = ds[0][tindx];
										double w = ds[1][tindx]*wnd[nlev][ady][adx];
										int bin = (int) (k * (d - hist_low));
										if ((w > 0) && (bin >= 0) && (bin < num_bins)) {
											stile_d[ltile] = d;
											stile_w[ltile] = w;
											sw += w;
//											swd += w * d;
											hist[bin] += w;
											num_non_empty++;
										}
									}
								}
							}
							if ((num_non_empty < min_non_empty) || (sw==0)) {
								continue; // go to the next radius level
							}
                            // try using histogram to remove outliers							
					    	double dl = discard_low * sw;
					    	double dh = discard_high * sw;
					    	double sh = 0.0;
					    	int indx = 0;
					    	for (; indx < num_bins; indx++) {
					    		sh+= hist[indx]; 
					    		if (sh >= dl) break;
					    	}
					    	double shp = sh- hist[indx];
					    	// step back from the overshoot indx
					    	double thr_low = hist_low+ (indx - (sh - dl)/(sh-shp))/num_bins *(hist_high-hist_low);
					    	indx = num_bins-1;
					    	sh = 0.0;
					    	for (; indx >= 0; indx--) {
					    		sh += hist[indx]; 
					    		if (sh >= dh) {
					    			break;
					    		}
					    	}
					    	shp = sh- hist[indx];
					    	double thr_high = hist_low+ (indx + (sh - dh)/(sh-shp))/num_bins *(hist_high-hist_low);
					    	numgood = 0;
//					    	boolean [] good = new boolean[ds[0].length];
					    	sw = 0.0;
//					    	swd = 0.0;
					    	
							for (int i = 0; i < idia; i++) {
								for (int j = 0; j < idia; j++) {
									int ltile = i * idia + j;
									if (stile_w[ltile] > 0) {
										if ((stile_d[ltile] >= thr_low) && (stile_d[ltile] <= thr_high)) {
								    		numgood++;
										} else {
											stile_w[ltile] = 0;
										}
									}
								}
							}
					    	
					    	if (numgood >= min_non_empty) {
								break; // go to the next radius level
					    	}
						}
						if (dbg2_img != null) {
							dbg2_img[0][ntileC] = nlev;
							dbg2_img[1][ntileC] = numgood;
						}
						// fit plane
						double [][][] mdata = new double [numgood][][];
						int mindx = 0;
						for (int i = 0; i < idia; i++) {
							int dy = i-irad+1;
							for (int j = 0; j < idia; j++) {
								int dx = j-irad+1;
								int ltile = i * idia + j;
								if (stile_w[ltile] > 0) {
									mdata[mindx] = new double[3][];
									mdata[mindx][0] = new double [2];
									mdata[mindx][0][0] = dx;
									mdata[mindx][0][1] = dy;
									mdata[mindx][1] = new double [1];
									mdata[mindx][1][0] =  stile_d[ltile]; 
									mdata[mindx][2] = new double [1];
									mdata[mindx][2][0] =  stile_w[ltile];
									mindx++;
								}
							}
						}

						double[][] approx2d = pa.quadraticApproximation(
								mdata,
								true,       // boolean forceLinear,  // use linear approximation
								damping,    // double [] damping, null OK
								-1);        // debug level
						stiles_coeff[nstile][0] = approx2d[0][2]; //  offset
						stiles_coeff[nstile][1] = approx2d[0][0]; //  tiltX
						stiles_coeff[nstile][2] = approx2d[0][1]; //  tiltY

					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
// Fill planes in each stile
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
			    	TileNeibs tn = new TileNeibs(tilesX,tilesY);
					for (int nstile = ai.getAndIncrement(); nstile < stiles_coeff.length; nstile = ai.getAndIncrement()) {
						int stileX = nstile % stilesX;
						int stileY = nstile / stilesX;
						int tileXC = stile_hstep + stile_step * stileX; // center tiles
						int tileYC = stile_hstep + stile_step * stileY;
						int ntileC = tileXC + tilesX * tileYC; 
						for (int dy = -stile_hstep; dy < stile_hstep; dy++) {
							for (int dx = -stile_hstep; dx < stile_hstep; dx++) {
								int tindx = tn.getNeibIndex(ntileC, dx, dy);
								if (tindx >= 0) {
									double d = stiles_coeff[nstile][0] +
											stiles_coeff[nstile][1] * dx+
											stiles_coeff[nstile][2] * dy;
									smooth_disparity[tindx] = d;		
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		// Maybe use Gaussian? to additionally smooth "seams"
		double [][] dbg_img = null;
    	if (dbg2_img !=null ) {
    		dbg_img = new double[7][];
    		dbg_img[0] = dls[2]; // strt
    		dbg_img[1] = dls[0]; // disp
    		dbg_img[2] = dls[1]; // lma
    		dbg_img[3] = smooth_disparity.clone();
    		dbg_img[5] = dbg2_img[0]; // nlev
    		dbg_img[6] = dbg2_img[1]; // numgood
    	}
		
    	if (sigma > 0) {
			(new DoubleGaussianBlur()).blurDouble(smooth_disparity,  tilesX, tilesY, sigma, sigma, 0.01);
    	}
    	if (dbg_img != null) {
    		dbg_img[4] = smooth_disparity;
    		String [] dbg_titles = {"str", "disp", "lma", "smooth", "blur", "nlev","numgood"};
    		ShowDoubleFloatArrays.showArrays(
    				dbg_img,
    				tp.getTilesX(),
    				tp.getTilesY(),
    				true,
    				getImageName()+"-smooth-ground.tiff",
    				dbg_titles); 
    	}
    	
    	// replace strong high enough objects?
    	
    	return smooth_disparity;
    	
    }
    
    
     public double [][] getGroundNoIms( // return to_ground_xyzatr;
    		CLTParameters clt_parameters,
    		boolean       use_lma,
    		boolean       use_parallel_proj,
    		double        disparity_offset,    		
    		double        discard_low,    // fraction of all pixels
    		double        discard_high,   // fraction of all pixels
    		double        discard_adisp,  // discard above/below this fraction of average height 
    		double        discard_rdisp,  // discard above/below this fraction of average height
    		double        pix_size,       // in meters
    		int           max_image_width,// increase pixel size as a power of 2 until image fits
    		double []     x0y0,           // initialize to double[2] to return width, height
    		int []        whs,            // initialize to int[3] to return {width, height, scale reduction}
    		int debug_level
    		) {
    	double [] z_tilts = null;
    	double normal_damping = 0.001; // pull to horizontal if not enough data 
    	double hist_rlow =    0.5;
    	double hist_rhigh =   2.0;
    	int min_good =       20; //number of good tiles
    	double rel_hight =  0.2; // when calculating scale, ignore objects far from plane 
    	int num_bins = 1000;
    	double [][] dls = getDLS();
    	if (dls==null) {
    		return null;
    	}
    	double [][] ds = new double [][] {dls[use_lma?1:0].clone(), dls[2]};
    	double sw=0, swd=0;
    	for (int i = 0; i < ds[0].length; i++) if (!Double.isNaN(ds[0][i])){
    		ds[0][i] -= disparity_offset;
    		sw +=   ds[1][i];
    		swd +=  ds[0][i] * ds[1][i];
    	}
    	double disp_avg = swd/sw;
    	double hist_low =  hist_rlow *  disp_avg;
    	double hist_high = hist_rhigh * disp_avg;
    	double k = num_bins / (hist_high - hist_low);
    	double [] hist = new double [num_bins];
    	sw = 0;
    	swd = 0;
    	for (int i = 0; i < ds[0].length; i++) if (!Double.isNaN(ds[0][i])){
    		double d = ds[0][i];
    		double w = ds[1][i];
    		int bin = (int) (k * (d - hist_low));
    		if ((bin >= 0) && (bin < num_bins)) {
    			sw += w;
    			swd += w * d;
    			hist[bin] += w;
    		}
    	}
    	if (sw == 0) {
    		if (debug_level > -3) {
    			System.out.println("Could not find ground - sum weights==0.0");
    		}
    		return null;
    	}
    	double dl = discard_low * sw;
    	double dh = discard_high * sw;
    	double sh = 0.0;
    	int indx = 0;
    	for (; indx < num_bins; indx++) {
    		sh+= hist[indx]; 
    		if (sh >= dl) break;
    	}
    	double shp = sh- hist[indx];
    	// step back from the overshoot indx
    	double thr_low = hist_low+ (indx - (sh - dl)/(sh-shp))/num_bins *(hist_high-hist_low);
    	indx = num_bins-1;
    	sh = 0.0;
    	for (; indx >= 0; indx--) {
    		sh += hist[indx]; 
    		if (sh >= dh) {
    			break;
    		}
    	}
    	shp = sh- hist[indx];
    	double thr_high = hist_low+ (indx + (sh - dh)/(sh-shp))/num_bins *(hist_high-hist_low);
    	int numgood = 0;
    	boolean [] good = new boolean[ds[0].length];
    	sw = 0.0;
    	swd = 0.0;
    	for (int i = 0; i < ds[0].length; i++) if (
    			(ds[1][i] > 0) && (ds[0][i] >= thr_low) && (ds[0][i] <= thr_high)) {
    		good[i] = true;
    		numgood++;
    		sw += ds[1][i];
    		swd += ds[1][i] * ds[0][i];
    	}

    	if (numgood < min_good) {
    		if (debug_level > -3) {
    			System.out.println("Could not find ground - number of good tiles = "+numgood+" < "+min_good);
    		}
    		return null; // too few good 
    	}

    	// fit plane
    	double [] ref_disparity = ds[0].clone();
    	for (int i = 0; i < ref_disparity.length; i++) {
    		if (!good[i]) {
    			ref_disparity[i] = Double.NaN;
    		}
    	}

    	double [][] wxyz = OpticalFlow.transformToWorldXYZ(
    			ref_disparity,  // final double []   disparity_ref, // invalid tiles - NaN in disparity
    			(QuadCLT) this, // final QuadCLT     quadClt, // now - may be null - for testing if scene is rotated ref
    			THREADS_MAX);   // int               threadsMax)
    	numgood = 0;
    	for (int i = 0; i < wxyz.length; i++) if (wxyz[i] != null) {
    		numgood++;	
    	}
    	double [][][] mdata = new double [numgood][][];
    	int mindx = 0;
    	for (int i = 0; i < wxyz.length; i++) if (wxyz[i] != null){
    		mdata[mindx] = new double[3][];
    		mdata[mindx][0] = new double [2];
    		mdata[mindx][0][0] =  wxyz[i][0];
    		mdata[mindx][0][1] =  wxyz[i][1];
    		mdata[mindx][1] = new double [1];
    		mdata[mindx][1][0] =  wxyz[i][2]; 
    		mdata[mindx][2] = new double [1];
    		mdata[mindx][2][0] =  ds[1][i];
    		mindx++;
    	}    	
    	PolynomialApproximation pa = new PolynomialApproximation();
    	double [] damping = new double [] {normal_damping, normal_damping};
    	double[][] approx2d = pa.quadraticApproximation(
    			mdata,
    			true,       // boolean forceLinear,  // use linear approximation
    			damping,    // double [] damping, null OK
    			-1);        // debug level
    	z_tilts= new double [] { approx2d[0][2], approx2d[0][0], approx2d[0][1]};
    	if (debug_level > -3) {
    		System.out.println("Found ground plane: level="+z_tilts[0]+", tiltX="+z_tilts[1]+", tiltY="+z_tilts[2]);
    	}

    	double [][] ground_xyzatr =  new double [][] {{0, 0, use_parallel_proj?0:z_tilts[0]},{Math.asin(z_tilts[1]), -Math.asin(z_tilts[2]), 0.0}};
    	// It is approximate for small angles. OK for now    		
    	double [][] to_ground_xyzatr = ErsCorrection.invertXYZATR(ground_xyzatr);
    	double [][] ground_xyzatr1 = new double [][] {{0, 0, z_tilts[0]},{Math.asin(z_tilts[1]), -Math.asin(z_tilts[2]), 0.0}};
    	double [][] to_ground_xyzatr1 = ErsCorrection.invertXYZATR(ground_xyzatr1);

    	// recalculate coordinates for all pixels including weak ones
    	ref_disparity = dls[0].clone();
    	for (int i = 0; i < ref_disparity.length; i++) {
    		if (!Double.isNaN(ref_disparity[i])) {
    			ref_disparity[i] -= disparity_offset;
    			if (ref_disparity[i] <=0) {
    				ref_disparity[i] = Double.NaN;
    			}
    		}
    	}
    	wxyz = OpticalFlow.transformToWorldXYZ(
    			ref_disparity,          // final double []   disparity_ref, // invalid tiles - NaN in disparity
    			(QuadCLT) this, // final QuadCLT     quadClt, // now - may be null - for testing if scene is rotated ref
    			THREADS_MAX);   // int               threadsMax)
    	double [][] plane_xyz=new double[wxyz.length][];
    	double [] x_min_max = null; // new int[2];
    	double [] y_min_max = null; // new int[2];
    	for (int i = 0; i < wxyz.length; i++) if (wxyz[i] != null) {
    		double [] wxyz3=new double[] {wxyz[i][0],wxyz[i][1],wxyz[i][2]};
    		plane_xyz[i] =ErsCorrection.applyXYZATR(
    				to_ground_xyzatr, // double [][] reference_xyzatr,
    				wxyz3); // double [][] scene_xyzatr)
    		double x = plane_xyz[i][0];
    		double y = plane_xyz[i][1];
    		double z = plane_xyz[i][2];
    		if (use_parallel_proj) {
    			z+=to_ground_xyzatr1[0][2];
    		}
    		//TODO - for use_parallel_proj subtract transformed Z and 
    		if (Math.abs(z)/Math.abs(z_tilts[0]) > rel_hight){
    			continue; // outlier Z
    		}

    		if (x_min_max == null) x_min_max = new double[] {x,x};
    		if (y_min_max == null) y_min_max = new double[] {y,y};
    		if      (x < x_min_max[0]) x_min_max[0] = x;
    		else if (x > x_min_max[1]) x_min_max[1] = x;
    		if      (y < y_min_max[0]) y_min_max[0] = y;
    		else if (y > y_min_max[1]) y_min_max[1] = y;
    	}
    	if ((x_min_max == null) || (y_min_max == null)) {
    		return null; // no points at all?
    	}
    	if (x0y0!=null) {
    		x0y0[0] = x_min_max[0]; // null
    		x0y0[1] = y_min_max[0];
    	}
    	int scale = 0;
    	double use_pix_size = pix_size;
    	int width, height;
    	do {
    		scale = (scale==0) ? 1 : scale * 2;
    		use_pix_size = scale * pix_size;
    		width =  (int) Math.floor((x_min_max[1]-x_min_max[0])/use_pix_size) + 1;
    		height = (int) Math.floor((y_min_max[1]-y_min_max[0])/use_pix_size) + 1;
    	} while ((width > max_image_width) || (height > max_image_width));
    	if (whs != null) {
    		whs[0] = width;
    		whs[1] = height;
    		whs[2] = scale;
    	}
    	if (debug_level > -2) {
    		System.out.println("Parameters for rendering:top left corner=["+x0y0[0]+"m, "+x0y0[1]+"m]");
    		System.out.println("                        : width="+whs[0]+"pix, height="+whs[1]+"pix, scale level="+whs[2]);
    		System.out.println("                        : pixel size: ="+(1000*use_pix_size)+"mm");
    	}

    	double [] dronexyz =ErsCorrection.applyXYZATR(
    			to_ground_xyzatr, // double [][] reference_xyzatr,
    			new double [3] ); // double [][] scene_xyzatr)
    	if (debug_level > -2) {
    		System.out.println("Drone position relative to the ground plane: x="+
    				dronexyz[0]+"m, y="+dronexyz[1]+"m, z="+dronexyz[2]+"m");
    	}
    	return to_ground_xyzatr; // from the camera coordinates to in-plane coordiantes
    }

     public double [][] getGroundNoImsAirplane( // return to_ground_xyzatr;
     		CLTParameters clt_parameters,
     		boolean       use_lma,
     		boolean       use_parallel_proj,
     		double        disparity_offset,    		
     		double        discard_low,    // fraction of all pixels
     		double        discard_high,   // fraction of all pixels
     		double        discard_adisp,  // discard above/below this fraction of average height 
     		double        discard_rdisp,  // discard above/below this fraction of average height
     		double        pix_size,       // in meters
     		int           max_image_width,// increase pixel size as a power of 2 until image fits
     		double []     x0y0,           // initialize to double[2] to return width, height
     		int []        whs,            // initialize to int[3] to return {width, height, scale reduction}
     		int debug_level
     		) {
     	double [] z_tilts = null;
     	double normal_damping = 0.001; // pull to horizontal if not enough data 
     	double hist_rlow =    0.5;
     	double hist_rhigh =   2.0;
     	int min_good =       20; //number of good tiles
     	double rel_hight =  0.2; // when calculating scale, ignore objects far from plane 
     	int num_bins = 1000;
     	double [][] dls = getDLS();
     	if (dls==null) {
     		return null;
     	}
     	double [][] ds = new double [][] {dls[use_lma?1:0].clone(), dls[2]};
     	double sw=0, swd=0;
     	for (int i = 0; i < ds[0].length; i++) if (!Double.isNaN(ds[0][i])){
     		ds[0][i] -= disparity_offset;
     		sw +=   ds[1][i];
     		swd +=  ds[0][i] * ds[1][i];
     	}
     	double disp_avg = swd/sw;
     	double hist_low =  0; // hist_rlow *  disp_avg;
     	double hist_high = 2.0 * disp_avg; // hist_rhigh * disp_avg;
     	double k = num_bins / (hist_high - hist_low);
     	double [] hist = new double [num_bins];
     	sw = 0;
     	swd = 0;
     	for (int i = 0; i < ds[0].length; i++) if (!Double.isNaN(ds[0][i])){
     		double d = ds[0][i];
     		double w = ds[1][i];
     		int bin = (int) (k * (d - hist_low));
     		if ((bin >= 0) && (bin < num_bins)) {
     			sw += w;
     			swd += w * d;
     			hist[bin] += w;
     		}
     	}
     	if (sw == 0) {
     		if (debug_level > -3) {
     			System.out.println("Could not find ground - sum weights==0.0");
     		}
     		return null;
     	}
     	
     	
     	
     	double dl = discard_low * sw;
     	double dh = discard_high * sw;
     	double sh = 0.0;
     	int indx = 0;
     	for (; indx < num_bins; indx++) {
     		sh+= hist[indx]; 
     		if (sh >= dl) break;
     	}
     	double shp = sh- hist[indx];
     	// step back from the overshoot indx
     	double thr_low = hist_low+ (indx - (sh - dl)/(sh-shp))/num_bins *(hist_high-hist_low);
     	indx = num_bins-1;
     	sh = 0.0;
     	for (; indx >= 0; indx--) {
     		sh += hist[indx]; 
     		if (sh >= dh) {
     			break;
     		}
     	}
     	shp = sh- hist[indx];
     	double thr_high = hist_low+ (indx + (sh - dh)/(sh-shp))/num_bins *(hist_high-hist_low);
     	int numgood = 0;
     	boolean [] good = new boolean[ds[0].length];
     	sw = 0.0;
     	swd = 0.0;
     	for (int i = 0; i < ds[0].length; i++) if (
     			(ds[1][i] > 0) && (ds[0][i] >= thr_low) && (ds[0][i] <= thr_high)) {
     		good[i] = true;
     		numgood++;
     		sw += ds[1][i];
     		swd += ds[1][i] * ds[0][i];
     	}

     	if (numgood < min_good) {
     		if (debug_level > -3) {
     			System.out.println("Could not find ground - number of good tiles = "+numgood+" < "+min_good);
     		}
     		return null; // too few good 
     	}

     	// fit plane
     	double [] ref_disparity = ds[0].clone();
     	for (int i = 0; i < ref_disparity.length; i++) {
     		if (!good[i]) {
     			ref_disparity[i] = Double.NaN;
     		}
     	}

     	double [][] wxyz = OpticalFlow.transformToWorldXYZ(
     			ref_disparity,  // final double []   disparity_ref, // invalid tiles - NaN in disparity
     			(QuadCLT) this, // final QuadCLT     quadClt, // now - may be null - for testing if scene is rotated ref
     			THREADS_MAX);   // int               threadsMax)
     	numgood = 0;
     	for (int i = 0; i < wxyz.length; i++) if (wxyz[i] != null) {
     		numgood++;	
     	}
     	double [][][] mdata = new double [numgood][][];
     	int mindx = 0;
     	for (int i = 0; i < wxyz.length; i++) if (wxyz[i] != null){
     		mdata[mindx] = new double[3][];
     		mdata[mindx][0] = new double [2];
     		mdata[mindx][0][0] =  wxyz[i][0];
     		mdata[mindx][0][1] =  wxyz[i][1];
     		mdata[mindx][1] = new double [1];
     		mdata[mindx][1][0] =  wxyz[i][2]; 
     		mdata[mindx][2] = new double [1];
     		mdata[mindx][2][0] =  ds[1][i];
     		mindx++;
     	}    	
     	PolynomialApproximation pa = new PolynomialApproximation();
     	double [] damping = new double [] {normal_damping, normal_damping};
     	double[][] approx2d = pa.quadraticApproximation(
     			mdata,
     			true,       // boolean forceLinear,  // use linear approximation
     			damping,    // double [] damping, null OK
     			-1);        // debug level
     	z_tilts= new double [] { approx2d[0][2], approx2d[0][0], approx2d[0][1]};
     	if (debug_level > -3) {
     		System.out.println("Found ground plane: level="+z_tilts[0]+", tiltX="+z_tilts[1]+", tiltY="+z_tilts[2]);
     	}

     	double [][] ground_xyzatr =  new double [][] {{0, 0, use_parallel_proj?0:z_tilts[0]},{Math.asin(z_tilts[1]), -Math.asin(z_tilts[2]), 0.0}};
     	// It is approximate for small angles. OK for now    		
     	double [][] to_ground_xyzatr = ErsCorrection.invertXYZATR(ground_xyzatr);
     	double [][] ground_xyzatr1 = new double [][] {{0, 0, z_tilts[0]},{Math.asin(z_tilts[1]), -Math.asin(z_tilts[2]), 0.0}};
     	double [][] to_ground_xyzatr1 = ErsCorrection.invertXYZATR(ground_xyzatr1);

     	// recalculate coordinates for all pixels including weak ones
     	ref_disparity = dls[0].clone();
     	for (int i = 0; i < ref_disparity.length; i++) {
     		if (!Double.isNaN(ref_disparity[i])) {
     			ref_disparity[i] -= disparity_offset;
     			if (ref_disparity[i] <=0) {
     				ref_disparity[i] = Double.NaN;
     			}
     		}
     	}
     	wxyz = OpticalFlow.transformToWorldXYZ(
     			ref_disparity,          // final double []   disparity_ref, // invalid tiles - NaN in disparity
     			(QuadCLT) this, // final QuadCLT     quadClt, // now - may be null - for testing if scene is rotated ref
     			THREADS_MAX);   // int               threadsMax)
     	double [][] plane_xyz=new double[wxyz.length][];
     	double [] x_min_max = null; // new int[2];
     	double [] y_min_max = null; // new int[2];
     	for (int i = 0; i < wxyz.length; i++) if (wxyz[i] != null) {
     		double [] wxyz3=new double[] {wxyz[i][0],wxyz[i][1],wxyz[i][2]};
     		plane_xyz[i] =ErsCorrection.applyXYZATR(
     				to_ground_xyzatr, // double [][] reference_xyzatr,
     				wxyz3); // double [][] scene_xyzatr)
     		double x = plane_xyz[i][0];
     		double y = plane_xyz[i][1];
     		double z = plane_xyz[i][2];
     		if (use_parallel_proj) {
     			z+=to_ground_xyzatr1[0][2];
     		}
     		//TODO - for use_parallel_proj subtract transformed Z and 
     		if (Math.abs(z)/Math.abs(z_tilts[0]) > rel_hight){
     			continue; // outlier Z
     		}

     		if (x_min_max == null) x_min_max = new double[] {x,x};
     		if (y_min_max == null) y_min_max = new double[] {y,y};
     		if      (x < x_min_max[0]) x_min_max[0] = x;
     		else if (x > x_min_max[1]) x_min_max[1] = x;
     		if      (y < y_min_max[0]) y_min_max[0] = y;
     		else if (y > y_min_max[1]) y_min_max[1] = y;
     	}
     	if ((x_min_max == null) || (y_min_max == null)) {
     		return null; // no points at all?
     	}
     	if (x0y0!=null) {
     		x0y0[0] = x_min_max[0]; // null
     		x0y0[1] = y_min_max[0];
     	}
     	int scale = 0;
     	double use_pix_size = pix_size;
     	int width, height;
     	do {
     		scale = (scale==0) ? 1 : scale * 2;
     		use_pix_size = scale * pix_size;
     		width =  (int) Math.floor((x_min_max[1]-x_min_max[0])/use_pix_size) + 1;
     		height = (int) Math.floor((y_min_max[1]-y_min_max[0])/use_pix_size) + 1;
     	} while ((width > max_image_width) || (height > max_image_width));
     	if (whs != null) {
     		whs[0] = width;
     		whs[1] = height;
     		whs[2] = scale;
     	}
     	if (debug_level > -2) {
     		System.out.println("Parameters for rendering:top left corner=["+x0y0[0]+"m, "+x0y0[1]+"m]");
     		System.out.println("                        : width="+whs[0]+"pix, height="+whs[1]+"pix, scale level="+whs[2]);
     		System.out.println("                        : pixel size: ="+(1000*use_pix_size)+"mm");
     	}

     	double [] dronexyz =ErsCorrection.applyXYZATR(
     			to_ground_xyzatr, // double [][] reference_xyzatr,
     			new double [3] ); // double [][] scene_xyzatr)
     	if (debug_level > -2) {
     		System.out.println("Drone position relative to the ground plane: x="+
     				dronexyz[0]+"m, y="+dronexyz[1]+"m, z="+dronexyz[2]+"m");
     	}
     	return to_ground_xyzatr; // from the camera coordinates to in-plane coordiantes
     }
     
     
     
    public double [][] getGroundIms( // return to_ground_xyzatr;
    		CLTParameters clt_parameters,
    		boolean       use_lma,
    		boolean       use_parallel_proj,    		
    		double        disparity_offset,    		
    		double        discard_low,    // fraction of all pixels
    		double        discard_high,   // fraction of all pixels
    		double        discard_adisp,  // discard above/below this fraction of average height 
    		double        discard_rdisp,  // discard above/below this fraction of average height
    		double        pix_size,       // in meters
    		int           max_image_width,// increase pixel size as a power of 2 until image fits
    		double []     x0y0,           // initialize to double[2] to return width, height
    		int []        whs,            // initialize to int[3] to return {width, height, scale reduction}
    		int debug_level
    		) {
    	boolean ims_use =         clt_parameters.imp.ims_use;
    	if (!ims_use || (did_ins_2 == null)) {
    		System.out.println("getGroundIns(): no INS data available.");
    		return null;
    	}
    	double    min_sfm_gain =  clt_parameters.gmap_min_sfm ; //10.0 minimal SfM gain to keep ground map
    	double    min_sfm_frac =    clt_parameters.gmap_frac_sfm ; // 0.25;  // Disregard SfM mask if lower good SfM area
    	double [] ims_ortho =     clt_parameters.imp.ims_ortho;
    	double [] ims_mount_atr = clt_parameters.imp.getImsMountATR(); // converts to radians
    	double [] ims_mount_xyz = clt_parameters.imp.ims_mount_xyz;    // not yet used
    	boolean   apply_quat_corr = clt_parameters.imp.apply_quat_corr; // apply camera orientation correction from predicted by IMS

    	Did_ins_2 d2 = did_ins_2;
    	double [] cam_quat_enu =Imx5.quaternionImsToCam(
    			d2.getQEnu(),
    			ims_mount_atr,
    			ims_ortho);
    	//
    	double [] quat_corr = getQuatCorr();
    	if (apply_quat_corr && (quat_corr != null)) {
    		cam_quat_enu=Imx5.applyQuaternionToQuaternion(quat_corr, cam_quat_enu, false);
    		if (debug_level > -3) {
    			System.out.println("Applying attitude correction from quaternion ["+
    					quat_corr[0]+", "+quat_corr[1]+", "+quat_corr[2]+", "+quat_corr[3]+"] to the ground plane");
    		}
    	} else {
    		if (debug_level > -3) {
    			System.out.println("No attitude correction is applied to the ground plane");
    		}
    	}
    	double [] scene_abs_atr_enu = Imx5.quatToCamAtr(cam_quat_enu);
		if (debug_level > -3) {
			System.out.println("##### scene_abs_atr_enu = "+scene_abs_atr_enu[0]+", "+scene_abs_atr_enu[1]+", "+scene_abs_atr_enu[2]+" #####");
		}
    	
    	
    	double [][] dls = getDLS();
    	if ((min_sfm_gain> 0) && (dls[3] != null)) {
    		int num_sfm = 0;
    		for (int i = 0; i < dls[2].length; i++) if (dls[3][i] >= min_sfm_gain){
    			num_sfm++;
    		}
    		double sfm_frac = (1.0 * num_sfm)/dls[3].length;
    		if (sfm_frac >=  min_sfm_frac) {
    			dls[2]=dls[2].clone();
    			for (int i = 0; i <dls[2].length; i++) {
    				if (dls[3][i] < min_sfm_gain) {
    					dls[2][i] = 0; // zero strength if low SfM gain
    				}
    			}
				if (debug_level > -3) {
					System.out.println("getGroundIms(): Fraction of SfM tiles = "+sfm_frac+" >= "+min_sfm_frac+", removing low SfM gain tiles.");
				}
    			
    		} else if (debug_level > -3) {
					System.out.println("getGroundIms(): Fraction of SfM tiles = "+sfm_frac+" < "+min_sfm_frac+", disregarding SfM filtering.");
    		}
    	}
    	if (dls==null) {
    		return null;
    	}
    	double    min_disparity = 0.2;
    	double    max_disparity = 15;
    	double [][] xy_range = new double[3][]; 
    	double agl = getAGL( // not used for the parallel projection
    			dls[use_lma?1:0].clone(), // double [] ref_disparity,
    			dls[2],                   // double [] ref_strength,
    			scene_abs_atr_enu,        // double [] reference_atr,
    			disparity_offset,         // double disparity_offset,
    			min_disparity,            // double    min_disparity,
    			max_disparity,            // double    max_disparity,
    			discard_low,              // double    discard_low,    // fraction of all pixels
    			discard_high,             // double    discard_high,   // fraction of all pixels
    			xy_range,                 // double [][] xy_range,     // should be double[2][]; meters
    			debug_level);             // int debug_level)
    	// camera position and orientation relative to the same lat/long on the ground below it 
    	//    	double [][] scene_abs_xyzatr_enu = new double [][] {new double[] {0,0,-agl},	scene_abs_atr_enu};
    	double [][] scene_abs_xyzatr_enu = new double [][] {new double[] {0,0,use_parallel_proj?0:-agl}, scene_abs_atr_enu};
    	//	    	double [][] ground_xyzatr = new double [][] {
    	//	    		{0,          0,          z_tilts[0]},
    	//	    		{Math.asin(z_tilts[1]), -Math.asin(z_tilts[2]), 0.0}};
    	double [][] to_ground_xyzatr = ErsCorrection.invertXYZATR(scene_abs_xyzatr_enu);
    	if ((xy_range[0] == null) || (xy_range[1] == null)) {
    		return null; // no points at all?
    	}
    	// FIXME: still something wrong with non-parallel bounds with IMS, noIMS - correct *****************
    	if (x0y0!=null) {
    		if (!use_parallel_proj) {
    			for (int i = 0; i < 2; i++) {
    				for (int j = 0; j < 2; j++) {
    					xy_range[i][j] -= xy_range[2][i];
    				}
    			}
    		}
    		x0y0[0] = xy_range[0][0]; // null
    		x0y0[1] = xy_range[1][0];

    	}

    	int scale = 0;
    	double use_pix_size = pix_size;
    	int width, height;
    	do {
    		scale = (scale==0) ? 1 : scale * 2;
    		use_pix_size = scale * pix_size;
    		width =  (int) Math.floor((xy_range[0][1] - xy_range[0][0])/use_pix_size) + 1;
    		height = (int) Math.floor((xy_range[1][1] - xy_range[1][0])/use_pix_size) + 1;
    	} while ((width > max_image_width) || (height > max_image_width));
    	if (whs != null) {
    		whs[0] = width;
    		whs[1] = height;
    		whs[2] = scale;
    	}
    	if (debug_level > -2) {
    		System.out.println("Parameters for rendering:top left corner=["+x0y0[0]+"m, "+x0y0[1]+"m]");
    		System.out.println("                        : width="+whs[0]+"pix, height="+whs[1]+"pix, scale level="+whs[2]);
    		System.out.println("                        : pixel size: ="+(1000*use_pix_size)+"mm");
    	}

    	double [] dronexyz =ErsCorrection.applyXYZATR(
    			to_ground_xyzatr, // double [][] reference_xyzatr,
    			new double [3] ); // double [][] scene_xyzatr)
    	if (debug_level > -2) {
    		System.out.println("Drone position relative to the ground plane: x="+
    				dronexyz[0]+"m, y="+dronexyz[1]+"m, z="+dronexyz[2]+"m");
    	}

    	return to_ground_xyzatr;
    }


    public double getAGL(
    		double [] ref_disparity, // will be modified
    		double [] ref_strength,
    		double [] reference_atr,
    		double    disparity_offset,
    		double    min_disparity,
    		double    max_disparity,
    		double    discard_low,    // fraction of all pixels
    		double    discard_high,   // fraction of all pixels

    		double [][] xy_range,     // should be double[2][];
    		int debug_level) {
    	boolean show_wxyz=false;
    	// find average AGL excluding high objects
    	double hist_rlow =    0.5;
    	double hist_rhigh =   2.0;
    	int    min_good =       20; //number of good tiles
    	//    	double rel_hight =  0.2; // when calculating scale, ignore objects far from plane 
    	int    num_bins = 1000;
    	double sw=0, swd=0;
    	boolean [] good_tiles = new boolean[ref_disparity.length];
    	for (int i = 0; i < ref_disparity.length; i++) if (!Double.isNaN(ref_disparity[i]) && (ref_strength[i] > 0)){ // some where NaN!
    		double d = ref_disparity[i] - disparity_offset;
    		ref_disparity[i] = d;
    		if ((ref_disparity[i] >= min_disparity) && (ref_disparity[i] <= max_disparity)) {
    			sw += ref_strength[i];
    			swd += ref_strength[i]*d;
    			good_tiles[i] = true;
    		}
    	}
    	double avg_disparity = swd/sw;
    	double average_Z= getGeometryCorrection().getZFromDisparity(avg_disparity);
    	double [][] wxyz = OpticalFlow.transformToWorldXYZ(
    			ref_disparity,     // final double [] disparity_ref, // invalid tiles - NaN in disparity
    			(QuadCLT) this,    // final QuadCLT   quadClt, // now - may be null - for testing if scene is rotated ref
    			OpticalFlow.ZERO3, // reference_xyz,  // this view position in world coordinates (typically ZERO3)
    			reference_atr,     // double []       reference_atr,  // this view orientation relative to world frame  (typically ZERO3)
    			THREADS_MAX);      // int             threadsMax)
    	if (show_wxyz) {
    		double [][] dbg_img = new double[3][wxyz.length];
    		for (int i = 0; i < dbg_img.length; i++) {
    			Arrays.fill(dbg_img[i], Double.NaN);
    		}
    		for (int i = 0; i < wxyz.length; i++) if (wxyz[i] != null) {
    			for (int j = 0; j < 3; j++) {
    				dbg_img[j][i] = wxyz[i][j];
    			}
    		}
    		ShowDoubleFloatArrays.showArrays(
    				dbg_img,
    				tp.getTilesX(),
    				tp.getTilesY(),
    				true,
    				"wxyz",
    				new String[] {"x","y","z"}); //	dsrbg_titles);

    	}

    	double hist_low =  hist_rlow *  average_Z;
    	double hist_high = hist_rhigh * average_Z;
    	double k = num_bins / (hist_high - hist_low);
    	double [] hist = new double [num_bins];
    	sw = 0;
    	swd = 0;
    	for (int i = 0; i < wxyz.length; i++) if (good_tiles[i] && (wxyz[i] != null)) { //  && !Double.isNaN(wxyz[i][2])){
    		double z = -wxyz[i][2];
    		double w = ref_strength[i];
    		int bin = (int) (k * (z - hist_low));
    		if ((bin >= 0) && (bin < num_bins)) {
    			sw += w;
    			swd += w * z;
    			hist[bin] += w;
    			if (xy_range != null) {
    				if (xy_range[0]== null) {
    					xy_range[0] = new double[] {wxyz[i][0],wxyz[i][0]}; // x, x
    					xy_range[1] = new double[] {wxyz[i][1],wxyz[i][1]}; // y, y
    				}
    				if      (wxyz[i][0] < xy_range[0][0]) xy_range[0][0] = wxyz[i][0];
    				else if (wxyz[i][0] > xy_range[0][1]) xy_range[0][1] = wxyz[i][0];
    				if      (wxyz[i][1] < xy_range[1][0]) xy_range[1][0] = wxyz[i][1];
    				else if (wxyz[i][1] > xy_range[1][1]) xy_range[1][1] = wxyz[i][1];
    			}
    		}
    	}
    	if (sw == 0) {
    		if (debug_level > -3) {
    			System.out.println("Could not find ground - sum weights==0.0");
    		}
    		return Double.NaN;
    	}

    	//    	double dl = discard_low * sw;
    	//    	double dh = discard_high * sw;
    	double dl = discard_high * sw;
    	double dh = discard_low * sw;
    	double sh = 0.0;
    	int indx = 0;
    	for (; indx < num_bins; indx++) {
    		sh+= hist[indx]; 
    		if (sh >= dl) break;
    	}
    	double shp = sh- hist[indx];
    	// step back from the overshoot indx
    	double thr_low = hist_low+ (indx - (sh - dl)/(sh-shp))/num_bins *(hist_high-hist_low);
    	indx = num_bins-1;
    	sh = 0.0;
    	for (; indx >= 0; indx--) {
    		sh += hist[indx]; 
    		if (sh >= dh) {
    			break;
    		}
    	}
    	shp = sh- hist[indx];
    	double thr_high = hist_low+ (indx + (sh - dh)/(sh-shp))/num_bins *(hist_high-hist_low);
    	int numgood = 0;
    	sw = 0.0;
    	swd = 0.0;
    	for (int i = 0; i < wxyz.length; i++) if (good_tiles[i] && (wxyz[i] != null)) { //  && !Double.isNaN(wxyz[i][2])){
    		double z = -wxyz[i][2];
    		double w = ref_strength[i];
    		if ((ref_strength[i] > 0) && (z >= thr_low) && (z <= thr_high)) {
    			numgood++;
    			sw += w;
    			swd += w * z;
    		}
    	}

    	if (numgood < min_good) {
    		if (debug_level > -3) {
    			System.out.println("Could not find ground - number of good tiles = "+numgood+" < "+min_good);
    		}
    		return Double.NaN; // too few good 
    	}
    	double agl = swd/sw; 
    	double [] center_xyz = ErsCorrection.applyXYZATR(
    			OpticalFlow.ZERO3, // reference_xyz,  // this view position in world coordinates (typically ZERO3)
    			reference_atr,     // double []       reference_atr,  // this view orientation relative to world frame  (typically ZERO3)
    			new double [] {0,0,-agl});
    	if (xy_range != null) {
    		xy_range[2] = center_xyz;
    	}
    	return agl;
    }


    public double [] smoothDisparity(
    		double [] disparity_in,
    		boolean [] reliable_ref, // optional
    		double     sigma,
    		double     max_diff,
    		boolean    apply_nan) {
    	final int tilesX =   tp.getTilesX();	
    	double [] disparity = disparity_in.clone();
    	if (reliable_ref != null) {
    		for (int i = 0; i < reliable_ref.length; i++) {
    			if (!reliable_ref[i]) {
    				disparity[i] = Double.NaN;
    			}
    		}
    	}
    	if (sigma > 0) {
    		boolean [] good_tiles = new boolean [disparity.length];
    		for (int i = 0; i < disparity.length; i++) {
    			good_tiles[i] = !Double.isNaN(disparity[i]);
    		}

    		double scale_sigma = 5.0; // non-NaN - 5 pixels from original NaN for each sigma
    		int grow = 2* Math.min((int) Math.ceil(sigma * scale_sigma), tilesX);
    		disparity = TileProcessor.fillNaNs(
    				disparity,    // final double [] data,
    				null,                     // final boolean [] prohibit,
    				tilesX,          // int       width,
    				// CAREFUL ! Remaining NaN is grown by unsharp mask filter ************* !
    				grow,           // 100, // 2*width, // 16,           // final int grow,
    				0.7,            // double    diagonal_weight, // relative to ortho
    				100,            // int       num_passes,
    				0.03);          // final double     max_rchange, //  = 0.01 - does not need to be accurate

    		(new DoubleGaussianBlur()).blurDouble(
    				disparity,          //
    				tilesX,
    				disparity.length/tilesX,
    				sigma,              // double sigmaX,
    				sigma,              // double sigmaY,
    				0.01);              // double accuracy)
    		if (max_diff > 0) {
    			for (int i = 0; i < disparity.length; i++) if (good_tiles[i]){
    				if (Math.abs(disparity[i] - disparity_in[i]) > max_diff) {
    					good_tiles[i] = false;
    				}
    			} 
    		}
    		if (apply_nan) {
    			for (int i = 0; i < disparity.length; i++) if (!good_tiles[i]){
    				disparity[i] = Double.NaN;
    			}
    		}
    		if (reliable_ref != null) {
    			System.arraycopy(good_tiles, 0, reliable_ref, 0, disparity.length);
    		}
    	}
    	return disparity;
    }

    public static void cloneInner(double [][] data){
    	if (data != null) {
    		for (int i = 0; i < data.length; i++) {
    			if (data[i] != null) {
    				data[i] = data[i].clone();
    			}
    		}
    	}
    }
    
    public double [][] getDLS(){ // get disparity, disparity_lma, strength, sfm_gain
    	return getDLS(this.dsi);
    }
    
    public double [][] getDLS(double [][] dsi){ // get disparity, disparity_lma, strength, sfm_gain
    	if (dsi == null) {
    		//    		System.out.println("dsi== null, use spawnQuadCLT(), restoreFromModel(), ... to set it");
    		return null;
    	}
//    	double [][] dls = new double[5][];
    	double [][] dls = new double[6][];
    	dls[0] = dsi[isAux()? TwoQuadCLT.DSI_DISPARITY_AUX :     TwoQuadCLT.DSI_DISPARITY_MAIN];
    	dls[1] = dsi[isAux()? TwoQuadCLT.DSI_DISPARITY_AUX_LMA : TwoQuadCLT.DSI_DISPARITY_MAIN_LMA];
    	dls[2] = dsi[isAux()? TwoQuadCLT.DSI_STRENGTH_AUX :      TwoQuadCLT.DSI_STRENGTH_MAIN];
    	dls[3] = dsi[isAux()? TwoQuadCLT.DSI_SFM_GAIN_AUX :      TwoQuadCLT.DSI_SFM_GAIN_MAIN];
    	dls[4] = dsi[isAux()? TwoQuadCLT.DSI_GROUND_AUX :        TwoQuadCLT.DSI_GROUND_MAIN];
    	dls[5] = dsi[isAux()? TwoQuadCLT.DSI_TERRAIN_AUX :       TwoQuadCLT.DSI_TERRAIN_MAIN];
    	return dls;
    }
    public double [][] comboFromMain(){  // may want to run cloneInner() to separate data from its source
    	return comboFromMain(this.dsi);
    }

    
    public double [][] comboFromMain(double [][] dsi){ // may want to run cloneInner() to separate data from its source
    	if (dsi == null) {
    		return null;
    	}
    	double [][] combo_dsi = new double [OpticalFlow.COMBO_DSN_TITLES.length][];
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_DISP] =     dsi[isAux()? TwoQuadCLT.DSI_DISPARITY_AUX :     TwoQuadCLT.DSI_DISPARITY_MAIN];
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_LMA] =      dsi[isAux()? TwoQuadCLT.DSI_DISPARITY_AUX_LMA : TwoQuadCLT.DSI_DISPARITY_MAIN_LMA];
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_STRENGTH] = dsi[isAux()? TwoQuadCLT.DSI_STRENGTH_AUX :      TwoQuadCLT.DSI_STRENGTH_MAIN];
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_BLUE_SKY] = dsi[isAux()? TwoQuadCLT.DSI_BLUE_SKY_AUX :      TwoQuadCLT.DSI_BLUE_SKY_MAIN];
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_SFM_GAIN] = dsi[isAux()? TwoQuadCLT.DSI_SFM_GAIN_AUX :      TwoQuadCLT.DSI_SFM_GAIN_MAIN];
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_GROUND] =   dsi[isAux()? TwoQuadCLT.DSI_GROUND_AUX :        TwoQuadCLT.DSI_GROUND_MAIN];
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_TERRAIN] =  dsi[isAux()? TwoQuadCLT.DSI_TERRAIN_AUX :       TwoQuadCLT.DSI_TERRAIN_MAIN];
    	return combo_dsi;
    }
    
    

    // Duplicate disparity to FG/BG to make OpticalFLow happy
    /*
	        	// BG mode
	        	double [] bg_lma = combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL].clone();
	        	double [] bg_str = combo_dsn_final[COMBO_DSN_INDX_STRENGTH].clone();
     */
    public double [][] comboFromMainFgBg(){  // may want to run cloneInner() to separate data from its source
    	return comboFromMainFgBg(this.dsi);
    }

    public double [][] comboFromMainFgBg(double [][] dsi){  // may want to run cloneInner() to separate data from its source
    	double [][] combo_dsi = comboFromMain(dsi);
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_DISP_BG] =      combo_dsi[OpticalFlow.COMBO_DSN_INDX_DISP];
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_STRENGTH_BG] =  combo_dsi[OpticalFlow.COMBO_DSN_INDX_STRENGTH];
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_LMA_BG] =       combo_dsi[OpticalFlow.COMBO_DSN_INDX_LMA];
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_DISP_BG_ALL] =  combo_dsi[OpticalFlow.COMBO_DSN_INDX_DISP];
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_DISP_FG] =      combo_dsi[OpticalFlow.COMBO_DSN_INDX_DISP];
    	combo_dsi[OpticalFlow.COMBO_DSN_INDX_BLUE_SKY] =     new double [combo_dsi[OpticalFlow.COMBO_DSN_INDX_DISP].length];
    	Arrays.fill(combo_dsi[OpticalFlow.COMBO_DSN_INDX_BLUE_SKY], Double.NaN);
    	return combo_dsi;
    }

    
    
    
    public TileProcessor getTileProcessor() {
    	return tp;
    }
    
    /*
    public int getTileLength() {
    	return tp.getTileSize()*tp.getTileSize();
    }
    public int getCltTileLength() {
    	return 4*tp.getTileSize()*tp.getTileSize();
    }
    
    */
    public int getTileSize() {
    	if (tp == null) {
    		return GPUTileProcessor.DTT_SIZE;
    	}
    	return tp.getTileSize();
    }
    
    public int getTilesX() {
    	if (tp == null) {
//    		if (geometryCorrection == null) {
    		// if QuadCLT use gpuQuad    			
//    		} else {
    			return geometryCorrection.getSensorWH()[0]/GPUTileProcessor.DTT_SIZE;
//   		}
    	}
    	return tp.getTilesX();
    }
    public int getTilesY() {
    	if (tp == null) {
//    		if (geometryCorrection == null) {
// if QuadCLT use gpuQuad    			
//    		} else {
    			return geometryCorrection.getSensorWH()[1]/GPUTileProcessor.DTT_SIZE;
//    		}
    	}
    	return tp.getTilesY();
    }
    public int getNumColors(){
    	boolean mono = (tp == null) ? geometryCorrection.isMonochrome() : tp.isMonochrome();
    	return mono?1:3;
    }
    
    
    //isMonochrome()
    public int getNumSensors() {
    	if (geometryCorrection == null) {
    		System.out.println("*** BUG! geometryCorrection is not set, number of sensors is unknown! Will use 4 sensors ****");
    		return 4;
    	}
    	return geometryCorrection.getNumSensors();
    }
    public QuadCLTCPU( // copies quat_corr
    		QuadCLTCPU  qParent,
    		String      name
    		){
    	// create from existing instance
    	this.properties =      new Properties(); // properties will be different
    	// is it needed at all?
    	if (qParent.properties != null) { // 05.30.2025
    		for (Enumeration<?> e = qParent.properties.propertyNames(); e.hasMoreElements();) {
    			String key = (String) e.nextElement();
    			this.properties.setProperty(key, qParent.properties.getProperty(key));
    		}
    		this.properties.putAll(qParent.properties);
    	}
    	this.eyesisCorrections=      qParent.eyesisCorrections;
    	this.correctionsParameters = qParent.correctionsParameters;
    	this.clt_kernels =           qParent.clt_kernels;
    	if (qParent.geometryCorrection != null) {
    		this.geometryCorrection = new ErsCorrection(qParent.geometryCorrection, true);
    	}
    	this.extrinsic_vect =        qParent.extrinsic_vect.clone();
    	this.extra_items =           qParent.extra_items;
    	this.eyesisKernelImage =     qParent.eyesisKernelImage; // most likely not needed

    	this.startTime =             qParent.startTime;     // start of batch processing
    	this.startSetTime =          qParent.startSetTime;  // start of set processing
    	this.startStepTime =         qParent.startStepTime; // start of step processing
    	this.fine_corr =             ErsCorrection.clone3d(qParent.fine_corr);

    	///tp will have only needed data, large array will be nulls, same with clt_3d_passes
    	if (qParent.tp != null) {
    		this.tp =                     new TileProcessor(qParent.tp);
    	}
    	this.image_name =             name; // qParent.image_name;
    	this.image_path =             qParent.image_path;
    	this.gps_lla =                ErsCorrection.clone1d(qParent.gps_lla);
    	if (qParent.image_data != null) this.image_data = qParent.image_data.clone(); // each camera will be re-written, not just modified, so shallow copy 
    	this.new_image_data =         qParent.new_image_data;
    	if (qParent.saturation_imp != null) this.saturation_imp = qParent.saturation_imp.clone(); // each camera will be re-written, not just modified, so shallow copy 
    	this.is_aux =                 qParent.is_aux;
    	//		this.is_mono =                 qParent.is_mono;

    	this.photometric_scene =      qParent.photometric_scene; 
    	this.lwir_offsets =           ErsCorrection.clone1d(qParent.lwir_offsets);
    	this.lwir_scales =            ErsCorrection.clone1d(qParent.lwir_scales);
    	this.lwir_scales2 =           ErsCorrection.clone1d(qParent.lwir_scales2);
    	this.lwir_offset =            qParent.lwir_offset;
    	this.lwir_cold_hot =          ErsCorrection.clone1d(qParent.lwir_cold_hot);
    	this.ds_from_main =           ErsCorrection.clone2d(qParent.ds_from_main);
    	this.tp =                     qParent.tp;
// 07.15.2025
    	this.quat_corr =       (qParent.quat_corr != null) ? qParent.quat_corr.clone():null;
    	this.enu_corr_metric = (qParent.enu_corr_metric != null) ? qParent.enu_corr_metric.clone():null;
    }

    public boolean hasGPU() {
    	return false;
    }

    public GpuQuad getGPUQuad() {
    	return null;
    }

    QuadCLT saveQuadClt() {
    	return null;
    }

    void restoreQuadClt(QuadCLT savedQuadClt) { // do nothing
    }

    void setQuadClt() { // do nothing
    }

    double [][][][][][]  getCltKernels() {                            
    	return clt_kernels;
    }
    @Deprecated
    public QuadCLT spawnQuadCLTWithNoise(
    		String               set_name,
    		CLTParameters        clt_parameters,
    		ColorProcParameters  colorProcParameters,
    		NoiseParameters		 noise_sigma_level,
    		QuadCLTCPU           ref_scene, // may be null if scale_fpn <= 0
    		int                  threadsMax,
    		int                  debugLevel)
    {
    	QuadCLT quadCLT = new QuadCLT(this, set_name);

    	quadCLT.restoreFromModel(
    			clt_parameters,
    			colorProcParameters,
    			noise_sigma_level, // double []            noise_sigma_level,
    			-1,  //  int                  noise_variant, // <0 - no-variants, compatible with old code
    			ref_scene,         // QuadCLTCPU     ref_scene, // may be null if scale_fpn <= 0
    			threadsMax,
    			debugLevel);

    	return quadCLT;
    }

    public QuadCLT spawnQuadCLTWithNoise(
    		String               set_name,
    		CLTParameters        clt_parameters,
    		ColorProcParameters  colorProcParameters,
    		NoiseParameters		 noise_sigma_level,
    		int                  noise_variant, // <0 - no-variants, compatible with old code
    		QuadCLTCPU           ref_scene, // may be null if scale_fpn <= 0
    		int                  threadsMax,
    		int                  debugLevel)
    {
    	QuadCLT quadCLT = new QuadCLT(this, set_name);

    	quadCLT.restoreFromModel(
    			clt_parameters,
    			colorProcParameters,
    			noise_sigma_level, // double []            noise_sigma_level,
    			noise_variant,     //int                  noise_variant, // <0 - no-variants, compatible with old code
    			ref_scene,         // QuadCLTCPU     ref_scene, // may be null if scale_fpn <= 0
    			threadsMax,
    			debugLevel);

    	return quadCLT;
    }

/*
    public QuadCLTCPU spawnQuadCLT( // never used
    		String              set_name,
    		CLTParameters       clt_parameters,
    		ColorProcParameters colorProcParameters, //
    		int                 threadsMax,
    		int                 debugLevel)
    {
    	QuadCLTCPU quadCLT;
    	if (this instanceof QuadCLT) {
    		quadCLT = new QuadCLT(this, set_name); //null
    	} else {
    		quadCLT = new QuadCLTCPU(this, set_name); //null
    	}

    	quadCLT.restoreFromModel(
    			clt_parameters,
    			colorProcParameters,
    			null,                 // double []    noise_sigma_level,
    			-1,                   // noise_variant, // <0 - no-variants, compatible with old code
    			null,                 // final QuadCLTCPU     ref_scene, // may be null if scale_fpn <= 0
    			threadsMax,
    			debugLevel);

    	return quadCLT;
    }
*/
    public static double getTimeStamp(String ts) {
    	String sts = ts.replace("_", ".");
    	//		Matcher m = Pattern.compile("\\d").matcher(sts);
    	Matcher m = Pattern.compile("\\d{5,10}\\.\\d{6}").matcher(sts);
    	//
    	if(m.find()) {
    		sts = sts.substring(m.start(), m.end());
    	}
    	return Double.parseDouble(sts);
    }
    public double getTimeStamp() {
    	return getTimeStamp(image_name);
    }
    public String getImageName() {
    	return image_name;
    }
    public void setImageName(String name) {
    	image_name = name;
    }

    public String getX3dTopDirectory() { // replace direct calculations
    	if (getImagePath() != null) {
    		Path x3dTopPath = Paths.get(getImagePath()).getParent().getParent();
    		return x3dTopPath.toString().replaceAll("[/\\\\]+$", ""); // remove potential trailing "/" or "\"
    	}

    	String x3d_path = correctionsParameters.selectX3dDirectory( // for x3d and obj
    			correctionsParameters.getModelName(image_name), // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
    			null,
    			true,  // smart,
    			true);  //newAllowed, // save
    	return x3d_path;
    }

    public String getX3dDirectory() { // replace direct calculations
    	return getX3dDirectory(false);
    }
    public String getX3dDirectory(boolean create) { // replace direct calculations
    	/* FIXME: A HACK!
    	 * Used common correctionsParameters.x3dModelVersion, could not read other versions.
    	 * Will try to use getImagePath() method, so far for center_CLT only not to break 
    	 * something in legacy code. Maybe will switch to it for all, requiring updating 
    	 * .image_path
    	 */
    	if (isCenterParent() && (getImagePath() != null)) {
    		return getImagePath();
    	}
    	// correctionsParameters.x3dDirectory may point to quadCLT_main's x3dDirectory, while the image_path is
    	// the path to the image in jp4 subdirectory. Try to restore x3d_path from it
    	if (getImagePath() != null) {
    		Path image_or_dir_path=Paths.get(getImagePath());
    		String name = image_or_dir_path.getName(image_or_dir_path.getNameCount()-1).toString();
    		Path x3dTopPath = Paths.get(getImagePath()).getParent(); // getParent();
    		if (name.contains(".")) { // version should not contain "." !
    			x3dTopPath = x3dTopPath.getParent();// if it was .tiff/.jp4, one extra getParent(); 
    		}
    		Path x3dPath = x3dTopPath.resolve(correctionsParameters.x3dModelVersion);
    		if (create && Files.notExists(x3dPath)) {
    			// create if does not exist
    			try {
    				Files.createDirectories(x3dPath);
    				System.out.println("getX3dDirectory(): created "+x3dPath.toString()); // to troubleshoot were it was missing
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				// e.printStackTrace();
    				System.out.println("getX3dDirectory(): Failed to create directory "+x3dPath);
    				return null;
    			}
    		}
    		return x3dPath.toString().replaceAll("[/\\\\]+$", ""); // remove potential trailing "/" or "\"
    	}
    	String x3d_path = correctionsParameters.selectX3dDirectory( // for x3d and obj
    			correctionsParameters.getModelName(image_name), // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
    			correctionsParameters.x3dModelVersion,
    			true,  // smart,
    			true);  //newAllowed, // save
    	return x3d_path; // may be non-existing ! 
    }

    // maybe will not be needed? TODO: Check
    public String getX3dDirectory(String name) { // replace direct calculations in CUAS
    	String x3d_path = correctionsParameters.selectX3dDirectory( // for x3d and obj
    			name,       // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
    			correctionsParameters.x3dModelVersion,
    			true,  // smart,
    			true);  //newAllowed, // save
    	return x3d_path;
    }
    
    // enable creation of the new directory
    public String setX3dDirectory(String name) { // replace direct calculations
    	String x3d_path = correctionsParameters.selectX3dDirectory( // for x3d and obj
    			name,       // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
    			correctionsParameters.x3dModelVersion,
    			true,  // smart,
    			true);  //newAllowed, // save
    	return x3d_path;
    }

    // do not allow new, if does not exist - returns config directory
    public String readX3dDirectory(String name) { // replace direct calculations  
    	String x3d_path = correctionsParameters.selectX3dDirectory( // for x3d and obj
    			name,       // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
    			correctionsParameters.x3dModelVersion,
    			false,  // smart,
    			false);  //newAllowed, // save
    	return x3d_path;
    }

    /**
     * Discriminate "blue sky" areas with no details at infinity. Such areas
     * have both low strength and low pixel value variations between channels
     * when calculated for 0 disparity. Assuming that there are no large enough
     * featureless areas in the scene itself (not always true, of course).
     * So first find tiles with a product of strength and disparity is less than
     * sky_seed (<sky_lim), shrink it by sky_shrink (to eliminate small non-infinity
     * false positive areas), then expand limited by  sky_lim (expecting a strong
     * enough skyline). The (optionally) expand by sky_expand_extra more.
     * Expansion by 1 is horizontal/vertical only, by 2 includes diagonals,
     * and so on. 
     *  
     * @param max_disparity maximal disparity for the sky area
     * @param max_disparity_strength maximal strength of the too high disparity to count
     * @param sky_seed minimal value of strength*spread to seed sky areas 
     * @param disparity_seed maximal disparity to seed sky areas (not needed for expand) 
     * @param sky_lim maximal value of strength*spread over which sky area will
     *                        be expanded
     * @param sky_shrink shrink initial sky area to eliminate small non-sky areas. 
     * @param sky_expand_extra additionally expand
     * @param int sky_bottleneck - shrink/reexpand from the seed detected sky to prevent "leaks"
     * @param width number of tiles in a row 
     * @param strength 1d array of tile strengths in scanline order
     * @param spread 1d array of tile spreads (second maximal difference from
     *                  of per-sensor pixel values in a tile to their average
     *                  in scanline order. 
     * @param disparity 1d array of tile disparity in scanline order
     * @param debugLevel debug level
     * @return boolean 1d array of the pixels belonging to the blue sky.
     */
    public static boolean [] getBlueSky  (
    		double max_disparity,
    		double max_disparity_strength,
    		double sky_seed, //  =       7.0;  // start with product of strength by diff_second below this
    		double disparity_seed, //          2.0;  // seed - disparity_lma limit
    		double seed_temp, //         0.5;  // seed colder that this point between min and max temp
    		double sky_lim, //   =      15.0; // then expand to product of strength by diff_second below this
    		double lim_temp, //          0.5;  // sky colder that this point between min and max temp
    		int    sky_shrink, //  =       4;
    		int    sky_expand_extra, //  = 100; // 1?
    		int    sky_bottleneck, //            
    		int    sky_reexpand_extra,  // 9; re-expand after bottleneck in addition to how it was shrank
    		double cold_scale, // =       0.2;  // <=1.0. 1.0 - disables temperature dependence
    		double cold_frac, // =        0.005; // this and lower will scale fom by  cold_scale
    		double hot_frac, // =         0.9;    // this and above will scale fom by 1.0
    		double min_strength, // =     0.08;
    		int    seed_rows, // =        5; // sky should appear in this top rows 
    		int    lowest_sky_row,        //  =     50;// appears that low - invalid, remove completely
    		double sky_temp_override,     // really cold average seed - ignore lowest_sky_row filter
    		int shrink_for_temp,          // shrink before finding hottest sky
    		double sky_highest_min,       //  =   -50; // 100; // lowest absolute value should not be higher (requires photometric)

    		boolean clouds_en,            // enable clouds in the sky detection / processing
    		double  clouds_fom,           // maximal FOM for clouds (must be <=)         
    		double  clouds_spread,        // maximal spread for clouds (must be <=)         
    		double  clouds_disparity,     // maximal disparity for strong clouds         
    		double  clouds_weak,          // maximal strength for near definite clouds         		
    		double  clouds_strength,      // minimal strength for far strong clouds (definitely cloud)         		
    		double  clouds_not_strength,  // maximal strength for near maybe clouds (if it has strong cloud neighbor)         		
    		boolean clouds_strong,        // true; // allow weak cloud if it has strong (1.5x) cloud neib

    		boolean  wall_en,             // enable smooth walls detection/processing
    		boolean  wall_dflt,           // default (undetected) is wall (false - sky)
    		double   wall_str,            // minimal strength of the far object (small - just non-NaN disparity)         
    		double   wall_far,            // maximal disparity to consider cluster to be sky         
    		double   wall_near,           // minimal disparity to consider cluster to be wall         

    		boolean  treeline_en,         // enable treeline processing
    		boolean  treeline_wide,       // look not only under, but diagonal too.

    		int      treeline_height,    // maximal height of the treeline (tiles)
    		int      treeline_width,     // minimal horizontal width of the treeline (tiles)
    		boolean  treeline_lim_high,  // limit too high treeline (false - delete completely)		    
    		double   treeline_str,       // treeline minimal strength 
    		double   treeline_far,       // treeline min disparity (pix)
    		double   treeline_near,      // treeline max disparity (pix)
    		double   treeline_fg_str,    // pre-treeline FG objects (such as flat ground) minimal strength 
    		double   treeline_fg_far,    // pre-treeline FG objects  min disparity (pix)
    		double   treeline_fg_near,   // pre-treeline FG objects  max disparity (pix)

    		boolean indoors_en,           // true; // allow weak cloud if it has strong (1.5x) cloud neib
    		double  indoors_str,          //  0.5; // minimal strength of the far object         
    		double  indoors_disp,         //  0.8; // maximal minimal outdoor strong disparity         
    		int     indoors_min_out,      //  10;   // minimal strong far tiles to deny indoors

    		double disp_boost_min,        //  = 0.5;
    		double disp_boost_diff,       //  = 0.35;
    		int    disp_boost_neibs,      //  = 2;
    		double disp_boost_amount,     //  = 2.0;

    		int    width,
    		double [] strength,
    		double [] spread,
    		double [] disparity,
    		double [] avg_val,
    		QuadCLT   scene,    // use to save debug images if not null
    		int debugLevel) { // >0 to show
    	boolean clouds_fill_isolated = true;
    	int max_proh_neibs = 0;
    	if ((strength == null) || (spread==null)) {
    		return null;
    	}
    	int height = strength.length/width;

    	int dbg_tile = -1150;
    	//		int sky_bottleneck = 5; // shrink full selection, then re-expand from seed to disable
    	// small (narrow) leaks (or just disable leaks near margins)?
    	//		int shrink_for_temp = 10;
    	//		double sky_temp_override = -300;      // really cold average seed - ignore lowest_sky_row filter
    	double [] temp_scales = null;
    	boolean failure = false;
    	double temp_cold = Double.NaN;
    	double temp_hot =  Double.NaN;
    	if (avg_val != null) {
    		int num_bins = 1000;
    		double min_temp = Double.NaN, max_temp=Double.NaN, avg_temp = 0;
    		int num_def = 0;
    		for (int i = 0; i < avg_val.length; i++) {
    			double d = avg_val[i];
    			if (!Double.isNaN(d)) {
    				if (!(d <= max_temp)) max_temp = d;
    				if (!(d >= min_temp)) min_temp = d;
    				avg_temp += d;
    				num_def++;
    			}
    		}
    		if (num_def > 0) {
    			avg_temp/= num_def;
    			// build a histogram from min to max
    			double [] hist = new double [num_bins];
    			double kbin = num_bins/(max_temp - min_temp);
    			int maxbin = num_bins - 1;
    			double s = 1.0/num_def;
    			for (int i = 0; i < avg_val.length; i++) {
    				double d = avg_val[i];
    				if (!Double.isNaN(d)) {
    					int ibin = (int) Math.floor((d - min_temp) * kbin);
    					if (ibin <0) ibin = 0;
    					else if (ibin > maxbin) {
    						ibin = maxbin;
    					}
    					hist[ibin] += s;
    				}
    			}
    			for (int i = 1; i < num_bins; i++) { // make cumulative, last is 1.0;
    				hist[i] += hist[i-1];
    			}
    			temp_cold = min_temp;
    			temp_hot =  max_temp;
    			for (int i = 0; i < num_bins; i++) {
    				if (hist[i] > cold_frac) {
    					double d = hist[i];
    					double d_prev = (i > 0)? hist[ i-1 ]: 0.0;
    					double b_cold = (cold_frac-d_prev)/(d-d_prev) + i;
    					temp_cold = min_temp + b_cold* (max_temp - min_temp) / num_bins;
    					break;
    				}
    			}
    			for (int i = num_bins - 2; i >= 0; i--) {
    				if (hist[i] <= hot_frac) {
    					double d =      hist[i];
    					double d_next = hist[i+1];
    					double b_hot = (hot_frac - d)/(d_next - d) + i;
    					temp_hot = min_temp + b_hot* (max_temp - min_temp) / num_bins;
    					break;
    				}
    			}
    			if (temp_cold > sky_highest_min) {
    				System.out.println("getBlueSky(): Coldest offset value "+temp_cold+" > "+sky_highest_min+" - invalid sky" );
    				failure = true;
    			}
    			temp_scales = new double[avg_val.length];
    			Arrays.fill(temp_scales, Double.NaN);
    			double kscale = (1.0 - cold_scale)/(temp_hot - temp_cold);
    			for (int i = 0; i < avg_val.length; i++) {
    				double d = avg_val[i];
    				if (!Double.isNaN(d)) {
    					if (d < temp_cold) {
    						temp_scales[i] = cold_scale;
    					} else if (d > temp_hot) {
    						temp_scales[i] = 1.0;
    					} else {
    						temp_scales[i] = cold_scale + (d - temp_cold) * kscale;
    					}
    				}
    			}
    		}
    	}


    	String [] dbg_in_titles =    {"fom", "strength", "spread", "disparity", "avg_val", "tscale"};
    	String [] dbg_titles = {"sky", "pre-fail", "treeline", "treeline_filt","wall", "clouds0", "clouds1", "clouds2", "seed", "max", "expanded", "shrank","temp_shrank","neck_shrank","reexpand"};

    	double [][] dbg_img = ((debugLevel>0) || (scene != null))? new double [dbg_titles.length][strength.length]:null;
    	TileNeibs tn = new TileNeibs(width,height);

    	boolean [] sky_tiles =      new  boolean [strength.length];
    	boolean [] prohibit_tiles = new  boolean [strength.length];
    	double [] fom = new double[strength.length];
    	for (int i = 0; i < sky_tiles.length; i++) {
    		fom[i] = Math.max(strength[i], min_strength) * spread[i];
    		if (temp_scales != null) {
    			fom[i] *=temp_scales[i];
    		}
    	}		
    	if (disp_boost_amount > 0) {
    		for (int nTile = 0; nTile < sky_tiles.length; nTile++) if (disparity[nTile] >= disp_boost_min){
    			int num_neibs = 0;
    			for (int dir = 0; dir < 8; dir++) {
    				int nTile1 = tn.getNeibIndex(nTile, dir);
    				if ((nTile1 >= 0) && (Math.abs(disparity[nTile] - disparity[nTile1]) <=disp_boost_diff)) {
    					num_neibs++;
    				}
    			}
    			if (num_neibs >= disp_boost_neibs ) {
    				fom[nTile] *= disp_boost_amount * num_neibs / disp_boost_neibs;
    			}
    		}			
    	}



    	if (debugLevel>0) {
    		ShowDoubleFloatArrays.showArrays(
    				new double[][] {fom, strength, spread, disparity, avg_val, temp_scales},
    				width,
    				fom.length/width,
    				true,
    				"sky_input",
    				dbg_in_titles); //	dsrbg_titles);

    	}
    	if (scene != null) {
    		scene.saveDoubleArrayInModelDirectory(
    				scene.getImageName() + "-sky_input",
    				dbg_in_titles,          // String []   labels, // or null
    				new double[][] {fom, strength, spread, disparity, avg_val, temp_scales}, // double [][] data,
    				width,                  // int         width,
    				fom.length/width);      // int         height)
    	}

    	double max_temp_seed = temp_cold * (1.0 - seed_temp) + temp_hot * seed_temp;
    	double max_temp_lim =  temp_cold * (1.0 - lim_temp) +  temp_hot * lim_temp;
    	boolean [][] clouds = clouds_en ? new boolean [3][strength.length]:null; // {clouds, clouds_strong, clouds_neib}

    	boolean [] treeline = new boolean[strength.length]; // define always, will all be false if not used
    	// Process treeline (and possibly far mountain ridge) that limits clouds/sky
    	int [] down_diag = {TileNeibs.DIR_SE,TileNeibs.DIR_SW};

    	if (treeline_en) {
    		// for now single-threaded
    		for (int tile = treeline.length-width-1; tile >=0; tile--) {
    			// see if the tile itself is OK
    			if ((strength[tile] >= treeline_str) &&
    					(disparity[tile] >= treeline_far) &&
    					(disparity[tile] <= treeline_near)) {
    				int tile_s = tn.getNeibIndex(tile, TileNeibs.DIR_S);
    				if (tile_s < 0) {
    					continue;
    				}
    				if (treeline[tile_s] || (
    						((strength[tile_s] >= treeline_fg_str) &&
    								(disparity[tile_s] >= treeline_fg_far) &&
    								(disparity[tile_s] <= treeline_fg_near)))) {
    					treeline[tile] = true;
    					continue;
    				}
    				if (treeline_wide) { // try diagonals
    					for (int dir:down_diag) {
    						int tile_d = tn.getNeibIndex(tile, dir);
    						if (tile_d < 0) {
    							continue;
    						}
    						if (treeline[tile_d] || (
    								((strength[tile_d] >= treeline_fg_str) &&
    										(disparity[tile_d] >= treeline_fg_far) &&
    										(disparity[tile_d] <= treeline_fg_near)))) {
    							treeline[tile] = true;
    							continue;
    						}
    					}
    				}
    			}
    		}
    		// filter from too high, too wide
    		if (dbg_img != null) {
    			for (int i = 0; i < sky_tiles.length; i++) {
    				dbg_img[2][i] = treeline[i]? 1 : 0;
    			}			
    		}
    		if (treeline_height > 0) { // filter by treeline maximal height
    			for (int x = 0; x < width; x++) {
    				for (int y0 = height-1; y0 >= 0;) { // iterate over bottoms of treelines (normally just 1)
    					for (; y0 >= 0; y0--) {
    						if (treeline[y0 * width + x]) {
    							break;
    						}
    					}
    					if (y0 >= 0) {
    						int y1 = y0 - 1;
    						for (; y1 >= 0; y1--) {
    							if (!treeline[y1 * width + x]) {
    								break;
    							}
    						}
    						if ((y0 - y1) > treeline_height) {
    							int yl = y0 + (treeline_lim_high ? treeline_height : 0);
    							for (int y = yl; y > y1; y--) {
    								treeline[y * width + x] = false;
    							}
    						}
    						y0 = y1;
    					}
    				}
    			}
    		}
    		if (treeline_width > 0) { // filter by treeline minimal width
    			for (int y=0; y < (height - 1); y++) {
    				for (int x0 = 0; x0 < width; ) {
    					for (; x0 < width; x0++) {
    						if (treeline[y * width + x0]) {
    							break;
    						}
    					}
    					if (x0 < width) {
    						int x1 = x0 + 1;
    						for (; x1 < width; x1++) {
    							if (!treeline[y * width + x1]) {
    								break;
    							}
    						}
    						if ((x1 - x0) < treeline_width) {
    							for (int x = x0; x < x1; x++) {
    								treeline[y * width + x] = false;
    							}
    						}
    						x0 = x1;
    					}
    				}					
    			}
    		}			
    		if (dbg_img != null) {
    			for (int i = 0; i < sky_tiles.length; i++) {
    				dbg_img[3][i] = treeline[i]? 1 : 0;
    			}			
    		}
    	}


    	for (int i = 0; i < sky_tiles.length; i++) {
    		if (i == dbg_tile) {
    			System.out.println("i="+i);
    		}
    		prohibit_tiles[i] = (fom[i] >= sky_lim) ||
    				((strength[i] >= max_disparity_strength) && (disparity[i] >= max_disparity));
    		if (treeline[i]) {
    			prohibit_tiles[i] = true;
    		} else if (clouds_en) { //  && prohibit_tiles[i]) {
    			if ((fom[i] <= clouds_fom) && (spread[i] <= clouds_spread)) {
    				if ((strength[i] > clouds_not_strength) && (disparity[i] > clouds_disparity)) {
    					// definitely not cloud
    				} else {
    					if (strength[i] <= clouds_strength) {
    						clouds[0][i] =      true;
    						prohibit_tiles[i] = false;
    					} else {
    						if (disparity[i] <= clouds_disparity) {
    							clouds[0][i] =      true;
    							prohibit_tiles[i] = false;
    							if (strength[i] > clouds_strength) {
    								clouds[1][i] =  true; // strong cloud, helps weak non-clouds
    							}								
    						} else {
    							clouds[2][i] = true; // maybe, if has strong neighbors
    							if (clouds_strong) {
    								prohibit_tiles[i] = false; // will restore if failed
    							}
    						}
    					}
    				}
    				/*
					if (strength[i] > clouds_strength_strong) {
						if (disparity[i] <= clouds_disparity) {
							clouds[0][i] = true;
							clouds[1][i] = true;
							prohibit_tiles[i] = false;
						}
					} else if ((strength[i] <= clouds_strength) || (disparity[i] <= clouds_disparity)) {
						clouds[0][i] =      true;
						prohibit_tiles[i] = false;
					} else {
						clouds[2][i] = true; // maybe, if has strong neighbors
						prohibit_tiles[i] = false; // will restore if failed
					}
    				 */
    			}
    		}
    		prohibit_tiles[i] |= (avg_val[i] >= max_temp_lim);
    	}

    	boolean indoors = false;
    	if (indoors_en) {
    		int num_outdoor = 0;
    		for (int i = 0; i < sky_tiles.length; i++) {
    			if ((strength[i] >= indoors_str) && (disparity[i] <= indoors_disp))	{
    				num_outdoor++;
    			}
    		}
    		indoors = num_outdoor < indoors_min_out; // too few definitely outdoor (far strong) tiles
    		if (debugLevel > -3) {
    			System.out.println("Number of far strong tiles: "+num_outdoor+ " (threshold="+indoors_min_out+")");
    		}
    		if (debugLevel > -3) {
    			System.out.println("Indoors mode =  "+indoors);
    		}
    		failure = indoors; // do not use sky
    	}

    	if (clouds_en &&  clouds_strong) {
    		// grow strong clouds
    		tn.growSelection(
    				2 ,        // int        shrink,           // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
    				clouds[1],       // boolean [] tiles,
    				null); // (null - with diagonals )prohibit_tiles); // boolean [] prohibit)
    		for (int i = 0; i < sky_tiles.length; i++) {
    			if (i == dbg_tile) {
    				System.out.println("i="+i);
    			}
    			if (clouds[2][i]) { // "maybe" prohibit was turned off
    				clouds [0][i] |= clouds[1][i];
    				prohibit_tiles[i] |= !clouds[1][i];
    			}
    			clouds [0][i] &= !prohibit_tiles[i]; // from (avg_val[i] >= max_temp_lim)
    			//clouds_fill_isolated
    		}
    		if (clouds_fill_isolated) { // fill isolated gaps in clouds
    			for (int i = 0; i < sky_tiles.length; i++) {
    				if (clouds[2][i] && prohibit_tiles[i]) {
    					int num_proh_neibs = 0;
    					for (int dir = 0; dir <8; dir++) {
    						int tile1 = tn.getNeibIndex(i, dir);
    						if ((tile1 >= 0) && prohibit_tiles[tile1]) {
    							num_proh_neibs++;
    						}
    					}
    					clouds [0][i] |= (num_proh_neibs <= max_proh_neibs);
    				}
    			}
    			for (int i = 0; i < sky_tiles.length; i++) {
    				prohibit_tiles[i] &= !clouds [0][i]; 
    			}				
    		}
    	}
    	// prohibit border tiles if they do not have non-border orthogonal neighbors
    	for (int i = 0; i < width; i++) {
    		prohibit_tiles[i] |= prohibit_tiles[i + width];
    		prohibit_tiles[i+(height -1) * width] |= prohibit_tiles[i + (height - 2) * width];
    	}
    	for (int i = 0; i < height; i++) {
    		prohibit_tiles[i * width] |=              prohibit_tiles[i * width + 1];
    		prohibit_tiles[i * width +(width - 1)] |= prohibit_tiles[i * width +(width - 2)];
    	}



    	if ((dbg_img != null) && (clouds != null)) {
    		for (int i = 0; i < sky_tiles.length; i++) {
    			dbg_img[5][i] = clouds[0][i]? 1 : 0;
    			dbg_img[6][i] = clouds[1][i]? 1 : 0;
    			dbg_img[7][i] = clouds[2][i]? 1 : 0;
    		}			
    	}

    	for (int i = 0; i < sky_tiles.length; i++) {
    		sky_tiles[i] = (fom[i] < sky_seed) &&
    				!(disparity[i] > disparity_seed) &&
    				!prohibit_tiles[i] &&
    				(avg_val[i] < max_temp_seed);
    	}




    	//seed_rows
    	if (dbg_img != null) {
    		for (int i = 0; i < sky_tiles.length; i++) {
    			dbg_img[8][i] = sky_tiles[i]? 1 : 0;
    			dbg_img[9][i] = prohibit_tiles[i]? 0 : 1;
    		}			
    	}
    	tn.shrinkSelection(
    			sky_shrink,    // int        shrink,
    			sky_tiles,     // boolean [] tiles,
    			null);         // boolean [] prohibit)
    	if (seed_rows > 0) {
    		Arrays.fill(sky_tiles, seed_rows * width, sky_tiles.length, false);
    	}
    	boolean [] seed_sky = sky_tiles.clone();
    	if (dbg_img != null) {
    		for (int i = 0; i < sky_tiles.length; i++) {
    			dbg_img[11][i] = sky_tiles[i]? 1 : 0;
    		}			
    	}

    	// Calculate average seed "temperature"

    	tn.growSelection(
    			4*width ,        // int        shrink,           // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
    			sky_tiles,       // boolean [] tiles,
    			prohibit_tiles); // boolean [] prohibit)
    	if (sky_expand_extra > 0) {
    		tn.growSelection(
    				sky_expand_extra , // int        shrink,           // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
    				sky_tiles,         // boolean [] tiles,
    				null);             // boolean [] prohibit)
    	}
    	if (dbg_img != null) {
    		for (int i = 0; i < sky_tiles.length; i++) {
    			dbg_img[10][i] = sky_tiles[i]? 1 : 0;
    		}			
    	}

    	//shrink_neck
    	// Remove leaks through small holes
    	if (sky_bottleneck > 0) {
    		tn.shrinkSelection(
    				sky_bottleneck, // int        shrink,           // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
    				sky_tiles,   // boolean [] tiles,
    				null);       // boolean [] prohibit)
    		boolean [] prohibit_neck = new boolean[sky_tiles.length];
    		for (int i = 0; i < prohibit_neck.length; i++) {
    			prohibit_neck[i] = !seed_sky[i] && !sky_tiles[i];
    		}
    		sky_tiles = seed_sky.clone();
    		tn.growSelection(
    				4*width ,       // int        shrink,
    				sky_tiles,      // boolean [] tiles,
    				prohibit_neck); // boolean [] prohibit)
    		tn.growSelection(
    				sky_bottleneck + sky_reexpand_extra , // int        shrink,
    				sky_tiles,       // boolean [] tiles,
    				prohibit_tiles);             // boolean [] prohibit)
    		if (dbg_img != null) {
    			for (int i = 0; i < sky_tiles.length; i++) {
    				dbg_img[13][i] = prohibit_neck[i]? 0 : 1;
    				dbg_img[14][i] = sky_tiles[i]?     1 : 0;
    			}
    		}

    		//prohibit_tiles
    	}

    	double sky_max_temp = Double.NaN;
    	if (avg_val != null) {
    		boolean [] shrank_sky = sky_tiles.clone();
    		tn.shrinkSelection(
    				shrink_for_temp,   // int        shrink,           // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
    				shrank_sky,        // boolean [] tiles,
    				null);             // boolean [] prohibit)

    		for (int i = 0; i < sky_tiles.length; i++) if (shrank_sky[i]){
    			if (!(sky_max_temp >= avg_val[i])) {
    				sky_max_temp = avg_val[i];
    			}
    		}
    		if (dbg_img != null) {
    			for (int i = 0; i < sky_tiles.length; i++) {
    				dbg_img[12][i] = shrank_sky[i]? 1 : 0;
    			}
    		}			
    	}



    	for (int i = lowest_sky_row*width; i < sky_tiles.length; i++) {
    		if (sky_tiles[i]) {
    			System.out.println("getBlueSky(): sky area appeared too low - at row "+(i/width)+" >= "+lowest_sky_row+" removing blue sky");
    			if (sky_max_temp < sky_temp_override) {
    				System.out.println("But detected sky is cold enough ("+sky_max_temp+" < "+sky_temp_override+
    						"), so this test is bypassed");
    			} else { 
    				failure = true;
    			}
    			break;
    		}
    	}

    	if (!failure && wall_en) {
    		int [] num_clusters = new int [1];
    		int [] clusters = tn.enumerateClusters(
    				sky_tiles,    // boolean [] tiles,
    				num_clusters, // int []     num_clusters,
    				false);       // boolean ordered)
    		if (num_clusters[0] > 1) { // do not filter single-clusters
    			boolean [][] has_near_far = new boolean [num_clusters[0]][2];
    			for (int i = 0; i < sky_tiles.length; i++) if (sky_tiles[i]) {
    				if (!Double.isNaN(disparity[i]) && (strength[i] >= wall_str)) {
    					int clust = clusters[i] - 1;
    					has_near_far[clust][0] |= disparity[i] <= wall_far; 
    					has_near_far[clust][1] |= disparity[i] >  wall_near; 
    				}
    			}
    			boolean [] is_wall = new boolean [has_near_far.length];
    			for (int clust = 0; clust < has_near_far.length; clust++) {
    				is_wall[clust] = wall_dflt;
    				if (has_near_far[clust][0] && !has_near_far[clust][1]) {
    					is_wall[clust] = false;
    				} else if (has_near_far[clust][1] && !has_near_far[clust][0]) {
    					is_wall[clust] = true;
    				} 
    			}
    			for (int i = 0; i < sky_tiles.length; i++) if (sky_tiles[i]) {
    				int clust = clusters[i] - 1;
    				if (is_wall[clust]) {
    					sky_tiles[i] = false;
    					if (dbg_img != null) {
    						dbg_img[4][i] = 1.0;
    					}						
    				}
    			}				
    		}
    	}

    	if (dbg_img != null) {
    		for (int i = 0; i < sky_tiles.length; i++) {
    			dbg_img[0][i] = (!failure && sky_tiles[i])? 1 : 0;
    			dbg_img[1][i] = sky_tiles[i]? 1 : 0;
    		}
    		if (debugLevel>0) {
    			ShowDoubleFloatArrays.showArrays(
    					dbg_img,
    					width,
    					sky_tiles.length/width,
    					true,
    					"sky_selection",
    					dbg_titles); //	dsrbg_titles);
    		}
    		if (scene != null) {
    			scene.saveDoubleArrayInModelDirectory(
    					scene.getImageName() + "-sky_selection",
    					dbg_titles,          // String []   labels, // or null
    					dbg_img, // double [][] data,
    					width,                  // int         width,
    					fom.length/width);      // int         height)
    		}
    	}

    	if (failure) { // Will still show in debug images !!!
    		Arrays.fill(sky_tiles, false);
    	}


    	return sky_tiles;
    }

    public double [] getDoubleBlueSky() {
    	if (this.dsi == null) {
    		return null;
    	}
    	return this.dsi[is_aux?TwoQuadCLT.DSI_BLUE_SKY_AUX:TwoQuadCLT.DSI_BLUE_SKY_MAIN];
    }
    public boolean[] getBooleanBlueSky() {
    	double [] double_blue_sky = getDoubleBlueSky();
    	return getBooleanBlueSky(double_blue_sky);
    }

    public static boolean[] getBooleanBlueSky(double [] double_blue_sky) {
    	if (double_blue_sky == null) {
    		return null;
    	}
    	boolean [] blue_sky = new boolean[double_blue_sky.length];
    	for (int i = 0; i < blue_sky.length; i++) {
    		blue_sky[i] = double_blue_sky[i] > 0;
    	}
    	return blue_sky;
    }
    
    
    
    public boolean hasBlueSky() {
    	return (getDoubleBlueSky() != null);

    }


    //	@Deprecated
    public void setBlueSky (boolean [] blue_sky) {
    	double [] double_blue_sky = new double [blue_sky.length];
    	for (int i = 0; i < blue_sky.length; i++) {
    		double_blue_sky[i] = blue_sky[i]? 1.0 : 0.0;
    	}
    	setBlueSky (double_blue_sky);
    }


    public void setBlueSky (double [] double_blue_sky) {
    	if (dsi == null) {	
    		dsi = new double [TwoQuadCLT.DSI_SLICES.length][]; // will not have DSI_SPREAD_AUX
    	}
    	int bs_index = is_aux?TwoQuadCLT.DSI_BLUE_SKY_AUX:TwoQuadCLT.DSI_BLUE_SKY_MAIN;
    	dsi[bs_index] = double_blue_sky;
    }


    public void setBlueSky (
    		double max_disparity,
    		double max_disparity_strength,
    		double sky_seed, //  =       7.0;  // start with product of strength by diff_second below this
    		double lma_seed, //          2.0;  // seed - disparity_lma limit
    		double seed_temp, //         0.5;  // seed colder that this point between min and max temp
    		double sky_lim, //   =      15.0;  // then expand to product of strength by diff_second below this
    		double lim_temp, //          0.5;  // sky colder that this point between min and max temp
    		int    sky_shrink, //  =       4;
    		int    sky_expand_extra, //  = 100; // 1?
    		int    sky_bottleneck,
    		int    sky_reexpand_extra,  // 9; re-expand after bottleneck in addition to how it was shrank
    		double cold_scale, // =       0.2;  // <=1.0. 1.0 - disables temperature dependence
    		double cold_frac, // =        0.005; // this and lower will scale fom by  cold_scale
    		double hot_frac, // =         0.9;    // this and above will scale fom by 1.0
    		double min_strength, // =     0.08;
    		int    seed_rows, // =        5; // sky should appear in this top rows
    		int    lowest_sky_row,        //  =     50;// appears that low - invalid, remove completely
    		double sky_temp_override,     // really cold average seed - ignore lowest_sky_row filter
    		int    shrink_for_temp,       // shrink before finding hottest sky
    		double sky_highest_max,       //  =   100; // lowest absolute value should not be higher (requires photometric)

    		boolean clouds_en,            // enable clouds in the sky detection / processing
    		double  clouds_fom,           // maximal FOM for clouds (must be <=)         
    		double  clouds_spread,        // maximal spread for clouds (must be <=)         
    		double  clouds_disparity,     // maximal disparity for strong clouds         
    		double  clouds_weak,          // maximal strength for near definite clouds         		
    		double  clouds_strength,      // minimal strength for far strong clouds (definitely cloud)         		
    		double  clouds_not_strength,  // maximal strength for near maybe clouds (if it has strong cloud neighbor)         		
    		boolean clouds_strong,        // true; // allow weak cloud if it has strong (1.5x) cloud neib

    		boolean  wall_en,             // enable smooth walls detection/processing
    		boolean  wall_dflt,           // default (undetected) is wall (false - sky)
    		double   wall_str,            // minimal strength of the far object (small - just non-NaN disparity)         
    		double   wall_far,            // maximal disparity to consider cluster to be sky         
    		double   wall_near,           // minimal disparity to consider cluster to be wall         

    		boolean  treeline_en,         // enable treeline processing
    		boolean  treeline_wide,       // look not only under, but diagonal too.
    		int      treeline_height,     // maximal height of the treeline (tiles)
    		int      treeline_width,      // minimal horizontal width of the treeline (tiles)
    		boolean  treeline_lim_high,   // limit too high treeline (false - delete completely)		    
    		double   treeline_str,        // treeline minimal strength 
    		double   treeline_far,        // treeline min disparity (pix)
    		double   treeline_near,       // treeline max disparity (pix)
    		double   treeline_fg_str,     // pre-treeline FG objects (such as flat ground) minimal strength 
    		double   treeline_fg_far,     // pre-treeline FG objects  min disparity (pix)
    		double   treeline_fg_near,    // pre-treeline FG objects  max disparity (pix)

    		boolean indoors_en,      // true; // allow weak cloud if it has strong (1.5x) cloud neib
    		double  indoors_str,     //  0.5; // minimal strength of the far object         
    		double  indoors_disp,    //  0.8; // maximal minimal outdoor strong disparity         
    		int     indoors_min_out, //  10;   // minimal strong far tiles to deny indoors

    		double disp_boost_min,    //  = 0.5;
    		double disp_boost_diff,   //  = 0.35;
    		int    disp_boost_neibs,  //  = 2;
    		double disp_boost_amount, //  = 2.0;
    		double [] strength,
    		double [] spread,
    		double [] disp_lma,
    		double [] avg_val,
    		QuadCLT   dbg_scene,    // use to save debug images if not null
    		int debugLevel) {
    	int width = tp.getTilesX();
    	//		this.blue_sky = getBlueSky  (
    	setBlueSky(getBlueSky  (
    			max_disparity,
    			max_disparity_strength,
    			sky_seed,   //  =       7.0;  // start with product of strength by diff_second below this
    			lma_seed,   //          2.0;  // seed - disparity_lma limit
    			seed_temp,  //double seed_temp, //         0.5;  // seed colder that this point between min and max temp
    			sky_lim,    //   =      15.0; // then expand to product of strength by diff_second below this
    			lim_temp,   //double lim_temp, //          0.5;  // sky colder that this point between min and max temp
    			sky_shrink, //  =       4;
    			sky_expand_extra, //  = 100; // 1?
    			sky_bottleneck,        // 
    			sky_reexpand_extra,    // int    sky_reexpand_extra,  // 9; re-expand after bottleneck in addition to how it was shrank				
    			cold_scale,            // 0.2;  // <=1.0. 1.0 - disables temperature dependence
    			cold_frac,             // 0.005; // this and lower will scale fom by  cold_scale
    			hot_frac,              // 0.9;    // this and above will scale fom by 1.0
    			min_strength,          // 0.08;
    			seed_rows,             // 5; // sky should appear in this top rows 
    			lowest_sky_row,        //50;// appears that low - invalid, remove completely
    			sky_temp_override,     // double sky_temp_override,     // really cold average seed - ignore lowest_sky_row filter
    			shrink_for_temp,       // int shrink_for_temp,          // shrink before finding hottest sky
    			sky_highest_max,       // 100; // lowest absolute value should not be higher (requires photometric)

    			clouds_en,             // enable clouds in the sky detection / processing
    			clouds_fom,            // maximal FOM for clouds (must be <=)         
    			clouds_spread,         // maximal spread for clouds (must be <=)         
    			clouds_disparity,      // maximal disparity for strong clouds         
    			clouds_weak,           // maximal strength for near definite clouds         		
    			clouds_strength,       // minimal strength for far strong clouds (definitely cloud)         		
    			clouds_not_strength,   // maximal strength for near maybe clouds (if it has strong cloud neighbor)         		
    			clouds_strong,         // true; // allow weak cloud if it has strong (1.5x) cloud neib

    			wall_en,               // enable smooth walls detection/processing
    			wall_dflt,             // default (undetected) is wall (false - sky)
    			wall_str,              // minimal strength of the far object (small - just non-NaN disparity)         
    			wall_far,              // maximal disparity to consider cluster to be sky         
    			wall_near,             // minimal disparity to consider cluster to be wall         

    			treeline_en,           // enable treeline processing
    			treeline_wide,         // look not only under, but diagonal too.
    			treeline_height,       // maximal height of the treeline (tiles)
    			treeline_width,        // minimal horizontal width of the treeline (tiles)
    			treeline_lim_high,     // limit too high treeline (false - delete completely)
    			treeline_str,          // treeline minimal strength 
    			treeline_far,          // treeline min disparity (pix)
    			treeline_near,         // treeline max disparity (pix)
    			treeline_fg_str,       // pre-treeline FG objects (such as flat ground) minimal strength 
    			treeline_fg_far,       // pre-treeline FG objects  min disparity (pix)
    			treeline_fg_near,      // pre-treeline FG objects  max disparity (pix)

    			indoors_en,            // true; // allow weak cloud if it has strong (1.5x) cloud neib
    			indoors_str,           //  0.5; // minimal strength of the far object         
    			indoors_disp,          //  0.8; // maximal minimal outdoor strong disparity         
    			indoors_min_out,       //  10;   // minimal strong far tiles to deny indoors

    			disp_boost_min,    //  = 0.5;
    			disp_boost_diff,   //  = 0.35;
    			disp_boost_neibs,  //  = 2;
    			disp_boost_amount, //  = 2.0;
    			width,
    			strength,
    			spread,
    			disp_lma,
    			avg_val,
    			dbg_scene,    // use to save debug images if not null
    			debugLevel));
    }
    public double [][] getDSI(){
    	return this.dsi;
    }
    
    public void setDSI(	double [][] dsi) {
    	this.dsi = dsi; // make sure available blue sky is not erased
    }
    
    /**
     * Set SFM gain layer in this.dsi from the squared SFM gains values
     * @param sfm_gain array to set or null
     */
    public void setSFMGain2(
    		final double [] sfm_gain2) {
    	if (sfm_gain2 == null) {
    		setSFMGain(null);
    	} else {
    		double [] sfm_gain = new double [sfm_gain2.length];
    		for (int i = 0; i < sfm_gain2.length; i++) {
    			sfm_gain[i] = Math.sqrt(sfm_gain2[i]);
    		}
    		setSFMGain(sfm_gain);
    	}
    }

    /**
     * Set SFM gain layer in this.dsi. SFM gain is the derivative of the pixel offset with respect to disparity.
     * @param sfm_gain array to set or null
     */
    public void setSFMGain(
    		double [] sfm_gain) {
    	this.dsi[is_aux?TwoQuadCLT.DSI_SFM_GAIN_AUX:TwoQuadCLT.DSI_SFM_GAIN_MAIN] = sfm_gain;
    }

    public void setDisparityDSI(
    		double [] disparity) {
    	this.dsi[is_aux?TwoQuadCLT.DSI_DISPARITY_AUX:TwoQuadCLT.DSI_DISPARITY_MAIN] = disparity;
    }

    public void setDisparityLmaDSI(
    		double [] disparity_lma) {
    	this.dsi[is_aux?TwoQuadCLT.DSI_DISPARITY_AUX_LMA:TwoQuadCLT.DSI_DISPARITY_MAIN_LMA] = disparity_lma;
    }

    public void setStrengthDSI(
    		double [] strength) {
    	this.dsi[is_aux?TwoQuadCLT.DSI_STRENGTH_AUX:TwoQuadCLT.DSI_STRENGTH_MAIN] = strength;
    }
    
    

    /**
     * Sets dsi from combo_dsi. Does not reset blue sky if it does not exist
     * @param combo_dsi
     */
    public void setDSIFromCombo(
    		double [][] combo_dsi) {
    	this.dsi_from_combo = true;
    	this.dsi = new double [TwoQuadCLT.DSI_SLICES.length][]; // will not have DSI_SPREAD_AUX
    	double [] combo_disp = combo_dsi[OpticalFlow.COMBO_DSN_INDX_DISP_FG];
    	if (combo_disp == null) {
    		combo_disp = combo_dsi[OpticalFlow.COMBO_DSN_INDX_DISP];
    	}
    	this.dsi[is_aux?TwoQuadCLT.DSI_DISPARITY_AUX:TwoQuadCLT.DSI_DISPARITY_MAIN] = combo_disp;
    	
    	this.dsi[is_aux?TwoQuadCLT.DSI_STRENGTH_AUX:TwoQuadCLT.DSI_STRENGTH_MAIN] =
    			combo_dsi[OpticalFlow.COMBO_DSN_INDX_STRENGTH];
    	this.dsi[is_aux?TwoQuadCLT.DSI_DISPARITY_AUX_LMA:TwoQuadCLT.DSI_DISPARITY_MAIN_LMA] =
    			combo_dsi[OpticalFlow.COMBO_DSN_INDX_LMA];
    	if ((combo_dsi.length > OpticalFlow.COMBO_DSN_INDX_BLUE_SKY) && (combo_dsi[OpticalFlow.COMBO_DSN_INDX_BLUE_SKY] != null)) {
    		this.dsi[is_aux?TwoQuadCLT.DSI_BLUE_SKY_AUX:TwoQuadCLT.DSI_BLUE_SKY_MAIN] =
    				combo_dsi[OpticalFlow.COMBO_DSN_INDX_BLUE_SKY];
    	}
    	if ((combo_dsi.length > OpticalFlow.COMBO_DSN_INDX_SFM_GAIN) && (combo_dsi[OpticalFlow.COMBO_DSN_INDX_SFM_GAIN] != null)) {
    		this.dsi[is_aux?TwoQuadCLT.DSI_SFM_GAIN_AUX:TwoQuadCLT.DSI_SFM_GAIN_MAIN] =
    				combo_dsi[OpticalFlow.COMBO_DSN_INDX_SFM_GAIN];
    	}
    	/*
    	 * Was a BUG!
    	if ((combo_dsi.length > OpticalFlow.COMBO_DSN_INDX_GROUND) && (combo_dsi[OpticalFlow.COMBO_DSN_INDX_GROUND] != null)) {
    		this.dsi[is_aux?TwoQuadCLT.DSI_SFM_GAIN_AUX:TwoQuadCLT.DSI_SFM_GAIN_MAIN] =
    				combo_dsi[OpticalFlow.COMBO_DSN_INDX_GROUND];
    	}

    	 */

    	if ((combo_dsi.length > OpticalFlow.COMBO_DSN_INDX_GROUND) && (combo_dsi[OpticalFlow.COMBO_DSN_INDX_GROUND] != null)) {
    		this.dsi[is_aux?TwoQuadCLT.DSI_GROUND_AUX:TwoQuadCLT.DSI_GROUND_MAIN] =
    				combo_dsi[OpticalFlow.COMBO_DSN_INDX_GROUND];
    	}

    	if ((combo_dsi.length > OpticalFlow.COMBO_DSN_INDX_TERRAIN) && (combo_dsi[OpticalFlow.COMBO_DSN_INDX_TERRAIN] != null)) {
    		this.dsi[is_aux?TwoQuadCLT.DSI_TERRAIN_AUX:TwoQuadCLT.DSI_TERRAIN_MAIN] =
    				combo_dsi[OpticalFlow.COMBO_DSN_INDX_TERRAIN];
    	}
    	return;
    }


    public boolean dsiExists() {
    	int num_slices = restoreDSI(
    			DSI_SUFFIXES[INDEX_DSI_MAIN], // String suffix, // "-DSI_COMBO", "-DSI_MAIN" (DSI_COMBO_SUFFIX, DSI_MAIN_SUFFIX)
    			null, // double [][] dsi, // if null - just check file exists
    			true); // boolean silent);
    	return num_slices >= 0;
    }

    public boolean interDsiExists() {
    	int num_slices = restoreDSI(
    			DSI_SUFFIXES[INDEX_INTER_LMA], // String suffix, // "-DSI_COMBO", "-DSI_MAIN" (DSI_COMBO_SUFFIX, DSI_MAIN_SUFFIX)
    			null, // double [][] dsi, // if null - just check file exists
    			true); // boolean silent);
/*    	if (num_slices >= 0) {
    		return true;
    	}
    	num_slices = restoreDSI(
    			DSI_SUFFIXES[INDEX_INTER], // String suffix, // "-DSI_COMBO", "-DSI_MAIN" (DSI_COMBO_SUFFIX, DSI_MAIN_SUFFIX)
    			null, // double [][] dsi, // if null - just check file exists
    			true); // boolean silent); */
    	return num_slices >= 0;
    }

     /**
     * Tries to read combo DSI, if successful - sets this.dsi and blue sky
     * @param silent
     * @return combo DSI if read, null if failed to read. Result has full length
     *               (OpticalFlow.COMBO_DSN_TITLES.length), missing slices are null
     */
    public double [][] restoreComboDSI (boolean silent) {
    	return restoreComboDSI (null, silent);
    }
    
    public double [][] restoreComboDSI (
    		String extra_suffix,
    		boolean silent) {
    	if (extra_suffix == null) {
    		extra_suffix = "";
    	}
    	double [][] combo_dsi = new double [OpticalFlow.COMBO_DSN_TITLES.length][];
    	for (int indx: new int[]{INDEX_INTER_LMA, INDEX_INTER}) {
    		int num_slices = restoreDSI(
    				extra_suffix+DSI_SUFFIXES[indx], // String suffix, // "-DSI_COMBO", "-DSI_MAIN" (DSI_COMBO_SUFFIX, DSI_MAIN_SUFFIX)
    				combo_dsi,          // double [][] dsi, // if null - just check file exists
    				silent);            // boolean silent);
    		if (num_slices >= 0) {
    			if (!silent) {
    				System.out.println ("restoreComboDSI(): used "+getX3dDirectory()+ Prefs.getFileSeparator() + image_name + extra_suffix + DSI_SUFFIXES[indx] + ".tiff");
    			}
    			setDSIFromCombo(combo_dsi); // reformat
    			return combo_dsi;
    		}
    	}
    	return null;
    }
    
    public void restoreAnyDSI (
    		int     debugLevel) {
		int dsi_result = -1;
		int max_length=OpticalFlow.COMBO_DSN_TITLES.length;
		if (max_length < TwoQuadCLT.DSI_SLICES.length) {
			max_length = TwoQuadCLT.DSI_SLICES.length;
		}
		double [][] dsi_tmp = new double [max_length][];
		for (int i = 0; i <DSI_SUFFIXES.length; i++) {
			dsi_result =restoreDSI(
					DSI_SUFFIXES[i],
					dsi_tmp,         //double [][] dsi,
					(i < (DSI_SUFFIXES.length -1))); // silent
			if (dsi_result >= 0) {
				System.out.println ("Using "+getX3dDirectory()+ Prefs.getFileSeparator() + image_name + DSI_SUFFIXES[i] + ".tiff");
				if (i < 2) { // DSI format for COMBO_DSN_INDX_* is different, 
					setDSIFromCombo(dsi_tmp); // reformat
				} else {
					setDSI(dsi_tmp); // as is
				}
				break;
			}
		}
		if (dsi_result < 0) {
				System.out.println("No DSI data for the scene "+this.getImageName()+", setting this.dsi=null");
				setDSI(null);
		}
		// WAS: restore only if (dsi_result < 0)...  else 
		restoreInterProperties( // restore properties for interscene processing (extrinsics, ers, ...) // get relative poses (98)
				null, // String path,             // full name with extension or null to use x3d directory
				false, // boolean all_properties,//				null, // Properties properties,   // if null - will only save extrinsics)
				debugLevel);
		return;
    }
    
    public double [][] readComboDSI (boolean silent) {
    	double [][] combo_dsi = new double [OpticalFlow.COMBO_DSN_TITLES.length][];
    	for (int indx: new int[]{INDEX_INTER_LMA, INDEX_INTER}) {
    		int num_slices = restoreDSI(
    				DSI_SUFFIXES[indx], // String suffix, // "-DSI_COMBO", "-DSI_MAIN" (DSI_COMBO_SUFFIX, DSI_MAIN_SUFFIX)
    				combo_dsi,          // double [][] dsi, // if null - just check file exists
    				silent);            // boolean silent);
    		if (num_slices >= 0) {
    			System.out.println ("readComboDSI(): used "+getX3dDirectory()+ Prefs.getFileSeparator() +
    					image_name + DSI_SUFFIXES[indx] + ".tiff, instance.dsi and blue sky are not updated!");
    			setDSIFromCombo(combo_dsi); // reformat
    			return combo_dsi;
    		}
    	}
    	return null;
    }





    public int restoreDSI(
    		String suffix,
    		boolean silent) // "-DSI_COMBO", "-DSI_MAIN" (DSI_COMBO_SUFFIX, DSI_MAIN_SUFFIX)
    {
    	this.dsi = new double [TwoQuadCLT.DSI_SLICES.length][];
    	return restoreDSI(
    			suffix,
    			dsi,
    			silent);
    }
    //min_ref_frac	
    public boolean [] getReliableTiles_old(
    		boolean   use_combo,
    		double    min_strength,
    		double    min_ref_frac,
    		boolean   needs_lma,
    		boolean   needs_lma_combo,
    		boolean   sfm_filter, 
    		double    sfm_minmax, 
    		double    sfm_fracmax,
    		double    sfm_fracall,    		
    		double [] reduced_strength, // if not null will return >0 if had to reduce strength (no change if did not reduce)
    		int       debugLevel
    		) {
    	int NUM_BINS = 1024;
    	// 10.15.2023 - was error here,	readComboDSI (silent) returns combo_dsi, not converted to this.dsi format;	
    	//		double [][] main_dsi = use_combo? readComboDSI (silent): readDsiMain();
    	double [][] main_dsi = null;
    	boolean silent = false;
    	if (use_combo) {
    		readComboDSI (silent);
    		main_dsi = this.dsi;
    		needs_lma = needs_lma_combo;
    	} else {
    		main_dsi =  readDsiMain();
    	}
    	if (main_dsi == null) {
    		return null;
    	}
    	
    	double [] disparity_lma = main_dsi[isAux()?TwoQuadCLT.DSI_DISPARITY_AUX_LMA:TwoQuadCLT.DSI_DISPARITY_MAIN_LMA];
    	double [] strength = main_dsi[isAux()?TwoQuadCLT.DSI_STRENGTH_AUX:TwoQuadCLT.DSI_STRENGTH_MAIN];
    	double [] sfm_gain = main_dsi[isAux()?TwoQuadCLT.DSI_SFM_GAIN_AUX:TwoQuadCLT.DSI_SFM_GAIN_MAIN];
    	
    	
    	
    	if ((strength == null) || (needs_lma && (disparity_lma == null) )) {
    		return null;
    	}
    	int	min_reliable = (int) Math.round (strength.length * min_ref_frac);
    	strength = strength.clone();
    	boolean [] reliable = new boolean [strength.length];
    	for (int i = 0; i < reliable.length; i++) {
    		reliable[i] = (strength[i] >= min_strength) &&
    				(!needs_lma || !Double.isNaN(disparity_lma[i]));
    	}
    	boolean [] blue_sky = getBooleanBlueSky();
    	if (blue_sky != null) {
    		for (int i = 0; i < reliable.length; i++) {
    			if (blue_sky[i]){
    				reliable[i] = false;
    				strength[i] = 0.0;
    			}
    		}
    	}
    	if (sfm_filter && (sfm_gain != null)) {
    		double sfm_max = 0.0;
    		for (int i = 0; i < reliable.length; i++) {
    			if (sfm_gain[i] > sfm_max) sfm_max = sfm_gain[i];
    		}
    		if (sfm_max > sfm_minmax) {
    			double sfm_thresh = sfm_max * sfm_fracmax;
    			boolean [] sfm_good = new boolean [reliable.length];
    			int num_sfm_gain = 0;
    			for (int i = 0; i < reliable.length; i++) {
    				if (sfm_gain[i] >= sfm_thresh) {
    					sfm_good[i] = true;
    					num_sfm_gain++;
    				}
    			}
    			if (num_sfm_gain > (sfm_fracall * reliable.length)) {
    				if (debugLevel > -3) {
    					System.out.println("getReliableTiles(): Using SfM filter");
    				}
    				for (int i = 0; i < reliable.length; i++) {
    					if (!sfm_good[i]) { // sfm_gain[i] <= 0){
    						reliable[i] = false;
    						strength[i] = 0.0;
    					}
    				}
    			}
    		}
    	}
    	int num_reliable = 0;
    	for (boolean b: reliable) if (b) num_reliable++;
		if (debugLevel > -3) {
			System.out.println("getReliableTiles(): num_reliable="+num_reliable);
		}

    	if (num_reliable < min_reliable) { // not enough, select best tiles, ignoring LMA
    		double max_str = 0;
    		for (double s:strength) if ((s > max_str) )max_str= s; // NaN OK
    		if (max_str == 0) return null;
    		int [] hist = new int[NUM_BINS];
    		int num_gt0 = 0;
    		for (double s:strength) if (s > 0) { // verify enough > 0
    			int bin = (int) Math.floor(NUM_BINS * s /max_str);
    			if (bin >= NUM_BINS) bin = NUM_BINS - 1;
    			hist[bin]++;
    			num_gt0++;
    		}
    		if (num_gt0 < min_reliable) {
    			return null; // not enough non-zero values
    		}
    		num_reliable = 0;
    		int num_prev = 0;
    		int bin = NUM_BINS - 1;
    		for (; num_reliable < min_reliable; bin--) {
    			num_prev =      num_reliable;
    			num_reliable +=	hist[bin];
    		}
    		double threshold = (bin + 1.0 * (num_reliable - min_reliable) / (num_reliable - num_prev)) * max_str / NUM_BINS;
    		num_reliable = 0;
    		for (int i = 0; i < reliable.length; i++) {
    			reliable[i] = (strength[i] > threshold);
    			if (reliable[i]) num_reliable++;
    		}
    		if (reduced_strength != null) {
    			reduced_strength[0] = threshold;
    		}
    		if (debugLevel > -3) {
    			System.out.println("getReliableTiles(): updated num_reliable="+num_reliable);
    		}
    	}
    	return reliable;
    }
    public boolean [] getReliableTiles(
    		boolean   use_combo,
    		double    min_strength,
    		double    min_ref_frac,
    		boolean   needs_lma,
    		boolean   needs_lma_combo,
    		boolean   sfm_filter, 
    		double    sfm_minmax, 
    		double    sfm_fracmax,
    		double    sfm_minabs,
    		double    sfm_fracall,
    		double    min_ref_str_sfm,
    		double [] reduced_strength, // if not null will return >0 if had to reduce strength (no change if did not reduce)
    		int       debugLevel
    		) {
    	// 10.15.2023 - was error here,	readComboDSI (silent) returns combo_dsi, not converted to this.dsi format;	
    	//		double [][] main_dsi = use_combo? readComboDSI (silent): readDsiMain();
    	double [][] main_dsi = null;
    	boolean silent = false;
    	if (use_combo) {
    		readComboDSI (silent);
    		main_dsi = this.dsi;
    		needs_lma = needs_lma_combo;
    	} else {
    		main_dsi =  readDsiMain();
    	}
    	if (main_dsi == null) {
    		return null;
    	}
    	boolean [] blue_sky = getBooleanBlueSky();
    	double [] disparity_lma = main_dsi[isAux()?TwoQuadCLT.DSI_DISPARITY_AUX_LMA:TwoQuadCLT.DSI_DISPARITY_MAIN_LMA];
    	double [] strength = main_dsi[isAux()?TwoQuadCLT.DSI_STRENGTH_AUX:TwoQuadCLT.DSI_STRENGTH_MAIN];
    	double [] sfm_gain = main_dsi[isAux()?TwoQuadCLT.DSI_SFM_GAIN_AUX:TwoQuadCLT.DSI_SFM_GAIN_MAIN];
    	return getReliableTiles(
    			min_strength,     // double     min_strength,
    			min_ref_frac,     // double     min_ref_frac,
    			needs_lma,        // boolean    needs_lma,
    			sfm_filter,       // boolean    sfm_filter, 
    			sfm_minmax,       // double     sfm_minmax, 
    			sfm_fracmax,      // double     sfm_fracmax,
    			sfm_minabs,       // double    sfm_minabs,
    			sfm_fracall,      // double     sfm_fracall,
    			min_ref_str_sfm,  // double    min_ref_str_sfm,
    			reduced_strength, // double []  reduced_strength, // if not null will return >0 if had to reduce strength (no change if did not reduce)
    			disparity_lma,    // double []  disparity_lma,
    			strength,         // double []  strength,
    			sfm_gain,         // double []  sfm_gain,
    			blue_sky,         // boolean [] blue_sky,
    			debugLevel);      // int        debugLevel)
    }
    
    public static boolean [] getReliableTiles(
    		double     min_strength,
    		double     min_ref_frac,
    		boolean    needs_lma,
    		boolean    sfm_filter, 
    		double     sfm_minmax, 
    		double     sfm_fracmax,
    		double     sfm_minabs,
    		double     sfm_fracall,
    		double     min_ref_str_sfm,
    		double []  reduced_strength, // if not null will return >0 if had to reduce strength (no change if did not reduce)
        	double []  disparity_lma,
        	double []  strength,
        	double []  sfm_gain,
        	boolean [] blue_sky,
       		int        debugLevel) {
    	int NUM_BINS = 1024;
    	if ((strength == null) || (needs_lma && (disparity_lma == null) )) {
    		return null;
    	}
    	int	min_reliable = (int) Math.round (strength.length * min_ref_frac);
    	strength = strength.clone();
    	boolean [] reliable = new boolean [strength.length];
    	for (int i = 0; i < reliable.length; i++) {
    		reliable[i] = (strength[i] >= min_strength) &&
    				(!needs_lma || !Double.isNaN(disparity_lma[i]));
    	}
//    	boolean [] blue_sky = getBooleanBlueSky();
    	if (blue_sky != null) {
    		for (int i = 0; i < reliable.length; i++) {
    			if (blue_sky[i]){
    				reliable[i] = false;
    				strength[i] = 0.0;
    			}
    		}
    	}
    	if (sfm_filter && (sfm_gain != null)) {
    		double sfm_max = 0.0;
    		for (int i = 0; i < reliable.length; i++) {
    			if (sfm_gain[i] > sfm_max) sfm_max = sfm_gain[i];
    		}
    		if (sfm_max > sfm_minmax) {
    			double sfm_thresh = Math.max(sfm_minabs, sfm_max * sfm_fracmax);
    			boolean [] sfm_good = new boolean [reliable.length];
    			int num_sfm_gain = 0;
    			for (int i = 0; i < reliable.length; i++) {
    				if (sfm_gain[i] >= sfm_thresh) {
    					if (strength[i] >= min_ref_str_sfm) {
    						sfm_good[i] = true;
    						num_sfm_gain++;
    					}
    				}
    			}
    			if (num_sfm_gain > (sfm_fracall * reliable.length)) { // enough tiles have SfM -> discard all others
    				// set threshold to sfm-based threshold
    				// min_strength = min_ref_str_sfm; // not needed, it is not used
    				if (debugLevel > -3) {
    					System.out.println("getReliableTiles(): Using SfM filter");
    				}
    				for (int i = 0; i < reliable.length; i++) {
    					reliable[i] = sfm_good[i];
    					if (!sfm_good[i]) { // sfm_gain[i] <= 0){
//    						reliable[i] = false;
    						strength[i] = 0.0;
    					}

    				}
    			}
    		}
    	}
    	int num_reliable = 0;
    	for (boolean b: reliable) if (b) num_reliable++;
		if (debugLevel > -3) {
			System.out.println("getReliableTiles(): num_reliable="+num_reliable);
		}

    	if (num_reliable < min_reliable) { // not enough, select best tiles, ignoring LMA
    		double max_str = 0;
    		for (double s:strength) if ((s > max_str) ) max_str= s; // NaN OK
    		if (max_str == 0) return null;
    		int [] hist = new int[NUM_BINS];
    		int num_gt0 = 0;
    		for (double s:strength) if (s > 0) { // verify enough > 0
    			int bin = (int) Math.floor(NUM_BINS * s /max_str);
    			if (bin >= NUM_BINS) bin = NUM_BINS - 1;
    			hist[bin]++;
    			num_gt0++;
    		}
    		if (num_gt0 < min_reliable) {
    			return null; // not enough non-zero values
    		}
    		num_reliable = 0;
    		int num_prev = 0;
    		int bin = NUM_BINS - 1;
    		for (; num_reliable < min_reliable; bin--) {
    			num_prev =      num_reliable;
    			num_reliable +=	hist[bin];
    		}
    		double threshold = (bin + 1.0 * (num_reliable - min_reliable) / (num_reliable - num_prev)) * max_str / NUM_BINS;
    		num_reliable = 0;
    		for (int i = 0; i < reliable.length; i++) {
    			reliable[i] = (strength[i] > threshold);
    			if (reliable[i]) num_reliable++;
    		}
    		if (reduced_strength != null) {
    			reduced_strength[0] = threshold;
    		}
    		if (debugLevel > -3) {
    			System.out.println("getReliableTiles(): updated num_reliable="+num_reliable);
    		}
    	}
    	return reliable;
    }
    
    
    
    public double [][] readDsiMain(){
    	double [][] main_dsi = new double [TwoQuadCLT.DSI_SLICES.length][];
    	int slices = restoreDSI(
    			DSI_SUFFIXES[INDEX_DSI_MAIN], // String suffix, // "-DSI_COMBO", "-DSI_MAIN" (DSI_COMBO_SUFFIX, DSI_MAIN_SUFFIX)
    			main_dsi,
    			true); // boolean silent)
    	if (slices < 3) {
    		return null;
    	}
    	return main_dsi;
    }
    public int restoreDSI(
    		String suffix, // "-DSI_COMBO", "-DSI_MAIN" (DSI_COMBO_SUFFIX, DSI_MAIN_SUFFIX)
    		double [][] dsi,
    		boolean silent) {
    	return restoreDSI(
    			false, // boolean this_version,
    			suffix, //String suffix, // "-DSI_COMBO", "-DSI_MAIN" (DSI_COMBO_SUFFIX, DSI_MAIN_SUFFIX)
    			dsi, // double [][] dsi,
    			silent); // boolean silent)
    }
    
    public int restoreDSI(
    		boolean this_version,
    		String suffix, // "-DSI_COMBO", "-DSI_MAIN" (DSI_COMBO_SUFFIX, DSI_MAIN_SUFFIX)
    		double [][] dsi,
    		boolean silent) {
    	String x3d_path = this_version? getImagePath(): getX3dDirectory();
    	String file_path = x3d_path + Prefs.getFileSeparator() + image_name + suffix + ".tiff";
    	return restoreDSI(
    			dsi,       // double [][] dsi,
    			file_path, // String file_path,
    			silent);   // boolean silent);
    }
    
    public static int restoreDSI( // new method 06/03/2025, switched argument order
    		double [][] dsi,
    		String file_path,
    		boolean silent) {
    	if (!(new File(file_path).exists())) {
    		if (!silent) {
    			System.out.println ("File does not exist: "+file_path);
    		}
    		return -1;
    	}
    	ImagePlus imp = null;
    	try {
    		imp = new ImagePlus(file_path);
    	} catch (Exception e) {
    		if (!silent) {
    			System.out.println ("Failed to open "+file_path);
    		}
    		return -1;
    	}
    	if (imp.getWidth()==0) { // file not found
    		if (!silent) {
    			System.out.println ("Failed to open "+file_path);
    		}
    		return -1;
    	}
    	if (!silent) {
    		if (dsi == null) {
    			System.out.println("restoreDSI(): has "+imp.getStackSize()+" slices in file: "+file_path);
    		} else {
    			System.out.println("restoreDSI(): got "+imp.getStackSize()+" slices from file: "+file_path);
    		}
    	}
    	if (imp.getStackSize() < 2) {
    		if (!silent) {
    			System.out.println ("Failed to read "+file_path);
    		}
    		return -1;
    	}
    	if (dsi == null) {
    		return 0; // just check file exists
    	}
    	int num_slices_read = 0;
    	ImageStack dsi_stack = imp.getStack();
    	for (int nl = 0; nl < imp.getStackSize(); nl++) {
    		multiformat:
    		{
    			for (int n = 0; n < TwoQuadCLT.DSI_SLICES.length; n++) {
    				if (TwoQuadCLT.DSI_SLICES[n].equals(dsi_stack.getSliceLabel(nl + 1))) {
    					float [] fpixels = (float[]) dsi_stack.getPixels(nl + 1);
    					dsi[n] = new double [fpixels.length];
    					for (int i = 0; i < fpixels.length; i++) {
    						dsi[n][i] = fpixels[i];
    					}
    					num_slices_read ++;
    					break multiformat;
    				}
    			}
    			for (int n = 0; n < OpticalFlow.COMBO_DSN_TITLES.length; n++) {
    				if (OpticalFlow.COMBO_DSN_TITLES[n].equals(dsi_stack.getSliceLabel(nl + 1))) {
    					float [] fpixels = (float[]) dsi_stack.getPixels(nl + 1);
    					dsi[n] = new double [fpixels.length];
    					for (int i = 0; i < fpixels.length; i++) {
    						dsi[n][i] = fpixels[i];
    					}
    					num_slices_read ++;
    					break multiformat;
    				}
    			}

    		}
    	}
    	return num_slices_read;
    }
    
    public void saveReliables(
    		QuadCLTCPU [] quadCLTs,
    		boolean  [][] reliables,
    		String        suffix) {
    	String [] titles = new String [quadCLTs.length];
    	double [][] dreliables = new double[reliables.length][];
    	for (int nscene = 0; nscene < quadCLTs.length; nscene++) if (quadCLTs[nscene] != null){
    		titles[nscene] = quadCLTs[nscene].getImageName();
    		if (reliables[nscene] != null) {
    			dreliables[nscene] = new double [reliables[nscene].length];
    			for (int i = 0; i < reliables[nscene].length; i++) {
    				dreliables[nscene][i] = reliables[nscene][i] ? 1.0 : 0.0; 
    			}
    		}
    	}
    	saveDoubleArrayInModelDirectory(
    			"-RELIABLE_TILES"+ suffix, // String      suffix,
    			titles,            // String []   labels, // or null
    			dreliables,        // double [][] data,
    			getTilesX(),       // int         width,
    			getTilesY());      //int         height)
    }

    /**
     * Save properties for field calibration (correction vector) in top model directory. If new calibration does not march old,
     * it invalidates older version subfolders, so it is better to start with it and use new version that depends on the calibration
     * calibration
     * @param debugLevel
     */
    public void saveFieldCalibrationProperties( // 
    		int debugLevel)
    {
    	// upgrade to ErsCorrection (including setting initial velocities and angular velocities, resets accelerations, resets scenes
    	if (!(geometryCorrection instanceof ErsCorrection)) {
    		geometryCorrection = new ErsCorrection(geometryCorrection, false); // no need to copy just created gc
    	}
		String x3d_path = getX3dTopDirectory();
		
		
		
    	String path = x3d_path+ Prefs.getFileSeparator()+ getImageName() + FIELD_CALIBRATION_SUFFIX+CONFIGURATION_EXTENSION;
    	Properties	field_calibration_properties = new Properties();
    	String prefix = is_aux?PREFIX_AUX:PREFIX; 
//    	setProperties(prefix,field_calibration_properties);  // null pointer
		GeometryCorrection gc = geometryCorrection;
		if (gc == null) { // if it was not yet created
			gc = new GeometryCorrection(this.extrinsic_vect); // not used in lwir
		}
		gc.setPropertiesExtrinsic(prefix, field_calibration_properties);
    	OutputStream os;
    	try {
    		os = new FileOutputStream(path);
    	} catch (FileNotFoundException e1) {
    		// missing config directory
    		File dir = (new File(path)).getParentFile();
    		if (!dir.exists()){
    			dir.mkdirs();
    			try {
    				os = new FileOutputStream(path);
    			} catch (FileNotFoundException e2) {
    				IJ.showMessage("Error","Failed to create directory "+dir.getName()+" to save field calibration configuration file: "+path);
    				return;
    			}
    		} else {
    			IJ.showMessage("Error","Failed to open field calibration configuration file: "+path);
    			return;
    		}
    	}
    	try {
    		field_calibration_properties.storeToXML(os,
    				"last updated " + new java.util.Date(), "UTF8");

    	} catch (IOException e) {
    		IJ.showMessage("Error","Failed to write XML field calibration configuration file: "+path);
    		return;
    	}
    	try {
    		os.close();
    	} catch (IOException e) {
    		// TODO Auto-generated catch block
    		e.printStackTrace();
    	}
    	if (debugLevel> -3) {
    		System.out.println("Field-calibration configuration parameters (correction vector) are saved to "+path);
    	}
    	return;
    }
    /**
     * Move old_path to new_path, if new_path exists - overwrite it
     * @param source_path
     * @param dest_path
     * @return true on success
     */
    public static boolean moveDir(
    		String source_path,
    		String dest_path) {
        Path source_dir = Paths.get(source_path);
        Path dest_dir =   Paths.get(dest_path);
        if (Files.notExists(source_dir)) {
            System.err.println("Source directory "+source_path+" does not exist");
        	return false;
        }
        try {
            // Ensure the target directory's parent exists
            if (Files.notExists(source_dir.getParent())) {
                Files.createDirectories(dest_dir.getParent());
            }
            // Move the empty directory
            Files.move(source_dir, dest_dir, StandardCopyOption.REPLACE_EXISTING);
            return true;

        } catch (IOException e) {
            System.err.println("Failed to move directory: " + e.getMessage());
            return false;
//            e.printStackTrace();
        }
    }
    
	/*
LogTee.install();         // once, early
LogTee.setSceneLog(path); // when you want to start per‑scene logging
LogTee.clearSceneLog();   // stop per‑scene logging
	 */
    public void startLogging() {
    	String x3d_path = getX3dTopDirectory();
    	Path log_path = Paths.get(x3d_path).resolve(getImageName()+SYSTEM_OUT_LOG_SUFFIX);  
    	try {
			LogTee.setSceneLog(log_path);
		} catch (IOException e) {
			System.err.println("startLogging(): failed to start logging to "+x3d_path);
		} // when you want to start per‑scene logging	
    	System.out.println("\n***** STARTING LOGGING: "+
    			(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime())+" *****\n"));
    }
    
    public void stopLogging() {
    	LogTee.clearSceneLog();   // stop per‑scene logging
    }
    
    
    /**
     * Read field-calibration from file and optionally update this calibration and properties
     * @param apply if true, apply to this scene instance, if false - just convert read properties to an array
     * of double
     * @param debugLevel debug level (debug if > -3)
     * @return extrinsic array
     */
    public double[] restoreFieldCalibration( // restore properties for interscene processing (extrinsics, ers, ...)
    		boolean apply,     // Apply to the current instance
    		int     debugLevel)  {
		String x3d_path = getX3dTopDirectory();
    	String path = x3d_path+ Prefs.getFileSeparator()+ getImageName()+FIELD_CALIBRATION_SUFFIX+CONFIGURATION_EXTENSION;
    	Properties properties = apply ? this.properties : null;
    	Properties new_properties = loadProperties( 
    			path,       // String path,
    			properties, // Properties properties) // will modify/add new
    			true);      // false); // boolean silent)
    	if (new_properties == null ) {
    		System.out.println("Failed to restore field calibration properties from :"+path);
    		if (apply) {
    			System.out.println("Keeping old properies");
    		}
    		return null;
    	}
//    	properties = new_properties; not needed - it is already there
    	if (debugLevel > -3) {
    		System.out.println("Restored field calibration properties from :"+path+(apply ? (" and applied to "+toString()):""));
    	}
    	String prefix = is_aux?PREFIX_AUX:PREFIX; 
		double [] new_extrinsic_vect = GeometryCorrection.getPropertiesExtrinsic(prefix, new_properties);
		if (apply) { // same instance -> set extrinsic vector
			for (int i = 0; i < new_extrinsic_vect.length; i++) {
				if (!Double.isNaN(new_extrinsic_vect[i])) {
	  				if (this.extrinsic_vect == null) { // not used in lwir
	  					// only create non-null array if there are saved values
	  					this.extrinsic_vect = new_extrinsic_vect.clone();
	  				}
	  				this.extrinsic_vect[i] =  new_extrinsic_vect[i];
	  				if (geometryCorrection != null){ // not used in lwir
	  					geometryCorrection.setCorrVector(i,this.extrinsic_vect[i]); // should be same number of cameras
	  				}
				}
			}
	    	if (debugLevel > -3) {
	    		System.out.println("Applied field calibration properties from :"+path+" to scene "+toString());
	    	}
		}
    	return new_extrinsic_vect;
    }
    

    public void saveInterProperties( // save properties for interscene processing (extrinsics, ers, ...)
    		String path,             // full name with extension or w/o path to use x3d directory
    		int debugLevel)
    {
    	// upgrade to ErsCorrection (including setting initial velocities and angular velocities, resets accelerations, resets scenes
    	if (!(geometryCorrection instanceof ErsCorrection)) {
    		geometryCorrection = new ErsCorrection(geometryCorrection, false); // no need to copy just created gc
    	}
    	// update properties from potentially modified parameters (others should be updated
    	if (path == null) {
    		path = image_name + INTERFRAME_SUFFIX + CONFIGURATION_EXTENSION;

    	}
    	if (!path.contains(Prefs.getFileSeparator())) {
    		String x3d_path = getX3dDirectory(true);
    		path = x3d_path+Prefs.getFileSeparator()+path;
    	}
    	Properties	inter_properties = new Properties();
    	String prefix = is_aux?PREFIX_AUX:PREFIX; 
    	setProperties(prefix,inter_properties);  // null pointer
    	OutputStream os;
    	try {
    		os = new FileOutputStream(path);
    	} catch (FileNotFoundException e1) {
    		// missing config directory
    		File dir = (new File(path)).getParentFile();
    		if (!dir.exists()){
    			dir.mkdirs();
    			try {
    				os = new FileOutputStream(path);
    			} catch (FileNotFoundException e2) {
    				IJ.showMessage("Error","Failed to create directory "+dir.getName()+" to save configuration file: "+path);
    				return;
    			}
    		} else {
    			IJ.showMessage("Error","Failed to open configuration file: "+path);
    			return;
    		}
    	}
    	try {
    		inter_properties.storeToXML(os,
    				"last updated " + new java.util.Date(), "UTF8");

    	} catch (IOException e) {
    		IJ.showMessage("Error","Failed to write XML configuration file: "+path);
    		return;
    	}
    	try {
    		os.close();
    	} catch (IOException e) {
    		// TODO Auto-generated catch block
    		e.printStackTrace();
    	}
    	if (debugLevel> -3) {
    		System.out.println("Configuration parameters are saved to "+path);
    	}
    }

    public Properties restoreInterProperties( // restore properties for interscene processing (extrinsics, ers, ...)
    		String path,             // full name with extension or null to use x3d directory
    		boolean all_properties,
    		int debugLevel)
    {
    	if (path == null) {
    		path = image_name + (all_properties? "": INTERFRAME_SUFFIX)+CONFIGURATION_EXTENSION;
    	}
    	if (!path.contains(Prefs.getFileSeparator())) {
    		String x3d_path = getX3dDirectory();
    		path = x3d_path+Prefs.getFileSeparator()+path;
    	}
    	Properties new_properties = loadProperties(
    			path, // String path,
    			properties, // Properties properties)
    			true); // false); // boolean silent)
    	if (new_properties == null ) {
    		System.out.println("Failed to restore interframe properties from :"+path);
///    		System.out.println("Resetting properties");
///    		properties = new Properties();
///    		return null;
    		System.out.println("Keeping old properies");
   		return null;
    	}
    	properties = new_properties; 
    	String prefix = is_aux?PREFIX_AUX:PREFIX; 
    	getProperties(prefix); // will set Geometry correction non-null
    	if (!(geometryCorrection instanceof ErsCorrection)) { // should only be for the new GeometryCorrection created in getProperties
    		geometryCorrection = new ErsCorrection(geometryCorrection, false); // no need to copy just created gc
    	}
    	ErsCorrection ers = (ErsCorrection) geometryCorrection;
    	ers.getPropertiesPose(prefix,   properties);
    	ers.getPropertiesERS(prefix,    properties);
    	ers.getPropertiesScenes(prefix, properties);
    	ers.getPropertiesLineTime(prefix, properties); // will set old value if not in the file
    	System.out.println("Restored interframe properties from :"+path); // here often gets configs folder:/media/elphel/NVME/lwir16-proc/eagle_mountain/configs
    	return properties;
    }
    
    
    public static void compareProperties() {
    	String path1 = ""; // /home/elphel/lwir16-proc/eagle_mountain/configs/M51-eagle_mountain_night_02-5_4-FONT8_enh25-frac0.07_0.1_0.3-A0.4TS0.055-RS4.5-RRMS0.37-HORIZ370-HR0.45_W1.5:0.05:0.7:2:1.5-R0_7-A2A0.5-SAVEALL_tar_str-0.2-NODEBUG.corr-xml";
    	String path2 = ""; // /home/elphel/lwir16-proc/eagle_mountain/configs/old/old_01/C062D-1747829570_155008-full_V5-NOINF_CALIBRATED_gmt-6-cuas_ref_sigma0_ser33-60scenes_nodebuglma_no-model_UM2-1.0_str0.28_min_str_lma0.8_frac0.1_mb_noraw_cuas.corr-xml";
    	boolean exclude_source_paths = true;
    	boolean edit_mismaches = false;
    	GenericJTabbedDialog gd = new GenericJTabbedDialog("Set image pair",1200,800);
    	gd.addStringField ("First  configuration path",     path1, 250, "Full path of the first configuration properties file.");	
    	gd.addStringField ("Second configuration path",     path2, 250, "Second path of the first configuration properties file.");
		gd.addCheckbox    ("Exclude source paths",          exclude_source_paths, 			"exclude CORRECTION_PARAMETERS.sourcePathXXX from the comparison.");
		gd.addCheckbox    ("Edit mismatches",               edit_mismaches, 	     		"Ask which differences to apply.");
    	gd.showDialog();
    	if (gd.wasCanceled()) return;
    	path1 =                gd.getNextString();
    	path2 =                gd.getNextString();
    	exclude_source_paths =  gd.getNextBoolean();
    	edit_mismaches =  gd.getNextBoolean();
    	compareProperties(path1,path2, exclude_source_paths, edit_mismaches);
    }

    public static void compareProperties(
    		String path1,
    		String path2,
    		boolean exclude_source_paths,
    		boolean edit_mismaches) {
    	if (path1.startsWith("file:///")) {
    		path1=path1.substring(7);
    	}
    	if (path2.startsWith("file:///")) {
    		path2=path2.substring(7);
    	}
    	Properties properties1, properties2;
    	properties1 = loadProperties(
    			path1,  // String path,
    			null,   // Properties properties)
    			false); // boolean silent) 
    	properties2 = loadProperties(
    			path2,  // String path,
    			null,   // Properties properties)
    			false); // boolean silent)
    	//	Enumeration<String> en1 = (Enumeration<String>) properties1.propertyNames();
    	//	Enumeration<String> en2 = (Enumeration<String>) properties2.propertyNames();
    	System.out.println("path1 = "+path1);
    	System.out.println("path2 = "+path2);
    	ArrayList<String> keys = new ArrayList<String>();
    	int num_diff = 0;
    	for (@SuppressWarnings("unchecked")
    	Enumeration<String> e = (Enumeration<String>) properties1.propertyNames(); e.hasMoreElements();) {
    		String key = e.nextElement();
    		String v1 = properties1.getProperty(key);
    		String v2 = properties2.getProperty(key);
    		if (!v1.equals(v2)) {
    			if (exclude_source_paths && key.contains("CORRECTION_PARAMETERS.sourcePath")) {
    				continue;
    			}
    			keys.add(key);
    			System.out.println("property1["+key+"] = " + v1+", property2["+key+"] = " +v2);
    			num_diff++;
    		}
    	}
    	for (@SuppressWarnings("unchecked")
    	Enumeration<String> e = (Enumeration<String>) properties2.propertyNames(); e.hasMoreElements();) {
    		String key = e.nextElement();
    		String v1 = properties1.getProperty(key);
    		String v2 = properties2.getProperty(key);

    		if (v1 == null ) { // other already printed
    			if (exclude_source_paths && key.contains("CORRECTION_PARAMETERS.sourcePath")) {
    				continue;
    			}
    			keys.add(key);
    			System.out.println("property1["+key+"] = " + v1+", property2["+key+"] = " +v2);
    			num_diff++;
    		}
    	}
    	System.out.println(num_diff+" differences found");
    	if (edit_mismaches) {
    		int indx = path2.lastIndexOf(".");
    		String ext = path2.substring(indx);
    		String save_path = path2.substring(0,indx)+"-modified"+ext;
    		boolean [] edits = new boolean[keys.size()];
    		boolean done = false;
    		while (!done) {
    			boolean select_all = false;
    			boolean select_none = false;
    	    	GenericJTabbedDialog gd = new GenericJTabbedDialog("Set image pair",1500,1000);
    	    	for (int i = 0; i <edits.length; i++) {
    	    		String key = keys.get(i);
    	    		String v1 = properties1.getProperty(key);
    	    		String v2 = properties2.getProperty(key);
        			gd.addCheckbox (key+": "+"\""+v1+"\" -> \""+v2+"\"", edits[i], 	"Replace old value of the \""+key+"\" property of the file2 with the value from file1");
    	    	}
    	    	gd.addMessage  ("--- Select/Deselect all changes, reopen dialog ---");
    			gd.addCheckbox ("Select all",          select_all, 	"Select all changes to apply, reopen dialog.");
    			gd.addCheckbox ("Deselect all",        select_none,	"Deselect all changes to apply, reopen dialog.");
    	    	gd.addStringField ("Save path",       save_path, 250, "Where to save modified second file.");	

    	    	String [] labels =   {"OK", "Apply", "Cancel"};
    			String [] actions =  {"OK", "_Apply", "Cancel"};
    			String [] tooltips = labels;
    			gd.addButtons(labels, actions, tooltips);
    			gd.showDialog();
    			
    	    	if (gd.wasCanceled()) {
    	    		done = true;
    	    			break;
    	    	}
    	    	for (int i = 0; i <edits.length; i++) {
    	    		edits[i] = gd.getNextBoolean();
    	    	}    	    	
    	    	select_all =   gd.getNextBoolean();
    	    	select_none =  gd.getNextBoolean();
    	    	path1 =        gd.getNextString();
    	    	if (select_all) {
    	    		Arrays.fill(edits,  true);
    	    		continue;
    	    	}
    	    	if (select_none) {
    	    		Arrays.fill(edits,  false);
    	    		continue;
    	    	}
    	    	
    	    	for (int i = 0; i <edits.length; i++) if (edits[i]){
    	    		String key = keys.get(i);
    	    		String v1 = properties1.getProperty(key);
    	    		if (v1==null) {
    	    			properties2.remove(key);
    	    		} else {
    	    			properties2.setProperty(key, v1);
    	    		}
    	    	}    	    	
    	    	// re-evaluate all properties:
    	    	keys = new ArrayList<String>();
    	    	num_diff = 0;
    	    	for (@SuppressWarnings("unchecked")
    	    	Enumeration<String> e = (Enumeration<String>) properties1.propertyNames(); e.hasMoreElements();) {
    	    		String key = e.nextElement();
    	    		String v1 = properties1.getProperty(key);
    	    		String v2 = properties2.getProperty(key);
    	    		if (!v1.equals(v2)) {
    	    			if (exclude_source_paths && key.contains("CORRECTION_PARAMETERS.sourcePath")) {
    	    				continue;
    	    			}
    	    			keys.add(key);
//    	    			System.out.println("property1["+key+"] = " + v1+", property2["+key+"] = " +v2);
    	    			num_diff++;
    	    		}
    	    	}
    	    	for (@SuppressWarnings("unchecked")
    	    	Enumeration<String> e = (Enumeration<String>) properties2.propertyNames(); e.hasMoreElements();) {
    	    		String key = e.nextElement();
    	    		String v1 = properties1.getProperty(key);
    	    		String v2 = properties2.getProperty(key);

    	    		if (v1 == null ) { // other already printed
    	    			if (exclude_source_paths && key.contains("CORRECTION_PARAMETERS.sourcePath")) {
    	    				continue;
    	    			}
    	    			keys.add(key);
//    	    			System.out.println("property1["+key+"] = " + v1+", property2["+key+"] = " +v2);
    	    			num_diff++;
    	    		}
    	    	}
    	    	System.out.println(num_diff+" differences found");
    	    	edits = new boolean[keys.size()]; // all unselected
    	    	if (!gd.wasOKed()){ // apply
    	    		OutputStream os;
    	    		try {
    	    			os = new FileOutputStream(save_path);
    	    		} catch (FileNotFoundException e1) {
    	    			// missing config directory
    	    			File dir = (new File(save_path)).getParentFile();
    	    			if (!dir.exists()) {
    	    				dir.mkdirs();
    	    				try {
    	    					os = new FileOutputStream(save_path);
    	    				} catch (FileNotFoundException e2) {
    	    					IJ.showMessage("Error",
    	    							"Failed to create directory " + dir.getName() + " to save configuration file: " + save_path);
    	    					return;
    	    				}
    	    			} else {
    	    				IJ.showMessage("Error", "Failed to open configuration file: " + save_path);
    	    				return;
    	    			}
    	    		}
    				try {
    					properties2.storeToXML(os, "last updated " + new java.util.Date(), "UTF8");
    				} catch (IOException e) {
    					IJ.showMessage("Error", "Failed to write XML configuration file: " + save_path);
    					return;
    				}
    				try {
    					os.close();
    				} catch (IOException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    				System.out.println("Configuration parameters are saved to " + save_path);
    				return;
    	    	}
    		}
    	}
    	return;
    }
    
    public boolean propertiesContainString (
    		String     needle) {
    	return propertiesContainString (
    			needle,
    			this.properties);
    }

    public static boolean propertiesContainString (
    		String     needle,
    		Properties properties) {
    	Set<String> set_keys = properties.stringPropertyNames();
    	boolean found = false;
    	for (String haystack:set_keys) {
    		if (haystack.indexOf(needle) >= 0) {
    			found = true;
    			break;
    		}
    	}
    	return found;
    }



    //	Moving here form QC:
    public double [][]  getDSRBG (){
    	return dsrbg;
    }
    public String [] getDSRGGTitles() {
    	return isMonochrome()?
    			(new String[]{"disparity","strength", "Y"}):
    				(new String[]{"disparity","strength", "R","B","G"});
    }

    @Deprecated
    public void setDSRBG(
    		CLTParameters  clt_parameters,
    		int            threadsMax,  // maximal number of threads to launch
    		boolean        updateStatus,
    		int            debugLevel)
    {
    	if (this.dsi == null) {
    		if (debugLevel > -4) {
    			System.out.println ("--- No DSI available for "+this.getImageName());
    		}
    		return;
    	}
    	setDSRBG( // will likely not be used at all. Use  getDLS() instead // null
    			this.dsi[is_aux?TwoQuadCLT.DSI_DISPARITY_AUX:TwoQuadCLT.DSI_DISPARITY_MAIN], // null
    			this.dsi[is_aux?TwoQuadCLT.DSI_STRENGTH_AUX:TwoQuadCLT.DSI_STRENGTH_MAIN],
    			this.dsi[is_aux?TwoQuadCLT.DSI_DISPARITY_AUX_LMA:TwoQuadCLT.DSI_DISPARITY_MAIN_LMA],
    			clt_parameters,
    			threadsMax,
    			updateStatus,
    			debugLevel);
    }

    public void setDSRBG(
    		double [] disparity,
    		double [] strength,
    		double [] disparity_lma,
    		CLTParameters  clt_parameters,
    		int            threadsMax,  // maximal number of threads to launch
    		boolean        updateStatus,
    		int            debugLevel)
    {
    	double snld =  clt_parameters.ofp.scale_no_lma_disparity;
    	if ((snld != 1.0) && (disparity_lma != null)) {
    		strength = strength.clone();
    		for (int i = 0; i < strength.length; i++) {
    			if (Double.isNaN(disparity_lma[i])) {
    				strength[i] *= snld;
    			}
    		}

    	}

    	double[][] rbg = getTileRBG(  // null
    			clt_parameters,
    			disparity, // null
    			strength,
    			disparity_lma,
    			threadsMax,  // maximal number of threads to launch
    			updateStatus,
    			debugLevel);
    	if (rbg == null) { // will likely not be used at all. Use  getDLS() instead
    		this.dsrbg = new double[][] {
    			disparity,
    			strength,
    			disparity_lma};

    	} else {
    		if (isMonochrome()) { // only [2] is non-zero
    			this.dsrbg = new double[][] {
    				disparity,
    				strength,
    				//    			disparity_lma,
    				((rbg.length>2)?rbg[2]:rbg[0])}; // [2] - for old compatibility, [0] - new (2021) java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
    		} else {
    			this.dsrbg = new double[][] {
    				disparity,
    				strength,
    				//    			disparity_lma,
    				rbg[0],rbg[1],rbg[2]};
    		}
    		if (debugLevel > 1) { // -2) {
    			String title = image_name+"-DSRBG";
    			String [] titles = getDSRGGTitles();
    			ShowDoubleFloatArrays.showArrays(
    					this.dsrbg,
    					tp.getTilesX(),
    					tp.getTilesY(),
    					true,
    					title,
    					titles);
    		}
    	}
    }

	public double[][] getTileRBG(
			CLTParameters  clt_parameters,
			double []      disparity,
			double []      strength,
    		double []      disparity_lma,
			int            threadsMax,  // maximal number of threads to launch
			boolean        updateStatus,
			int            debugLevel)
	{
		CLTPass3d scan = new CLTPass3d(tp);
		scan.setCalcDisparityStrength(
				disparity,
				strength);
		if (disparity_lma == null) {
			scan.resetLMA();
		} else {
			scan.setLMA(disparity_lma);
		}
		boolean [] selection = new boolean [disparity.length]; //null
		for (int i = 0; i < disparity.length; i++) {
			selection[i] = (!Double.isNaN(disparity[i]) && ((strength == null) || (strength[i] > 0)));
		}
		scan.setTileOpDisparity(selection, disparity);
		// will work only with GPU
		// reset bayer source, geometry correction/vector
		//this.new_image_data =     true;
		QuadCLT savedQuadClt =  saveQuadClt(); // does nothing with CPU
		/*
			QuadCLT savedQuadClt =  gpuQuad.getQuadCLT();
			if (savedQuadClt != this) {
				gpuQuad.updateQuadCLT(this);
			} else {
				savedQuadClt = null;
			}
		*/
		setPassAvgRBGA( // get image from a single pass, return relative path for x3d // USED in lwir
				clt_parameters, // CLTParameters           CLTParameters           clt_parameters,,
				scan,
				threadsMax,  // maximal number of threads to launch
				updateStatus,
				debugLevel);
		double [][] rgba = scan.getTilesRBGA();
		if (debugLevel > -1) { // -2) {
			String title = image_name+"-RBGA"; // max A = 0.00297 with LWIR
			String [] titles = isMonochrome()? (new String [] {"Y","A"}):(new String []{"R","B","G","A"});
			ShowDoubleFloatArrays.showArrays(
					rgba,
					tp.getTilesX(),
					tp.getTilesY(),
					true,
					title,
					titles);
		}
        // Maybe resotore y caller?
		restoreQuadClt(savedQuadClt);
		/*
		if (savedQuadClt !=  null) {
			gpuQuad.updateQuadCLT(savedQuadClt);
		}
		*/
		return rgba;
	}
	

	public QuadCLTCPU restoreFromModel( // restores dsi
			CLTParameters        clt_parameters,
			ColorProcParameters  colorProcParameters,
			NoiseParameters	     noise_sigma_level,
			int                  noise_variant, // <0 - no-variants, compatible with old code
			QuadCLTCPU           ref_scene, // may be null if scale_fpn <= 0
			int                  threadsMax,
			int                  debugLevel)

	{
		if (getGPUQuad() != null) {
			getGPUQuad().deallocate4Images();
		}
		final int        debugLevelInner=clt_parameters.batch_run? -2: debugLevel;
		String jp4_copy_path= correctionsParameters.selectX3dDirectory(
				this.image_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
				correctionsParameters.jp4SubDir,
				true,  // smart,
				true);  //newAllowed, // save
		String [] sourceFiles = correctionsParameters.selectSourceFileInSet(jp4_copy_path, debugLevel);
		if (sourceFiles.length < getNumSensors()) {
			if (sourceFiles.length > 0) {
				System.out.println("not enough source files ("+sourceFiles.length+") in "+jp4_copy_path);
			}
			return null;
		}
		SetChannels [] set_channels=setChannels(
				null, // single set name
				sourceFiles,
				debugLevel);
		// sets set name to jp4, overwrite
		set_channels[0].set_name = this.image_name; // set_name;
		double [] referenceExposures = null;
		if (!isLwir()) { // colorProcParameters.lwir_islwir) {
			referenceExposures = eyesisCorrections.calcReferenceExposures(sourceFiles, debugLevel);
		}
		// move after restoring properties
		// try to restore DSI generated from interscene if available, if not use single-scene -DSI_MAIN
		restoreAnyDSI(debugLevel);
		int [] channelFiles = set_channels[0].fileNumber();		
		boolean [][] saturation_imp = (clt_parameters.sat_level > 0.0)? new boolean[channelFiles.length][] : null;
		double []    scaleExposures = new double[channelFiles.length];
		conditionImageSet(
				clt_parameters,                 // EyesisCorrectionParameters.CLTParameters  clt_parameters,
				colorProcParameters,            //  ColorProcParameters                       colorProcParameters, //
				sourceFiles,                    // String []                                 sourceFiles,
				this.image_name,                       // String                                    set_name,
				referenceExposures,             // double []                                 referenceExposures,
				channelFiles,                   // int []                                    channelFiles,
				scaleExposures,                 // output  // double [] scaleExposures
				saturation_imp,                 // output  // boolean [][]                              saturation_imp,
				threadsMax,                     // int                                       threadsMax,
				debugLevelInner);               // int                                       debugLevel);
		if (noise_sigma_level != null) {
			generateAddNoise(
					"-NOISE",
					ref_scene, // final QuadCLTCPU ref_scene, // may be null if scale_fpn <= 0
					noise_sigma_level,
					noise_variant, //final int       noise_variant, // <0 - no-variants, compatible with old code
					threadsMax,
					1); // debugLevel); // final int       debug_level)
		}
		// optionally restore/generate IMS data
		if (clt_parameters.imp.ims_use) {
			restoreIms(
					clt_parameters, // // CLTParameters clt_parameters,
					null, // String ims_path,
					true, // boolean create,
					debugLevelInner); // int debugLevel);
		}
		return this; //  can only be QuadCLT instance
	}
	
	public boolean hasSrcFiles() {
		String jp4_copy_path= correctionsParameters.selectX3dDirectory(
				this.image_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
				correctionsParameters.jp4SubDir,
				true,  // smart,
				true); // false); // true);  //newAllowed, // save
		int num_src_files=correctionsParameters.selectSourceFileInSet(jp4_copy_path, -5).length;
		return (num_src_files >= getNumSensors());
	}
	
	public QuadCLTCPU restoreNoModel(
			CLTParameters        clt_parameters,
			ColorProcParameters  colorProcParameters,
			NoiseParameters	     noise_sigma_level,
			int                  noise_variant, // <0 - no-variants, compatible with old code
			QuadCLTCPU           ref_scene, // may be null if scale_fpn <= 0
			int                  threadsMax,
			int                  debugLevel)

	{
		final int        debugLevelInner=clt_parameters.batch_run? -2: debugLevel;
		String jp4_copy_path= correctionsParameters.selectX3dDirectory(
				this.image_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
				correctionsParameters.jp4SubDir,
				true,  // smart,
				true); // false); // true);  //newAllowed, // save
		String [] sourceFiles = correctionsParameters.selectSourceFileInSet(jp4_copy_path, debugLevel);
		if (sourceFiles.length < getNumSensors()) {
			if (sourceFiles.length > 0) {
				System.out.println("not enough source files ("+sourceFiles.length+") in "+jp4_copy_path);
			}
			return null;
		}
		SetChannels [] set_channels=setChannels(
				null, // single set name
				sourceFiles,
				debugLevel);
		// sets set name to jp4, overwrite
		set_channels[0].set_name = this.image_name; // set_name;
		double [] referenceExposures = null;
		if (!isLwir()) { // colorProcParameters.lwir_islwir) {
			referenceExposures = eyesisCorrections.calcReferenceExposures(sourceFiles, debugLevel);
		}
		int [] channelFiles = set_channels[0].fileNumber();		
		boolean [][] saturation_imp = (clt_parameters.sat_level > 0.0)? new boolean[channelFiles.length][] : null;
		double []    scaleExposures = new double[channelFiles.length];
//		ImagePlus [] imp_srcs = 
		conditionImageSet(
				clt_parameters,                 // EyesisCorrectionParameters.CLTParameters  clt_parameters,
				colorProcParameters,            //  ColorProcParameters                       colorProcParameters, //
				sourceFiles,                    // String []                                 sourceFiles,
				this.image_name,                       // String                                    set_name,
				referenceExposures,             // double []                                 referenceExposures,
				channelFiles,                   // int []                                    channelFiles,
				scaleExposures,                 // output  // double [] scaleExposures
				saturation_imp,                 // output  // boolean [][]                              saturation_imp,
				threadsMax,                     // int                                       threadsMax,
				debugLevelInner);               // int                                       debugLevel);
		if (noise_sigma_level != null) {
			generateAddNoise(
					"-NOISE",
					ref_scene, // final QuadCLTCPU ref_scene, // may be null if scale_fpn <= 0
					noise_sigma_level,
					noise_variant, //final int       noise_variant, // <0 - no-variants, compatible with old code
					threadsMax,
					1); // debugLevel); // final int       debug_level)
		}
		// optionally restore/generate IMS data
		if (clt_parameters.imp.ims_use) {
			restoreIms(
					clt_parameters, // // CLTParameters clt_parameters,
					null, // String ims_path,
					true, // boolean create,
					debugLevelInner); // int debugLevel);
		}
 		return this; //  can only be QuadCLT instance
	}
	
	/**
	 * Generate if needed and save several interpolated IMS DID-s
	 * @param ims_offset difference between the IMS snapshot time and images.
	 *                    Positive if IMS has lower latency
	 * @param gmt_plus    hours from GMT to add to filename timestamp to get IMU log timestamp                    
	 * @param ims_path    full file path or null (will be generated)
	 * @param debugLevel  debug level
	 * @return true on success, false on failure
	 */
	public boolean saveIms(
			CLTParameters clt_parameters,
//			double ims_offset,
//			double gmt_plus,
			String ims_path,
			int debugLevel) {
		double ims_offset = clt_parameters.imp.ims_offset;
		double gmt_plus = clt_parameters.imp.gmt_plus;
		double this_ts; 
		if (ims_path == null) {
			String model_dir = correctionsParameters.selectX3dDirectory(
					image_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
					null,
					true,  // smart,
					true);  //newAllowed, // save
			if (model_dir == null) {
				return false;
			}
			ims_path = model_dir + Prefs.getFileSeparator()+image_name+correctionsParameters.imsSuffix; // String path,
			this_ts= getTimeStamp();
		} else { 
			this_ts=getTimeStamp(ims_path.substring(ims_path.lastIndexOf(Prefs.getFileSeparator())+1));
		}
		if ((getImageName()==null) || (getTimeStamp() != this_ts) || (ims_offset != this.ims_offs) || (gmt_plus != this.gmt_plus)){
			// invalidate
			this.ims_offs =    ims_offset;
			this.gmt_plus =    gmt_plus;
			did_ins_1 =        null;
			did_ins_2 =        null;
			did_pimu =         null;
			did_gps1_pos =     null;
			did_gps2_pos =     null;
			did_gps1_ubx_pos = null;
		}
		EventLogger eventLogger = EventLogger.getEventLogger();
		if (eventLogger == null) {
			System.out.println("Preparing IMS data as it is needed but does not exist");
			String imx_logs = correctionsParameters.getImsSourceDirectory(); // sourceImsDirectory;
			eventLogger = EventLogger.getEventLogger( // synchronized method
					clt_parameters, //CLTParameters clt_parameters,
					imx_logs,
					null);
			if (eventLogger == null) {
				System.out.println("Failed to create eventLogger instance.");
				return false;
			} else {
				System.out.println("Created eventLogger instance.");
			}
//			Eyesis_Correction.EVENT_LOGGER = new EventLogger (imx_logs, null);
		} else {
			eventLogger = EventLogger.getEventLogger();
//			System.out.println("Already had it!");
		}
		/*
		if (Eyesis_Correction.EVENT_LOGGER == null) {
			System.out.println("Preparing IMS data as it is needed but does not exist");
			String imx_logs = correctionsParameters.getImsSourceDirectory(); // sourceImsDirectory;
			Eyesis_Correction.EVENT_LOGGER = new EventLogger (imx_logs, null);
		}
		EventLogger eventLogger = Eyesis_Correction.EVENT_LOGGER; 

		*/
		double ims_timestamp = this_ts + this.ims_offs + 3600 * gmt_plus;
		if (did_ins_1 == null) {
			try {did_ins_1=eventLogger.interpolateDidIns1(ims_timestamp, 0);}
			catch (IOException e) {e.printStackTrace();}
		}
		if (did_ins_2 == null) {
			try {
				did_ins_2=eventLogger.interpolateDidIns2(ims_timestamp, 0);
				}
			catch (IOException e) {e.printStackTrace();}
		}
		if (did_pimu == null) {
			try {did_pimu=eventLogger.interpolateDidPimu(ims_timestamp, 0);}
			catch (IOException e) {e.printStackTrace();}
		}
		if (did_gps1_pos == null) {
			try {did_gps1_pos=eventLogger.interpolateDidGpsPos(ims_timestamp, Imx5.DID_GPS1_POS, 0);}
			catch (IOException e) {e.printStackTrace();}
		}
		if (did_gps2_pos == null) {
			try {did_gps2_pos=eventLogger.interpolateDidGpsPos(ims_timestamp, Imx5.DID_GPS2_POS, 0);}
			catch (IOException e) {e.printStackTrace();}
		}
		if (did_gps1_ubx_pos == null) {
			try {did_gps1_ubx_pos=eventLogger.interpolateDidGpsPos(ims_timestamp, Imx5.DID_GPS1_UBX_POS, 0);}
			catch (IOException e) {e.printStackTrace();}
		}
		if (did_ins_2 == null) {
			System.out.println("saveIms(): "+getImageName()+": did_ins_2==null");
		}
		Properties ims_properties = new Properties();
		ims_properties.setProperty("ims_offs",         this.ims_offs+"");
		ims_properties.setProperty("gmt_plus",         this.gmt_plus+"");
		
		
		if (did_ins_1 != null)         did_ins_1.setProperties("did_ins_1-", ims_properties);
		if (did_ins_2 != null)         did_ins_2.setProperties("did_ins_2-", ims_properties);
		if (did_pimu != null)          did_pimu.setProperties("did_pimu-", ims_properties);
		if (did_gps1_pos != null)      did_gps1_pos.setProperties("did_gps1_pos-", ims_properties);
		if (did_gps2_pos != null)      did_gps2_pos.setProperties("did_gps2_pos-", ims_properties);
		if (did_gps1_ubx_pos != null)  did_gps1_ubx_pos.setProperties("did_gps1_ubx_pos-", ims_properties);

//Properties setProperties(String prefix, Properties properties)		
		// ims_path
		OutputStream os;
		try {
			os = new FileOutputStream(ims_path);
		} catch (FileNotFoundException e1) {
			// missing config directory
			File dir = (new File(ims_path)).getParentFile();
			if (!dir.exists()){
				dir.mkdirs();
				try {
					os = new FileOutputStream(ims_path);
				} catch (FileNotFoundException e2) {
					IJ.showMessage("Error","Failed to create directory "+dir.getName()+" to save configuration file: "+ims_path);
					return false;
				}
			} else {
				IJ.showMessage("Error","Failed to open configuration file: "+ims_path);
				return false;
			}
		}
		try {
			ims_properties.storeToXML(os,
					"last updated " + new java.util.Date(), "UTF8");
		} catch (IOException e) {
			IJ.showMessage("Error","Failed to write XML configuration file: "+ims_path);
			return false;
		}
		try {
			os.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		ims_last_path = ims_path;
		if (debugLevel> -3) {
			System.out.println("IMS parameters are saved to "+ims_path);
		}
		
		return true;
	}
	
	/**
	 * Restore IMS data, regenerate and save if it does not exist or was generated for
	 * different  ims_offset
	 * @param ims_offset difference between the IMS snapshot time and images.
	 *                    Positive if IMS has lower latency
	 * @param gmt_plus    hours from GMT to add to filename timestamp to get IMU log timestamp                    
	 * @param ims_path    full file path or null (will be generated)
	 * @param create      generate if does not exist
	 * @param debugLevel  debug level
	 * @return true on success, false on failure
	 */
	public boolean restoreIms(
			CLTParameters clt_parameters,
			String ims_path,
			boolean create,
			//boolean rebuild,
			int debugLevel) {
		int debug_threshold = 0; // use -3 for verbose
		double ims_offset = clt_parameters.imp.ims_offset;
		double gmt_plus = clt_parameters.imp.gmt_plus;
		if (ims_path == null) {
			String model_dir = correctionsParameters.selectX3dDirectory(
					image_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
					null,
					true,  // smart,
					false);  //newAllowed, // save
			if (model_dir == null) {
				return false;
			}
			ims_path = model_dir + Prefs.getFileSeparator()+image_name+correctionsParameters.imsSuffix; // String path,
		}
		if (ims_path.equals(this.ims_last_path) && (ims_offset == this.ims_offs) && (gmt_plus == this.gmt_plus)) {
			return true;
		}
		Properties ims_properties = loadProperties(
				ims_path, // String path,
				null, // Properties properties)
				true); // boolean silent)
		// temporary to recalculate if no did_ins_2:
		if (ims_properties != null){ // may become null, so  
			Did_ins_2 did_ins_2 = new Did_ins_2("did_ins_2-", ims_properties);
			if ((did_ins_2.lla == null) || ((did_ins_2.lla[0]==0) && (did_ins_2.lla[1]==0))) {
				ims_properties = null;
				System.out.println("restoreIms(): "+getImageName()+": no did_ins_2 - invalidating and recalculating config");
			}
		}
		if (ims_properties == null) { // may become null above
			if (create) {
				saveIms(
						clt_parameters, // CLTParameters clt_parameters,						
//						ims_offset,
//						gmt_plus,
						ims_path,
						debugLevel);
				ims_properties = loadProperties(
						ims_path, // String path,
						null, // Properties properties)
						true); // boolean silent)				
			}
			if (ims_properties == null) {

				if (debugLevel> -3) {
					System.out.println("restoreIms(): Failed to read IMS properties file: "+ims_path);
				}
				return false;
			}
		}
		this.ims_offs = Double.NaN;
		this.gmt_plus = Double.NaN;
		if (ims_properties.getProperty("ims_offs")!=null) this.ims_offs=Double.parseDouble(ims_properties.getProperty("ims_offs"));
		if (ims_properties.getProperty("gmt_plus")!=null) this.gmt_plus=Double.parseDouble(ims_properties.getProperty("gmt_plus"));
		if ((this.ims_offs != ims_offset) || (this.gmt_plus != gmt_plus)) {
			if (debugLevel> -3) {
				System.out.println("IMS offset differs, recalculating "+ims_path);
			}
			saveIms(
					clt_parameters, // CLTParameters clt_parameters,						
					ims_path,
					debugLevel);			
		}
		did_ins_1 =        new Did_ins_1("did_ins_1-", ims_properties);
		did_ins_2 =        new Did_ins_2("did_ins_2-", ims_properties);
		did_pimu =         new Did_pimu("did_pimu-", ims_properties);
		did_gps1_pos =     new Did_gps_pos("did_gps1_pos-", ims_properties);
		did_gps2_pos =     new Did_gps_pos("did_gps2_pos-", ims_properties);
		did_gps1_ubx_pos = new Did_gps_pos("did_gps1_ubx_pos-", ims_properties);
		ims_last_path =    ims_path;
		if (debugLevel > debug_threshold) {
			System.out.println("IMS parameters are restored from "+ims_path);
		}
		return true;
	}
	
	/**
	 * Read ims_offset from the saved IMS data. Return Double.NaN if file does not exist
	 * @param ims_path    full file path or null (will be generated)
	 * @param debugLevel  debug level
	 * @return difference between the IMS snapshot time and images or NaN if file does not exist.
	 *         Positive if IMS has lower latency
	 */
	public double [] readImsOffset( // not used
			String ims_path,
			int debugLevel) {
		if (ims_path == null) {
			String model_dir = correctionsParameters.selectX3dDirectory(
					image_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
					null,
					true,  // smart,
					false);  //newAllowed, // save
			if (model_dir == null) {
				return null;
			}
			ims_path = model_dir + Prefs.getFileSeparator()+image_name+correctionsParameters.imsSuffix; // String path,
		}
		Properties ims_properties = loadProperties(
				ims_path, // String path,
				null, // Properties properties)
				true); // boolean silent)
				
		if (ims_properties == null) {
			System.out.println("readImsOffset(): Failed to read IMS properties file: "+ims_path);
//			IJ.showMessage("Error","Failed to read IMS properties file: "+ims_path);
			return null;
		}
		double [] rslt = {Double.NaN,Double.NaN};
		if (ims_properties.getProperty("ims_offs")!=null) rslt[0] = Double.parseDouble(ims_properties.getProperty("ims_offs"));
		if (ims_properties.getProperty("gmt_plus")!=null) rslt[1] = Double.parseDouble(ims_properties.getProperty("gmt_plus"));
		return rslt;
	}
	
	
	
	// generate and save noise file (each Bayer component amplitude same as the corresponding image average,
	// apply Gaussian blur with sigma (before Bayer scaling)
	// If file with the same sigma already exists in the model directory - just use it, multiply by noise_sigma_level[0] and add to the non-zero Bayer
	
	
	// generate and save noise file (each Bayer component amplitude same as the corresponding image average,
	// apply gaussian blur with sigma (before Bayer scaling)
	// If file with the same sigma already exists in the model directory - just use it, multiply by noise_sigma_level[0] and add to the non-zero Bayer
	
	public void generateAddNoise(
			final String    suffix_novar,
			final QuadCLTCPU ref_scene, // may be null if scale_fpn <= 0
			final NoiseParameters noise_sigma_level,
			final int       noise_variant, // <0 - no-variants, compatible with old code
			final int       threadsMax,
			final int       debug_level)
	{
		final double [][][] image_data = getImageData();
		final double scale_random = noise_sigma_level.scale_random; // _sigma_level[0];
		final double scale_fpn =    noise_sigma_level.scale_fpn;    // noise_sigma_level[0];
		final double sigma =        noise_sigma_level.sigma; // [1];
		final String    suffix = suffix_novar + ((noise_variant >= 0) ? ("-"+noise_variant+"-"):""); 
		ImagePlus imp = generateAddNoise(
				suffix,       // final String    suffix,
				sigma,        // final double    sigma,
				threadsMax, // final int       threadsMax,
				debug_level); // final int       debug_level) : null;
		
		ImagePlus imp_ref = null;
		if (scale_fpn >0){
			if (ref_scene !=null) {
			imp_ref= ref_scene.generateAddNoise(
					suffix,       // final String    suffix,
					sigma,        // final double    sigma,
					threadsMax, // final int       threadsMax,
					debug_level); // final int       debug_level) : null;
			} else {
				imp_ref= imp; // when calculating ref_scene itself it is provided as null
			}
		}
		
		
		final int num_cams = image_data.length;
		final int num_cols = image_data[0].length;
		final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		ImageStack imageStack = imp.getStack();
		final float [][] fpixels = new float [num_cams][];
		for (int q = 0; q < num_cams; q++) {
			fpixels[q] = (float[]) imageStack.getPixels(q+1);
		}
		final float [][] fpixels_ref = (imp_ref != null) ? (new float [num_cams][]): null;
		if (imp_ref != null) {
			ImageStack imageStack_ref = imp_ref.getStack();
			for (int q = 0; q < num_cams; q++) {
				fpixels_ref[q] = (float[]) imageStack_ref.getPixels(q+1);
			}
		}
		
		
		for (int q = 0; q < num_cams; q++) {
			final int fq = q;
			for (int c =0; c < num_cols; c++) {
				final int fc = c;
				ai.set(0);
				for (int ithread = 0; ithread < threads.length; ithread++) {
					threads[ithread] = new Thread() {
						public void run() {
							for (int i = ai.getAndIncrement(); i < image_data[fq][fc].length; i = ai.getAndIncrement()) {
								if (image_data[fq][fc][i] != 0.0) {
									image_data[fq][fc][i] += scale_random * fpixels[fq][i];
									if (fpixels_ref != null) {
										image_data[fq][fc][i] += scale_fpn * fpixels_ref[fq][i];
									}
								}
							}
						}
					};
				}		      
				ImageDtt.startAndJoin(threads);
			}			
		}
		if (debug_level > 100) {
			double [][] dbg_data = new double [num_cams*num_cols][];
			for (int q = 0; q < num_cams;q++) {
				for (int c = 0; c < num_cols; c++) {
					dbg_data[q*num_cols+c] = image_data[q][c];
				}
			}
			int [] image_wh = geometryCorrection.getSensorWH();
			String noise_suffix = suffix + sigma;
			saveDoubleArrayInModelDirectory(
					noise_suffix + "-MIXED-RND"+scale_random+"-FPN"+scale_fpn, // noise_sigma_level[0],  // String      suffix,
					null,          // String []   labels, // or null
					dbg_data,         // double [][] data,
					image_wh[0],   // int         width,
					image_wh[1]);  // int         height)
		}
	}
	

	
	// May need to run twice - for both refscene and this one if fpn >=0
	/**
	 * Load existing noise image, generate if it did no exist 
	 * @param suffix file name suffix (sigma will be added)
	 * @param sigma blur sigma (in pixels), the amount of added noise will be used by caller, noise image only depends on sigma 
	 * @param threadsMax
	 * @param debug_level
	 * @return Noise image, one slice per sensor (Bayer mosaic still use 1 slice per sensor)
	 */
	public ImagePlus generateAddNoise(
			final String    suffix,
			final double    sigma,
			final int       threadsMax,
			final int       debug_level)
	{
		final double [][][] image_data = getImageData();
		final int num_cams = image_data.length;
		final int num_cols = image_data[0].length;
		final int [] image_wh = geometryCorrection.getSensorWH();
		String x3d_path = getX3dDirectory(true);
		String noise_suffix = suffix + sigma;
		String file_name = image_name + noise_suffix;
		String file_path = x3d_path + Prefs.getFileSeparator() + file_name + ".tiff";
		ImagePlus imp = null;
		try {
			imp = new ImagePlus(file_path);
		} catch (Exception e) {
			System.out.println ("Failed to open "+file_path+", will generate it");
		}
		final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		if ((imp == null) || (imp.getTitle() == null) || (imp.getTitle().equals(""))) {
			System.out.println ("Empty "+file_path+", will generate it");
			int num_pix = image_wh[0] * image_wh[1];
			final double [][] noise = new double [num_cams][num_pix];
			for (int q = 0; q < num_cams; q++) {
				final int fq = q;
				ai.set(0);
				for (int ithread = 0; ithread < threads.length; ithread++) {
					threads[ithread] = new Thread() {
						public void run() {
							Random random = new Random();
							for (int i = ai.getAndIncrement(); i < noise[0].length; i = ai.getAndIncrement()) {
								noise[fq][i] = random.nextGaussian();
							}
						}
					};
				}		      
				ImageDtt.startAndJoin(threads);
			}
			ai.set(0);
			if (sigma > 0) {
				for (int ithread = 0; ithread < threads.length; ithread++) {
					threads[ithread] = new Thread() {
						public void run() {
							for (int q = ai.getAndIncrement(); q <num_cams; q = ai.getAndIncrement()) {
								(new DoubleGaussianBlur()).blurDouble(noise[q],  image_wh[0], image_wh[1], sigma, sigma, 0.01);
							}
						}
					};
				}		      
				ImageDtt.startAndJoin(threads);
			}
			if (isLwir()) {
				for (int q = 0; q < num_cams; q++) {
					final int fq = q;
					double s1 = 0.0, s2 = 0.0;
					for (int i =0; i < image_data[q][0].length; i++) {
						s1  += image_data[q][0][i];
						s2 += image_data[q][0][i] * image_data[q][0][i];
					}
					double s0 = image_data[q][0].length;
					final double sb = 2.0 * Math.sqrt(s2*s0 - s1*s1) / s0; // 2.0 - to match calculation for RGB (average value)  
					ai.set(0);
					for (int ithread = 0; ithread < threads.length; ithread++) {
						threads[ithread] = new Thread() {
							public void run() {
								for (int i = ai.getAndIncrement(); i < noise[0].length; i = ai.getAndIncrement()) {
									noise[fq][i] *= sb;
								}
							}
						};
					}		      
					ImageDtt.startAndJoin(threads);
				}
			} else {
				for (int q = 0; q < num_cams; q++) {
					final int fq = q;
					double [] sc = new double [num_cols];
					for (int c =0; c < num_cols; c++) {
						for (int i =0; i < image_data[q][c].length; i++) {
							sc[c] += image_data[q][c][i];
						}
					}
					if (isMonochrome()) {
						final double sb = sc[0]/num_pix; 
						ai.set(0);
						for (int ithread = 0; ithread < threads.length; ithread++) {
							threads[ithread] = new Thread() {
								public void run() {
									for (int i = ai.getAndIncrement(); i < noise[0].length; i = ai.getAndIncrement()) {
										noise[fq][i] *= sb;
									}
								}
							};
						}		      
						ImageDtt.startAndJoin(threads);

					} else {
						final double [][] sb = {
								{sc[2] * 2.0 / num_pix, sc[0] * 4.0 / num_pix},
								{sc[1] * 4.0 / num_pix, sc[2] * 2.0 / num_pix}};
						ai.set(0);
						for (int ithread = 0; ithread < threads.length; ithread++) {
							threads[ithread] = new Thread() {
								public void run() {
									for (int i = ai.getAndIncrement(); i < noise[0].length; i = ai.getAndIncrement()) {
										int dx = (i % image_wh[0]) & 1;
										int dy = (i / image_wh[0]) & 1;
										noise[fq][i] *= sb[dy][dx];
									}
								}
							};
						}		      
						ImageDtt.startAndJoin(threads);
					}
				}
			}
			imp = saveDoubleArrayInModelDirectory(
					noise_suffix,  // String      suffix,
					null,          // String []   labels, // or null
					noise,         // double [][] data,
					image_wh[0],   // int         width,
					image_wh[1]);  // int         height)
		}
		return imp;
	}	
	
	public boolean appendStringInModelDirectory(
			String  string,
			String  suffix) { // includes extension
		return saveStringInModelDirectory(string, suffix, true);
	}

	public boolean saveStringInModelDirectory(
			String  string,
			String  suffix, // includes extension
			boolean append) {
		String x3d_path = getX3dDirectory(true);
		String file_name = image_name + suffix;
		String file_path = x3d_path + Prefs.getFileSeparator() + file_name;
		try {
			BufferedWriter out = new BufferedWriter(
					new FileWriter(file_path, append));
			out.write(string);
			out.close();
			if (!append) { // may be too many in append mode
				System.out.println("saveStringInModelDirectory(): saved "+file_path);
			}
			return true;
        } catch (IOException e) {
            // Display message when exception occurs
            System.out.println("exception occurred" + e);
            return false;
        }
	}
	
	
	
	public ImagePlus saveDoubleArrayInModelDirectory(
			String      suffix,
			String []   labels, // or null
			double [][] data,
			int         width,
			int         height)
	{
		String x3d_path = getX3dDirectory(true); // create if does not exist
		String file_name = image_name + suffix;
		String file_path = x3d_path + Prefs.getFileSeparator() + file_name + ".tiff";
		ImageStack imageStack = ShowDoubleFloatArrays.makeStack(data, width, height, labels);
		ImagePlus imp = new ImagePlus( file_name, imageStack);
		FileSaver fs=new FileSaver(imp);
		fs.saveAsTiff(file_path);
		System.out.println("saveDoubleArrayInModelDirectory(): saved "+file_path);
		return imp;
	}

	public ImagePlus saveFloatArrayInModelDirectory(
			String      suffix,
			String []   labels, // or null
			float [][]  data,
			int         width,
			int         height)
	{
		String x3d_path = getX3dDirectory(true);
		String file_name = image_name + suffix;
		String file_path = x3d_path + Prefs.getFileSeparator() + file_name + ".tiff";
		ImageStack imageStack = ShowDoubleFloatArrays.makeStack(data, width, height, labels, false);
		ImagePlus imp = new ImagePlus( file_name, imageStack);
		FileSaver fs=new FileSaver(imp);
		fs.saveAsTiff(file_path);
		System.out.println("saveFloatArrayInModelDirectory(): saved "+file_path);
		return imp;
	}
	
	
	
	public ImagePlus saveDoubleArrayInTopModelDirectory(
			String      suffix,
			String []   labels, // or null
			double [][] data,
			int         width,
			int         height)
	{
		String x3d_path = getX3dTopDirectory();
		String file_name = image_name + suffix;
		String file_path = x3d_path + Prefs.getFileSeparator() + file_name + ".tiff";
		ImageStack imageStack = ShowDoubleFloatArrays.makeStack(data, width, height, labels);
		ImagePlus imp = new ImagePlus( file_name, imageStack);
		FileSaver fs=new FileSaver(imp);
		fs.saveAsTiff(file_path);
		System.out.println("saveDoubleArrayInTopModelDirectory(): saved "+file_path);
		return imp;
	}
	
	
	public ImagePlus saveDoubleArrayInModelDirectory(
			String      suffix,
			double []   data,
			int         width,
			int         height)
	{
		double [][] data2= new double[][] {data};
		String x3d_path = getX3dDirectory(true);
		String file_name = image_name + suffix;
		String file_path = x3d_path + Prefs.getFileSeparator() + file_name + ".tiff";
		ImageStack imageStack = ShowDoubleFloatArrays.makeStack(data2, width, height, null);
		ImagePlus imp = new ImagePlus( file_name, imageStack);
		FileSaver fs=new FileSaver(imp);
		fs.saveAsTiff(file_path);
		System.out.println("saveDoubleArrayInModelDirectory(): saved "+file_path);
		return imp;
	}
	
	
	public String saveImagePlusInModelDirectory( // use title as filename
			ImagePlus   imp) {
		return saveImagePlusInModelDirectory(
				null, // String      suffix, // null - use title from the imp
				imp); // ImagePlus   imp)
	}
	
	public String saveImagePlusInModelDirectory(
			String      suffix, // null - use title from the imp
			ImagePlus   imp)
	{
		String x3d_path = getX3dDirectory(true);
		String file_name = (suffix==null) ? imp.getTitle():(image_name + suffix);
		String file_path = x3d_path + Prefs.getFileSeparator() + file_name; // + ".tiff";
		if (!file_path.endsWith(".tiff")) {
			file_path +=".tiff";
		}
		if (imp == null) {
			System.out.println("saveImagePlusInModelDirectory(): imp is null. file_path="+file_path);
			return file_path;
		}

		FileSaver fs=new FileSaver(imp);
		fs.saveAsTiff(file_path); // image processor null?
		System.out.println("saveImagePlusInModelDirectory(): saved "+file_path);
		return file_path;
	}
	
	public static String saveImagePlusInDirectory(
			ImagePlus imp,
			String    dir) {
		String file_name = imp.getTitle();
		if (!file_name.endsWith(".tiff")) {
			file_name +=".tiff";
		}
		if (!dir.endsWith(Prefs.getFileSeparator())) {
			dir += Prefs.getFileSeparator();
		}
		String file_path = dir + file_name; // + ".tiff";
		FileSaver fs=new FileSaver(imp);
		fs.saveAsTiff(file_path); // image processor null?
		System.out.println("saveImagePlusInDirectory(): saved "+file_path);
		return file_path;
	}

	/*
		File list_to_concat = new File (video_dir,concat_list_name);

	 */
	public String saveConfInModelDirectory()
	{
		String x3d_path = getX3dDirectory(true);
		String file_name = image_name + "-SETTINGS";
		String file_path = x3d_path + Prefs.getFileSeparator() + file_name;
		Properties properties=new Properties();
		Eyesis_Correction.saveProperties(
				file_path,
				null,
				true,
				properties,
				0); // DEBUG_LEVEL >-3);
		return file_path;
	}
	
	
	public String saveAVIInModelDirectory(
			boolean     dry_run, 
			String      suffix, // null - use title from the imp
			int         mode_avi,
			int         avi_JPEG_quality,
			double      fps,
			ImagePlus   imp) throws IOException
	{
		String [] remove_ext = {".tiff", ".tif", ".avi"};
		String x3d_path = getX3dDirectory(true);
		String file_name = (suffix==null) ? imp.getTitle():(image_name + suffix);
		String file_path = x3d_path + Prefs.getFileSeparator() + file_name; // + ".tiff";
		
		/*
		for (String ext:remove_ext) {
			if (file_path.endsWith(ext)) {
				file_path = file_path.substring(0,file_path.length()-ext.length());
			}
		}
		file_path += ".avi";
		imp.getCalibration().fps = fps;
		if (dry_run) {
			System.out.println("saveAVIInModelDirectory(): simulated writing "+file_path);
		} else {
			(new AVI_Writer()).writeImage (
					imp, // ImagePlus imp,
					file_path, // String path,
					mode_avi, // int compression,
					avi_JPEG_quality); //int jpegQuality)
			System.out.println("saveAVIInModelDirectory(): saved "+file_path);
		}
		return file_path;
		*/
		return saveAVI(
				file_path,        // String      file_path, // forces avi, removes .tif, tiff, and .avi
				dry_run,          // boolean     dry_run, 
				mode_avi,         // int         mode_avi,
				avi_JPEG_quality, // int         avi_JPEG_quality,
				fps,              // double      fps,
				imp);             // ImagePlus   imp) throws IOException
	}

	
	
	public static String saveAVI(
			String      file_path, // forces avi, removes .tif, tiff, and .avi
			boolean     dry_run, 
			int         mode_avi,
			int         avi_JPEG_quality,
			double      fps,
			ImagePlus   imp) throws IOException
	{
		String [] remove_ext = {".tiff", ".tif", ".avi"};
		for (String ext:remove_ext) {
			if (file_path.endsWith(ext)) {
				file_path = file_path.substring(0,file_path.length()-ext.length());
			}
		}
		file_path += ".avi";
		imp.getCalibration().fps = fps;
		if (dry_run) {
			System.out.println("saveAVIInModelDirectory(): simulated writing "+file_path);
		} else {
			(new AVI_Writer()).writeImage (
					imp, // ImagePlus imp,
					file_path, // String path,
					mode_avi, // int compression,
					avi_JPEG_quality); //int jpegQuality)
			System.out.println("saveAVIInModelDirectory(): saved "+file_path);
		}
		return file_path;
	}
	
	
	
	

	
	
	public double [][] readDoubleArrayFromModelDirectory(
			String      suffix,
			int         num_slices, // (0 - all)
			int []      wh)
	{
		float [][] fdata = readFloatArrayFromModelDirectory(
				suffix,     // String      suffix,
				num_slices, // int         num_slices, // (0 - all)
				wh);        // int []      wh)
		if (fdata == null) {
			return null;
		}
		double [][] result = new double [fdata.length][]; 
		for (int n = 0; n < fdata.length; n++) if (fdata[n]!=null) {
			result[n] = new double [fdata[n].length];
			for (int i = 0; i < fdata[n].length; i++) {
				result[n][i] = fdata[n][i];
			}
		}
		return result;
	}
		
	public float [][] readFloatArrayFromModelDirectory(
			String      suffix,
			int         num_slices, // (0 - all)
			int []      wh)
	{
		//		String x3d_path = getX3dDirectory();
		String x3d_path = (isCenterParent() && (getImagePath() != null))?getImagePath():
			readX3dDirectory(correctionsParameters.getModelName(image_name));
		if (x3d_path == null) {
			System.out.println("BUG in readFloatArrayFromModelDirectory(): x3d_path==null.");
			/*
			String x1 = correctionsParameters.selectX3dDirectory(
					false, // boolean smart,
					false); // boolean newAllowed);
			x3d_path = correctionsParameters.selectX3dDirectory(
					correctionsParameters.getModelName(image_name), // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
					correctionsParameters.x3dModelVersion,					
					false, // boolean smart,
					false); // boolean newAllowed);
			*/
		}
		
		
		String file_name = image_name + suffix;
		String file_path = x3d_path + Prefs.getFileSeparator() + file_name + ".tiff";
		return ShowDoubleFloatArrays.readFloatArray(
				file_path,  // String      file_path,
				num_slices, // int         num_slices, // (0 - all)
				wh);        // int []      wh)
	}
	

	
	public ImagePlus readImagePlusFromModelDirectory(
			String      suffix)
	{
		//		String x3d_path = getX3dDirectory();
		String x3d_path = readX3dDirectory(correctionsParameters.getModelName(image_name));
		String file_name = image_name + suffix;
		String file_path = x3d_path + Prefs.getFileSeparator() + file_name + ".tiff";
		System.out.println("readImagePlusFromModelDirectory(): reading "+file_path);
		ImagePlus imp = null;
		try {
			imp = new ImagePlus(file_path);
		} catch (Exception e) {
			System.out.println ("Failed to open "+file_path+", maybe will generate it");
		}
		if ((imp == null) || (imp.getTitle() == null) || (imp.getTitle().equals(""))) {
			return null;
		}
		return imp;
	}
	
	

	public void saveDSI() { saveDSI(this.dsi);}
	
	public void saveDSI(
			double [][] dsi
			)
	{
		String x3d_path = getX3dDirectory(true);
		String title = image_name+TwoQuadCLT.DSI_COMBO_SUFFIX;
		ImagePlus imp = ShowDoubleFloatArrays.makeArrays(
				dsi,tp.getTilesX(), tp.getTilesY(),  title, TwoQuadCLT.DSI_SLICES);
		eyesisCorrections.saveAndShow(
				imp,      // ImagePlus             imp,
				x3d_path, // String                path,
				false,    // boolean               png,
				false,    // boolean               show,
				0);       // int                   jpegQuality)
	}

	public void showDSI(){ showDSI(this.dsi);}
	public void showDSI(double [][] dsi)
	{
		  String title = image_name + TwoQuadCLT.DSI_COMBO_SUFFIX;
		  ShowDoubleFloatArrays.showArrays(
				  dsi, tp.getTilesX(), tp.getTilesY(), true, title, TwoQuadCLT.DSI_SLICES);
	}

	public void saveDSIMain(){saveDSIMain(this.dsi);}
	public void saveDSIMain(
			double [][] dsi) // DSI_SLICES.length
	{
		String x3d_path = getX3dDirectory(true);
		String title = image_name+"-DSI_MAIN";
		String []   titles =   {
				TwoQuadCLT.DSI_SLICES[TwoQuadCLT.DSI_DISPARITY_MAIN],
				TwoQuadCLT.DSI_SLICES[TwoQuadCLT.DSI_STRENGTH_MAIN]};
		double [][] dsi_main = {
				dsi[TwoQuadCLT.DSI_DISPARITY_MAIN],
				dsi[TwoQuadCLT.DSI_STRENGTH_MAIN]};

		ImagePlus imp = ShowDoubleFloatArrays.makeArrays(
				dsi_main, tp.getTilesX(), tp.getTilesY(),  title, titles);
		eyesisCorrections.saveAndShow(
				imp,      // ImagePlus             imp,
				x3d_path, // String                path,
				false,    // boolean               png,
				false,    // boolean               show,
				0);       // int                   jpegQuality)
	}

	public void saveDSIAll(
			String suffix, // "-DSI_MAIN"
			double [][] dsi) // DSI_SLICES.length
	{
		String x3d_path = getX3dDirectory(true);
		String title = image_name+suffix; // "-DSI_MAIN";
		ImagePlus imp = ShowDoubleFloatArrays.makeArrays(
				dsi, tp.getTilesX(), tp.getTilesY(),  title, TwoQuadCLT.DSI_SLICES);
		eyesisCorrections.saveAndShow(
				imp,      // ImagePlus             imp,
				x3d_path, // String                path,
				false,    // boolean               png,
				false,    // boolean               show,
				0);       // int                   jpegQuality)
	}

	public void offsetDSI(
			double inf_disp) {
		offsetDSI(
				inf_disp, // double inf_disp,
				this.dsi); // double [][] dsi)
		return;
	}

	public static void offsetDSI(  // applies to all disparity slices
			double inf_disp,
			double [][] dsi) {
		if (dsi == null) {
			throw new IllegalArgumentException ("offsetDSI(): can not apply as dsi==null");
		}
		for (int nslice:TwoQuadCLT.DSI_DISPARITY)  if (dsi[nslice] != null){
			double [] slice = dsi[nslice];
			for (int i = 0; i < slice.length; i++) {
				slice[i] -= inf_disp;
			}
		}
		return;
	}
	
	public boolean offsetComboDSI(
			CLTParameters clt_parameters,
			double        inf_disp,
			boolean       silent) {
		double [][] combo_dsi = restoreComboDSI ( silent);
		if (combo_dsi==null) {
			return false;
		}
		offsetComboDSI(
				inf_disp, // double inf_disp,
				combo_dsi); // double [][] dsi)
		String rslt_suffix = "-INTER-INTRA";
		rslt_suffix += (clt_parameters.correlate_lma?"-LMA":"-NOLMA");
		saveDoubleArrayInModelDirectory( // error
				rslt_suffix,           // String      suffix,
				OpticalFlow.COMBO_DSN_TITLES, // combo_dsn_titles_full, // null,          // String []   labels, // or null
				combo_dsi,             // dbg_data,         // double [][] data,
				getTilesX(),                // int         width,
				getTilesY());               // int         height)
		return true;
	}
	
	
	public static void offsetComboDSI( // offsets all combo disparity slices
			double inf_disp,
			double [][] combo_dsi) {
		if (combo_dsi == null) {
			throw new IllegalArgumentException ("offsetDSI(): can not apply as dsi==null");
		}
		for (int nslice:OpticalFlow.COMBO_DSN_DISPARITY)  if (combo_dsi[nslice] != null){ 
			double [] slice = combo_dsi[nslice];
			for (int i = 0; i < slice.length; i++) {
				slice[i] -= inf_disp;
			}
		}
		return;
	}
	

	// Save GT from main and AUX calculated DS
	public void saveDSIGTAux(
			QuadCLT quadCLT_aux,
			double [][] dsi_aux_from_main)
	{
		String x3d_path = getX3dDirectory(true);
		String title = quadCLT_aux.image_name+"-DSI_GT-AUX";
//		String []   titles =   {DSI_SLICES[DSI_DISPARITY_MAIN], DSI_SLICES[DSI_STRENGTH_MAIN]};
//		double [][] dsi_main = {dsi[DSI_DISPARITY_MAIN],        dsi[DSI_STRENGTH_MAIN]};

		ImagePlus imp = ShowDoubleFloatArrays.makeArrays(
				dsi_aux_from_main, // dsi_main,
				quadCLT_aux.tp.getTilesX(),
				quadCLT_aux.tp.getTilesY(),
				title,
				QuadCLT.FGBG_TITLES_AUX); // titles);
		eyesisCorrections.saveAndShow(
				imp,      // ImagePlus             imp,
				x3d_path, // String                path,
				false,    // boolean               png,
				false,    // boolean               show,
				0);       // int                   jpegQuality)
	}
	public void showDSIMain() {
		showDSIMain(
				this.dsi,
				isAux());
	}
	
	public void showDSIMain(
			double [][] dsi,
			boolean use_aux)
	{
		  String title = image_name+"-DSI_MAIN";
		  String []   titles =   {
				  TwoQuadCLT.DSI_SLICES[use_aux?TwoQuadCLT.DSI_DISPARITY_AUX:TwoQuadCLT.DSI_DISPARITY_MAIN],
				  TwoQuadCLT.DSI_SLICES[use_aux?TwoQuadCLT.DSI_DISPARITY_AUX_LMA:TwoQuadCLT.DSI_DISPARITY_MAIN_LMA],
				  TwoQuadCLT.DSI_SLICES[use_aux?TwoQuadCLT.DSI_STRENGTH_AUX:TwoQuadCLT.DSI_STRENGTH_MAIN]};
		  double [][] dsi_main = {
				  dsi[use_aux?TwoQuadCLT.DSI_DISPARITY_AUX:TwoQuadCLT.DSI_DISPARITY_MAIN],
				  dsi[use_aux?TwoQuadCLT.DSI_DISPARITY_AUX_LMA:TwoQuadCLT.DSI_DISPARITY_MAIN_LMA],
				  dsi[use_aux?TwoQuadCLT.DSI_STRENGTH_AUX:TwoQuadCLT.DSI_STRENGTH_MAIN]};

		  ShowDoubleFloatArrays.showArrays(dsi_main,tp.getTilesX(), tp.getTilesY(), true, title, titles);
	}
	
	
	
    
    public boolean hasNewImageData() {
    	return new_image_data;
    }
    
    public void setHasNewImageData(boolean has_data) {
    	new_image_data = has_data;
    }
    
    public double [][][] getResetImageData(){
    	new_image_data = false;
    	if (image_data_alt != null) {
    		return image_data_alt;
    	} else {
    		return image_data;
    	}
    }
    public double [][][] getImageData(){ // does not reset new data
    	if (image_data_alt != null) {
    		return image_data_alt;
    	} else {
    		return image_data;
    	}
    }

    public double [][][] getOrigImageData(){ // does not reset new data
    	return image_data;
    }
    public double [][][] getAltImageData(){ // does not reset new data
    	return image_data_alt;
    }
    
    public void setImageData(double [][][] data) {
    	image_data = data;
    	new_image_data = true;
    	image_fpn_applied = null;
    	image_fpn = null;
    }

    public void setImageDataAlt(double [][][] data) {
    	image_data_alt = data;
    	new_image_data = true; // even if null set new data as it will use image_data after image_data_alt
//    	image_fpn_applied = null;
//    	image_fpn = null;
    }
    

    public double [][] getResetImageCenter(){
    	new_image_data = false;
    	boolean debug = true; // false;
    	if (debug) {
    		if (image_center == null) {
    			System.out.println("--- getResetImageCenter(): image_center== null");

    		} else {
    			System.out.println("--- getResetImageCenter(): image_center={"+image_center[0][0]+","+image_center[0][1]+","+image_center[0][2]+","+image_center[0][3]);

    		}
    	}
    	return image_center;
    }
    public double [][] getImageCenter(){ // does not reset new data
    	boolean debug = false;
    	if (debug) {
    		if (image_center == null) {
    			System.out.println("--- getImageCenter(): image_center== null");

    		} else {
    			System.out.println("--- getImageCenter(): image_center={"+image_center[0][0]+","+image_center[0][1]+","+image_center[0][2]+","+image_center[0][3]);

    		}
    	}
    	return image_center;
    }
    public void setImageCenter(double [][] data) {
    	image_center = data;
    	new_image_data = true;
//    	image_fpn_applied = null;
//    	image_fpn = null;
    	boolean debug = false;
    	if (debug) {
    		if (image_center == null) {
    			System.out.println("+++ setImageCenter(): image_center== null");

    		} else {
    			System.out.println("+++ setImageCenter(): image_center={"+image_center[0][0]+","+image_center[0][1]+","+image_center[0][2]+","+image_center[0][3]);
    		}
    	}
    	return;
    }

    
    

    public void setFpnApplied(double [][][] data) {
    	image_fpn_applied = data;
    }
    
// magic scale should be set before using  TileProcessor (calculated disparities depend on it)

    public boolean isAux()        {return is_aux;} // USED in lwir
    public String  sAux()         {return isAux()?"-AUX":"";} // USED in lwir

////    public boolean isLwir()       {return !Double.isNaN(lwir_offset);} // clt_kernels // USED in lwir
////    public boolean isMonochrome() {return is_mono;}  // USED in lwir
    public boolean isLwir()       {return geometryCorrection.isLwir();} // clt_kernels // USED in lwir
    public boolean isMonochrome() {return geometryCorrection.isMonochrome();} // clt_kernels // USED in lwir
    
    public double  getLwirOffset() {
    	double s = 0;
    	if (lwir_offsets == null) {
    		throw new IllegalArgumentException ("Trying to use non-existing lwir_offsets!");
    	}
    	for (double offset:lwir_offsets) s+=offset;
    	lwir_offset = s/lwir_offsets.length;
    	return lwir_offset;
    }
    
    public boolean isLwirCalibrated() {
    	return lwir_offsets != null;
    }
    public void resetLwirCalibration() {
    	lwir_offsets = null;
    	lwir_scales =  null;
    	lwir_scales2 = null;
    }
    public double [] getLwirOffsets() {
    	if (lwir_offsets == null) {
    		lwir_offsets = new double [getNumSensors()];
    	}
    	return lwir_offsets;
    }
	public double [] getLwirScales() {
    	if (lwir_scales == null) {
    		lwir_scales = new double [getNumSensors()];
    		Arrays.fill(lwir_scales, 1.0);
    	}
    	return lwir_scales;
    }
    public double [] getLwirScales2() {
    	if (lwir_scales2 == null) {
    		lwir_scales2 = new double [getNumSensors()]; // all 0;
    	}
    	return lwir_scales2;
    }
    public void setLwirOffsets(double [] offsets) {
    	lwir_offsets = offsets; // will need to update properties!
    	if (offsets != null) {
    		double s = 0;
    		for (double offset:offsets) s+=offset;
    		lwir_offset = s/offsets.length;
    	} else {
    		lwir_offset = Double.NaN;
    	}
//    	this.photometric_scene = getImageName();
    }

    public String getPhotometricScene() {
    	return this.photometric_scene;
    }
    public void setPhotometricScene(String scene_name) {
    	this.photometric_scene = scene_name;
    }

    public void setPhotometricScene() {
    	setPhotometricScene(getImageName());
    }
    
    public boolean isPhotometricThis() {
    	if (this.photometric_scene == null) {
    		return false;
    	}
    	return this.photometric_scene.equals(getImageName());
    }
    

    public void setLwirScales(double [] scales) {
    	lwir_scales = scales; // will need to update properties!
//    	this.photometric_scene = getImageName();
    }
    public void setLwirScales2(double [] scales2) {
    	lwir_scales2 = scales2; // will need to update properties!
//    	this.photometric_scene = getImageName();
    }

    public double [] getColdHot() { // USED in lwir
    	return lwir_cold_hot;
    }
    public void setColdHot(double [] cold_hot) { // USED in lwir
    	lwir_cold_hot = cold_hot;
    }
    public void setColdHot(double cold, double hot) { // not used in lwir
    	lwir_cold_hot = new double[2];
    	lwir_cold_hot[0] = cold;
    	lwir_cold_hot[1] = hot;
    }

    public void    resetGroundTruthByRig() { // not used in lwir
    	tp.rig_disparity_strength = null;
    }
    public double [][] getGroundTruthByRig(){ // not used in lwir
    	if (tp == null) return null;
    	return tp.rig_disparity_strength;
    }
	public void setTiles (
			ImagePlus imp, // set tp.tilesX, tp.tilesY // USED in lwir
			int numSensors,
			CLTParameters    clt_parameters,
			int threadsMax
			){
		setTiles(
				numSensors,
				clt_parameters,
				imp.getWidth()/clt_parameters.transform_size,
				imp.getHeight()/clt_parameters.transform_size,
				threadsMax);
	}

	public void setTiles ( // USED in lwir
			int numSensors,
			CLTParameters    clt_parameters,
			int tilesX,
			int tilesY,
			int threadsMax
			){
		if (tp == null){
			tp = new TileProcessor(
					tilesX,
					tilesY,
					clt_parameters.transform_size,
					clt_parameters.stSize,
					numSensors,
					isMonochrome(),
					isLwir(),
					isAux(),
					clt_parameters.corr_magic_scale,
					clt_parameters.grow_disp_trust,
					clt_parameters.max_overexposure, // double maxOverexposure,
					threadsMax);
		}
	}

// used for aux camera only
	public boolean setupImageData( // not used in lwir Called for quadCLT_aux instance
			String image_name,
			String [] sourceFiles,
			CLTParameters       clt_parameters,
			ColorProcParameters                            colorProcParameters, //
			int threadsMax,
			int debugLevel) {
		  SetChannels [] set_channels_aux =  setChannels(image_name, debugLevel);
		  if ((set_channels_aux == null) || (set_channels_aux.length==0)) {
			  System.out.println("No files for the auxiliary camera match series "+image_name);
			  return false;
		  }
		  double [] referenceExposures_aux =  eyesisCorrections.calcReferenceExposures(debugLevel); // multiply each image by this and divide by individual (if not NaN)
		  int [] channelFiles_aux =  set_channels_aux[0].fileNumber();
		  // make single
		  boolean [][] saturation_aux =  (clt_parameters.sat_level > 0.0)? new boolean[channelFiles_aux.length][] : null;
		  double [] scaleExposures_aux =  new double[channelFiles_aux.length];
//		  ImagePlus [] imp_srcs_aux = 
		  conditionImageSet(
				  clt_parameters,               // EyesisCorrectionParameters.CLTParameters  clt_parameters,
				  colorProcParameters,          //  ColorProcParameters                       colorProcParameters, //
				  sourceFiles,                  // String []                                 sourceFiles,
				  image_name,      // set_channels_aux[0].name(), // String                                    set_name,
				  referenceExposures_aux,       // double []                                 referenceExposures,
				  channelFiles_aux,             // int []                                    channelFiles,
				  scaleExposures_aux,           //output  // double [] scaleExposures
				  saturation_aux,               //output  // boolean [][]                              saturation_imp,
				  threadsMax,                 // int                                       threadsMax,
				  debugLevel); // int                                       debugLevel);

		  return true;

	}


	public QuadCLTCPU(
			String                                          prefix,
			Properties                                      properties,
			EyesisCorrections                               eyesisCorrections,
			EyesisCorrectionParameters.CorrectionParameters correctionsParameters
			){
		this.eyesisCorrections=      eyesisCorrections;
		this.correctionsParameters = correctionsParameters;
		this.properties =            properties;
		is_aux =                     prefix.equals(PREFIX_AUX);
		getProperties(prefix); // failed with aux
	}

	public static Properties loadProperties(
			String path,
			Properties properties,
			boolean silent){
		if (properties == null) {
			properties = new Properties();
		}
		InputStream is;
		try {
			is = new FileInputStream(path);
		} catch (FileNotFoundException e) {
			if (!silent) {
				IJ.showMessage("Error","Failed to open configuration file: "+path);
			}
			return null;
		}
		try {
			properties.loadFromXML(is);

		} catch (IOException e) {
			if (!silent) {
				IJ.showMessage("Error","Failed to read XML configuration file: "+path);
			}
			return null;
		}
		try {
			is.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return properties;
		//	     getAllProperties(properties);
		//		 if (DEBUG_LEVEL>0) System.out.println("Configuration parameters are restored from "+path);
	}

	public Properties setProperties(String prefix, Properties properties){ // save // USED in lwir
		if (properties == null) {
			properties = this.properties;
		}
		for (int n = 0; n < fine_corr.length; n++){
			for (int d = 0; d < fine_corr[n].length; d++){
				for (int i = 0; i < fine_corr[n][d].length; i++){
					String name = prefix+"fine_corr_"+n+fine_corr_dir_names[d]+fine_corr_coeff_names[i];
					properties.setProperty(name,   this.fine_corr[n][d][i]+"");
				}
			}
		}
		GeometryCorrection gc = geometryCorrection;
		if (gc == null) { // if it was not yet created
			gc = new GeometryCorrection(this.extrinsic_vect); // not used in lwir
		}
		gc.setPropertiesExtrinsic(prefix, properties);
		if (is_aux && (gc.rigOffset != null)) {
			gc.rigOffset.setProperties(prefix,properties);
		}
		if (gc instanceof ErsCorrection) {
			ErsCorrection ers = (ErsCorrection) gc;
			ers.setPropertiesPose(prefix, properties);
			ers.setPropertiesERS(prefix, properties);
			ers.setPropertiesScenes(prefix, properties);  // null pointer
			ers.setPropertiesLineTime(prefix, properties);
		}
		properties.setProperty(prefix+"num_orient",        this.num_orient+"");
		properties.setProperty(prefix+"num_accum",         this.num_accum+"");
		properties.setProperty(prefix+"disp_infinity_ref", this.disp_infinity_ref+"");
		
		if (this.lwir_offsets != null) {
			properties.setProperty(prefix+"lwir_offsets",
					IntersceneMatchParameters.doublesToString(this.lwir_offsets));
		}
		if (this.lwir_scales != null) {
			properties.setProperty(prefix+"lwir_scales",
					IntersceneMatchParameters.doublesToString(this.lwir_scales));
		}
		if (this.lwir_scales2 != null) {
			properties.setProperty(prefix+"lwir_scales2",
					IntersceneMatchParameters.doublesToString(this.lwir_scales2));
		}
		if (this.photometric_scene != null) {
			properties.setProperty(prefix+"photometric_scene", this.photometric_scene);
		}
		
		if (this.timestamp_reference != null) {
			properties.setProperty(prefix+"timestamp_reference", this.timestamp_reference);
		}
		if (this.timestamp_first != null) {
			properties.setProperty(prefix+"timestamp_first", this.timestamp_first);
		}
		if (this.timestamp_quarter1 != null) {
			properties.setProperty(prefix+"timestamp_quarter1", this.timestamp_quarter1);
		}
		if (this.timestamp_index != null) {
			properties.setProperty(prefix+"timestamp_index", this.timestamp_index);
		}
		if (this.timestamp_last != null) {
			properties.setProperty(prefix+"timestamp_last", this.timestamp_last);
		}
		if (this.timestamp_quarter3 != null) {
			properties.setProperty(prefix+"timestamp_quarter3", this.timestamp_quarter3);
		}
		
		if (this.quat_corr != null) {
			properties.setProperty(prefix+"quat_corr",  IntersceneMatchParameters.doublesToString(this.quat_corr));		
		}

		if (this.enu_corr_metric != null) {
			properties.setProperty(prefix+"enu_corr_metric",  IntersceneMatchParameters.doublesToString(this.enu_corr_metric));		
		}
		
		setPropertiesReferenceScenes(prefix, properties, ref_scenes);
		
		if (ground_plane != null) ground_plane.setProperties(prefix, properties);
		return properties;
	}

	public static HashSet<String> getPropertiesReferenceScenes(String parent_prefix,Properties properties) {
		String prefix = parent_prefix+"refscenes_";
		HashSet<String> timestamps = new HashSet<String>();
		for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
			String key = (String) e.nextElement();
			if (key.startsWith(prefix)) { // 
				timestamps.add(key.substring(prefix.length()));
			}
		}
		if (timestamps.isEmpty()) {
			return null;
		}
		return timestamps;
	}

	public static void setPropertiesReferenceScenes(String parent_prefix,Properties properties, HashSet<String> ref_timestamps) {
		String prefix = parent_prefix+"refscenes_";
		if (ref_timestamps != null) {
			for (String ts : ref_timestamps) {
				properties.setProperty(prefix+ts,  ""); // set empty string - nothing to put there		
			}
		}
	
	}
	
	
	

	public void copyPropertiesFrom(Properties other_properties, String other_prefix, String this_prefix){ // save // not used in lwir
		for (int n = 0; n < fine_corr.length; n++){
			for (int d = 0; d < fine_corr[n].length; d++){
				for (int i = 0; i < fine_corr[n][d].length; i++){
					String other_name = other_prefix+"fine_corr_"+n+fine_corr_dir_names[d]+fine_corr_coeff_names[i];
					String this_name = this_prefix+"fine_corr_"+n+fine_corr_dir_names[d]+fine_corr_coeff_names[i];
		  			if (other_properties.getProperty(other_name)!=null) {
		  				this.fine_corr[n][d][i]=Double.parseDouble(other_properties.getProperty(other_name));
						properties.setProperty(this_name,   this.fine_corr[n][d][i]+"");
		  			}
				}
			}
		}
		double [] other_extrinsic_vect = GeometryCorrection.getPropertiesExtrinsic(other_prefix, other_properties);
		int num_cams = CorrVector.getCamerasFromEV(other_extrinsic_vect.length);
		GeometryCorrection.setPropertiesExtrinsic(this_prefix, properties, other_extrinsic_vect);

		if (other_properties.getProperty(other_prefix+"num_orient")!=null) {
			this.num_orient = Integer.parseInt(other_properties.getProperty(other_prefix+"num_orient"));
			properties.setProperty(this_prefix+"num_orient",   this.num_orient+"");
		}
		if (other_properties.getProperty(other_prefix+"num_accum")!=null) {
			this.num_accum = Integer.parseInt(other_properties.getProperty(other_prefix+"num_accum"));
			properties.setProperty(this_prefix+"num_accum",   this.num_accum+"");
		}

		if (other_properties.getProperty(other_prefix+"disp_infinity_ref")!=null) {
			this.disp_infinity_ref = Double.parseDouble(other_properties.getProperty(other_prefix+"disp_infinity_ref"));
			properties.setProperty(this_prefix+"disp_infinity_ref",   this.disp_infinity_ref+"");
		}
		
		// copy offsets and scales
		if (other_properties.getProperty(other_prefix+"lwir_offsets")!=null) {
			this.lwir_offsets= IntersceneMatchParameters.StringToDoubles(
					other_properties.getProperty(other_prefix+"lwir_offsets"),0);
			properties.setProperty(this_prefix+"lwir_offsets",
					IntersceneMatchParameters.doublesToString(this.lwir_offsets));
		}
		if (other_properties.getProperty(other_prefix+"lwir_scales")!=null) {
			this.lwir_scales= IntersceneMatchParameters.StringToDoubles(
					other_properties.getProperty(other_prefix+"lwir_scales"),0);
			properties.setProperty(this_prefix+"lwir_scales",
					IntersceneMatchParameters.doublesToString(this.lwir_scales));
		}
		if (other_properties.getProperty(other_prefix+"lwir_scales2")!=null) {
			this.lwir_scales2= IntersceneMatchParameters.StringToDoubles(
					other_properties.getProperty(other_prefix+"lwir_scales2"),0);
			properties.setProperty(this_prefix+"lwir_scales2",
					IntersceneMatchParameters.doublesToString(this.lwir_scales2));
		}
		if (other_properties.getProperty(other_prefix+"photometric_scene")!=null) {
			this.photometric_scene= (String) other_properties.getProperty(other_prefix+"photometric_scene");
			properties.setProperty(this_prefix+"photometric_scene", this.photometric_scene);
		}
//		System.out.println("Done copyPropertiesFrom");
	}

	public GeometryCorrection  getGeometryCorrection() { // USED in lwir
		return geometryCorrection;
	}
	
	public ErsCorrection getErsCorrection() { // USED in lwir
		if (geometryCorrection instanceof ErsCorrection) {
			return (ErsCorrection) geometryCorrection;
		} else {
			return new ErsCorrection (geometryCorrection, false); // just upgrade
		}
	}
	
	public boolean tsExists(String ts) {
		ErsCorrection ers_reference = getErsCorrection();
		return ((ers_reference.getSceneXYZ(ts) != null) && (ers_reference.getSceneATR(ts) != null));
	}
	
	
	
	public double [][][][][][] getCLTKernels(){ // USED in lwir
		return clt_kernels;
	}
	public void listGeometryCorrection(boolean full){ // not used in lwir
		GeometryCorrection gc = geometryCorrection;
		if (gc == null) { // if it was not yet created
			gc = new GeometryCorrection(this.extrinsic_vect);
		}
		gc.listGeometryCorrection(full);
	}

	public void getProperties(String prefix){ // restore // USED in lwir
//		System.out.println("getProperties("+prefix+")");
		for (int n = 0; n < fine_corr.length; n++){
			for (int d = 0; d < fine_corr[n].length; d++){
				for (int i = 0; i < fine_corr[n][d].length; i++){
					String name = prefix+"fine_corr_"+n+fine_corr_dir_names[d]+fine_corr_coeff_names[i];
		  			if (properties.getProperty(name)!=null) this.fine_corr[n][d][i]=Double.parseDouble(properties.getProperty(name));// null
				}
			}
		}
		// always set extrinsic_corr
		double [] new_extrinsic_vect = GeometryCorrection.getPropertiesExtrinsic(prefix, properties);
//		int num_cams = CorrVector.getCamerasFromEV(new_extrinsic_vect.length);
		for (int i = 0; i < new_extrinsic_vect.length; i++) {
			if (!Double.isNaN(new_extrinsic_vect[i])) {
  				if (this.extrinsic_vect == null) { // not used in lwir
  					// only create non-null array if there are saved values
  					this.extrinsic_vect = new_extrinsic_vect.clone();
  				}
  				this.extrinsic_vect[i] =  new_extrinsic_vect[i];
  				if (geometryCorrection != null){ // not used in lwir
  					geometryCorrection.setCorrVector(i,this.extrinsic_vect[i]); // should be same number of cameras
  				}
			}
		}
		
		if (geometryCorrection == null) {
			double [] extrinsic_vect_saved = this.extrinsic_vect.clone();
			boolean OK = initGeometryCorrection(0); // int debugLevel);
			if (!OK) { // not used in lwir
				throw new IllegalArgumentException ("Failed to initialize geometry correction");
			}
			// Substitute vector generated in initGeometryCorrection with the saved from properties one:
			// it also replaces data inside geometryCorrection. TODO: redo to isolate this.extrinsic_vect from geometryCorrection
			if (extrinsic_vect_saved.length == this.extrinsic_vect.length) {
				this.extrinsic_vect = 	extrinsic_vect_saved;
			} else {
				System.out.println("Ignoring incompatible saved extrinsic vector ("+extrinsic_vect_saved.length+
						" long) as current vector length is " + this.extrinsic_vect.length);
			}
			geometryCorrection.setCorrVector(this.extrinsic_vect);
//			geometryCorrection = new GeometryCorrection(this.extrinsic_vect);
		}
		if (is_aux) {
			geometryCorrection.setRigOffsetFromProperies(prefix, properties);
		}
		// inter-frame properties only make sense for, well, scenes. So they will only be read
		if (properties.getProperty(prefix+"num_orient")!=null) {
			this.num_orient=Integer.parseInt(properties.getProperty(prefix+"num_orient"));
		}

		if (properties.getProperty(prefix+"num_accum")!=null) {
			this.num_accum= Integer.parseInt(properties.getProperty(prefix+"num_accum"));
		}
		
		if (properties.getProperty(prefix+"disp_infinity_ref")!=null) {
			this.disp_infinity_ref= Double.parseDouble(properties.getProperty(prefix+"disp_infinity_ref"));
		}
		
		if (properties.getProperty(prefix+"lwir_offsets")!=null) {
			this.lwir_offsets= IntersceneMatchParameters.StringToDoubles(
					properties.getProperty(prefix+"lwir_offsets"),0);
		}
		if (properties.getProperty(prefix+"lwir_scales")!=null) {
			this.lwir_scales= IntersceneMatchParameters.StringToDoubles(
					properties.getProperty(prefix+"lwir_scales"),0);
		}
		if (properties.getProperty(prefix+"lwir_scales2")!=null) {
			this.lwir_scales2= IntersceneMatchParameters.StringToDoubles(
					properties.getProperty(prefix+"lwir_scales2"),0);
		}
		if (properties.getProperty(prefix+"photometric_scene")!=null) {
			this.photometric_scene= (String) properties.getProperty(prefix+"photometric_scene");
		}
		if (properties.getProperty(prefix+"timestamp_reference")!=null) {
			this.timestamp_reference= (String) properties.getProperty(prefix+"timestamp_reference");
		}
		
		if (properties.getProperty(prefix+"timestamp_first")!=null) {
			this.timestamp_first= (String) properties.getProperty(prefix+"timestamp_first");
		}
		if (properties.getProperty(prefix+"timestamp_quarter1")!=null) {
			this.timestamp_quarter1= (String) properties.getProperty(prefix+"timestamp_quarter1");
		}
		if (properties.getProperty(prefix+"timestamp_last")!=null) {
			this.timestamp_last= (String) properties.getProperty(prefix+"timestamp_last");
		}
		if (properties.getProperty(prefix+"timestamp_quarter3")!=null) {
			this.timestamp_quarter3= (String) properties.getProperty(prefix+"timestamp_quarter3");
		}
		if (properties.getProperty(prefix+"timestamp_index")!=null) {
			this.timestamp_index= (String) properties.getProperty(prefix+"timestamp_index");
		}
		if (properties.getProperty(prefix+"quat_corr")!=null) {
	        this.quat_corr= IntersceneMatchParameters.StringToDoubles(properties.getProperty(prefix+"quat_corr"),4);
		}
		if (properties.getProperty(prefix+"enu_corr_metric")!=null) {
	        this.enu_corr_metric= IntersceneMatchParameters.StringToDoubles(properties.getProperty(prefix+"enu_corr_metric"),3);
		}
		this.ref_scenes = getPropertiesReferenceScenes(prefix,properties);
		
		this.ground_plane = GroundPlane.getGroundPlaneProperties(prefix, properties);
	
	}

	public void setKernelImageFile(ImagePlus img_kernels){ // not used in lwir
		eyesisKernelImage = img_kernels;
	}

	public boolean kernelImageSet(){ // not used in lwir
		return eyesisKernelImage != null;
	}

	public boolean CLTKernelsAvailable(){ // USED in lwir
		return clt_kernels != null;
	}
	public boolean geometryCorrectionAvailable(){ // USED in lwir
		return (geometryCorrection != null) && geometryCorrection.isInitialized();
	}
	public void resetGeometryCorrection() {
		geometryCorrection = null;
//		extrinsic_vect = new double [GeometryCorrection.CORR_NAMES.length];
		extrinsic_vect = null;
	}
	public boolean initGeometryCorrection(int debugLevel){ // USED in lwir
		// keep rig offsets if edited
		if (eyesisCorrections.pixelMapping == null) {
			// need to initialize sensor data
//			eyesisCorrections.initSensorFiles(.debugLevel..);
			eyesisCorrections.initPixelMapping(debugLevel);
		}
		PixelMapping.SensorData [] sensors =  eyesisCorrections.pixelMapping.sensors;
		// verify that all sensors have the same distortion parameters
		int numSensors = sensors.length;
// if num_sensors mismatch extrinsic_vect - reset extrinsic_vect and
		int vector_length = CorrVector.getLength(numSensors);
		if ((extrinsic_vect == null) || (extrinsic_vect.length != vector_length)) {
			if (extrinsic_vect == null) {
				System.out.println("initGeometryCorrection(): Was not expecting extrinsic_vect == null");
			} else {
				System.out.println("initGeometryCorrection(): extrinsic_vect.length="+extrinsic_vect.length+
						" does not match "+numSensors+ " sensors (it should be "+vector_length+")");
			}
			System.out.println("Resetting geometryCorrection");
			geometryCorrection = null;
			System.out.println("Resetting extrinsic_vect");
			extrinsic_vect = new double[vector_length];
		}
		if (geometryCorrection == null) {
			geometryCorrection = new GeometryCorrection(extrinsic_vect);
		}
		
		
		
		
		for (int i = 1; i < numSensors; i++){
			if (//	(sensors[0].focalLength !=           sensors[i].focalLength) || // null pointer
					(sensors[0].distortionC !=           sensors[i].distortionC) ||
					(sensors[0].distortionB !=           sensors[i].distortionB) ||
					(sensors[0].distortionA !=           sensors[i].distortionA) ||
					(sensors[0].distortionA5 !=          sensors[i].distortionA5) ||
					(sensors[0].distortionA6 !=          sensors[i].distortionA6) ||
					(sensors[0].distortionA7 !=          sensors[i].distortionA7) ||
					(sensors[0].distortionA8 !=          sensors[i].distortionA8) ||
					(sensors[0].distortionRadius !=      sensors[i].distortionRadius) ||
					(sensors[0].pixelCorrectionWidth !=  sensors[i].pixelCorrectionWidth) ||
					(sensors[0].pixelCorrectionHeight != sensors[i].pixelCorrectionHeight) ||
					(sensors[0].pixelSize !=             sensors[i].pixelSize) ||
					(sensors[0].lineTime !=              sensors[i].lineTime) ||
					(sensors[0].monochrome !=            sensors[i].monochrome) ||
					(sensors[0].lwir !=                  sensors[i].lwir)
					){
				System.out.println("initGeometryCorrection(): All sensors have to have the same distortion model, but channels 0 and "+i+" mismatch");
				return false; // not used in lwir
			}
		}


		// TODO: Verify correction sign!
		double [] f_lengths = new double [sensors.length];
		for (int i = 0; i < f_lengths.length; i++) {
			f_lengths[i] = sensors[i].focalLength;
		}
		double f_avg = geometryCorrection.getCorrVector().setZoomsFromF(f_lengths);

		// following parameters are used for scaling extrinsic corrections
		geometryCorrection.focalLength = f_avg;
		geometryCorrection.pixelSize =  sensors[0].pixelSize; 
		//geometryCorrection.line_time =  sensors[0].lineTime;  // set in setDistortion() 
		//geometryCorrection.monochrome = sensors[0].monochrome // set in setDistortion();
		//geometryCorrection.lwir =       sensors[0].lwir;      // set in setDistortion()
		
		geometryCorrection.distortionRadius = sensors[0].distortionRadius;

		// What was that below? Started smth?
//		for (int i = CorrVector.LENGTH_ANGLES; i < CorrVector.LENGTH; i++){
//		}
		// set common distportion parameters
		geometryCorrection.setDistortion(
				f_avg, // sensors[0].focalLength,
				sensors[0].distortionC,
				sensors[0].distortionB,
				sensors[0].distortionA,
				sensors[0].distortionA5,
				sensors[0].distortionA6,
				sensors[0].distortionA7,
				sensors[0].distortionA8,
				sensors[0].distortionRadius,
				sensors[0].pixelCorrectionWidth,   // virtual camera center is at (pixelCorrectionWidth/2, pixelCorrectionHeight/2)
				sensors[0].pixelCorrectionHeight,
				sensors[0].pixelSize,
				sensors[0].lineTime,
				sensors[0].monochrome,
				sensors[0].lwir
				);
		// set other/individual sensor parameters
		double [] thetas = new double [sensors.length];
		for (int i = 0; i < thetas.length; i++) thetas[i] = sensors[i].theta;
		double theta_avg = geometryCorrection.getCorrVector().setTiltsFromThetas(thetas);

		double [] headings = new double [sensors.length];
		for (int i = 0; i < headings.length; i++) headings[i] = sensors[i].heading;
		double heading_avg = geometryCorrection.getCorrVector().setAzimuthsFromHeadings(headings);
		double []   forward = new double[numSensors];
		double []   right =   new double[numSensors];
		double []   height =  new double[numSensors];
		double []   roll =    new double[numSensors];
		double [][] pXY0 =    new double[numSensors][2];
		for (int i = 0; i < numSensors; i++){
			forward[i] = sensors[i].forward;
			right[i] =   sensors[i].right;
			height[i] =  sensors[i].height;
			roll[i] =    sensors[i].psi;
			pXY0[i][0] = sensors[i].px0;
			pXY0[i][1] = sensors[i].py0;
		}
		geometryCorrection.setSensors(
				numSensors,
				theta_avg, // sensors[0].theta,
				heading_avg, // sensors[0].heading,
				forward,
				right,
				height,
				roll,
				pXY0);
		geometryCorrection.planeProjectLenses(); // project all lenses to the common plane

		// calculate reverse distortion as a table to be linear interpolated (now cubic!)
		geometryCorrection.calcReverseDistortionTable();

//		if (numSensors == 4){
		geometryCorrection.adustSquare();
		System.out.println("Adjusted camera to orient X Y along the sides of a square (now universal), numSensors = "+numSensors);
		// Print parameters
		if (debugLevel > 0){
			geometryCorrection.listGeometryCorrection(debugLevel > 1);
			System.out.println("=== Extrinsic corrections ===");
			System.out.println(geometryCorrection.getCorrVector().toString());
		}
//		double [] dbg_objects = geometryCorrection.toDoubleArray();
		double [] dbg_double =  geometryCorrection.toDoubleArray();
		float  [] dbg_float =   geometryCorrection.toFloatArray();
		System.out.println("toFloatArray().length="+geometryCorrection.toFloatArray().length);
		System.out.println();
		
		
//listGeometryCorrection
		return true;
	}

//GeometryCorrection

	  public double [][][][][] calculateCLTKernel ( // per color/per tileY/ per tileX/per quadrant (plus offset as 5-th)/per pixel // not used in lwir
			  final PixelMapping.SensorData sensor, // to calculate extra shift
			  final ImageStack kernelStack,  // first stack with 3 colors/slices convolution kernels
			  final int          kernelSize, // 64
			  final CLTParameters clt_parameters,

			  final int          threadsMax,  // maximal number of threads to launch
			  final boolean      updateStatus,
			  final int          globalDebugLevel) // update status info
	  {
		  if (kernelStack==null) return null;
		  final int kernelWidth=kernelStack.getWidth();
		  final int kernelNumHor=kernelWidth/kernelSize;
		  final int kernelNumVert=kernelStack.getHeight()/kernelSize;
		  final int nChn=kernelStack.getSize();
		  final int dtt_size =      clt_parameters.transform_size;
		  final int dtt_len = dtt_size* dtt_size;
		  // Assuming kernels array match image size with 2 extras (one on each side)
		  int image_width = this.getGeometryCorrection().getSensorWH()[0];
		  final int kernel_pitch = image_width / (kernelNumHor - 2); 
		  
		  
		  final double [][][][][] clt_kernels = new double [nChn][kernelNumVert][kernelNumHor][5][];
		  for (int chn = 0; chn < nChn; chn++){
			  for (int tileY = 0; tileY < kernelNumVert ; tileY++){
				  for (int tileX = 0; tileX < kernelNumHor ; tileX++){
					  for (int n = 0; n<4; n++){
						  clt_kernels[chn][tileY][tileX][n] = new double [dtt_len];
					  }
					  clt_kernels[chn][tileY][tileX][4] = new double [extra_items];
				  }
			  }
		  }
		  // currently each 64x64 kernel corresponds to 16x16 original pixels tile, 2 tiles margin each side
		  final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		  final AtomicInteger ai = new AtomicInteger(0);
		  final int numberOfKernels=     kernelNumHor*kernelNumVert*nChn;
		  final int numberOfKernelsInChn=kernelNumHor*kernelNumVert;
		  final double [] norm_sym_weights = clt_parameters.norm_kern ? new double [dtt_size*dtt_size]:null;
		  if (norm_sym_weights != null) {
			  for (int i = 0; i < dtt_size; i++){
				  for (int j = 0; j < dtt_size; j++){
					  norm_sym_weights[i*dtt_size+j] = Math.cos(Math.PI*i/(2*dtt_size))*Math.cos(Math.PI*j/(2*dtt_size));
				  }
			  }
		  }

		  // testing
		  boolean debug_k00 = false; // true;
		  if (debug_k00) {
			  boolean show_raw = true;
			  boolean show_decimated = true;
			  float [] kernelPixels= null; // will be initialized at first use NOT yet?
			  double [] kernel=      new double[kernelSize*kernelSize];
			  int centered_len = (2*dtt_size-1) * (2*dtt_size-1);
			  double [] kernel_centered = new double [centered_len + extra_items];
			  ImageDtt image_dtt = new ImageDtt(
					  getNumSensors(),
					  clt_parameters.transform_size,
					  clt_parameters.img_dtt,
					  isAux(),
					  isMonochrome(),
					  isLwir(),
					  clt_parameters.getScaleStrength(isAux()));
			  int chn,tileY,tileX;

			  int nTile = 9;

			  chn=nTile/numberOfKernelsInChn;
			  tileY =(nTile % numberOfKernelsInChn)/kernelNumHor;
			  tileX = nTile % kernelNumHor;
			  if (tileX==0) {
				  if (updateStatus) IJ.showStatus("Processing kernels, channel "+(chn+1)+" of "+nChn+", row "+(tileY+1)+" of "+kernelNumVert);
				  if (globalDebugLevel>2) System.out.println("Processing kernels, channel "+(chn+1)+" of "+nChn+", row "+(tileY+1)+" of "+kernelNumVert+" : "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
			  }
			  kernelPixels=(float[]) kernelStack.getPixels(chn+1);

			  /* read convolution kernel */
			  extractOneKernel(
					  kernelPixels, //  array of combined square kernels, each
					  kernel, // will be filled, should have correct size before call
					  kernelNumHor, // number of kernels in a row
					  tileX, // horizontal number of kernel to extract
					  tileY); // vertical number of kernel to extract
			  if (show_raw) {
				  int length=kernel.length;
				  int size=(int) Math.sqrt(length);
				  double s =0.0;
				  for (int i=0;i<kernel.length;i++) s+=kernel[i];
				  System.out.println("calculateCLTKernel(): sum(kernel_raw)="+s);
				  ShowDoubleFloatArrays.showArrays(
						  kernel,
						  size,
						  size,
						  "raw_kernel-"+chn+"-X"+(clt_parameters.tileX/2)+"-Y"+(clt_parameters.tileY/2));

			  }
			  // now has 64x64
			  image_dtt.clt_convert_double_kernel_centered( // converts double resolution kernel
					  kernel,          // double []   src_kernel, //
					  kernel_centered, // double []   dst_kernel, // should be (2*dtt_size-1) * (2*dtt_size-1) + extra_items size - kernel and dx, dy to the nearest 1/2 pixels
					  // also actual full center shifts in sensor pixels
					  kernelSize); // ,      // int src_size, // 64
			  if (show_decimated) {
				  int length=kernel_centered.length;
				  int size=(int) Math.sqrt(length);
				  double s =0.0;
				  int klen = (2*dtt_size-1) * (2*dtt_size-1);
				  for (int i = 0; i < klen; i++) s += kernel_centered[i];
				  System.out.println("calculateCLTKernel(): sum(kernel_centered)="+s);
				  ShowDoubleFloatArrays.showArrays(
						  kernel_centered,
						  size,
						  size,
						  "kernel_centered-"+chn+"-X"+(clt_parameters.tileX/2)+"-Y"+(clt_parameters.tileY/2));
			  }
		  }
		  
		  // ----------------
		  final long startTime = System.nanoTime();
		  System.out.println("calculateCLTKernel():numberOfKernels="+numberOfKernels);
		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  @Override
				public void run() {
					  float [] kernelPixels= null; // will be initialized at first use NOT yet?
					  double [] kernel=      new double[kernelSize*kernelSize];
					  int centered_len = (2*dtt_size-1) * (2*dtt_size-1);
					  double [] kernel_centered = new double [centered_len + extra_items];
					  ImageDtt image_dtt = new ImageDtt(
							  getNumSensors(),
							  clt_parameters.transform_size,
							  clt_parameters.img_dtt,
							  isAux(),
							  isMonochrome(),
							  isLwir(),
							  clt_parameters.getScaleStrength(isAux()));
					  int chn,tileY,tileX;
					  DttRad2 dtt = new DttRad2(dtt_size);

					  for (int nTile = ai.getAndIncrement(); nTile < numberOfKernels; nTile = ai.getAndIncrement()) {
						  chn=nTile/numberOfKernelsInChn;
						  tileY =(nTile % numberOfKernelsInChn)/kernelNumHor;
						  tileX = nTile % kernelNumHor;
						  if (tileX==0) {
							  if (updateStatus) IJ.showStatus("Processing kernels, channel "+(chn+1)+" of "+nChn+", row "+(tileY+1)+" of "+kernelNumVert);
							  if (globalDebugLevel>2) System.out.println("Processing kernels, channel "+(chn+1)+" of "+nChn+", row "+(tileY+1)+" of "+kernelNumVert+" : "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
						  }
						  kernelPixels=(float[]) kernelStack.getPixels(chn+1);

						  /* read convolution kernel */
						  extractOneKernel(
								  kernelPixels, //  array of combined square kernels, each
								  kernel, // will be filled, should have correct size before call
								  kernelNumHor, // number of kernels in a row
								  tileX, // horizontal number of kernel to extract
								  tileY); // vertical number of kernel to extract
						  if ((globalDebugLevel > 0) && (tileY == clt_parameters.tileY/2)  && (tileX == clt_parameters.tileX/2)) {
							  int length=kernel.length;
							  int size=(int) Math.sqrt(length);
							  double s =0.0;
							  for (int i=0;i<kernel.length;i++) s+=kernel[i];
							  System.out.println("calculateCLTKernel(): sum(kernel_raw)="+s);
							  if (globalDebugLevel > 1) ShowDoubleFloatArrays.showArrays(
									  kernel,
									  size,
									  size,
									  "raw_kernel-"+chn+"-X"+(clt_parameters.tileX/2)+"-Y"+(clt_parameters.tileY/2));

						  }

						  // now has 64x64
						  image_dtt.clt_convert_double_kernel_centered ( // clt_convert_double_kernel( // converts double resolution kernel
								  kernel,          // double []   src_kernel, //
								  kernel_centered, // double []   dst_kernel, // should be (2*dtt_size-1) * (2*dtt_size-1) + extra_items size - kernel and dx, dy to the nearest 1/2 pixels
								                   // also actual full center shifts in sensor pixels
								  kernelSize); // ,      // int src_size, // 64
						  if ((globalDebugLevel > 0) && (tileY == clt_parameters.tileY/2)  && (tileX == clt_parameters.tileX/2)) {
							  int length=kernel_centered.length;
							  int size=(int) Math.sqrt(length);
							  double s =0.0;
							  int klen = (2*dtt_size-1) * (2*dtt_size-1);
							  for (int i = 0; i < klen; i++) s += kernel_centered[i];
							  System.out.println("calculateCLTKernel(): sum(kernel_centered)="+s);
							  if (globalDebugLevel > 1) ShowDoubleFloatArrays.showArrays(
									  kernel_centered,
									  size,
									  size,
									  "kernel_centered-"+chn+"-X"+(clt_parameters.tileX/2)+"-Y"+(clt_parameters.tileY/2));
						  }

						  if (norm_sym_weights != null) {
							  image_dtt.clt_normalize_kernel( //
									  kernel_centered, // double []   kernel, // should be (2*dtt_size-1) * (2*dtt_size-1) + extra_items size (last (2*dtt_size-1) are not modified)
									  norm_sym_weights, // double []   window, // normalizes result kernel * window to have sum of elements == 1.0
///									  dtt_size,
									  (globalDebugLevel > 0) && (tileY == clt_parameters.tileY/2)  && (tileX == clt_parameters.tileX/2)); // 8
							  if ((globalDebugLevel > 0) && (tileY == clt_parameters.tileY/2)  && (tileX == clt_parameters.tileX/2)) {
								  int length=kernel_centered.length;
								  int size=(int) Math.sqrt(length);
								  double s =0.0;
								  int klen = (2*dtt_size-1) * (2*dtt_size-1);
								  for (int i = 0; i < klen; i++) s += kernel_centered[i];
								  System.out.println("calculateCLTKernel(): sum(kernel_normalized)="+s);
								  if (globalDebugLevel > 1) ShowDoubleFloatArrays.showArrays(
										  kernel_centered,
										  size,
										  size,
										  "kernel_normalized-"+chn+"-X"+(clt_parameters.tileX/2)+"-Y"+(clt_parameters.tileY/2));

							  }
						  }
						  image_dtt.clt_symmetrize_kernel(// each quadrant will have appropriate symmetry - SS, AS, SA, SS
								  kernel_centered, // double []     kernel,      // should be (2*dtt_size-1) * (2*dtt_size-1) +4 size (last 4 are not modified)
								  clt_kernels[chn][tileY][tileX]); // , // 	double [][]   sym_kernels, // set of 4 SS, AS, SA, AA kdernels, each dtt_size * dtt_size (may have 5-th with center shift

						  for (int i = 0; i < extra_items; i++){
							  clt_kernels[chn][tileY][tileX][4][i] = kernel_centered [centered_len + i];
						  }
						  if ((globalDebugLevel > 0) && (tileY == clt_parameters.tileY/2)  && (tileX == clt_parameters.tileX/2)) {
							  double [][] dbg_clt = {
									  clt_kernels[chn][tileY][tileX][0],
									  clt_kernels[chn][tileY][tileX][1],
									  clt_kernels[chn][tileY][tileX][2],
									  clt_kernels[chn][tileY][tileX][3]};
							  String [] titles = {"CC", "SC", "CS", "SS"};
							  int length=dbg_clt[0].length;
							  int size=(int) Math.sqrt(length);
							  if (globalDebugLevel > 1) ShowDoubleFloatArrays.showArrays(
									  dbg_clt,
									  size,
									  size,
									  true,
									  "pre_clt_kernels-"+chn,
									  titles);
						  }
//						  image_dtt.clt_dtt3_kernel( //
						  ImageDtt.clt_dtt3_kernel( //
								  clt_kernels[chn][tileY][tileX], // double [][]   kernels, // set of 4 SS, AS, SA, AA kdernels, each dtt_size * dtt_size (may have 5-th with center shift
								  dtt_size, // 8
								  dtt);

						  if ((globalDebugLevel > 0) && (tileY == clt_parameters.tileY/2)  && (tileX == clt_parameters.tileX/2)) {
							  System.out.println("calculateCLTKernel() - before corr: chn="+chn+" "+
									  "tileX = "+clt_parameters.tileX+" ("+(clt_parameters.tileX/2)+") "+
									  "tileY = "+clt_parameters.tileY+" ("+(clt_parameters.tileY/2)+") "+
									  "center_x = "+clt_kernels[chn][tileY][tileX][4][0]+", "+
									  "center_y = "+clt_kernels[chn][tileY][tileX][4][1]+", "+
									  "full_dx =  "+clt_kernels[chn][tileY][tileX][4][2]+", "+
									  "full_dy =  "+clt_kernels[chn][tileY][tileX][4][3]);
						  }
						  // Add sensor geometry correction  - no it is added during correction
						  // Kernel center in pixels
						  double kpx0 = (tileX -1 +0.5) * kernel_pitch; // clt_parameters.kernel_step;
						  double kpy0 = (tileY -1 +0.5) * kernel_pitch; // clt_parameters.kernel_step;
						  double [] corrPxPy = sensor.interpolateCorrectionVector(false, kpx0, kpy0);
						  image_dtt.offsetKernelSensor(
								  clt_kernels[chn][tileY][tileX], // double [][] clt_tile, // clt tile, including [4] - metadata
								  //								  (corrPxPy[0] - kpx0),  //	double dx,
								  //								  (corrPxPy[1] - kpy0)); // double dy)
								  -corrPxPy[0],  //	double dx,
								  -corrPxPy[1]); // double dy)

						  if ((globalDebugLevel > 0) && (tileY == clt_parameters.tileY/2)  && (tileX == clt_parameters.tileX/2)) {
							  System.out.println("calculateCLTKernel() - after  corr: chn="+chn+" "+
									  "tileX = "+clt_parameters.tileX+" ("+(clt_parameters.tileX/2)+") "+
									  "tileY = "+clt_parameters.tileY+" ("+(clt_parameters.tileY/2)+") "+
									  "center_x = "+clt_kernels[chn][tileY][tileX][4][0]+", "+
									  "center_y = "+clt_kernels[chn][tileY][tileX][4][1]+", "+
									  "full_dx =  "+clt_kernels[chn][tileY][tileX][4][2]+", "+
									  "full_dy =  "+clt_kernels[chn][tileY][tileX][4][3]);
							  System.out.println("calculateCLTKernel() - after  corr: chn="+chn+" "+
									  "kpx0 = "+kpx0+
									  " kpy0 = "+kpy0+
									  " corrPxPy[0] = "+corrPxPy[0]+
									  " corrPxPy[1] = "+corrPxPy[1]);
						  }

						  if ((globalDebugLevel > 0) && (tileY == clt_parameters.tileY/2)  && (tileX == clt_parameters.tileX/2)) {
							  double [][] dbg_clt = {
									  clt_kernels[chn][tileY][tileX][0],
									  clt_kernels[chn][tileY][tileX][1],
									  clt_kernels[chn][tileY][tileX][2],
									  clt_kernels[chn][tileY][tileX][3]};
							  String [] titles = {"CC", "SC", "CS", "SS"};
							  int length=dbg_clt[0].length;
							  int size=(int) Math.sqrt(length);
							  if (globalDebugLevel > 1) ShowDoubleFloatArrays.showArrays(
									  dbg_clt,
									  size,
									  size,
									  true,
									  "dbg_clt_kernels-"+chn,
									  titles);
							  System.out.println("calculateCLTKernel() chn="+chn+" "+
									  "tileX = "+clt_parameters.tileX+" ("+(clt_parameters.tileX/2)+") "+
									  "tileY = "+clt_parameters.tileY+" ("+(clt_parameters.tileY/2)+") "+
									  "center_x = "+clt_kernels[chn][tileY][tileX][4][0]+", "+
									  "center_y = "+clt_kernels[chn][tileY][tileX][4][1]+", "+
									  "full_dx =  "+clt_kernels[chn][tileY][tileX][4][2]+", "+
									  "full_dy =  "+clt_kernels[chn][tileY][tileX][4][3]);
						  }
					  }
				  }
			  };
		  }
		  ImageDtt.startAndJoin(threads);
		  if (globalDebugLevel > 1) System.out.println("Threads done at "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
		  System.out.println("1.Threads done at "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
		  // Calculate differential offsets to interpolate for tiles between kernel centers
		  ImageDtt image_dtt = new ImageDtt(
				  getNumSensors(),
				  clt_parameters.transform_size,
				  clt_parameters.img_dtt,
				  isAux(),
				  isMonochrome(),
				  isLwir(),
				  clt_parameters.getScaleStrength(isAux()));
		  image_dtt.clt_fill_coord_corr(
				  kernel_pitch,  // clt_parameters.kernel_step,  //  final int             kern_step, // distance between kernel centers, in pixels.
				  clt_kernels,   // final double [][][][] clt_data,
				  threadsMax,    // maximal number of threads to launch
				  globalDebugLevel);
		  return clt_kernels;
	  }

	  public double [][] flattenCLTKernels (      // per color, save 4 kernelas and displacement as (2*dtt_size+1)*(2*dtt_size) tiles in an image (last row - 4 values shift x,y) // not used in lwir
			  final double [][][][][] kernels,    // per color/per tileY/ per tileX/per quadrant (plus offset as 5-th)/per pixel
			  final int          threadsMax,      // maximal number of threads to launch
			  final boolean      updateStatus,
			  final int          globalDebugLevel) // update status info
	  {
		  if (kernels==null) return null;
		  final int nChn = kernels.length;
		  final int kernelNumVert=kernels[0].length;
		  final int kernelNumHor=kernels[0][0].length;
		  final int dtt_len = kernels[0][0][0][0].length;
		  final int dtt_size =      (int) Math.sqrt(dtt_len);
		  final int tileWidth =  2 * dtt_size;
		  final int tileHeight = 2 * dtt_size + 1; // last row - shift with 0.5 pix steps
		  final int width =  tileWidth *  kernelNumHor;
		  final int height = tileHeight * kernelNumVert;
		  final double [][] clt_flat = new double [nChn][width * height];
		  // currently each 64x64 kernel corresponds to 16x16 original pixels tile, 2 tiles margin each side
		  final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		  final AtomicInteger ai = new AtomicInteger(0);
		  final int numberOfKernels=     kernelNumHor*kernelNumVert*nChn;
		  final int numberOfKernelsInChn=kernelNumHor*kernelNumVert;

		  final long startTime = System.nanoTime();
		  System.out.println("flattenCLTKernels():numberOfKernels="+numberOfKernels);
		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  @Override
				public void run() {
					  int chn,tileY,tileX;
					  for (int nTile = ai.getAndIncrement(); nTile < numberOfKernels; nTile = ai.getAndIncrement()) {
						  chn=nTile/numberOfKernelsInChn;
						  tileY =(nTile % numberOfKernelsInChn)/kernelNumHor;
						  tileX = nTile % kernelNumHor;
						  for (int i = 0; i < dtt_size; i++){
							System.arraycopy(
									kernels[chn][tileY][tileX][0],
									i * dtt_size,
									clt_flat[chn],
									(tileY*tileHeight + i)            * width + (tileX * tileWidth),
									dtt_size);
							System.arraycopy(
									kernels[chn][tileY][tileX][1],
									i * dtt_size,
									clt_flat[chn],
									(tileY*tileHeight + i)            * width + (tileX * tileWidth) + dtt_size,
									dtt_size);

							System.arraycopy(
									kernels[chn][tileY][tileX][2],
									i * dtt_size,
									clt_flat[chn],
									(tileY*tileHeight + i + dtt_size) * width + (tileX * tileWidth),
									dtt_size);
							System.arraycopy(
									kernels[chn][tileY][tileX][3],
									i * dtt_size,
									clt_flat[chn],
									(tileY*tileHeight + i + dtt_size) * width + (tileX * tileWidth) + 1 * dtt_size,
									dtt_size);
						  }
						  System.arraycopy(
								  kernels[chn][tileY][tileX][4], // just 2 values
								  0,
								  clt_flat[chn],
								  (tileY*tileHeight + 2 * dtt_size) * width + (tileX * tileWidth),
								  extra_items);
					  }
				  }
			  };
		  }
		  ImageDtt.startAndJoin(threads);
		  if (globalDebugLevel > 1) System.out.println("Threads done at "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
		  System.out.println("1.Threads done at "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
		  /* prepare result stack to return */
		  return clt_flat;
	  }

	  public void showCLTKernels( // not used in lwir
			  final int          threadsMax,      // maximal number of threads to launch
			  final boolean      updateStatus,
			  final int          globalDebugLevel) // update status info
	  {
		  for (int chn=0;chn < clt_kernels.length; chn++){
			  if (clt_kernels[chn]!=null){
				  //					System.out.println("showKernels("+chn+")");
				  showCLTKernels(
						  chn,
						  threadsMax,
						  updateStatus,
						  globalDebugLevel);
			  }
		  }
	  }

	  public void showCLTKernels( // not used in lwir
			  int chn,
			  final int          threadsMax,      // maximal number of threads to launch
			  final boolean      updateStatus,
			  final int          globalDebugLevel) // update status info
	  {
		  double [][] flat_kernels = flattenCLTKernels (      // per color, save 4 kernelas and displacement as (2*dtt_size+1)*(2*dtt_size) tiles in an image (last row - shift x,y)
				  clt_kernels[chn],     // per color/per tileY/ per tileX/per quadrant (plus offset as 5-th)/per pixel
				  threadsMax,      // maximal number of threads to launch
				  updateStatus,
				  globalDebugLevel); // update status info
		  int dtt_len = clt_kernels[chn][0][0][0][0].length;
		  int dtt_size= (int)Math.sqrt(dtt_len);
		  String [] titles = {"red", "blue", "green"};
		  ShowDoubleFloatArrays.showArrays(
				  flat_kernels,
				  clt_kernels[chn][0][0].length*(2*dtt_size),
				  clt_kernels[chn][0].length*(2*dtt_size+1),
				  true,
				  "clt_kernels-"+chn,
				  titles);
	  }

	  // USED in lwir
	  public double [][][][][] extractCLTKernels (      // per color, save 4 kernelas and displacement as (2*dtt_size+1)*(2*dtt_size) tiles in an image (last row - shift x,y)
			  final float [][]   flat_kernels,    // per color/per tileY/ per tileX/per quadrant (plus offset as 5-th)/per pixel
			  final int          width,
			  final int          dtt_size,
			  final int          threadsMax,      // maximal number of threads to launch
			  final boolean      updateStatus,
			  final int          globalDebugLevel) // update status info
	  {
		  if (flat_kernels==null) return null;
		  final int nChn =       flat_kernels.length;
		  final int height =     flat_kernels[0].length/width;
		  final int tileWidth =  2 * dtt_size;
		  final int tileHeight = 2 * dtt_size + 1; // last row - shift with 0.5 pix steps
		  final int kernelNumHor =  width / tileWidth;
		  final int kernelNumVert = height / tileHeight;
		  final int dtt_len = dtt_size*dtt_size;
		  final double [][][][][] clt_kernels = new double [nChn][kernelNumVert][kernelNumHor][5][];
		  for (int chn = 0; chn < nChn; chn++){
			  for (int tileY = 0; tileY < kernelNumVert ; tileY++){
				  for (int tileX = 0; tileX < kernelNumHor ; tileX++){
					  for (int n = 0; n<4; n++){
						  clt_kernels[chn][tileY][tileX][n] = new double [dtt_len];
					  }
					  clt_kernels[chn][tileY][tileX][4] = new double [extra_items];
				  }
			  }
		  }

		  // currently each 64x64 kernel corresponds to 16x16 original pixels tile, 2 tiles margin each side
		  final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		  final AtomicInteger ai = new AtomicInteger(0);
		  final int numberOfKernels=     kernelNumHor*kernelNumVert*nChn;
		  final int numberOfKernelsInChn=kernelNumHor*kernelNumVert;

		  final long startTime = System.nanoTime();
		  System.out.println("flattenCLTKernels():numberOfKernels="+numberOfKernels);
		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  @Override
				public void run() {
					  int chn,tileY,tileX;
					  for (int nTile = ai.getAndIncrement(); nTile < numberOfKernels; nTile = ai.getAndIncrement()) {
						  chn=nTile/numberOfKernelsInChn;
						  tileY =(nTile % numberOfKernelsInChn)/kernelNumHor;
						  tileX = nTile % kernelNumHor;
						  for (int i = 0; i < dtt_size; i++){
							  for (int j = 0; j<dtt_size; j++){
								  int indx = i*dtt_size+j;
								  int baddr = (tileY*tileHeight + i) * width + (tileX * tileWidth) + j;
								  clt_kernels[chn][tileY][tileX][0][indx] = flat_kernels[chn][baddr];
								  clt_kernels[chn][tileY][tileX][1][indx] = flat_kernels[chn][baddr + dtt_size];
								  clt_kernels[chn][tileY][tileX][2][indx] = flat_kernels[chn][baddr + dtt_size * width];
								  clt_kernels[chn][tileY][tileX][3][indx] = flat_kernels[chn][baddr + dtt_size * width + dtt_size];
							  }
						  }
						  for (int i = 0; i < extra_items; i ++) {
							  clt_kernels[chn][tileY][tileX][4][i] = flat_kernels[chn][(tileY*tileHeight + 2 * dtt_size) * width + (tileX * tileWidth) + i];
						  }
					  }
				  }
			  };
		  }
		  ImageDtt.startAndJoin(threads);
		  if (globalDebugLevel > 1) System.out.println("Threads done at "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
		  System.out.println("1.Threads done at "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
		  /* prepare result stack to return */
		  return clt_kernels;
	  }


	  public boolean createCLTKernels( // not used in lwir
			  CLTParameters clt_parameters,
			  int          srcKernelSize,
			  int          threadsMax,  // maximal number of threads to launch
			  boolean      updateStatus,
			  int          debugLevel
			  ){
		  // get sensor geometry correction to apply to kernels as extra shifts
		  PixelMapping.SensorData [] sensors =  eyesisCorrections.pixelMapping.sensors;

		  String [] sharpKernelPaths= correctionsParameters.selectKernelChannelFiles(
				  0,  // 0 - sharp, 1 - smooth
				  correctionsParameters.firstSubCameraConfig,
				  correctionsParameters.numSubCameras,
				  eyesisCorrections.debugLevel);
		  if (sharpKernelPaths==null) return false;
		  for (int i=0;i<sharpKernelPaths.length;i++){
			  System.out.println(i+":"+sharpKernelPaths[i]);
		  }
		  if (clt_kernels == null){
			  clt_kernels = new double[eyesisCorrections.usedChannels.length][][][][][];
			  for (int chn=0;chn<eyesisCorrections.usedChannels.length;chn++){
				  clt_kernels[chn] = null;
			  }
		  }
		  for (int chn=0;chn<eyesisCorrections.usedChannels.length;chn++){
			  if (eyesisCorrections.usedChannels[chn] && (sharpKernelPaths[chn]!=null) && (clt_kernels[chn]==null)){
				  ImagePlus imp_kernel_sharp=new ImagePlus(sharpKernelPaths[chn]);
				  if ((imp_kernel_sharp.getStackSize()<3) && (imp_kernel_sharp.getStackSize() != 1)) {
					  System.out.println("Need a 3-layer stack with Bayer or single for mono kernels");
					  sharpKernelPaths[chn]=null;
					  continue;
				  }
				  ImageStack kernel_sharp_stack= imp_kernel_sharp.getStack();
				  System.out.println("debugLevel = "+debugLevel+" kernel_sharp_stack.getWidth() = "+kernel_sharp_stack.getWidth()+
						  " kernel_sharp_stack.getHeight() = "+kernel_sharp_stack.getHeight());
				  // debugging
				  if (chn==1000) {
					  int test_chn = 15;
					  ImagePlus imp_kernel_test=new ImagePlus(sharpKernelPaths[test_chn]);
					  ImageStack kernel_test_stack= imp_kernel_test.getStack();
					  
					  System.out.println("+++++ createCLTKernels() testing calculateCLTKernel() with chn=15 tile=9");
					  calculateCLTKernel ( // per color/per tileY/ per tileX/per quadrant (plus offset as 5-th)/per pixel
							  sensors[test_chn],                            // to calculate extra shift (kernels are centered around green)
							  kernel_test_stack,                      // final ImageStack kernelStack,  // first stack with 3 colors/slices convolution kernels
							  srcKernelSize,                           // final int          kernelSize, // 64
							  clt_parameters,                          // final EyesisCorrectionParameters.CLTParameters clt_parameters,
							  threadsMax,  // maximal number of threads to launch
							  updateStatus,
							  debugLevel); // update status info
				  }

				  double [][][][][] kernels = calculateCLTKernel ( // per color/per tileY/ per tileX/per quadrant (plus offset as 5-th)/per pixel
						  sensors[chn],                            // to calculate extra shift (kernels are centered around green)
						  kernel_sharp_stack,                      // final ImageStack kernelStack,  // first stack with 3 colors/slices convolution kernels
						  srcKernelSize,                           // final int          kernelSize, // 64
						  clt_parameters,                          // final EyesisCorrectionParameters.CLTParameters clt_parameters,
						  threadsMax,  // maximal number of threads to launch
						  updateStatus,
						  debugLevel); // update status info

				  double [][] flat_kernels = flattenCLTKernels (      // per color, save 4 kernels and displacement as (2*dtt_size+1)*(2*dtt_size) tiles in an image (last row - shift x,y)
						  kernels,    // per color/per tileY/ per tileX/per quadrant (plus offset as 5-th)/per pixel
						  threadsMax,  // maximal number of threads to launch
						  updateStatus,
						  debugLevel); // update status info
				  int kernelNumHor=kernels[0][0].length;
				  int dtt_len = kernels[0][0][0][0].length;
				  int dtt_size =      (int) Math.sqrt(dtt_len);
				  int tileWidth =  2 * dtt_size;
				  int width =  tileWidth *  kernelNumHor;
				  int height = flat_kernels[0].length/width;
				  String [] layerNames = {"red_clt_kernels","blue_clt_kernels","green_clt_kernels"};
				  if (flat_kernels.length ==1){
					  layerNames = new String[1];
					  layerNames[0] = "mono_clt_kernels";
				  }
				  ImageStack cltStack = ShowDoubleFloatArrays.makeStack(
						  flat_kernels,
						  width,
						  height,
						  layerNames);
				  String cltPath=correctionsParameters.cltKernelDirectory+
						  Prefs.getFileSeparator()+
						  correctionsParameters.cltKernelPrefix+
						  String.format("%02d",chn + correctionsParameters.firstSubCameraConfig)+
						  correctionsParameters.cltSuffix;
				  String msg="Saving CLT convolution kernels to "+cltPath;
				  IJ.showStatus(msg);
				  if (debugLevel>0) System.out.println(msg);
				  ImagePlus imp_clt=new ImagePlus(imp_kernel_sharp.getTitle()+"-clt",cltStack);
				  if (debugLevel > 0) {
					  imp_clt.getProcessor().resetMinAndMax();
					  imp_clt.show();
				  }
				  FileSaver fs=new FileSaver(imp_clt);
//				  fs.saveAsTiffStack(cltPath); // directory does not exist
				  fs.saveAsTiff(cltPath); // directory does not exist
			  }
		  }
		  return true;
	  }



	  public boolean readCLTKernels( // USED in lwir
			  CLTParameters clt_parameters,
			  int          threadsMax,  // maximal number of threads to launch
			  boolean      updateStatus,
			  int          debugLevel
			  ){
		  int dtt_size = clt_parameters.transform_size;
		  String [] cltKernelPaths = correctionsParameters.selectCLTChannelFiles(
				  correctionsParameters.firstSubCameraConfig,
				  //					0,  // 0 - sharp, 1 - smooth
				  eyesisCorrections.usedChannels.length, // numChannels, // number of channels
				  eyesisCorrections.debugLevel);
		  if (cltKernelPaths==null) return false;

		  for (int i=0;i<cltKernelPaths.length;i++){
			  System.out.println(i+":"+cltKernelPaths[i]); // some may be null!
		  }

		  if (clt_kernels == null){
			  clt_kernels = new double[eyesisCorrections.usedChannels.length][][][][][];
			  for (int chn=0;chn<eyesisCorrections.usedChannels.length;chn++){
				  clt_kernels[chn] = null;
			  }
		  }

		  for (int chn=0;chn<eyesisCorrections.usedChannels.length;chn++){
			  if (eyesisCorrections.usedChannels[chn] && (cltKernelPaths[chn]!=null)){
				  ImagePlus imp_kernel_clt=new ImagePlus(cltKernelPaths[chn]);
				  if ((imp_kernel_clt.getStackSize()< 3) && (imp_kernel_clt.getStackSize()!= 1))   {
					  System.out.println("Need a 3-layer stack or Bayer and 1-layer for mono with CLT kernels");
					  cltKernelPaths[chn]=null;
					  continue;
				  }

				  ImageStack kernel_clt_stack=  imp_kernel_clt.getStack();
				  if (debugLevel>0){
					  System.out.println(" kernel_clt_stack.getWidth() = "+kernel_clt_stack.getWidth()+
							  " kernel_clt_stack.getHeight() = "+kernel_clt_stack.getHeight());
				  }
				  int nColors = kernel_clt_stack.getSize();
				  float [][] flat_kernels = new float [nColors][];
				  for (int nc = 0; nc < nColors; nc++){
					  flat_kernels[nc]= (float[]) kernel_clt_stack.getPixels(nc + 1);
				  }
				  clt_kernels[chn] =  extractCLTKernels ( // per color, save 4 kernelas and displacement as (2*dtt_size+1)*(2*dtt_size) tiles in an image (last row - shift x,y)
						  flat_kernels,                   // per color/per tileY/ per tileX/per quadrant (plus offset as 5-th)/per pixel
						  kernel_clt_stack.getWidth(),    // final int          width,
						  dtt_size,
						  threadsMax,                     // maximal number of threads to launch
						  updateStatus,
						  debugLevel);                    // update status info


				  if (debugLevel>10) { // (sdfa_instance != null){
					  for (int nc = 0; nc < clt_kernels[chn].length; nc++){
					  double [][] dbg_clt = {
							  clt_kernels[chn][nc][clt_parameters.tileY/2][clt_parameters.tileX/2][0],
							  clt_kernels[chn][nc][clt_parameters.tileY/2][clt_parameters.tileX/2][1],
							  clt_kernels[chn][nc][clt_parameters.tileY/2][clt_parameters.tileX/2][2],
							  clt_kernels[chn][nc][clt_parameters.tileY/2][clt_parameters.tileX/2][3]};
					  String [] titles = {"CC", "SC", "CS", "SS"};
					  int length=dbg_clt[0].length;
					  int size=(int) Math.sqrt(length);
					  ShowDoubleFloatArrays.showArrays(
							  dbg_clt,
							  size,
							  size,
							  true,
							  "dbg_clt-"+nc,
							  titles);
					  System.out.println("readCLTKernels() chn="+chn+", color="+nc+" "+
							  "tileX = "+clt_parameters.tileX+" ("+(clt_parameters.tileX/2)+") "+
							  "tileY = "+clt_parameters.tileY+" ("+(clt_parameters.tileY/2)+") "+
							  "center_x = "+clt_kernels[chn][nc][clt_parameters.tileY/2][clt_parameters.tileX/2][4][0]+", "+
							  "center_y = "+clt_kernels[chn][nc][clt_parameters.tileY/2][clt_parameters.tileX/2][4][1]);
					  }
				  }
			  }
		  }
		  return true;
	  }


// mostly for testing
//eyesisKernelImage
	  public double [] extractOneKernelFromStack( // not used in lwir
			  final int          kernelSize, // 64
			  final int chn,
			  final int xTile, // horizontal number of kernel to extract
			  final int yTile)  // vertical number of kernel to extract
	  {
		  if (eyesisKernelImage == null) return null;
		  final ImageStack kernelStack = eyesisKernelImage.getStack();
		  return extractOneKernelFromStack(
				  kernelStack,  // first stack with 3 colors/slices convolution kernels
				  kernelSize, // 64
				  chn,
				  xTile, // horizontal number of kernel to extract
				  yTile);  // vertical number of kernel to extract
	  }

	  public double [] extractOneKernelFromStack( // not used in lwir
			  final ImageStack kernelStack,  // first stack with 3 colors/slices convolution kernels
			  final int          kernelSize, // 64
			  final int chn,
			  final int xTile, // horizontal number of kernel to extract
			  final int yTile)  // vertical number of kernel to extract
	  {

		  final int kernelWidth=kernelStack.getWidth();
		  final int kernelNumHor=kernelWidth/kernelSize;
		  double [] kernel = new double [kernelSize*kernelSize];
		  extractOneKernel((float[]) kernelStack.getPixels(chn+1), //  array of combined square kernels, each
				  kernel,        // will be filled, should have correct size before call
				  kernelNumHor,  // number of kernels in a row
				  xTile,         // horizontal number of kernel to extract
				  yTile);
		  return kernel;
	  }
	  // to be used in threaded method
	  private void extractOneKernel(float [] pixels, //  array of combined square kernels, each // not used in lwir
			  double [] kernel, // will be filled, should have correct size before call
			  int numHor, // number of kernels in a row
			  int xTile, // horizontal number of kernel to extract
			  int yTile) { // vertical number of kernel to extract
		  int length=kernel.length;
		  int size=(int) Math.sqrt(length);
		  int i,j;
		  int pixelsWidth=numHor*size;
		  int pixelsHeight=pixels.length/pixelsWidth;
		  int numVert=pixelsHeight/size;
		  /* limit tile numbers - effectively add margins around the known kernels */
		  if (xTile<0) xTile=0;
		  else if (xTile>=numHor) xTile=numHor-1;
		  if (yTile<0) yTile=0;
		  else if (yTile>=numVert) yTile=numVert-1;
		  int base=(yTile*pixelsWidth+xTile)*size;
		  for (i=0;i<size;i++) for (j=0;j<size;j++) kernel [i*size+j]=pixels[base+i*pixelsWidth+j];
	  }

	  public double [] reformatKernel( // not used in lwir
			  double [] src_kernel,// will be blured in-place
			  int       src_size,  // typical 64
			  int       dst_size,  // typical 15 // destination size
			  int       decimation,// typical 2
			  double    sigma)
	  {
		  double [] dst_kernel = new double [dst_size*dst_size];
		  DoubleGaussianBlur gb = null;
		  if (sigma > 0) gb = new DoubleGaussianBlur();
		  reformatKernel(
				  src_kernel,
				  dst_kernel,
				  src_size,
				  dst_size,
				  decimation,
				  sigma,
				  gb);
		  return dst_kernel;

	  }
	  // to be used in threaded method
	  private void reformatKernel( // not used in lwir
			  double [] src_kernel, // will be blured in-place
			  double [] dst_kernel,
			  int src_size,
			  int dst_size,
			  int decimation,
			  double sigma,
			  DoubleGaussianBlur gb)
	  {
		  if (gb != null) gb.blurDouble(src_kernel, src_size, src_size, sigma, sigma, 0.01);
		  int src_center = src_size / 2; // 32
		  int dst_center = dst_size / 2; // 7
		  for (int i = 0; i< dst_size; i++){
			  int src_i = (i - dst_center)*decimation + src_center;
			  if ((src_i >= 0) && (src_i < src_size)) {
				  for (int j = 0; j< dst_size; j++) {
					  int src_j = (j - dst_center)*decimation + src_center;
					  if ((src_j >= 0) && (src_j < src_size)) {
						  dst_kernel[i*dst_size + j] = src_kernel[src_i*src_size + src_j];
					  } else {
						  dst_kernel[i*dst_size + j] = 0;
					  }
				  }
			  } else {
				  for (int j = 0; j< dst_size; j++) dst_kernel[i*dst_size + j] = 0;
			  }
		  }
	  }
	  public static double []reformatKernel2( // averages by exactly 2 (decimate==2) // not used in lwir
			  double [] src_kernel, //
			  int src_size,
			  int dst_size){
		  double [] dst_kernel = new double [dst_size*dst_size];
		  reformatKernel2(
				  src_kernel,
				  dst_kernel,
				  src_size,
				  dst_size);
		  return dst_kernel;
	  }

	  private static void reformatKernel2( // averages by exactly 2 (decimate==2) // not used in lwir
			  double [] src_kernel, //
			  double [] dst_kernel,
			  int src_size,
			  int dst_size)
	  {
		  int decimation = 2;
		  int [] indices = {0,-src_size,-1,1,src_size,-src_size-1,-src_size+1,src_size-1,src_size+1};
		  double [] weights = {0.25,0.125,0.125,0.125,0.125,0.0625,0.0625,0.0625,0.0625};
		  int src_center = src_size / 2; // 32
		  int dst_center = dst_size / 2; // 7
		  int src_len = src_size*src_size;
		  for (int i = 0; i< dst_size; i++){
			  int src_i = (i - dst_center)*decimation + src_center;
			  if ((src_i >= 0) && (src_i < src_size)) {
				  for (int j = 0; j< dst_size; j++) {
					  int src_j = (j - dst_center)*decimation + src_center;
					  int dst_index = i*dst_size + j;
					  dst_kernel[dst_index] = 0.0;
					  if ((src_j >= 0) && (src_j < src_size)) {
						  int src_index = src_i*src_size + src_j;
						  for (int k = 0; k < indices.length; k++){
							  int indx = src_index + indices[k]; // normally source kernel should be larger, these lines just to save from "out of bounds"
							  if      (indx < 0)        indx += src_len;
							  else if (indx >= src_len) indx -= src_len;
							  dst_kernel[dst_index] += weights[k]*src_kernel[indx];
						  }
					  }
				  }
			  } else {
				  for (int j = 0; j< dst_size; j++) dst_kernel[i*dst_size + j] = 0;
			  }
		  }
	  }

	  public void resetCLTKernels() // and geometry correction too // not used in lwir
	  {
		  clt_kernels = null;
		  geometryCorrection=null;

	  }

	  public static ImageStack  YPrPbToRGB(double [][] yPrPb,  // USED in lwir
			  double Kr,        // 0.299;
			  double Kb,        // 0.114;
			  int width
			  ) {
		  int length = yPrPb[0].length;
		  int height = length/width;

		  float [] fpixels_r= new float [length];
		  float [] fpixels_g= new float [length];
		  float [] fpixels_b= new float [length];
		  double Kg=1.0-Kr-Kb;
		  int i;
		  /**
	R= Y+ Pr*2.0*(1-Kr)
	B= Y+ Pb*2.0*(1-Kb)
	G= Y  +Pr*(- 2*Kr*(1-Kr))/Kg + Pb*(-2*Kb*(1-Kb))/Kg

		   */
		  double KPrR=  2.0*(1-Kr);
		  double KPbB=  2.0*(1-Kb);
		  double KPrG= -2.0*Kr*(1-Kr)/Kg;
		  double KPbG= -2.0*Kb*(1-Kb)/Kg;
		  double Y,Pr,Pb;
		  for (i=0;i<length;i++) {
			  Pb=yPrPb[2][i];
			  Pr=yPrPb[1][i];
			  Y =yPrPb[0][i];
			  fpixels_r[i]=(float) (Y+ Pr*KPrR);
			  fpixels_b[i]=(float) (Y+ Pb*KPbB);
			  fpixels_g[i]=(float) (Y+ Pr*KPrG + Pb*KPbG);
		  }
		  ImageStack stack=new ImageStack(width,height);
		  stack.addSlice("red",   fpixels_r);
		  stack.addSlice("green", fpixels_g);
		  stack.addSlice("blue",  fpixels_b);
		  return stack;

	  }

	  public static double [][]  YPrPbToRBG(double [][] yPrPb, // not used in lwir
			  double Kr,        // 0.299;
			  double Kb,        // 0.114;
			  int width
			  ) {
		  int length = yPrPb[0].length;
		  //			int height = length/width;

		  double [][]rbg = new double[3][length];
		  double Kg=1.0-Kr-Kb;
		  int i;
		  /**
	R= Y+ Pr*2.0*(1-Kr)
	B= Y+ Pb*2.0*(1-Kb)
	G= Y  +Pr*(- 2*Kr*(1-Kr))/Kg + Pb*(-2*Kb*(1-Kb))/Kg

		   */
		  double KPrR=  2.0*(1-Kr);
		  double KPbB=  2.0*(1-Kb);
		  double KPrG= -2.0*Kr*(1-Kr)/Kg;
		  double KPbG= -2.0*Kb*(1-Kb)/Kg;
		  double Y,Pr,Pb;
		  for (i=0;i<length;i++) {
			  Pb=yPrPb[2][i];
			  Pr=yPrPb[1][i];
			  Y =yPrPb[0][i];
			  rbg[0][i]=(float) (Y+ Pr*KPrR);
			  rbg[1][i]=(float) (Y+ Pb*KPbB);
			  rbg[2][i]=(float) (Y+ Pr*KPrG + Pb*KPbG);
		  }
		  return rbg;
	  }

	  public static void debayer_rbg( // not used in lwir
			  ImageStack stack_rbg){
		  debayer_rbg(stack_rbg, 1.0);
	  }

	  // Simple in-place debayer by (bi) linear approximation, assumes [0R/00], [00/B0], [G0/0G] slices
	  public static void debayer_rbg( // not used in lwir
			  ImageStack stack_rbg,
			  double scale)
	  {
		  int width = stack_rbg.getWidth();
		  int height = stack_rbg.getHeight();
		  float [] fpixels_r = (float[]) stack_rbg.getPixels(1);
		  float [] fpixels_b = (float[]) stack_rbg.getPixels(2);
		  float [] fpixels_g = (float[]) stack_rbg.getPixels(3);
		  int [][][] av_row = {
				  {{1,1},{-1,1},{-1,-1}},
				  {{1,1},{-1,1},{-1,-1}},
				  {{1,1},{-1,1},{-1,-1}}};
		  int [][][] av_col =  {
				  {{ width, width},{ width, width},{ width, width}},
				  {{-width, width},{-width, width},{-width, width}},
				  {{-width,-width},{-width,-width},{-width,-width}}};

		  int [][][] av_xcross =  {
				  {{ width+1, width+1, width+1, width+1}, { width-1, width+1, width-1, width+1}, { width-1, width-1, width-1, width-1}},
				  {{-width+1, width+1,-width+1, width+1}, {-width-1,-width+1, width-1, width+1}, {-width-1,-width-1, width-1, width-1}},
				  {{-width+1,-width+1,-width+1,-width+1}, {-width-1,-width+1,-width-1,-width+1}, {-width-1,-width-1,-width-1,-width-1}}};
		  int [][][] av_plus =  {
				  {{ width,         1,       1, width},   { width,        -1,       1, width  }, { width,        -1,      -1, width  }},
				  {{-width,         1,       1, width},   {-width,        -1,       1, width  }, {-width,        -1,      -1, width  }},
				  {{-width,         1,       1,-width},   {-width,        -1,       1,-width  }, {-width,        -1,      -1,-width  }}};
		  for (int y = 0; y < height; y++){
			  boolean odd_row = (y & 1) != 0;
			  int row_type = (y==0)? 0: ((y==(height-1))?2:1);
			  for (int x = 0; x < width; x++){
				  int indx = y*width+x;
				  boolean odd_col =   (x & 1) != 0;
				  int col_type = (x==0)? 0: ((x==(width-1))?2:1);
				  if (odd_row){
					  if (odd_col){ // GB site
						  fpixels_r[indx] = 0.5f*(
								  fpixels_r[indx+av_col[row_type][col_type][0]]+
								  fpixels_r[indx+av_col[row_type][col_type][1]]);
						  fpixels_b[indx] = 0.5f*(
								  fpixels_b[indx+av_row[row_type][col_type][0]]+
								  fpixels_b[indx+av_row[row_type][col_type][1]]);
					  } else { // !odd col  // B site
						  fpixels_r[indx] = 0.25f*(
								  fpixels_r[indx+av_xcross[row_type][col_type][0]]+
								  fpixels_r[indx+av_xcross[row_type][col_type][1]]+
								  fpixels_r[indx+av_xcross[row_type][col_type][2]]+
								  fpixels_r[indx+av_xcross[row_type][col_type][3]]);
						  fpixels_g[indx] = 0.25f*(
								  fpixels_g[indx+av_plus[row_type][col_type][0]]+
								  fpixels_g[indx+av_plus[row_type][col_type][1]]+
								  fpixels_g[indx+av_plus[row_type][col_type][2]]+
								  fpixels_g[indx+av_plus[row_type][col_type][3]]);
					  }

				  } else { // !odd_row
					  if (odd_col){  // R site
						  fpixels_b[indx] = 0.25f*(
								  fpixels_b[indx+av_xcross[row_type][col_type][0]]+
								  fpixels_b[indx+av_xcross[row_type][col_type][1]]+
								  fpixels_b[indx+av_xcross[row_type][col_type][2]]+
								  fpixels_b[indx+av_xcross[row_type][col_type][3]]);
						  fpixels_g[indx] = 0.25f*(
								  fpixels_g[indx+av_plus[row_type][col_type][0]]+
								  fpixels_g[indx+av_plus[row_type][col_type][1]]+
								  fpixels_g[indx+av_plus[row_type][col_type][2]]+
								  fpixels_g[indx+av_plus[row_type][col_type][3]]);
					  } else { // !odd col  // G site
						  fpixels_r[indx] = 0.5f*(
								  fpixels_r[indx+av_row[row_type][col_type][0]]+
								  fpixels_r[indx+av_row[row_type][col_type][1]]);
						  fpixels_b[indx] = 0.5f*(
								  fpixels_b[indx+av_col[row_type][col_type][0]]+
								  fpixels_b[indx+av_col[row_type][col_type][1]]);
					  }
				  }
			  }
		  }
		  if (scale !=1.0){
			  for (int i = 0; i< fpixels_r.length; i++){
				  fpixels_r[i] *= scale;
				  fpixels_b[i] *= scale;
				  fpixels_g[i] *= scale;
			  }
		  }
	  }

	  public void processCLTChannelImages( // not used in lwir
			  CLTParameters           clt_parameters,
			  ColorProcParameters colorProcParameters,
			  CorrectionColorProc.ColorGainsParameters     channelGainParameters,
			  EyesisCorrectionParameters.RGBParameters             rgbParameters,
			  EyesisCorrectionParameters.EquirectangularParameters equirectangularParameters,
			  final int          threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel)
	  {
		  this.startTime=System.nanoTime();
		  String [] sourceFiles=correctionsParameters.getSourcePaths();
		  boolean [] enabledFiles=new boolean[sourceFiles.length];
		  for (int i=0;i<enabledFiles.length;i++) enabledFiles[i]=false;
		  int numFilesToProcess=0;
		  int numImagesToProcess=0;
		  for (int nFile=0;nFile<enabledFiles.length;nFile++){
			  if ((sourceFiles[nFile]!=null) && (sourceFiles[nFile].length()>1)) {
				  int [] channels= fileChannelToSensorChannels(correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile]));
				  if (channels!=null){
					  for (int i=0;i<channels.length;i++) if (eyesisCorrections.isChannelEnabled(channels[i])){
						  if (!enabledFiles[nFile]) numFilesToProcess++;
						  enabledFiles[nFile]=true;
						  numImagesToProcess++;
					  }
				  }
			  }
		  }
		  if (numFilesToProcess==0){
			  System.out.println("No files to process (of "+sourceFiles.length+")");
			  return;
		  } else {
			  if (debugLevel>0) System.out.println(numFilesToProcess+ " files to process (of "+sourceFiles.length+"), "+numImagesToProcess+" images to process");
		  }
		  double [] referenceExposures=eyesisCorrections.calcReferenceExposures(debugLevel); // multiply each image by this and divide by individual (if not NaN)
		  int [][] fileIndices=new int [numImagesToProcess][2]; // file index, channel number
		  int index=0;
		  for (int nFile=0;nFile<enabledFiles.length;nFile++){
			  if ((sourceFiles[nFile]!=null) && (sourceFiles[nFile].length()>1)) {
				  int [] channels= fileChannelToSensorChannels(correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile]));
				  if (channels!=null){
					  for (int i=0;i<channels.length;i++) if (eyesisCorrections.isChannelEnabled(channels[i])){
						  fileIndices[index  ][0]=nFile;
						  fileIndices[index++][1]=channels[i];
					  }
				  }
			  }
		  }
		  for (int iImage=0;iImage<fileIndices.length;iImage++){
			  int nFile=fileIndices[iImage][0];
			  ImagePlus imp_src=null;
			  //				  int srcChannel=correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile]);
			  int srcChannel=fileIndices[iImage][1];

			  imp_src = eyesisCorrections.getJp4Tiff(sourceFiles[nFile], this.geometryCorrection.woi_tops, this.geometryCorrection.camera_heights);

			  double scaleExposure=1.0;
			  if (!Double.isNaN(referenceExposures[nFile]) && (imp_src.getProperty("EXPOSURE")!=null)){
				  scaleExposure=referenceExposures[nFile]/Double.parseDouble((String) imp_src.getProperty("EXPOSURE"));
				  //					  imp_src.setProperty("scaleExposure", scaleExposure); // it may already have channel
				  if (debugLevel>0) System.out.println("Will scale intensity (to compensate for exposure) by  "+scaleExposure);
			  }
			  imp_src.setProperty("name",    correctionsParameters.getNameFromSourceTiff(sourceFiles[nFile]));
			  imp_src.setProperty("channel", srcChannel); // it may already have channel
			  imp_src.setProperty("path",    sourceFiles[nFile]); // it may already have channel
			  //				  ImagePlus result=processChannelImage( // returns ImagePlus, but it already should be saved/shown
			  processCLTChannelImage( // returns ImagePlus, but it already should be saved/shown
					  imp_src, // should have properties "name"(base for saving results), "channel","path"
					  clt_parameters,
					  colorProcParameters,
					  channelGainParameters,
					  rgbParameters,
					  scaleExposure,
					  threadsMax,  // maximal number of threads to launch
					  updateStatus,
					  debugLevel);
			  // warp result (add support for different color modes)
			  if (this.correctionsParameters.equirectangular){
				  if (equirectangularParameters.clearFullMap) eyesisCorrections.pixelMapping.deleteEquirectangularMapFull(srcChannel); // save memory? //removeUnusedSensorData - no, use equirectangular specific settings
				  if (equirectangularParameters.clearAllMaps) eyesisCorrections.pixelMapping.deleteEquirectangularMapAll(srcChannel); // save memory? //removeUnusedSensorData - no, use equirectangular specific settings
			  }
			  //pixelMapping
			  if (debugLevel >-1) System.out.println("Processing image "+(iImage+1)+" (of "+fileIndices.length+") finished at "+
					  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory4="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
			  if (eyesisCorrections.stopRequested.get()>0) {
				  System.out.println("User requested stop");
				  return;
			  }
		  }
		  System.out.println("processCLTChannelImages(): Processing "+fileIndices.length+" files finished at "+
				  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
	  }

	  public ImagePlus processCLTChannelImage( // not used in lwir
			  ImagePlus imp_src, // should have properties "name"(base for saving results), "channel","path"
			  CLTParameters           clt_parameters,
			  ColorProcParameters colorProcParameters,
			  CorrectionColorProc.ColorGainsParameters     channelGainParameters,
			  EyesisCorrectionParameters.RGBParameters             rgbParameters,
			  double 		     scaleExposure,
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel){
		  boolean advanced=this.correctionsParameters.zcorrect || this.correctionsParameters.equirectangular;
		  boolean crop=      advanced? true: this.correctionsParameters.crop;
		  boolean rotate=    advanced? false: this.correctionsParameters.rotate;
		  double JPEG_scale= advanced? 1.0: this.correctionsParameters.JPEG_scale;
		  boolean toRGB=     advanced? true: this.correctionsParameters.toRGB;

		  // may use this.StartTime to report intermediate steps execution times
		  String name=(String) imp_src.getProperty("name");
		  //		int channel= Integer.parseInt((String) imp_src.getProperty("channel"));
		  int channel= (Integer) imp_src.getProperty("channel");
		  String path= (String) imp_src.getProperty("path");
		  if (this.correctionsParameters.pixelDefects && (eyesisCorrections.defectsXY!=null)&& (eyesisCorrections.defectsXY[channel]!=null)){
			  // apply pixel correction
			  int numApplied=	eyesisCorrections.correctDefects(
					  imp_src,
					  channel,
					  debugLevel);
			  if ((debugLevel>0) && (numApplied>0)) { // reduce verbosity after verified defect correction works
				  System.out.println("Corrected "+numApplied+" pixels in "+path);
			  }
		  }

		  if (this.correctionsParameters.vignetting){
			  if ((eyesisCorrections.channelVignettingCorrection==null) || (channel<0) || (channel>=eyesisCorrections.channelVignettingCorrection.length) || (eyesisCorrections.channelVignettingCorrection[channel]==null)){
				  System.out.println("No vignetting data for channel "+channel);
				  return null;
			  }
			  float [] pixels=(float []) imp_src.getProcessor().getPixels();
			  if (pixels.length!=eyesisCorrections.channelVignettingCorrection[channel].length){
				  System.out.println("Vignetting data for channel "+channel+" has "+eyesisCorrections.channelVignettingCorrection[channel].length+" pixels, image "+path+" has "+pixels.length);
				  return null;
			  }
			  // TODO: Move to do it once:
			  double min_non_zero = 0.0;
			  for (int i=0;i<pixels.length;i++){
				  double d = eyesisCorrections.channelVignettingCorrection[channel][i];
				  if ((d > 0.0) && ((min_non_zero == 0) || (min_non_zero > d))){
					  min_non_zero = d;
				  }
			  }
			  double max_vign_corr = clt_parameters.vignetting_range*min_non_zero;

			  System.out.println("Vignetting data: channel="+channel+", min = "+min_non_zero);
			  for (int i=0;i<pixels.length;i++){
				  double d = eyesisCorrections.channelVignettingCorrection[channel][i];
				  if (d > max_vign_corr) d = max_vign_corr;
				  pixels[i]*=d;
			  }
			  // Scale here, combine with vignetting later?
			  int width =  imp_src.getWidth();
			  int height = imp_src.getHeight();
			  for (int y = 0; y < height-1; y+=2){
				  for (int x = 0; x < width-1; x+=2){
					  pixels[y*width+x        ] *= clt_parameters.scale_g;
					  pixels[y*width+x+width+1] *= clt_parameters.scale_g;
					  pixels[y*width+x      +1] *= clt_parameters.scale_r;
					  pixels[y*width+x+width  ] *= clt_parameters.scale_b;
				  }
			  }

		  } else { // assuming GR/BG pattern
			  System.out.println("Applying fixed color gain correction parameters: Gr="+
					  clt_parameters.novignetting_r+", Gg="+clt_parameters.novignetting_g+", Gb="+clt_parameters.novignetting_b);
			  float [] pixels=(float []) imp_src.getProcessor().getPixels();
			  int width =  imp_src.getWidth();
			  int height = imp_src.getHeight();
			  double kr = clt_parameters.scale_r/clt_parameters.novignetting_r;
			  double kg = clt_parameters.scale_g/clt_parameters.novignetting_g;
			  double kb = clt_parameters.scale_b/clt_parameters.novignetting_b;
			  for (int y = 0; y < height-1; y+=2){
				  for (int x = 0; x < width-1; x+=2){
					  pixels[y*width+x        ] *= kg;
					  pixels[y*width+x+width+1] *= kg;
					  pixels[y*width+x      +1] *= kr;
					  pixels[y*width+x+width  ] *= kb;
				  }
			  }
		  }
		  if (clt_parameters.gain_equalize){

		  }

//		  String title=name+"-"+String.format("%02d", channel);
		  String title=String.format("%s%s-%02d",name, sAux(), channel);
		  ImagePlus result=imp_src;
		  if (debugLevel>1) System.out.println("processing: "+path);
		  result.setTitle(title+"RAW");
		  if (!this.correctionsParameters.split){
			  eyesisCorrections.saveAndShow(result, this.correctionsParameters);
			  return result;
		  }
		  // Generate split parameters for DCT processing mode
		  EyesisCorrectionParameters.SplitParameters splitParameters = new EyesisCorrectionParameters.SplitParameters(
				                 1,  // oversample; // currently source kernels are oversampled
						  clt_parameters.transform_size/2, // addLeft
						  clt_parameters.transform_size/2, // addTop
						  clt_parameters.transform_size/2, // addRight
						  clt_parameters.transform_size/2  // addBottom
				  );

		  // Split into Bayer components, oversample, increase canvas

		  double [][] double_stack = eyesisCorrections.bayerToDoubleStack(
				  result, // source Bayer image, linearized, 32-bit (float))
				  null, // no margins, no oversample
//				  this.is_mono);
		          isMonochrome()); // this.is_mono);

//		  ImageStack stack= eyesisCorrections.bayerToStack(
//				  result, // source Bayer image, linearized, 32-bit (float))
//				  splitParameters);
		  String titleFull=title+"-SPLIT";
		  if (debugLevel > -1){
			  double [] chn_avg = {0.0,0.0,0.0};
			  int width =  imp_src.getWidth();
			  int height = imp_src.getHeight();
			  for (int c = 0; c < 3; c++){
				  for (int i = 0; i<double_stack[c].length; i++){
					  chn_avg[c] += double_stack[c][i];
				  }
			  }
			  chn_avg[0] /= width*height/4;
			  chn_avg[1] /= width*height/4;
			  chn_avg[2] /= width*height/2;
			  System.out.println("Split channels averages: R="+chn_avg[0]+", G="+chn_avg[2]+", B="+chn_avg[1]);
		  }
		  String [] rbg_titles = {"Red", "Blue", "Green"};
		  ImageStack stack;
		  if (!this.correctionsParameters.debayer) {
			  stack = ShowDoubleFloatArrays.makeStack(double_stack, imp_src.getWidth(), imp_src.getHeight(), rbg_titles);
			  result= new ImagePlus(titleFull, stack);
			  eyesisCorrections.saveAndShow(result, this.correctionsParameters);
			  return result;
		  }
		  // =================
		  if (debugLevel > 0) {
			  System.out.println("Showing image BEFORE_CLT_PROC");
			  ShowDoubleFloatArrays.showArrays(double_stack,  imp_src.getWidth(), imp_src.getHeight(), true, "BEFORE_CLT_PROC", rbg_titles);
		  }
		  if (this.correctionsParameters.deconvolve) { // process with DCT, otherwise use simple debayer
			  ImageDtt image_dtt = new ImageDtt(
					  getNumSensors(),
					  clt_parameters.transform_size,
					  clt_parameters.img_dtt,
					  isAux(),
					  isMonochrome(),
					  isLwir(),
					  clt_parameters.getScaleStrength(isAux()));
			  for (int i =0 ; i < double_stack[0].length; i++){
				  double_stack[2][i]*=0.5; // Scale blue twice to compensate less pixels than green
			  }
			  double [][][][][] clt_data = image_dtt.clt_aberrations(
					  double_stack,                 // final double [][]       imade_data,
					  imp_src.getWidth(),           //	final int               width,
					  clt_kernels[channel],         // final double [][][][][] clt_kernels, // [color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
					  clt_parameters.clt_window,
					  clt_parameters.shift_x,       // final int               shiftX, // shift image horizontally (positive - right) - just for testing
					  clt_parameters.shift_y,       // final int               shiftY, // shift image vertically (positive - down)
					  clt_parameters.tileX,         // final int               debug_tileX,
					  clt_parameters.tileY,         // final int               debug_tileY,
					  (clt_parameters.dbg_mode & 64) != 0, // no fract shift
					  (clt_parameters.dbg_mode & 128) != 0, // no convolve
					  (clt_parameters.dbg_mode & 256) != 0, // transpose convolve
					  threadsMax,
					  debugLevel);
//					  updateStatus);


			  System.out.println("clt_data.length="+clt_data.length+" clt_data[0].length="+clt_data[0].length
					  +" clt_data[0][0].length="+clt_data[0][0].length+" clt_data[0][0][0].length="+
					  clt_data[0][0][0].length+" clt_data[0][0][0][0].length="+clt_data[0][0][0][0].length);
			  /*
			  if (dct_parameters.color_DCT){ // convert RBG -> YPrPb
				  dct_data = image_dtt.dct_color_convert(
						  dct_data,
						  colorProcParameters.kr,
						  colorProcParameters.kb,
						  dct_parameters.sigma_rb,        // blur of channels 0,1 (r,b) in addition to 2 (g)
						  dct_parameters.sigma_y,         // blur of Y from G
						  dct_parameters.sigma_color,     // blur of Pr, Pb in addition to Y
						  threadsMax,
						  debugLevel);
			  } else { // just LPF RGB
			  */
				  if (clt_parameters.getCorrSigma(image_dtt.isMonochrome()) > 0){ // no filter at all
					  for (int chn = 0; chn < clt_data.length; chn++) {
						  image_dtt.clt_lpf(
								  clt_parameters.getCorrSigma(image_dtt.isMonochrome()),
								  clt_data[chn],
///								  clt_parameters.transform_size,
								  threadsMax,
								  debugLevel);
					  }
				  }
/*
			  }
*/
			  int tilesY = imp_src.getHeight()/image_dtt.transform_size;
			  int tilesX = imp_src.getWidth()/image_dtt.transform_size;
			  if (debugLevel > 0){
				  System.out.println("--tp.tilesX="+tilesX);
				  System.out.println("--tp.tilesY="+tilesY);
			  }
			  if (debugLevel > 1){
			        double [][] clt = new double [clt_data.length*4][];
			        for (int chn = 0; chn < clt_data.length; chn++) {
			        	double [][] clt_set = image_dtt.clt_dbg(
			        			clt_data [chn],
								  threadsMax,
								  debugLevel);
			        	for (int ii = 0; ii < clt_set.length; ii++) clt[chn*4+ii] = clt_set[ii];
			        }

			        if (debugLevel > 0){
			        	ShowDoubleFloatArrays.showArrays(clt,
			        			tilesX*image_dtt.transform_size,
			        			tilesY*image_dtt.transform_size,
			        			true,
			        			result.getTitle()+"-CLT");
			        }
			  }
			  double [][] iclt_data = new double [clt_data.length][];
			  for (int chn=0; chn<clt_data.length;chn++){
				  iclt_data[chn] = image_dtt.iclt_2d(
						  clt_data[chn],                  // scanline representation of dcd data, organized as dct_size x dct_size tiles
//						  image_dtt.transform_size,  // final int
						  clt_parameters.clt_window,      // window_type
						  15,                             // clt_parameters.iclt_mask,       //which of 4 to transform back
						  0,                              // clt_parameters.dbg_mode,        //which of 4 to transform back
						  threadsMax,
						  debugLevel);

			  }
					  if (debugLevel > -1) ShowDoubleFloatArrays.showArrays(
							  iclt_data,
							  (tilesX + 1) * image_dtt.transform_size,
							  (tilesY + 1) * image_dtt.transform_size,
							  true,
							  result.getTitle()+"-rbg_sigma");
				/*
				  }
			  }
			 */
			  if (debugLevel > 0) ShowDoubleFloatArrays.showArrays(iclt_data,
					  (tilesX + 1) * image_dtt.transform_size,
					  (tilesY + 1) * image_dtt.transform_size,
					  true,
					  result.getTitle()+"-ICLT-RGB");

			  // convert to ImageStack of 3 slices
			  String [] sliceNames = {"red", "blue", "green"};
			  stack = ShowDoubleFloatArrays.makeStack(
					  iclt_data,
					  (tilesX + 1) * image_dtt.transform_size,
					  (tilesY + 1) * image_dtt.transform_size,
					  sliceNames); // or use null to get chn-nn slice names


		  } else { // if (this.correctionsParameters.deconvolve) - here use a simple debayer
			  System.out.println("Bypassing CLT-based aberration correction");
			  stack = ShowDoubleFloatArrays.makeStack(double_stack, imp_src.getWidth(), imp_src.getHeight(), rbg_titles);
			  debayer_rbg(stack, 0.25); // simple standard 3x3 kernel debayer
		  }
		  if (debugLevel > -1){
			  double [] chn_avg = {0.0,0.0,0.0};
			  float [] pixels;
			  int width =  stack.getWidth();
			  int height = stack.getHeight();

			  for (int c = 0; c <3; c++){
				  pixels = (float[]) stack.getPixels(c+1);
				  for (int i = 0; i<pixels.length; i++){
					  chn_avg[c] += pixels[i];
				  }
			  }
			  chn_avg[0] /= width*height;
			  chn_avg[1] /= width*height;
			  chn_avg[2] /= width*height;
			  System.out.println("Processed channels averages: R="+chn_avg[0]+", G="+chn_avg[2]+", B="+chn_avg[1]);
		  }

		  if (!this.correctionsParameters.colorProc){
			  result= new ImagePlus(titleFull, stack);
			  eyesisCorrections.saveAndShow(
					  result,
					  this.correctionsParameters);
			  return result;
		  }
		  if (debugLevel > 1) System.out.println("before colors.1");
		  //Processing colors - changing stack sequence to r-g-b (was r-b-g)
		  if (!eyesisCorrections.fixSliceSequence(
				  stack,
				  debugLevel)){
			  if (debugLevel > -1) System.out.println("fixSliceSequence() returned false");
			  return null;
		  }
		  if (debugLevel > 1) System.out.println("before colors.2");
		  if (debugLevel > 1){
			  ImagePlus imp_dbg=new ImagePlus(imp_src.getTitle()+"-"+channel+"-preColors",stack);
			  eyesisCorrections.saveAndShow(
					  imp_dbg,
					  this.correctionsParameters);
		  }
		  if (debugLevel > 1) System.out.println("before colors.3, scaleExposure="+scaleExposure+" scale = "+(255.0/eyesisCorrections.psfSubpixelShouldBe4/eyesisCorrections.psfSubpixelShouldBe4/scaleExposure));
		  CorrectionColorProc correctionColorProc=new CorrectionColorProc(eyesisCorrections.stopRequested);
		  double [][] yPrPb=new double [3][];
		  //			if (dct_parameters.color_DCT){
		  // need to get YPbPr - not RGB here
		  //			} else {
		  correctionColorProc.processColorsWeights(stack, // just gamma convert? TODO: Cleanup? Convert directly form the linear YPrPb
				  //					  255.0/this.psfSubpixelShouldBe4/this.psfSubpixelShouldBe4, //  double scale,     // initial maximal pixel value (16))
				  //					  255.0/eyesisCorrections.psfSubpixelShouldBe4/eyesisCorrections.psfSubpixelShouldBe4/scaleExposure, //  double scale,     // initial maximal pixel value (16))
				  //					  255.0/2/2/scaleExposure, //  double scale,     // initial maximal pixel value (16))
				  255.0/scaleExposure, //  double scale,     // initial maximal pixel value (16))
				  colorProcParameters,
				  channelGainParameters,
				  channel,
				  null, //correctionDenoise.getDenoiseMask(),
				  this.correctionsParameters.blueProc,
				  debugLevel);
		  if (debugLevel > 1) System.out.println("Processed colors to YPbPr, total number of slices="+stack.getSize());
		  if (debugLevel > 1) {
			  ImagePlus imp_dbg=new ImagePlus("procColors",stack);
			  eyesisCorrections.saveAndShow(
					  imp_dbg,
					  this.correctionsParameters);
		  }
		  float [] fpixels;
		  int [] slices_YPrPb = {8,6,7};
		  yPrPb=new double [3][];
		  for (int n = 0; n < slices_YPrPb.length; n++){
			  fpixels = (float[]) stack.getPixels(slices_YPrPb[n]);
			  yPrPb[n] = new double [fpixels.length];
			  for (int i = 0; i < fpixels.length; i++) yPrPb[n][i] = fpixels[i];
		  }

		  if (toRGB) {
			  if (debugLevel > 0){
				  System.out.println("correctionColorProc.YPrPbToRGB");
			  }
			  stack =  YPrPbToRGB(yPrPb,
					  colorProcParameters.kr,        // 0.299;
					  colorProcParameters.kb,        // 0.114;
					  stack.getWidth());

			  title=titleFull; // including "-DECONV" or "-COMBO"
			  titleFull=title+"-RGB-float";
			  //Trim stack to just first 3 slices
			  if (debugLevel > 1){ // 2){
				  ImagePlus imp_dbg=new ImagePlus("YPrPbToRGB",stack);
				  eyesisCorrections.saveAndShow(
						  imp_dbg,
						  this.correctionsParameters);
			  }
			  while (stack.getSize() > 3) stack.deleteLastSlice();
			  if (debugLevel > 1) System.out.println("Trimming color stack");
		  } else {
			  title=titleFull; // including "-DECONV" or "-COMBO"
			  titleFull=title+"-YPrPb"; // including "-DECONV" or "-COMBO"
			  if (debugLevel > 1) System.out.println("Using full stack, including YPbPr");
		  }

		  result= new ImagePlus(titleFull, stack);
		  // Crop image to match original one (scaled to oversampling)
		  if (crop){ // always crop if equirectangular
			  if (debugLevel > 1) System.out.println("cropping");
			  stack = eyesisCorrections.cropStack32(stack,splitParameters);
			  if (debugLevel > 2) { // 2){
				  ImagePlus imp_dbg=new ImagePlus("cropped",stack);
				  eyesisCorrections.saveAndShow(
						  imp_dbg,
						  this.correctionsParameters);
			  }
		  }
		  // rotate the result
		  if (rotate){ // never rotate for equirectangular
			  stack=eyesisCorrections.rotateStack32CW(stack);
		  }
		  if (!toRGB && !this.correctionsParameters.jpeg){ // toRGB set for equirectangular
			  if (debugLevel > 1) System.out.println("!toRGB && !this.correctionsParameters.jpeg");
			  eyesisCorrections.saveAndShow(result, this.correctionsParameters);
			  return result;
		  } else { // that's not the end result, save if required
			  if (debugLevel > 1) System.out.println("!toRGB && !this.correctionsParameters.jpeg - else");
			  eyesisCorrections.saveAndShow(result,
					  eyesisCorrections.correctionsParameters,
					  eyesisCorrections.correctionsParameters.save32,
					  false,
					  eyesisCorrections.correctionsParameters.JPEG_quality); // save, no show
		  }
		  // convert to RGB48 (16 bits per color component)
		  ImagePlus imp_RGB;
		  stack=eyesisCorrections.convertRGB32toRGB16Stack(
				  stack,
				  rgbParameters);

		  titleFull=title+"-RGB48";
		  result= new ImagePlus(titleFull, stack);
		  //			  ImagePlus imp_RGB24;
		  result.updateAndDraw();
		  if (debugLevel > 1) System.out.println("result.updateAndDraw(), "+titleFull+"-RGB48");

		  CompositeImage compositeImage=eyesisCorrections.convertToComposite(result);

		  if (!this.correctionsParameters.jpeg && !advanced){ // RGB48 was the end result
			  if (debugLevel > 1) System.out.println("if (!this.correctionsParameters.jpeg && !advanced)");
			  eyesisCorrections.saveAndShow(compositeImage, this.correctionsParameters);
			  return result;
		  } else { // that's not the end result, save if required
			  if (debugLevel > 1) System.out.println("if (!this.correctionsParameters.jpeg && !advanced) - else");
			  eyesisCorrections.saveAndShow(compositeImage, this.correctionsParameters, this.correctionsParameters.save16, false); // save, no show
			  //				  eyesisCorrections.saveAndShow(compositeImage, this.correctionsParameters, this.correctionsParameters.save16, true); // save, no show
		  }

		  imp_RGB=eyesisCorrections.convertRGB48toRGB24(
				  stack,
				  title+"-RGB24",
				  0, 65536, // r range 0->0, 65536->256
				  0, 65536, // g range
				  0, 65536,// b range
				  0, 65536);// alpha range
		  if (JPEG_scale!=1.0){
			  ImageProcessor ip=imp_RGB.getProcessor();
			  ip.setInterpolationMethod(ImageProcessor.BICUBIC);
			  ip=ip.resize((int)(ip.getWidth()*JPEG_scale),(int) (ip.getHeight()*JPEG_scale));
			  imp_RGB= new ImagePlus(imp_RGB.getTitle(),ip);
			  imp_RGB.updateAndDraw();
		  }
		  eyesisCorrections.saveAndShow(imp_RGB, this.correctionsParameters);

		  return result;
	  }

// Processing sets of 4 images together
	  public void processCLTSets( // not used in lwir
			  CLTParameters           clt_parameters,
			  ColorProcParameters colorProcParameters,
			  CorrectionColorProc.ColorGainsParameters     channelGainParameters,
			  EyesisCorrectionParameters.RGBParameters             rgbParameters,
			  EyesisCorrectionParameters.EquirectangularParameters equirectangularParameters,
			  final int          threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel)
	  {
		  this.startTime=System.nanoTime();
		  String [] sourceFiles=correctionsParameters.getSourcePaths();
		  boolean [] enabledFiles=new boolean[sourceFiles.length];
		  for (int i=0;i<enabledFiles.length;i++) enabledFiles[i]=false;
		  int numFilesToProcess=0;
		  int numImagesToProcess=0;
		  for (int nFile=0;nFile<enabledFiles.length;nFile++){
			  if ((sourceFiles[nFile]!=null) && (sourceFiles[nFile].length()>1)) {
				  int [] channels= fileChannelToSensorChannels(correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile]));
				  if (channels!=null){
					  for (int i=0;i<channels.length;i++) if (eyesisCorrections.isChannelEnabled(channels[i])){
						  if (!enabledFiles[nFile]) numFilesToProcess++;
						  enabledFiles[nFile]=true;
						  numImagesToProcess++;
					  }
				  }
			  }
		  }
		  if (numFilesToProcess==0){
			  System.out.println("No files to process (of "+sourceFiles.length+")");
			  return;
		  } else {
			  if (debugLevel>0) System.out.println(numFilesToProcess+ " files to process (of "+sourceFiles.length+"), "+numImagesToProcess+" images to process");
		  }
		  double [] referenceExposures=eyesisCorrections.calcReferenceExposures(debugLevel); // multiply each image by this and divide by individual (if not NaN)
		  int [][] fileIndices=new int [numImagesToProcess][2]; // file index, channel number
		  int index=0;
		  for (int nFile=0;nFile<enabledFiles.length;nFile++){
			  if ((sourceFiles[nFile]!=null) && (sourceFiles[nFile].length()>1)) {
				  int [] channels= fileChannelToSensorChannels(correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile]));
				  if (channels!=null){
					  for (int i=0;i<channels.length;i++) if (eyesisCorrections.isChannelEnabled(channels[i])){
						  fileIndices[index  ][0]=nFile;
						  fileIndices[index++][1]=channels[i];
					  }
				  }
			  }
		  }
		  ArrayList<String> setNames = new ArrayList<String>();
		  ArrayList<ArrayList<Integer>> setFiles = new ArrayList<ArrayList<Integer>>();

		  for (int iImage=0;iImage<fileIndices.length;iImage++){
			  int nFile=fileIndices[iImage][0];
			  String setName = correctionsParameters.getNameFromSourceTiff(sourceFiles[nFile]);
			  if (!setNames.contains(setName)) {
				  setNames.add(setName);
				  setFiles.add(new ArrayList<Integer>());
			  }
//			  setFiles.get(setNames.indexOf(setName)).add(new Integer(nFile));
			  setFiles.get(setNames.indexOf(setName)).add(nFile);
		  }
		  int iImage = 0;
		  for (int nSet = 0; nSet < setNames.size(); nSet++){
			  int maxChn = 0;
			  for (int i = 0; i < setFiles.get(nSet).size(); i++){
				  int chn = fileIndices[setFiles.get(nSet).get(i)][1];
				  if (chn > maxChn) maxChn = chn;
			  }
			  int [] channelFiles = new int[maxChn+1];
			  for (int i =0; i < channelFiles.length; i++) channelFiles[i] = -1;
			  for (int i = 0; i < setFiles.get(nSet).size(); i++){
				  channelFiles[fileIndices[setFiles.get(nSet).get(i)][1]] = setFiles.get(nSet).get(i);
			  }

			  ImagePlus [] imp_srcs = new ImagePlus[channelFiles.length];
			  double [] scaleExposure = new double[channelFiles.length];
			  for (int srcChannel=0; srcChannel<channelFiles.length; srcChannel++){
				  int nFile=channelFiles[srcChannel];
				  imp_srcs[srcChannel]=null;
				  if (nFile >=0){
					  imp_srcs[srcChannel] = eyesisCorrections.getJp4Tiff(sourceFiles[nFile], this.geometryCorrection.woi_tops, this.geometryCorrection.camera_heights);

					  scaleExposure[srcChannel] = 1.0;
					  if (!Double.isNaN(referenceExposures[nFile]) && (imp_srcs[srcChannel].getProperty("EXPOSURE")!=null)){
						  scaleExposure[srcChannel] = referenceExposures[nFile]/Double.parseDouble((String) imp_srcs[srcChannel].getProperty("EXPOSURE"));
						  if (debugLevel>0) System.out.println("Will scale intensity (to compensate for exposure) by  "+scaleExposure);
					  }
					  imp_srcs[srcChannel].setProperty("name",    correctionsParameters.getNameFromSourceTiff(sourceFiles[nFile]));
					  imp_srcs[srcChannel].setProperty("channel", srcChannel); // it may already have channel
					  imp_srcs[srcChannel].setProperty("path",    sourceFiles[nFile]); // it may already have channel

					  if (this.correctionsParameters.pixelDefects && (eyesisCorrections.defectsXY!=null)&& (eyesisCorrections.defectsXY[srcChannel]!=null)){
						  // apply pixel correction
						  int numApplied=	eyesisCorrections.correctDefects(
								  imp_srcs[srcChannel],
								  srcChannel,
								  debugLevel);
						  if ((debugLevel>0) && (numApplied>0)) { // reduce verbosity after verified defect correction works
							  System.out.println("Corrected "+numApplied+" pixels in "+sourceFiles[nFile]);
						  }
					  }

					  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
					  int width =  imp_srcs[srcChannel].getWidth();
					  int height = imp_srcs[srcChannel].getHeight();

					  if (clt_parameters.sat_level > 0.0){
						  double [] saturations = {
								  Double.parseDouble((String) imp_srcs[srcChannel].getProperty("saturation_1")),
								  Double.parseDouble((String) imp_srcs[srcChannel].getProperty("saturation_0")),
								  Double.parseDouble((String) imp_srcs[srcChannel].getProperty("saturation_3")),
								  Double.parseDouble((String) imp_srcs[srcChannel].getProperty("saturation_2"))};
						  saturation_imp[srcChannel] = new boolean[width*height];
						  System.out.println(String.format("channel %d saturations = %6.2f %6.2f %6.2f %6.2f", srcChannel,
								  saturations[0],saturations[1],saturations[2],saturations[3]));
						  double [] scaled_saturations = new double [saturations.length];
						  for (int i = 0; i < scaled_saturations.length; i++){
							  scaled_saturations[i] = saturations[i] * clt_parameters.sat_level;
						  }
						  for (int y = 0; y < height-1; y+=2){
							  for (int x = 0; x < width-1; x+=2){
								  if (pixels[y*width+x        ] > scaled_saturations[0])  saturation_imp[srcChannel][y*width+x        ] = true;
								  if (pixels[y*width+x+      1] > scaled_saturations[1])  saturation_imp[srcChannel][y*width+x      +1] = true;
								  if (pixels[y*width+x+width  ] > scaled_saturations[2])  saturation_imp[srcChannel][y*width+x+width  ] = true;
								  if (pixels[y*width+x+width+1] > scaled_saturations[3])  saturation_imp[srcChannel][y*width+x+width+1] = true;
							  }
						  }
					  }



					  if (this.correctionsParameters.vignetting){
						  if ((eyesisCorrections.channelVignettingCorrection==null) || (srcChannel<0) || (srcChannel>=eyesisCorrections.channelVignettingCorrection.length) || (eyesisCorrections.channelVignettingCorrection[srcChannel]==null)){
							  System.out.println("No vignetting data for channel "+srcChannel);
							  return;
						  }
///						  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
						  if (pixels.length!=eyesisCorrections.channelVignettingCorrection[srcChannel].length){
							  System.out.println("Vignetting data for channel "+srcChannel+" has "+eyesisCorrections.channelVignettingCorrection[srcChannel].length+" pixels, image "+sourceFiles[nFile]+" has "+pixels.length);
							  return;
						  }
						  // TODO: Move to do it once:
						  double min_non_zero = 0.0;
						  for (int i=0;i<pixels.length;i++){
							  double d = eyesisCorrections.channelVignettingCorrection[srcChannel][i];
							  if ((d > 0.0) && ((min_non_zero == 0) || (min_non_zero > d))){
								  min_non_zero = d;
							  }
						  }
						  double max_vign_corr = clt_parameters.vignetting_range*min_non_zero;

						  System.out.println("Vignetting data: channel="+srcChannel+", min = "+min_non_zero);
						  for (int i=0;i<pixels.length;i++){
							  double d = eyesisCorrections.channelVignettingCorrection[srcChannel][i];
							  if (d > max_vign_corr) d = max_vign_corr;
							  pixels[i]*=d;
						  }
						  // Scale here, combine with vignetting later?
///						  int width =  imp_srcs[srcChannel].getWidth();
///						  int height = imp_srcs[srcChannel].getHeight();
						  for (int y = 0; y < height-1; y+=2){
							  for (int x = 0; x < width-1; x+=2){
								  pixels[y*width+x        ] *= clt_parameters.scale_g;
								  pixels[y*width+x+width+1] *= clt_parameters.scale_g;
								  pixels[y*width+x      +1] *= clt_parameters.scale_r;
								  pixels[y*width+x+width  ] *= clt_parameters.scale_b;
							  }
						  }

					  } else { // assuming GR/BG pattern
						  System.out.println("Applying fixed color gain correction parameters: Gr="+
								  clt_parameters.novignetting_r+", Gg="+clt_parameters.novignetting_g+", Gb="+clt_parameters.novignetting_b);
///						  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
///						  int width =  imp_srcs[srcChannel].getWidth();
///						  int height = imp_srcs[srcChannel].getHeight();
						  double kr = clt_parameters.scale_r/clt_parameters.novignetting_r;
						  double kg = clt_parameters.scale_g/clt_parameters.novignetting_g;
						  double kb = clt_parameters.scale_b/clt_parameters.novignetting_b;
						  for (int y = 0; y < height-1; y+=2){
							  for (int x = 0; x < width-1; x+=2){
								  pixels[y*width+x        ] *= kg;
								  pixels[y*width+x+width+1] *= kg;
								  pixels[y*width+x      +1] *= kr;
								  pixels[y*width+x+width  ] *= kb;
							  }
						  }
					  }
				  }
			  }
			  // may need to equalize gains between channels
			  // may need to equalize gains between channels
			  if (clt_parameters.gain_equalize || clt_parameters.colors_equalize){
				  channelGainsEqualize(
						  clt_parameters.gain_equalize,
						  clt_parameters.colors_equalize,
						  clt_parameters.nosat_equalize, // boolean nosat_equalize,
						  channelFiles,
						  imp_srcs,
						  saturation_imp, // boolean[][] saturated,
						  setNames.get(nSet), // just for debug messeges == setNames.get(nSet)
						  debugLevel);
			  }
			  for (int srcChannel=0; srcChannel<channelFiles.length; srcChannel++){
				  int nFile=channelFiles[srcChannel];
				  if (nFile >=0){
					  processCLTSetImage( // returns ImagePlus, but it already should be saved/shown
							  imp_srcs[srcChannel], // should have properties "name"(base for saving results), "channel","path"
							  clt_parameters,
							  colorProcParameters,
							  channelGainParameters,
							  rgbParameters,
							  scaleExposure[srcChannel],
							  threadsMax,  // maximal number of threads to launch
							  updateStatus,
							  debugLevel);
					  // warp result (add support for different color modes)
					  if (this.correctionsParameters.equirectangular){
						  if (equirectangularParameters.clearFullMap) eyesisCorrections.pixelMapping.deleteEquirectangularMapFull(srcChannel); // save memory? //removeUnusedSensorData - no, use equirectangular specific settings
						  if (equirectangularParameters.clearAllMaps) eyesisCorrections.pixelMapping.deleteEquirectangularMapAll(srcChannel); // save memory? //removeUnusedSensorData - no, use equirectangular specific settings
					  }
					  //pixelMapping
					  if (debugLevel >-1) System.out.println("Processing image "+(iImage+1)+" (of "+fileIndices.length+") finished at "+
							  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory5="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
					  if (eyesisCorrections.stopRequested.get()>0) {
						  System.out.println("User requested stop");
						  return;
					  }
					  iImage++;
				  }
			  }
		  }
		  System.out.println("processCLTSets(): processing "+fileIndices.length+" files finished at "+
				  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory6="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
	  }

	  public ImagePlus processCLTSetImage( // not used in lwir
			  ImagePlus imp_src, // should have properties "name"(base for saving results), "channel","path"
			  CLTParameters           clt_parameters,
			  ColorProcParameters colorProcParameters,
			  CorrectionColorProc.ColorGainsParameters     channelGainParameters,
			  EyesisCorrectionParameters.RGBParameters             rgbParameters,
			  double 		     scaleExposure,
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel){
		  boolean advanced=this.correctionsParameters.zcorrect || this.correctionsParameters.equirectangular;
		  boolean rotate=    advanced? false: this.correctionsParameters.rotate;
		  double JPEG_scale= advanced? 1.0: this.correctionsParameters.JPEG_scale;
		  boolean toRGB=     advanced? true: this.correctionsParameters.toRGB;
		  // may use this.StartTime to report intermediate steps execution times
		  String name=(String) imp_src.getProperty("name");
		  int channel= (Integer) imp_src.getProperty("channel");
		  String path= (String) imp_src.getProperty("path");

		  String title=String.format("%s%s-%02d",name, sAux(), channel);
		  ImagePlus result=imp_src;
		  if (debugLevel>1) System.out.println("processing: "+path);
		  result.setTitle(title+"RAW");
		  if (!this.correctionsParameters.split){
			  eyesisCorrections.saveAndShow(result, this.correctionsParameters);
			  return result;
		  }
		  // Generate split parameters for DCT processing mode
		  // Split into Bayer components, oversample, increase canvas

		  double [][] double_stack = eyesisCorrections.bayerToDoubleStack(
				  result, // source Bayer image, linearized, 32-bit (float))
				  null, // no margins, no oversample
				  isMonochrome()); // this.is_mono);

		  String titleFull=title+"-SPLIT";
		  if (debugLevel > -1){
			  double [] chn_avg = {0.0,0.0,0.0};
			  int width =  imp_src.getWidth();
			  int height = imp_src.getHeight();
			  for (int c = 0; c < 3; c++){
				  for (int i = 0; i<double_stack[c].length; i++){
					  chn_avg[c] += double_stack[c][i];
				  }
			  }
			  chn_avg[0] /= width*height/4;
			  chn_avg[1] /= width*height/4;
			  chn_avg[2] /= width*height/2;
			  System.out.println("Split channels averages: R="+chn_avg[0]+", G="+chn_avg[2]+", B="+chn_avg[1]);
		  }
		  String [] rbg_titles = {"Red", "Blue", "Green"};
		  ImageStack stack;
		  if (!this.correctionsParameters.debayer) {
			  stack = ShowDoubleFloatArrays.makeStack(double_stack, imp_src.getWidth(), imp_src.getHeight(), rbg_titles);
			  result= new ImagePlus(titleFull, stack);
			  eyesisCorrections.saveAndShow(result, this.correctionsParameters);
			  return result;
		  }
		  // =================
		  if (debugLevel > 0) {
			  System.out.println("Showing image BEFORE_CLT_PROC");
			  ShowDoubleFloatArrays.showArrays(double_stack,  imp_src.getWidth(), imp_src.getHeight(), true, "BEFORE_CLT_PROC", rbg_titles);
		  }
		  if (this.correctionsParameters.deconvolve) { // process with DCT, otherwise use simple debayer
			  ImageDtt image_dtt = new ImageDtt(
					  getNumSensors(),
					  clt_parameters.transform_size,
					  clt_parameters.img_dtt,
					  isAux(),
					  isMonochrome(),
					  isLwir(),
					  clt_parameters.getScaleStrength(isAux()));
			  for (int i =0 ; i < double_stack[0].length; i++){
				  double_stack[2][i]*=0.5; // Scale blue twice to compensate less pixels than green
			  }
			  double [][][][][] clt_data = image_dtt.clt_aberrations(
					  double_stack,                 // final double [][]       imade_data,
					  imp_src.getWidth(),           //	final int               width,
					  clt_kernels[channel],         // final double [][][][][] clt_kernels, // [color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
//					  clt_parameters.kernel_step,
//					  image_dtt.transform_size,
					  clt_parameters.clt_window,
					  clt_parameters.shift_x,       // final int               shiftX, // shift image horizontally (positive - right) - just for testing
					  clt_parameters.shift_y,       // final int               shiftY, // shift image vertically (positive - down)
					  clt_parameters.tileX,         // final int               debug_tileX,
					  clt_parameters.tileY,         // final int               debug_tileY,
					  (clt_parameters.dbg_mode & 64) != 0, // no fract shift
					  (clt_parameters.dbg_mode & 128) != 0, // no convolve
					  (clt_parameters.dbg_mode & 256) != 0, // transpose convolve
					  threadsMax,
					  debugLevel);
//					  updateStatus);


			  System.out.println("clt_data.length="+clt_data.length+" clt_data[0].length="+clt_data[0].length
					  +" clt_data[0][0].length="+clt_data[0][0].length+" clt_data[0][0][0].length="+
					  clt_data[0][0][0].length+" clt_data[0][0][0][0].length="+clt_data[0][0][0][0].length);
			  /*
			  if (dct_parameters.color_DCT){ // convert RBG -> YPrPb
				  dct_data = image_dtt.dct_color_convert(
						  dct_data,
						  colorProcParameters.kr,
						  colorProcParameters.kb,
						  dct_parameters.sigma_rb,        // blur of channels 0,1 (r,b) in addition to 2 (g)
						  dct_parameters.sigma_y,         // blur of Y from G
						  dct_parameters.sigma_color,     // blur of Pr, Pb in addition to Y
						  threadsMax,
						  debugLevel);
			  } else { // just LPF RGB
			  */
				  if (clt_parameters.getCorrSigma(image_dtt.isMonochrome()) > 0){ // no filter at all
					  for (int chn = 0; chn < clt_data.length; chn++) {
						  image_dtt.clt_lpf(
								  clt_parameters.getCorrSigma(image_dtt.isMonochrome()),
								  clt_data[chn],
///								  image_dtt.transform_size,
								  threadsMax,
								  debugLevel);
					  }
				  }
/*
			  }
*/
			  int tilesY = imp_src.getHeight()/image_dtt.transform_size;
			  int tilesX = imp_src.getWidth()/image_dtt.transform_size;
			  if (debugLevel > 0){
				  System.out.println("--tilesX="+tilesX);
				  System.out.println("--tilesY="+tilesY);
			  }
			  if (debugLevel > 1){
			        double [][] clt = new double [clt_data.length*4][];
			        for (int chn = 0; chn < clt_data.length; chn++) {
			        	double [][] clt_set = image_dtt.clt_dbg(
			        			clt_data [chn],
								  threadsMax,
								  debugLevel);
			        	for (int ii = 0; ii < clt_set.length; ii++) clt[chn*4+ii] = clt_set[ii];
			        }

			        if (debugLevel > 0){
			        	ShowDoubleFloatArrays.showArrays(clt,
			        			tilesX*image_dtt.transform_size,
			        			tilesY*image_dtt.transform_size,
			        			true,
			        			result.getTitle()+"-CLT");
			        }
			  }
			  double [][] iclt_data = new double [clt_data.length][];
			  for (int chn=0; chn<clt_data.length;chn++){
				  iclt_data[chn] = image_dtt.iclt_2d(
						  clt_data[chn],                  // scanline representation of dcd data, organized as dct_size x dct_size tiles
///						  image_dtt.transform_size,  // final int
						  clt_parameters.clt_window,      // window_type
						  15,                             // clt_parameters.iclt_mask,       //which of 4 to transform back
						  0,                              // clt_parameters.dbg_mode,        //which of 4 to transform back
						  threadsMax,
						  debugLevel);

			  }

//					  if (debugLevel > -1) System.out.println("Applyed LPF, sigma = "+dct_parameters.dbg_sigma);
					  if (debugLevel > 0) ShowDoubleFloatArrays.showArrays(
							  iclt_data,
							  (tilesX + 1) * image_dtt.transform_size,
							  (tilesY + 1) * image_dtt.transform_size,
							  true,
							  result.getTitle()+"-rbg_sigma");
				/*
				  }
			  }
			 */
			  if (debugLevel > 0) ShowDoubleFloatArrays.showArrays(iclt_data,
					  (tilesX + 0) * image_dtt.transform_size,
					  (tilesY + 0) * image_dtt.transform_size,
					  true,
					  result.getTitle()+"-ICLT-RGB");

			  // convert to ImageStack of 3 slices
			  String [] sliceNames = {"red", "blue", "green"};
			  stack = ShowDoubleFloatArrays.makeStack(
					  iclt_data,
					  (tilesX + 0) * image_dtt.transform_size,
					  (tilesY + 0) * image_dtt.transform_size,
					  sliceNames); // or use null to get chn-nn slice names


		  } else { // if (this.correctionsParameters.deconvolve) - here use a simple debayer
			  System.out.println("Bypassing CLT-based aberration correction");
			  stack = ShowDoubleFloatArrays.makeStack(double_stack, imp_src.getWidth(), imp_src.getHeight(), rbg_titles);
			  debayer_rbg(stack, 0.25); // simple standard 3x3 kernel debayer
		  }
		  if (debugLevel > -1){
			  double [] chn_avg = {0.0,0.0,0.0};
			  float [] pixels;
			  int width =  stack.getWidth();
			  int height = stack.getHeight();

			  for (int c = 0; c <3; c++){
				  pixels = (float[]) stack.getPixels(c+1);
				  for (int i = 0; i<pixels.length; i++){
					  chn_avg[c] += pixels[i];
				  }
			  }
			  chn_avg[0] /= width*height;
			  chn_avg[1] /= width*height;
			  chn_avg[2] /= width*height;
			  System.out.println("Processed channels averages: R="+chn_avg[0]+", G="+chn_avg[2]+", B="+chn_avg[1]);
		  }

		  if (!this.correctionsParameters.colorProc){
			  result= new ImagePlus(titleFull, stack);
			  eyesisCorrections.saveAndShow(
					  result,
					  this.correctionsParameters);
			  return result;
		  }
		  if (debugLevel > 1) System.out.println("before colors.1");
		  //Processing colors - changing stack sequence to r-g-b (was r-b-g)
		  if (!eyesisCorrections.fixSliceSequence(
				  stack,
				  debugLevel)){
			  if (debugLevel > -1) System.out.println("fixSliceSequence() returned false");
			  return null;
		  }
		  if (debugLevel > 1) System.out.println("before colors.2");
		  if (debugLevel > 1){
			  ImagePlus imp_dbg=new ImagePlus(imp_src.getTitle()+"-"+channel+"-preColors",stack);
			  eyesisCorrections.saveAndShow(
					  imp_dbg,
					  this.correctionsParameters);
		  }
		  if (debugLevel > 1) System.out.println("before colors.3, scaleExposure="+scaleExposure+" scale = "+(255.0/eyesisCorrections.psfSubpixelShouldBe4/eyesisCorrections.psfSubpixelShouldBe4/scaleExposure));
		  CorrectionColorProc correctionColorProc=new CorrectionColorProc(eyesisCorrections.stopRequested);
		  double [][] yPrPb=new double [3][];
		  //			if (dct_parameters.color_DCT){
		  // need to get YPbPr - not RGB here
		  //			} else {
		  correctionColorProc.processColorsWeights(stack, // just gamma convert? TODO: Cleanup? Convert directly form the linear YPrPb
				  //					  255.0/this.psfSubpixelShouldBe4/this.psfSubpixelShouldBe4, //  double scale,     // initial maximal pixel value (16))
				  //					  255.0/eyesisCorrections.psfSubpixelShouldBe4/eyesisCorrections.psfSubpixelShouldBe4/scaleExposure, //  double scale,     // initial maximal pixel value (16))
				  //					  255.0/2/2/scaleExposure, //  double scale,     // initial maximal pixel value (16))
				  255.0/scaleExposure, //  double scale,     // initial maximal pixel value (16))
				  colorProcParameters,
				  channelGainParameters,
				  channel,
				  null, //correctionDenoise.getDenoiseMask(),
				  this.correctionsParameters.blueProc,
				  debugLevel);
		  if (debugLevel > 1) System.out.println("Processed colors to YPbPr, total number of slices="+stack.getSize());
		  if (debugLevel > 1) {
			  ImagePlus imp_dbg=new ImagePlus("procColors",stack);
			  eyesisCorrections.saveAndShow(
					  imp_dbg,
					  this.correctionsParameters);
		  }
		  float [] fpixels;
		  int [] slices_YPrPb = {8,6,7};
		  yPrPb=new double [3][];
		  for (int n = 0; n < slices_YPrPb.length; n++){
			  fpixels = (float[]) stack.getPixels(slices_YPrPb[n]);
			  yPrPb[n] = new double [fpixels.length];
			  for (int i = 0; i < fpixels.length; i++) yPrPb[n][i] = fpixels[i];
		  }

		  if (toRGB) {
			  if (debugLevel > 0){
				  System.out.println("correctionColorProc.YPrPbToRGB");
			  }
			  stack =  YPrPbToRGB(yPrPb,
					  colorProcParameters.kr,        // 0.299;
					  colorProcParameters.kb,        // 0.114;
					  stack.getWidth());

			  title=titleFull; // including "-DECONV" or "-COMBO"
			  titleFull=title+"-RGB-float";
			  //Trim stack to just first 3 slices
			  if (debugLevel > 1){ // 2){
				  ImagePlus imp_dbg=new ImagePlus("YPrPbToRGB",stack);
				  eyesisCorrections.saveAndShow(
						  imp_dbg,
						  this.correctionsParameters);
			  }
			  while (stack.getSize() > 3) stack.deleteLastSlice();
			  if (debugLevel > 1) System.out.println("Trimming color stack");
		  } else {
			  title=titleFull; // including "-DECONV" or "-COMBO"
			  titleFull=title+"-YPrPb"; // including "-DECONV" or "-COMBO"
			  if (debugLevel > 1) System.out.println("Using full stack, including YPbPr");
		  }

		  result= new ImagePlus(titleFull, stack);
		  // Crop image to match original one (scaled to oversampling)
/*
		  if (crop){ // always crop if equirectangular
			  if (debugLevel > 1) System.out.println("cropping");
			  stack = eyesisCorrections.cropStack32(stack,splitParameters);
			  if (debugLevel > 2) { // 2){
				  ImagePlus imp_dbg=new ImagePlus("cropped",stack);
				  eyesisCorrections.saveAndShow(
						  imp_dbg,
						  this.correctionsParameters);
			  }
		  }
*/
		  // rotate the result
		  if (rotate){ // never rotate for equirectangular
			  stack=eyesisCorrections.rotateStack32CW(stack);
		  }
		  if (!toRGB && !this.correctionsParameters.jpeg){ // toRGB set for equirectangular
			  if (debugLevel > 1) System.out.println("!toRGB && !this.correctionsParameters.jpeg");
			  eyesisCorrections.saveAndShow(result, this.correctionsParameters);
			  return result;
		  } else { // that's not the end result, save if required
			  if (debugLevel > 1) System.out.println("!toRGB && !this.correctionsParameters.jpeg - else");
			  eyesisCorrections.saveAndShow(result,
					  eyesisCorrections.correctionsParameters,
					  eyesisCorrections.correctionsParameters.save32,
					  false,
					  eyesisCorrections.correctionsParameters.JPEG_quality); // save, no show
		  }
		  // convert to RGB48 (16 bits per color component)
		  ImagePlus imp_RGB;
		  stack=eyesisCorrections.convertRGB32toRGB16Stack(
				  stack,
				  rgbParameters);

		  titleFull=title+"-RGB48";
		  result= new ImagePlus(titleFull, stack);
		  //			  ImagePlus imp_RGB24;
		  result.updateAndDraw();
		  if (debugLevel > 1) System.out.println("result.updateAndDraw(), "+titleFull+"-RGB48");

		  CompositeImage compositeImage=eyesisCorrections.convertToComposite(result);

		  if (!this.correctionsParameters.jpeg && !advanced){ // RGB48 was the end result
			  if (debugLevel > 1) System.out.println("if (!this.correctionsParameters.jpeg && !advanced)");
			  eyesisCorrections.saveAndShow(compositeImage, this.correctionsParameters);
			  return result;
		  } else { // that's not the end result, save if required
			  if (debugLevel > 1) System.out.println("if (!this.correctionsParameters.jpeg && !advanced) - else");
			  eyesisCorrections.saveAndShow(compositeImage, this.correctionsParameters, this.correctionsParameters.save16, false); // save, no show
			  //				  eyesisCorrections.saveAndShow(compositeImage, this.correctionsParameters, this.correctionsParameters.save16, true); // save, no show
		  }

		  imp_RGB=eyesisCorrections.convertRGB48toRGB24(
				  stack,
				  title+"-RGB24",
				  0, 65536, // r range 0->0, 65536->256
				  0, 65536, // g range
				  0, 65536,// b range
				  0, 65536);// alpha range
		  if (JPEG_scale!=1.0){
			  ImageProcessor ip=imp_RGB.getProcessor();
			  ip.setInterpolationMethod(ImageProcessor.BICUBIC);
			  ip=ip.resize((int)(ip.getWidth()*JPEG_scale),(int) (ip.getHeight()*JPEG_scale));
			  imp_RGB= new ImagePlus(imp_RGB.getTitle(),ip);
			  imp_RGB.updateAndDraw();
		  }
		  eyesisCorrections.saveAndShow(imp_RGB, this.correctionsParameters);

		  return result;
	  }
/*	  
	  public class SetChannels{ // USED in lwir
		  public String set_name;    // set name (timestamp)
		  int [] file_number; // array of file numbers for channels
		  public SetChannels(String name, int[] fn){ // USED in lwir
			  set_name = name;
			  file_number = fn;
		  }
		  public String name() { // USED in lwir
			  return set_name;
		  }
		  public int [] fileNumber() { // USED in lwir
			  return file_number;
		  }
		  public int fileNumber(int i) { // not used in lwir
			  return file_number[i];
		  }
	  }
*/	  

	  public SetChannels [] setChannels( // USED in lwir
			  int debugLevel) {
		  return setChannels(null, debugLevel);
	  }

	  public int [] fileChannelToSensorChannels(int file_channel) { // USED in lwir
		  if (!eyesisCorrections.pixelMapping.subcamerasUsed()) { // not an Eyesis-type system
			  // Here use firstSubCameraConfig - subcamera, corresponding to sensors[0] of this PixelMapping instance (1 for Eyesis, 2 for Rig/LWIR)
			  return eyesisCorrections.pixelMapping.channelsForSubCamera(file_channel - correctionsParameters.firstSubCameraConfig);
		  } else 	if (correctionsParameters.isJP4()){ // not used in lwir
			  // Here use firstSubCamera - first filename index to be processed by this PixelMapping instance (1 for Eyesis, 2 for Rig/LWIR)
			  int subCamera= file_channel- correctionsParameters.firstSubCamera; // to match those in the sensor files
			  return  eyesisCorrections.pixelMapping.channelsForSubCamera(subCamera);
		  } else {
			  int [] channels = {file_channel};
			  return channels;
		  }
	  }

	  SetChannels [] setChannels( // USED in lwir
			  String single_set_name, // process only files that contain specified series (timestamp) in the name
			  int debugLevel) {
		  String [] sourceFiles=correctionsParameters.getSourcePaths();
		  return setChannels( // USED in lwir
				  single_set_name, // process only files that contain specified series (timestamp) in the name
				  sourceFiles,
				  debugLevel);
	  }
	  
	  SetChannels [] setChannels( // USED in lwir
			  String single_set_name, // process only files that contain specified series (timestamp) in the name
			  String [] sourceFiles,
			  int debugLevel) {
//		  String [] sourceFiles=correctionsParameters.getSourcePaths();
		  boolean [] enabledFiles=new boolean[sourceFiles.length];
		  for (int i=0;i<enabledFiles.length;i++) enabledFiles[i]=false;
		  int numFilesToProcess=0;
		  int numImagesToProcess=0;
		  /*
		  for (int nFile=0;nFile<enabledFiles.length;nFile++){
			  if (    (sourceFiles[nFile]!=null) &&
					  (sourceFiles[nFile].length() > 1) &&
					  ((single_set_name == null) || ((correctionsParameters.getNameFromTiff(sourceFiles[nFile]) != null) && correctionsParameters.getNameFromTiff(sourceFiles[nFile]).contains(single_set_name)))) {
				  int [] channels= fileChannelToSensorChannels(correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile]));
				  if (channels!=null){
					  for (int i=0;i<channels.length;i++) if (eyesisCorrections.isChannelEnabled(channels[i])){
						  if (!enabledFiles[nFile]) numFilesToProcess++;
						  enabledFiles[nFile]=true;
						  numImagesToProcess++;
					  }
				  }
			  }
		  }
		   */
		  int [][] channels_files = new int[enabledFiles.length][]; // (single_set_name == null) ? null: new int[enabledFiles.length][];
		  for (int nFile=0;nFile<enabledFiles.length;nFile++) if ((sourceFiles[nFile]!=null) && (sourceFiles[nFile].length() > 1)) {
			  boolean do_proc = (single_set_name == null);
			  if (!do_proc) {
				  String name_from_tiff=correctionsParameters.getNameFromTiff(sourceFiles[nFile]);
				  if ((name_from_tiff != null) && (name_from_tiff.contains(single_set_name))) {
					  do_proc = true;
				  }
			  }
			  if (do_proc) {
				  int [] channels= fileChannelToSensorChannels(correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile]));
				  channels_files[nFile] = channels;
				  if (channels!=null){
					  for (int i=0;i<channels.length;i++) if (eyesisCorrections.isChannelEnabled(channels[i])){
						  if (!enabledFiles[nFile]) numFilesToProcess++;
						  enabledFiles[nFile]=true;
						  numImagesToProcess++;
					  }
				  }
			  }
		  }
		  if (numFilesToProcess==0){
			  System.out.println("No files to process (of "+sourceFiles.length+")");
			  return null; // not used in lwir
		  } else {
			  if (debugLevel>0) System.out.println(numFilesToProcess+ " files to process (of "+sourceFiles.length+"), "+numImagesToProcess+" images to process");
		  }
		  int [][] fileIndices=new int [numImagesToProcess][2]; // file index, channel number
		  int index=0;
		  for (int nFile=0;nFile<enabledFiles.length;nFile++) if ((sourceFiles[nFile]!=null) && (sourceFiles[nFile].length()>1)) { // enabledFiles not used anymore?
			  /*
			  if (    (sourceFiles[nFile]!=null) &&
					  (sourceFiles[nFile].length()>1) &&
					  ((single_set_name == null) || ((correctionsParameters.getNameFromTiff(sourceFiles[nFile]) != null)&& correctionsParameters.getNameFromTiff(sourceFiles[nFile]).contains(single_set_name)))) { // not used in lwir
				  int [] channels= fileChannelToSensorChannels(correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile]));
				  if (channels!=null){
					  for (int i=0;i<channels.length;i++) if (eyesisCorrections.isChannelEnabled(channels[i])){
						  fileIndices[index  ][0]=nFile;
						  fileIndices[index++][1]=channels[i];
					  }
				  }
			  }
			   */
			  if (channels_files[nFile] != null) {
				  int [] channels= channels_files[nFile]; // fileChannelToSensorChannels(correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile]));
				  if (channels!=null){
					  for (int i=0;i<channels.length;i++) if (eyesisCorrections.isChannelEnabled(channels[i])){
						  fileIndices[index  ][0]=nFile;
						  fileIndices[index++][1]=channels[i];
					  }
				  }
			  }
		  }
		  ArrayList<String> setNames = new ArrayList<String>();
		  ArrayList<ArrayList<Integer>> setFiles = new ArrayList<ArrayList<Integer>>();

		  for (int iImage=0;iImage<fileIndices.length;iImage++){
			  int nFile=fileIndices[iImage][0];
			  String setName = correctionsParameters.getNameFromSourceTiff(sourceFiles[nFile]); // supports set directory name
			  if (!setNames.contains(setName)) {
				  setNames.add(setName);
				  setFiles.add(new ArrayList<Integer>());
			  }
			  setFiles.get(setNames.indexOf(setName)).add(iImage); // .add(new Integer(iImage));
		  }
		  SetChannels [] sc = new SetChannels[setNames.size()];
		  for (int nSet = 0; nSet < setNames.size(); nSet++){
			  int maxChn = 0;
			  for (int i = 0; i < setFiles.get(nSet).size(); i++){
				  int chn = fileIndices[setFiles.get(nSet).get(i)][1];
				  if (chn > maxChn) maxChn = chn;
			  }
			  int [] channelFiles = new int[maxChn+1];
			  for (int i =0; i < channelFiles.length; i++) channelFiles[i] = -1;
			  for (int i = 0; i < setFiles.get(nSet).size(); i++){
				  channelFiles[fileIndices[setFiles.get(nSet).get(i)][1]] = fileIndices[setFiles.get(nSet).get(i)][0];
			  }
			  sc[nSet] = new SetChannels(setNames.get(nSet), channelFiles);
		  }
		  return sc;
	  }


	  int getTotalFiles(SetChannels [] sc) { // USED in lwir
		  int nf = 0;
		  if (sc != null) {
			  for (int i = 0; i < sc.length; i++) nf+=sc[i].fileNumber().length;
		  }
		  return nf;
	  }


	  /**
	   * Conditions images for a single image set
	   * @param clt_parameters      various parameters
	   * @param sourceFiles         array of source file paths matching indices in channelFiles
	   * @param set_name            name of the current image set (normally timestamp with "_" for decimal point
	   * @param referenceExposures  array of per-channel reference exposures, data will be scaled using Exif exposure of each file
	   * @param channelFiles        array of file indices (in sourceFiles array) for the camera channels ([0] - index of the first channel file)
	   * @param scaleExposures      array of per-channel "brightening" of images (reference exposure/ actual exposure
	   * @param saturation_imp      per-channel bitmask of the saturated pixels or null. Should be initialized by the caller, will be filled here
	   * @param threadsMax          maximal number of threads to use
	   * @param debugLevel          debug (verbosity) level
	   * @return array of per-channel ImagePlus objects to process (with saturation_imp)
	   */
	  public ImagePlus[] conditionImageSet( // USED in lwir
			  CLTParameters  clt_parameters,
			  ColorProcParameters                       colorProcParameters, //
			  String []                                 sourceFiles,
			  String                                    set_name,
			  double []                                 referenceExposures,
			  int []                                    channelFiles,
			  double []                                 scaleExposures,
			  boolean [][]                              saturation_imp,
			  int                                       threadsMax,
			  int                                       debugLevel)
	  {
		  this.image_name = set_name;
		  ImagePlus [] imp_srcs = new ImagePlus[channelFiles.length];
		  this.geometryCorrection.woi_tops = new int [channelFiles.length];
		  this.geometryCorrection.camera_heights = new int [channelFiles.length];
		  
		  double [][] dbg_dpixels = new double [channelFiles.length][];
		  boolean is_lwir =            isLwir(); // colorProcParameters.lwir_islwir;
		  boolean ignore_saturation =  is_lwir;
		  boolean lwir_subtract_dc =   colorProcParameters.lwir_subtract_dc;
		  boolean lwir_eq_chn =        colorProcParameters.lwir_eq_chn;
		  boolean correct_vignetting = colorProcParameters.correct_vignetting;
		  boolean recalc_lwir_offsets = false;

		  final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		  final AtomicInteger ai = new AtomicInteger(0);
		  final AtomicBoolean aReturnNull = new AtomicBoolean(false); 

		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  public void run() {
//					  for (int srcChannel=0; srcChannel < channelFiles.length; srcChannel++){
					  for (int srcChannel = ai.getAndIncrement(); srcChannel < channelFiles.length; srcChannel = ai.getAndIncrement()) {

						  int nFile=channelFiles[srcChannel]; // channelFiles[srcChannel];
						  imp_srcs[srcChannel]=null;
						  if (nFile >=0){
							  imp_srcs[srcChannel] = eyesisCorrections.getJp4Tiff(sourceFiles[nFile], geometryCorrection.woi_tops, geometryCorrection.camera_heights);

							  scaleExposures[srcChannel] = 1.0;
							  if (!(referenceExposures == null) && !Double.isNaN(referenceExposures[nFile]) && (imp_srcs[srcChannel].getProperty("EXPOSURE")!=null)){
								  scaleExposures[srcChannel] = referenceExposures[nFile]/Double.parseDouble((String) imp_srcs[srcChannel].getProperty("EXPOSURE"));
								  if (debugLevel > -1) {
									  System.out.println("Will scale intensity (to compensate for exposure) by  "+scaleExposures[srcChannel]+
											  ", EXPOSURE = "+imp_srcs[srcChannel].getProperty("EXPOSURE"));
								  }
							  }
							  String name_from_dir = correctionsParameters.getNameFromSourceTiff(sourceFiles[nFile]);
							  if (name_from_dir.equals("jp4")) {
								  name_from_dir = set_name; // to fix save source files copy in the model/jp4
							  }
							  imp_srcs[srcChannel].setProperty("name",    name_from_dir);
							  imp_srcs[srcChannel].setProperty("channel", srcChannel); // it may already have channel
							  imp_srcs[srcChannel].setProperty("path",    sourceFiles[nFile]); // it may already have channel

							  if (correctionsParameters.pixelDefects && (eyesisCorrections.defectsXY!=null)&& (eyesisCorrections.defectsXY[srcChannel]!=null)){
								  // apply pixel correction
								  int numApplied=	eyesisCorrections.correctDefects( // not used in lwir
										  imp_srcs[srcChannel],
										  srcChannel,
										  debugLevel);
								  if ((debugLevel>0) && (numApplied>0)) { // reduce verbosity after verified defect correction works
									  System.out.println("Corrected "+numApplied+" pixels in "+sourceFiles[nFile]);
								  }
							  }
							  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
							  int width =  imp_srcs[srcChannel].getWidth();
							  int height = imp_srcs[srcChannel].getHeight();
							  if ((debugLevel > -1) && (!isMonochrome())) {
								  double [] max_pix= {0.0, 0.0, 0.0, 0.0};
								  //					  for (int y = 0; y < height-1; y+=2){
								  for (int y = 0; (y < 499) && (y < height); y+=2){
									  //						  for (int x = 0; x < width-1; x+=2){
									  for (int x = width/2; x < width-1; x+=2){
										  if (pixels[y*width+x        ] > max_pix[0])  max_pix[0] = pixels[y*width+x        ];
										  if (pixels[y*width+x+      1] > max_pix[1])  max_pix[1] = pixels[y*width+x+      1];
										  if (pixels[y*width+x+width  ] > max_pix[2])  max_pix[2] = pixels[y*width+x+width  ];
										  if (pixels[y*width+x+width+1] > max_pix[3])  max_pix[3] = pixels[y*width+x+width+1];
									  }
								  }
								  if (debugLevel > -2) {
									  System.out.println(String.format("channel %d max_pix[] = %6.2f %6.2f %6.2f %6.2f", srcChannel, max_pix[0], max_pix[1], max_pix[2], max_pix[3]));
								  }
								  dbg_dpixels[srcChannel] = new double [pixels.length];
								  for (int i = 0; i < pixels.length; i++) dbg_dpixels[srcChannel][i] = pixels[i];
								  //						  imp_srcs[srcChannel].show();
							  }
							  if (clt_parameters.sat_level > 0.0){
								  saturation_imp[srcChannel] = new boolean[width*height];
								  if (!ignore_saturation) {
									  double [] saturations = {
											  Double.parseDouble((String) imp_srcs[srcChannel].getProperty("saturation_1")),
											  Double.parseDouble((String) imp_srcs[srcChannel].getProperty("saturation_0")),
											  Double.parseDouble((String) imp_srcs[srcChannel].getProperty("saturation_3")),
											  Double.parseDouble((String) imp_srcs[srcChannel].getProperty("saturation_2"))};
									  if (debugLevel > -2) {
										  System.out.println(String.format("channel %d saturations = %6.2f %6.2f %6.2f %6.2f", srcChannel,
												  saturations[0],saturations[1],saturations[2],saturations[3]));
									  }
									  double [] scaled_saturations = new double [saturations.length];
									  for (int i = 0; i < scaled_saturations.length; i++){
										  scaled_saturations[i] = saturations[i] * clt_parameters.sat_level;
									  }
									  for (int y = 0; y < height-1; y+=2){
										  for (int x = 0; x < width-1; x+=2){
											  if (pixels[y*width+x        ] > scaled_saturations[0])  saturation_imp[srcChannel][y*width+x        ] = true;
											  if (pixels[y*width+x+      1] > scaled_saturations[1])  saturation_imp[srcChannel][y*width+x      +1] = true;
											  if (pixels[y*width+x+width  ] > scaled_saturations[2])  saturation_imp[srcChannel][y*width+x+width  ] = true;
											  if (pixels[y*width+x+width+1] > scaled_saturations[3])  saturation_imp[srcChannel][y*width+x+width+1] = true;
										  }
									  }
								  }
							  }

							  if (!is_lwir) { // no vigneting correction and no color scaling
								  if (correctionsParameters.vignetting && correct_vignetting){
									  if ((eyesisCorrections.channelVignettingCorrection==null) || (srcChannel<0) || (srcChannel>=eyesisCorrections.channelVignettingCorrection.length) || (eyesisCorrections.channelVignettingCorrection[srcChannel]==null)){
										  if (debugLevel > -3) {
											  System.out.println("No vignetting data for channel "+srcChannel);
										  }
										  aReturnNull.set(true);
										  continue;  // return null;
									  }
									  ///						  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();

									  float [] vign_pixels = eyesisCorrections.channelVignettingCorrection[srcChannel];
									  if (pixels.length!=vign_pixels.length){
										  //							  System.out.println("Vignetting data for channel "+srcChannel+" has "+vign_pixels.length+" pixels, image "+sourceFiles[nFile]+" has "+pixels.length);
										  int woi_width =  Integer.parseInt((String) imp_srcs[srcChannel].getProperty("WOI_WIDTH"));
										  int woi_height = Integer.parseInt((String) imp_srcs[srcChannel].getProperty("WOI_HEIGHT"));
										  int woi_top =  Integer.parseInt((String) imp_srcs[srcChannel].getProperty("WOI_TOP"));
										  int woi_left =  Integer.parseInt((String) imp_srcs[srcChannel].getProperty("WOI_LEFT"));
										  int vign_width =  eyesisCorrections.pixelMapping.sensors[srcChannel].pixelCorrectionWidth;
										  int vign_height = eyesisCorrections.pixelMapping.sensors[srcChannel].pixelCorrectionHeight;

										  if (pixels.length != woi_width * woi_height){
											  System.out.println("Vignetting data for channel "+srcChannel+" has "+vign_pixels.length+" pixels, < "+
													  sourceFiles[nFile]+" has "+pixels.length);
											  woi_width = width;
											  woi_height = height;
										  }
										  if (vign_width < (woi_left + woi_width)) {
											  System.out.println("Vignetting data for channel "+srcChannel+
													  " has width + left ("+(woi_left+woi_width)+") > vign_width ("+vign_width+")");
											  aReturnNull.set(true);
											  continue;  // return null;
										  }
										  if (vign_height < (woi_top + woi_height)) {
											  System.out.println("Vignetting data for channel "+srcChannel+
													  " has height + top ("+(woi_top+woi_height)+") > vign_height ("+vign_width+")");
											  aReturnNull.set(true);
											  continue;  // return null;
										  }
										  if (pixels.length != woi_width * woi_height){
											  System.out.println("Vignetting data for channel "+srcChannel+" has "+vign_pixels.length+" pixels, < "+
													  sourceFiles[nFile]+" has "+pixels.length);
											  aReturnNull.set(true);
											  continue;  // return null;
										  }
										  vign_pixels = new float[woi_width * woi_height];
										  for (int row = 0; row < woi_height; row++) {
											  System.arraycopy(
													  eyesisCorrections.channelVignettingCorrection[srcChannel], // src
													  (woi_top + row) * vign_width + woi_left, // srcPos,
													  vign_pixels,                             // dest,
													  row * woi_width,                         // destPos,
													  woi_width);                              // length);
										  }

									  }
									  // TODO: Move to do it once:
									  double min_non_zero = 0.0;
									  for (int i=0;i<pixels.length;i++){
										  double d = vign_pixels[i];
										  if ((d > 0.0) && ((min_non_zero == 0) || (min_non_zero > d))){
											  min_non_zero = d;
										  }
									  }
									  double max_vign_corr = clt_parameters.vignetting_range*min_non_zero;
									  if (debugLevel > -2) {
										  System.out.println("Vignetting data: channel="+srcChannel+", min = "+min_non_zero);
									  }
									  for (int i=0;i<pixels.length;i++){
										  double d = vign_pixels[i];
										  if (d > max_vign_corr) d = max_vign_corr;
										  pixels[i]*=d;
									  }
									  // Scale here, combine with vignetting later?
									  ///						  int width =  imp_srcs[srcChannel].getWidth();
									  ///						  int height = imp_srcs[srcChannel].getHeight();
									  for (int y = 0; y < height-1; y+=2){
										  for (int x = 0; x < width-1; x+=2){
											  pixels[y*width+x        ] *= clt_parameters.scale_g;
											  pixels[y*width+x+width+1] *= clt_parameters.scale_g;
											  pixels[y*width+x      +1] *= clt_parameters.scale_r;
											  pixels[y*width+x+width  ] *= clt_parameters.scale_b;
										  }
									  }

								  } else { // assuming GR/BG pattern // not used in lwir
									  if (debugLevel > -2) {

										  System.out.println("Applying fixed color gain correction parameters: Gr="+
												  clt_parameters.novignetting_r+", Gg="+clt_parameters.novignetting_g+", Gb="+clt_parameters.novignetting_b);
									  }
									  ///						  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
									  ///						  int width =  imp_srcs[srcChannel].getWidth();
									  ///						  int height = imp_srcs[srcChannel].getHeight();
									  double kr = clt_parameters.scale_r/clt_parameters.novignetting_r;
									  double kg = clt_parameters.scale_g/clt_parameters.novignetting_g;
									  double kb = clt_parameters.scale_b/clt_parameters.novignetting_b;
									  for (int y = 0; y < height-1; y+=2){
										  for (int x = 0; x < width-1; x+=2){
											  pixels[y*width+x        ] *= kg;
											  pixels[y*width+x+width+1] *= kg;
											  pixels[y*width+x      +1] *= kr;
											  pixels[y*width+x+width  ] *= kb;
										  }
									  }
								  }
							  }
						  }
					  } // (int srcChannel=0; srcChannel < channelFiles.length; srcChannel++){
				  }
			  };
		  }		      
		  ImageDtt.startAndJoin(threads);
		  if (aReturnNull.get()) {
			  return null;
		  };
		  
		  // temporary applying scaleExposures[srcChannel] here, setting it to all 1.0
		  if (debugLevel > -2) {
			  System.out.println("Temporarily applying scaleExposures[] here - 1" );
		  }
		  
		  ai.set(0);
		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  public void run() {
					  //					  for (int srcChannel=0; srcChannel<channelFiles.length; srcChannel++){
					  for (int srcChannel = ai.getAndIncrement(); srcChannel < channelFiles.length; srcChannel = ai.getAndIncrement()) {
						  if (!is_lwir) {
							  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
							  for (int i = 0; i < pixels.length; i++){
								  pixels[i] *= scaleExposures[srcChannel];
							  }
						  }
						  scaleExposures[srcChannel] = 1.0;
					  }
				  }
			  };
		  }		      
		  ImageDtt.startAndJoin(threads);


		  if ((debugLevel > -1) && (saturation_imp != null) && !is_lwir){
			  String [] titles = {"chn0","chn1","chn2","chn3"};
			  double [][] dbg_satur = new double [saturation_imp.length] [saturation_imp[0].length];
			  for (int srcChannel=0; srcChannel<channelFiles.length; srcChannel++){
				  for (int i = 0; i < saturation_imp[srcChannel].length; i++){
					  dbg_satur[srcChannel][i] = saturation_imp[srcChannel][i]? 1.0 : 0.0;
				  }
			  }
			  int width =  imp_srcs[0].getWidth();
			  int height = imp_srcs[0].getHeight();
			  ShowDoubleFloatArrays.showArrays(dbg_satur, width, height, true, "Saturated" , titles);

			  if ((debugLevel > -1) && !isMonochrome()) { // 0){
				  double [][] dbg_dpixels_norm = new double [channelFiles.length][];
				  for (int srcChannel=0; srcChannel<channelFiles.length; srcChannel++){
					  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
					  dbg_dpixels_norm[srcChannel] = new double[pixels.length];
					  for (int i = 0; i < pixels.length; i++){
						  dbg_dpixels_norm[srcChannel][i] = pixels[i];
					  }
				  }
				  ShowDoubleFloatArrays.showArrays(dbg_dpixels, width, height, true, "dpixels" , titles);
				  ShowDoubleFloatArrays.showArrays(dbg_dpixels_norm, width, height, true, "dpixels_norm" , titles);
				  double [][] dbg_dpixels_split = new double [4 * dbg_dpixels.length][dbg_dpixels[0].length / 4];
				  String [] dbg_titles = {"g1_0","r_0","b_0","g2_0","g1_2","r_1","b_1","g2_1","g1_2","r_2","b_2","g2_2","g1_3","r_3","b_3","g2_3"};
				  for (int srcChn = 0; srcChn < 4; srcChn++) {
					  for (int y = 0; y < height-1; y+=2){
						  for (int x = 0; x < width-1; x+=2){
							  dbg_dpixels_split[ 0 + 4 * srcChn][ y*width/4 +x/2 ] = dbg_dpixels_norm[srcChn][y * width + x             ];
							  dbg_dpixels_split[ 3 + 4 * srcChn][ y*width/4 +x/2 ] = dbg_dpixels_norm[srcChn][y * width + x  + width + 1];
							  dbg_dpixels_split[ 1 + 4 * srcChn][ y*width/4 +x/2 ] = dbg_dpixels_norm[srcChn][y * width + x  +         1];
							  dbg_dpixels_split[ 2 + 4 * srcChn][ y*width/4 +x/2 ] = dbg_dpixels_norm[srcChn][y * width + x  + width    ];
						  }
					  }
				  }
				  ShowDoubleFloatArrays.showArrays(dbg_dpixels_split, width/2, height/2, true, "dpixels_split" , dbg_titles);
			  }

		  }
		  //			  Overlay ovl = imp_srcs[0].getOverlay();
		  // once per quad here
		  // may need to equalize gains between channels
		  if (!is_lwir && (clt_parameters.gain_equalize || clt_parameters.colors_equalize)){ // false, true
			  channelGainsEqualize( // TODO: not multithreaded - convert
					  clt_parameters.gain_equalize, //false
					  clt_parameters.colors_equalize, // true
					  clt_parameters.nosat_equalize, // boolean nosat_equalize, // true
					  channelFiles,
					  imp_srcs,
					  saturation_imp, // boolean[][] saturated,
					  set_name,       // setNames.get(nSet), // just for debug messages == setNames.get(nSet)
					  debugLevel);
		  }
		  if (is_lwir && (lwir_subtract_dc || lwir_eq_chn)) {
			  if (recalc_lwir_offsets || !isLwirCalibrated()) {
//				  this.lwir_offsets = 
				  setLwirOffsets( // may have 4096 offset
						  channelLwirEqualize( // now only calculates offsets, does not apply
								  channelFiles,
								  imp_srcs,
								  lwir_subtract_dc, // boolean      remove_dc,
								  set_name, // just for debug messages == setNames.get(nSet)
								  threadsMax,
								  debugLevel));
				  // will actually is now calculated by setLwirOffsets()
				  int num_avg = 0;
				  this.lwir_offset = 0.0;
				  for (int srcChannel=0; srcChannel < channelFiles.length; srcChannel++){
					  int nFile=channelFiles[srcChannel];
					  if (nFile >=0){
						  this.lwir_offset += this.lwir_offsets[srcChannel];
						  num_avg++;
					  }
				  }
				  this.lwir_offset /= num_avg;
			  }
			  double [] offsets =  getLwirOffsets();
			  double [] scales =   getLwirScales();
			  double [] scales2 =  getLwirScales2();
			  int needs_fix = -1;
			  for (int i = 0; i < imp_srcs.length; i++) {
				  if (ImagejJp4Tiff.needsFix000E6410C435(imp_srcs[i])) {
					  needs_fix = i;
					  break; // only one, and always 6
				  }
			  }
			  if (needs_fix >= 0) {
				  double [] fix_offsets = channelLwirEqualize( // now only calculates offsets, does not apply
						  channelFiles,
						  imp_srcs,
						  lwir_subtract_dc, // boolean      remove_dc,
						  set_name, // just for debug messages == setNames.get(nSet)
						  threadsMax,
						  debugLevel);
				  double avg_foffs = 0.0;
				  for (int i = 0; i < fix_offsets.length; i++) if (i != needs_fix) {
					  avg_foffs += fix_offsets[i];
				  }
				  avg_foffs /= (fix_offsets.length - 1);
				  int icorr =  (int) Math.round((avg_foffs - fix_offsets[needs_fix])/4096);
				  if (icorr != 0) {
					  float fcorr = icorr*4096;
					  System.out.println("Correcting "+imp_srcs[needs_fix].getTitle()+" by "+fcorr);
					  float [] pixels = (float []) imp_srcs[needs_fix].getProcessor().getPixels();
					  for (int i = 0; i < pixels.length; i++) {
						  pixels[i] += fcorr;
					  }
				  }
				  // There was a bug (11/25/2003) that offsets[6] was 4096 lower
				  double avg_offs = 0.0;
				  for (int i = 0; i < fix_offsets.length; i++) if (i != needs_fix) {
					  avg_offs += fix_offsets[i];
				  }
				  avg_offs /= (fix_offsets.length - 1);
				  int icorr_bug = (int) Math.round((avg_offs - offsets[needs_fix])/4096);
				  if (icorr_bug != 0) {
					  System.out.println("conditionImageSet()): Fixing correction bug for "+imp_srcs[needs_fix].getTitle());
					  offsets[needs_fix]+=icorr_bug*4096;
					  setLwirOffsets(offsets);
				  }
				  
				  
			  } else {
				  for (int i = 0; i < imp_srcs.length; i++) {
					  if (ImagejJp4Tiff.needsFix000E6410C435(imp_srcs[i])) {
						  needs_fix = i;
						  break; // only one, and always 6
					  }
				  }
				  if (needs_fix >= 0) {
					  System.out.println("Need to correct "+imp_srcs[needs_fix].getTitle()+" **************");
				  }
			  }
			  
			  channelLwirApplyEqualize( // now apply (was part of channelLwirEqualize() )
					  channelFiles, // int [] channelFiles,
					  imp_srcs,     // ImagePlus [] imp_srcs,
					  offsets,      // double []    offsets,
					  scales,       // double []    scales,
					  scales2,      // double []    scales2,
					  threadsMax,
					  debugLevel);
		  }
// 08/12/2020 common part moved here, from getRigImageStacks()
		  image_name = (String) imp_srcs[0].getProperty("name");
		  image_path=  (String) imp_srcs[0].getProperty("path");		  
		  this.saturation_imp = saturation_imp;
//		  image_data =          new double [imp_srcs.length][][];
//		  this.new_image_data = true;
		  setImageData(new double [imp_srcs.length][][]);
		  final double [][][] image_data = getImageData();
		  ai.set(0);
		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  public void run() {
					  //for (int i = 0; i < image_data.length; i++){
					  for (int i = ai.getAndIncrement(); i < image_data.length; i = ai.getAndIncrement()) {
						  image_data[i] = eyesisCorrections.bayerToDoubleStack(
								  imp_srcs[i], // source Bayer image, linearized, 32-bit (float))
								  null, // no margins, no oversample
								  isMonochrome()); // is_mono);
						  // TODO: Scale greens here ?
						  //			  if (!is_mono && (image_data[i].length > 2)) {
						  if (!isMonochrome() && (image_data[i].length > 2)) {
							  for (int j =0 ; j < image_data[i][0].length; j++){
								  image_data[i][2][j]*=0.5; // Scale green 0.5 to compensate more pixels than R,B
							  }
						  }
					  }
				  }
			  };
		  }
		  ImageDtt.startAndJoin(threads);
		  applyFPN(); // if non-null, fpn should be set for each scene with setFPN (double [][][] fpn)
		  setTiles (imp_srcs[0], // set global tp.tilesX, tp.tilesY
				  getNumSensors(), // tp.getNumSensors(),
				  clt_parameters,
				  threadsMax); // where to get it? Use instance member
		  tp.setTrustedCorrelation(clt_parameters.grow_disp_trust);
		  tp.resetCLTPasses();
		  return imp_srcs;
	  }

	  public double [][][] getAppliedFPN(){
		  return image_fpn_applied;
	  }
	  public double [][][] getFPN(){
		  return this.image_fpn;
	  }
	  public void setFPN (double [][][] fpn) {
		  this.image_fpn = fpn;
	  }
	  
	  public void resetRowCol() {
		  setRowCol(null,null);
	  }
	  
	  public void setRowCol(
			  double [][] row,
			  double [][] col) {
		  image_row_avg = row;
		  image_col_avg = col;
	  }
	  
	  public double [][] getRowCorr(){
		  return image_row_avg;
	  }
	  
	  public double [][] getColCorr(){
		  return image_col_avg;
	  }
	  public double [][] getRowApplied(){
		  return image_row_avg_applied;
	  }
	  
	  public double [][] getColApplied(){
		  return image_col_avg_applied;
	  }
	 
	  public void setRowColApplied(
			  double [][] row,
			  double [][] col) {
		  image_row_avg_applied = row;
		  image_col_avg_applied = col;
	  }
	  
	  public boolean rowColDiffers(
			  double [][] row,
			  double [][] col) {
		  return (image_row_avg_applied != row) || (image_col_avg_applied != col);
	  }

	  public boolean rowColDiffers() {
		  return rowColDiffers(
				  image_row_avg, // double [][] row,
				  image_col_avg); // double [][] col)
		  
	  }


	  public void applyFPN() {
		  getCorrectionFPN().applyFPN(this.image_fpn);
	  }
	  public void applyRowCol() {
		  CorrectionFPN.applyRowCol(
				  (QuadCLT) this, //final QuadCLT       scene,
				  getRowCorr(), // final double [][]   row,
				  getColCorr()); // final double [][]   col )
	  }

	  @Deprecated
	  public void processCLTQuadCorrsTestERS(
			  CLTParameters           clt_parameters,
			  ColorProcParameters colorProcParameters,
			  CorrectionColorProc.ColorGainsParameters     channelGainParameters,
			  EyesisCorrectionParameters.RGBParameters             rgbParameters,
			  //			  int          convolveFFTSize, // 128 - fft size, kernel size should be size/2
			  final boolean    apply_corr, // calculate and apply additional fine geometry correction
			  final boolean    infinity_corr, // calculate and apply geometry correction at infinity
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel)
	  {
		  if (infinity_corr && (clt_parameters.z_correction != 0.0)){
			  System.out.println(
					  "****************************************\n"+
							  "* Resetting manual infinity correction *\n"+
					  "****************************************\n");
			  clt_parameters.z_correction = 0.0;
		  }

		  this.startTime=System.nanoTime();
		  String [] sourceFiles=correctionsParameters.getSourcePaths();
		  SetChannels [] set_channels=setChannels(debugLevel);
		  if ((set_channels == null) || (set_channels.length==0)) {
			  System.out.println("No files to process (of "+sourceFiles.length+")");
			  return;
		  }
		  // multiply each image by this and divide by individual (if not NaN)
		  double [] referenceExposures = null;
		  //		  if (!colorProcParameters.lwir_islwir) {
		  if (!isLwir()) {
			  referenceExposures=eyesisCorrections.calcReferenceExposures(debugLevel);
		  }
		  for (int nSet = 0; nSet < set_channels.length; nSet++){
			  int [] channelFiles = set_channels[nSet].fileNumber();
			  boolean [][] saturation_imp = (clt_parameters.sat_level > 0.0)? new boolean[channelFiles.length][] : null;
			  double [] scaleExposures = new double[channelFiles.length];

			  ImagePlus [] imp_srcs = conditionImageSet(
					  clt_parameters,             // EyesisCorrectionParameters.CLTParameters  clt_parameters,
					  colorProcParameters,
					  sourceFiles,                // String []                                 sourceFiles,
					  set_channels[nSet].name(),  // String                                    set_name,
					  referenceExposures,         // double []                                 referenceExposures,
					  channelFiles,               // int []                                    channelFiles,
					  scaleExposures,   //output  // double [] scaleExposures
					  saturation_imp,   //output  // boolean [][]                              saturation_imp,
					  threadsMax,                 // int                                       threadsMax,
					  debugLevel); // int                                       debugLevel);


			  // once per quad here
			  processCLTQuadCorrTestERS( // returns ImagePlus, but it already should be saved/shown
					  imp_srcs, // [srcChannel], // should have properties "name"(base for saving results), "channel","path"
					  saturation_imp, // boolean [][] saturation_imp, // (near) saturated pixels or null
					  clt_parameters,
					  colorProcParameters,
					  channelGainParameters,
					  rgbParameters,
					  scaleExposures,
					  apply_corr, // calculate and apply additional fine geometry correction
					  infinity_corr, // calculate and apply geometry correction at infinity
					  threadsMax,  // maximal number of threads to launch
					  updateStatus,
					  debugLevel);
			  if (debugLevel >-1) System.out.println("Processing set "+(nSet+1)+" (of "+set_channels.length+") finished at "+
					  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory12="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
			  if (eyesisCorrections.stopRequested.get()>0) {
				  System.out.println("User requested stop");
				  System.out.println("Processing "+(nSet + 1)+" file sets (of "+set_channels.length+") finished at "+
						  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory13="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
				  return;
			  }
		  }
		  System.out.println("processCLTQuadCorrs(): processing "+getTotalFiles(set_channels)+" files ("+set_channels.length+" file sets) finished at "+
				  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory14="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
	  }

	  @Deprecated
	  public void processCLTQuadCorrsTest( // only from run menu
			  CLTParameters           clt_parameters,
			  ColorProcParameters colorProcParameters,
			  CorrectionColorProc.ColorGainsParameters     channelGainParameters,
			  EyesisCorrectionParameters.RGBParameters             rgbParameters,
			  //			  int          convolveFFTSize, // 128 - fft size, kernel size should be size/2
			  final boolean    apply_corr, // calculate and apply additional fine geometry correction
			  final boolean    infinity_corr, // calculate and apply geometry correction at infinity
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel)
	  {
		  if (infinity_corr && (clt_parameters.z_correction != 0.0)){
			  System.out.println(
					  "****************************************\n"+
							  "* Resetting manual infinity correction *\n"+
					  "****************************************\n");
			  clt_parameters.z_correction = 0.0;
		  }

		  this.startTime=System.nanoTime();
		  String [] sourceFiles=correctionsParameters.getSourcePaths();
		  SetChannels [] set_channels=setChannels(debugLevel);
		  if ((set_channels == null) || (set_channels.length==0)) {
			  System.out.println("No files to process (of "+sourceFiles.length+")");
			  return;
		  }
		  // multiply each image by this and divide by individual (if not NaN)
		  double [] referenceExposures = null;
		  //		  if (!colorProcParameters.lwir_islwir) {
		  if (!isLwir()) {			  
			  referenceExposures=eyesisCorrections.calcReferenceExposures(debugLevel);
		  }
		  for (int nSet = 0; nSet < set_channels.length; nSet++){
			  int [] channelFiles = set_channels[nSet].fileNumber();
			  boolean [][] saturation_imp = (clt_parameters.sat_level > 0.0)? new boolean[channelFiles.length][] : null;
			  double [] scaleExposures = new double[channelFiles.length];

			  ImagePlus [] imp_srcs = conditionImageSet(
					  clt_parameters,             // EyesisCorrectionParameters.CLTParameters  clt_parameters,
					  colorProcParameters,
					  sourceFiles,                // String []                                 sourceFiles,
					  set_channels[nSet].name(),  // String                                    set_name,
					  referenceExposures,         // double []                                 referenceExposures,
					  channelFiles,               // int []                                    channelFiles,
					  scaleExposures,   //output  // double [] scaleExposures
					  saturation_imp,   //output  // boolean [][]                              saturation_imp,
					  threadsMax,                 // int                                       threadsMax,
					  debugLevel); // int                                       debugLevel);


			  // once per quad here
			  processCLTQuadCorrTest( // returns ImagePlus, but it already should be saved/shown
					  imp_srcs, // [srcChannel], // should have properties "name"(base for saving results), "channel","path"
					  saturation_imp, // boolean [][] saturation_imp, // (near) saturated pixels or null
					  clt_parameters,
					  colorProcParameters,
					  channelGainParameters,
					  rgbParameters,
					  scaleExposures,
					  apply_corr, // calculate and apply additional fine geometry correction
					  infinity_corr, // calculate and apply geometry correction at infinity
					  threadsMax,  // maximal number of threads to launch
					  updateStatus,
					  debugLevel);
			  if (debugLevel >-1) System.out.println("Processing set "+(nSet+1)+" (of "+set_channels.length+") finished at "+
					  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory15="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
			  if (eyesisCorrections.stopRequested.get()>0) {
				  System.out.println("User requested stop");
				  System.out.println("Processing "+(nSet + 1)+" file sets (of "+set_channels.length+") finished at "+
						  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory16="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
				  return;
			  }
		  }
		  System.out.println("processCLTQuadCorrs(): processing "+getTotalFiles(set_channels)+" files ("+set_channels.length+" file sets) finished at "+
				  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory17="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
	  }

	  public static void channelGainsEqualize( // USED in lwir
			  boolean gain_equalize,
			  boolean colors_equalize,
			  boolean nosat_equalize,
			  int [] channelFiles,
			  ImagePlus [] imp_srcs,
			  boolean[][] saturated,
			  String setName, // just for debug messages == setNames.get(nSet)
			  int debugLevel){
		  boolean use_new = true; // false;
		  if (use_new){
			  channelGainsEqualize_new(
					  gain_equalize,
					  colors_equalize,
					  nosat_equalize,
					  channelFiles,
					  imp_srcs,
					  saturated,
					  setName, // just for debug messages == setNames.get(nSet)
					  debugLevel);
		  } else { // not used in lwir
			  channelGainsEqualize_old(
					  gain_equalize,
					  colors_equalize,
					  nosat_equalize,
					  channelFiles,
					  imp_srcs,
					  saturated,
					  setName, // just for debug messages == setNames.get(nSet)
					  debugLevel);
		  }
	  }
	  public static void channelGainsEqualize_old( // not used in lwir
			  boolean gain_equalize,
			  boolean colors_equalize,
			  boolean nosat_equalize,
			  int [] channelFiles,
			  ImagePlus [] imp_srcs,
			  boolean[][] saturated,
			  String setName, // just for debug messages == setNames.get(nSet)
			  int debugLevel){
		  double [][] avr_pix = new double [channelFiles.length][3];
		  double [] avr_RGB = {0.0,0.0,0.0};
		  int numChn = 0;
		  for (int srcChannel=0; srcChannel < channelFiles.length; srcChannel++){
			  int nFile=channelFiles[srcChannel];
			  if (nFile >=0){
				  for (int i = 0; i < avr_pix[srcChannel].length; i++) avr_pix[srcChannel][i] = 0;
				  //				  int [] num_nonsat = {0,0,0};
				  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
				  int width =  imp_srcs[srcChannel].getWidth();
				  int height = imp_srcs[srcChannel].getHeight();
				  for (int y = 0; y < height-1; y+=2){
					  for (int x = 0; x < width-1; x+=2){
						  avr_pix[srcChannel][0] += pixels[y*width+x      +1];
						  avr_pix[srcChannel][2] += pixels[y*width+x+width  ];
						  avr_pix[srcChannel][1] += pixels[y*width+x        ];
						  avr_pix[srcChannel][1] += pixels[y*width+x+width+1];
					  }
				  }
				  avr_pix[srcChannel][0] /= 0.25*width*height;
				  avr_pix[srcChannel][1] /= 0.5 *width*height;
				  avr_pix[srcChannel][2] /= 0.25*width*height;
				  for (int j=0; j < avr_RGB.length; j++) avr_RGB[j] += avr_pix[srcChannel][j];
				  numChn++;
				  if (debugLevel > -2) {
					  System.out.println("processCLTSets(): set "+ setName + " channel "+srcChannel+
							  " R"+avr_pix[srcChannel][0]+" G"+avr_pix[srcChannel][1]+" B"+avr_pix[srcChannel][2]);
				  }

			  }
		  }
		  for (int j=0; j < avr_RGB.length; j++) avr_RGB[j] /= numChn;
		  if (debugLevel > -2) {
			  System.out.println("processCLTSets(): set "+ setName + "average color values: "+
					  " R="+avr_RGB[0]+" G=" + avr_RGB[1]+" B=" + avr_RGB[2]);
		  }
		  for (int srcChannel=0; srcChannel < channelFiles.length; srcChannel++){
			  int nFile=channelFiles[srcChannel];
			  if (nFile >=0){
				  double [] scales = new double [avr_RGB.length];
				  for (int j=0;j < scales.length; j++){
					  scales[j] = 1.0;
					  if (gain_equalize){
						  scales[j] *=  avr_RGB[1]/avr_pix[srcChannel][1]; // 1 - index of green color
					  }
					  if (colors_equalize){
						  scales[j] *=  avr_RGB[j]/avr_pix[srcChannel][j] / (avr_RGB[1]/avr_pix[srcChannel][1]);
					  }
				  }
				  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
				  System.out.println("Channel "+srcChannel+ ":  scales[] = "+scales[0]+", "+scales[1]+", "+scales[2]);
				  int width =  imp_srcs[srcChannel].getWidth();
				  int height = imp_srcs[srcChannel].getHeight();
				  for (int y = 0; y < height-1; y+=2){
					  for (int x = 0; x < width-1; x+=2){
						  pixels[y*width+x        ] *= scales[1];
						  pixels[y*width+x+width+1] *= scales[1];
						  pixels[y*width+x      +1] *= scales[0];
						  pixels[y*width+x+width  ] *= scales[2];
					  }
				  }
			  }
		  }
	  }

	  public static void channelGainsEqualize_new( // USED in lwir
			  boolean gain_equalize,
			  boolean colors_equalize,
			  boolean nosat_equalize,
			  int [] channelFiles,
			  ImagePlus [] imp_srcs,
			  boolean[][] saturated,
			  String setName, // just for debug messages == setNames.get(nSet)
			  int debugLevel){
		  double [][] avr_pix = new double [channelFiles.length][4];
		  double [] avr_RGGB = {0.0,0.0,0.0,0.0};
		  int numChn = 0;
		  for (int srcChannel=0; srcChannel < channelFiles.length; srcChannel++){
			  int nFile=channelFiles[srcChannel];
			  if (nFile >=0){
				  for (int i = 0; i < avr_pix[srcChannel].length; i++) avr_pix[srcChannel][i] = 0;
				  int [] num_nonsat = {0,0,0,0};
				  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
				  int width =  imp_srcs[srcChannel].getWidth();
				  int height = imp_srcs[srcChannel].getHeight();
				  int indx;
				  for (int y = 0; y < height-1; y+=2){
					  for (int x = 0; x < width-1; x+=2){
						  indx = y * width + (x +1);  // R
						  if (!nosat_equalize || (saturated == null) || !saturated[srcChannel][indx]) {
							  avr_pix[srcChannel][0] += pixels[indx];
							  num_nonsat[0]++;
						  }

						  indx = (y + 1) * width + x;  // B
						  if (!nosat_equalize || (saturated == null) || !saturated[srcChannel][indx]) {
							  avr_pix[srcChannel][3] += pixels[indx];
							  num_nonsat[3]++;
						  }
						  indx = y * width + x;  // G1
						  if (!nosat_equalize || (saturated == null) || !saturated[srcChannel][indx]) {
							  avr_pix[srcChannel][1] += pixels[indx];
							  num_nonsat[1]++;
						  }
						  indx = (y + 1) * width + (x + 1);  // G2
						  if (!nosat_equalize || (saturated == null) || !saturated[srcChannel][indx]) {
							  avr_pix[srcChannel][2] += pixels[indx];
							  num_nonsat[2]++;
						  }
					  }
				  }
				  for (int i = 0; i < num_nonsat.length; i++){
					  avr_pix[srcChannel][i] /= num_nonsat[i];
				  }
				  for (int j=0; j < avr_RGGB.length; j++) avr_RGGB[j] += avr_pix[srcChannel][j];
				  numChn++;
				  if (debugLevel > -2) {
					  System.out.println("processCLTSets(): set "+ setName + " channel "+srcChannel+
							  " R="+avr_pix[srcChannel][0]+" G1="+avr_pix[srcChannel][1]+" G2="+avr_pix[srcChannel][2]+" B="+avr_pix[srcChannel][3]);
				  }

			  }
		  }
		  for (int j=0; j < avr_RGGB.length; j++) avr_RGGB[j] /= numChn;
		  double avr_G = 0.5 * (avr_RGGB[1] + avr_RGGB[2]);
		  if (debugLevel > -2) {
			  System.out.println("processCLTSets(): set "+ setName + " average color values: "+
					  " R="+avr_RGGB[0]+" G=" + avr_G+" ("+avr_RGGB[1]+", "+avr_RGGB[2]+") B=" + avr_RGGB[3]);
		  }
		  for (int srcChannel=0; srcChannel < channelFiles.length; srcChannel++){
			  int nFile=channelFiles[srcChannel];
			  if (nFile >=0){
				  double [] scales = new double [avr_RGGB.length];
				  double avr_g = 0.5 * (avr_pix[srcChannel][1] + avr_pix[srcChannel][2]);
				  for (int j=0;j < scales.length; j++){
					  scales[j] = 1.0;
					  if (gain_equalize){ // not used in lwir
						  scales[j] *=  avr_G/avr_g;
					  }
					  if (colors_equalize){
						  switch (j) {
						  case 1: // G1
						  case 2: // G2
//							  scales[j] *=  avr_G/avr_pix[srcChannel][j] / (avr_G/avr_g);
							  scales[j] *=  avr_g/avr_pix[srcChannel][j];
							  break;
						  default: // R, B
							  scales[j] *=  avr_RGGB[j]/avr_pix[srcChannel][j] / (avr_G/avr_g);
						  }
//						  scales[j] *=  avr_RGGB[j]/avr_pix[srcChannel][j] / (avr_G/avr_g);
					  }
				  }
				  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
				  int width =  imp_srcs[srcChannel].getWidth();
				  int height = imp_srcs[srcChannel].getHeight();
				  System.out.println("Channel "+srcChannel+ ":  scales[] = "+scales[0]+", "+scales[1]+", "+scales[2]+", "+scales[3]);

				  for (int y = 0; y < height-1; y+=2){
					  for (int x = 0; x < width-1; x+=2){
						  pixels[y*width+x        ] *= scales[1];
						  pixels[y*width+x+width+1] *= scales[2];
						  pixels[y*width+x      +1] *= scales[0];
						  pixels[y*width+x+width  ] *= scales[3];
					  }
				  }
			  }
		  }
	  }
	  
	  public static double []  channelLwirEqualize( // USED in lwir
			  int [] channelFiles,
			  ImagePlus [] imp_srcs,
			  boolean      remove_dc,
			  String setName, // just for debug messages == setNames.get(nSet)
			  final int threadsMax,
			  int debugLevel){
		  double [] offsets = new double [channelFiles.length];
		  double [][] avr_pix = new double [channelFiles.length][2]; // val/weight
		  DoubleAccumulator atotal_s = new DoubleAccumulator(Double::sum, 0L);
		  DoubleAccumulator atotal_w = new DoubleAccumulator(Double::sum, 0L);
		  final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		  final AtomicInteger ai = new AtomicInteger(0);
		  
		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  public void run() {
					  double [] wnd_x;
					  double [] wnd_y;
					  for (int srcChannel = ai.getAndIncrement(); srcChannel < channelFiles.length; srcChannel = ai.getAndIncrement()) {
						  int nFile=channelFiles[srcChannel];
						  if (nFile >=0){
							  avr_pix[srcChannel][0] = 0.0;
							  avr_pix[srcChannel][1] = 0.0;
							  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
							  int width =  imp_srcs[srcChannel].getWidth();
							  int height = imp_srcs[srcChannel].getHeight();
								  wnd_x = new double[width];
								  for (int i = 0; i < width; i++) {
									  wnd_x[i] = 0.5 - 0.5*Math.cos(2*Math.PI * (i+1) / (width + 1));
								  }
								  wnd_y = new double[height];
								  for (int i = 0; i < height; i++) {
									  wnd_y[i] = 0.5 - 0.5*Math.cos(2*Math.PI * (i+1) / (height + 1));
								  }
							  int indx = 0;
							  for (int y = 0; y < height; y++) {
								  for (int x = 0; x < width; x++) {
									  double w = wnd_y[y]*wnd_x[x];
									  avr_pix[srcChannel][0] += w * pixels[indx++];
									  avr_pix[srcChannel][1] += w;
								  }
							  }
							  atotal_s.accumulate(avr_pix[srcChannel][0]);
							  atotal_w.accumulate(avr_pix[srcChannel][1]);
							  avr_pix[srcChannel][0]/=avr_pix[srcChannel][1]; // weighted average
						  }
					  }
				  }
			  };
		  }		      
		  ImageDtt.startAndJoin(threads);
		  double avg = atotal_s.get()/atotal_w.get();
		  
		  if (!remove_dc) { // not used in lwir
			  for (int srcChannel=0; srcChannel < channelFiles.length; srcChannel++) if (channelFiles[srcChannel] >=0){
				  avr_pix[srcChannel][0] -= avg;
			  }

		  }
		  
		  ai.set(0);
		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  public void run() {
					  for (int srcChannel = ai.getAndIncrement(); srcChannel < channelFiles.length; srcChannel = ai.getAndIncrement()) {
						  int nFile=channelFiles[srcChannel];
						  if (nFile >=0) {
							  offsets[srcChannel]= avr_pix[srcChannel][0];
						  }
					  }
				  }
			  };
		  }		      
		  ImageDtt.startAndJoin(threads);
		  return offsets;
	  }

	  public static void channelLwirApplyEqualize(
			  int [] channelFiles,
			  ImagePlus [] imp_srcs,
			  double []    offsets,
			  double []    scales,
			  double []    scales2,
			  final int    threadsMax,
			  int          debugLevel){
		  final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		  final AtomicInteger ai = new AtomicInteger(0);
		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  public void run() {
					  for (int srcChannel = ai.getAndIncrement(); srcChannel < channelFiles.length; srcChannel = ai.getAndIncrement()) {
						  int nFile=channelFiles[srcChannel];
						  if (nFile >=0) {
							  float fd = (float)offsets[srcChannel];
							  float fscale =  (float) scales[srcChannel];
							  float fscale2 = (float) scales2[srcChannel];
							  float [] pixels = (float []) imp_srcs[srcChannel].getProcessor().getPixels();
							  for (int i = 0; i < pixels.length; i++) {
								  float poffs = (pixels[i] - fd);
								  pixels[i] = poffs * fscale + poffs*poffs*fscale2;
							  }
						  }
					  }
				  }
			  };
		  }		      
		  ImageDtt.startAndJoin(threads);
	  }  
	  
	  
//	  public ImagePlus [] processCLTQuadCorrCPU( // USED in lwir
	  public void processCLTQuadCorrCPU( // USED in lwir
//			  ImagePlus [] imp_quad, // should have properties "name"(base for saving results), "channel","path"
			  boolean [][] saturation_imp, // (near) saturated pixels or null // Not needed use this.saturation_imp
			  CLTParameters           clt_parameters,
			  ColorProcParameters                              colorProcParameters,
			  CorrectionColorProc.ColorGainsParameters         channelGainParameters,
			  EyesisCorrectionParameters.RGBParameters             rgbParameters,
			  double []	       scaleExposures, // probably not needed here
			  final boolean    apply_corr, // calculate and apply additional fine geometry correction
			  final boolean    infinity_corr, // calculate and apply geometry correction at infinity
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel){
		  final boolean      batch_mode = clt_parameters.batch_run; //disable any debug images
		  boolean advanced=  this.correctionsParameters.zcorrect || this.correctionsParameters.equirectangular;
		  boolean toRGB=     advanced? true: this.correctionsParameters.toRGB;
		  if (debugLevel>1) System.out.println("processing: "+image_path);
		  ImageDtt image_dtt = new ImageDtt(
				  getNumSensors(),
				  clt_parameters.transform_size,
				  clt_parameters.img_dtt,
				  isAux(),
				  isMonochrome(),
				  isLwir(),
				  clt_parameters.getScaleStrength(isAux()));
		  image_dtt.getCorrelation2d(); // initiate image_dtt.correlation2d, needed if disparity_map != null  
		  // temporary setting up tile task file (one integer per tile, bitmask
		  // for testing defined for a window, later the tiles to process will be calculated based on previous passes results

		  int [][]    tile_op = tp.setSameTileOp(clt_parameters,  clt_parameters.tile_task_op, debugLevel);
		  double [][] disparity_array = tp.setSameDisparity(clt_parameters.disparity); // 0.0); // [tp.tilesY][tp.tilesX] - individual per-tile expected disparity

		  //TODO: Add array of default disparity - use for combining images in force disparity mode (no correlation), when disparity is predicted from other tiles

//		  double [][][][][]   clt_corr_partial = null; // [tp.tilesY][tp.tilesX][pair][color][(2*transform_size-1)*(2*transform_size-1)]
		  double [][][][]     clt_corr_out =     null; // will be used instead of clt_corr_partial
		  double [][][][]     clt_combo_out =    null; // will be used instead of clt_corr_combo
		  double [][][][]     clt_combo_dbg =    null; // generate partial rotated/scaled pairs
		  double [][][][]     texture_tiles =    null; // [tp.tilesY][tp.tilesX]["RGBA".length()][]; // tiles will be 16x16, 2 visualization mode full 16 or overlapped
		  double [][]         disparity_map =    null;
		  // undecided, so 2 modes of combining alpha - same as rgb, or use center tile only
		  final int tilesX = tp.getTilesX();
		  final int tilesY = tp.getTilesY();

		  if (clt_parameters.correlate){ // true
			  Correlation2d correlation2d = image_dtt.getCorrelation2d();
			  texture_tiles =     new double [tilesY][tilesX][][]; // ["RGBA".length()][];
			  for (int i = 0; i < tilesY; i++){
				  for (int j = 0; j < tilesX; j++){
					  texture_tiles[i][j] = null;
				  }
			  }
			  if (!infinity_corr && clt_parameters.corr_keep){ // true
				  int num_pairs = correlation2d.getCorrTitles().length;
				  int num_combo = correlation2d.getComboTitles().length;
				  clt_corr_out = new double [num_pairs][][][];
				  clt_combo_out = new double [num_combo][][][];
				  if (clt_parameters.img_dtt.mcorr_comb_dbg) {
					  clt_combo_dbg = new double [num_pairs][][][];
				  }
			  } // clt_parameters.corr_mismatch = false
//			  disparity_map = new double [ImageDtt.DISPARITY_TITLES.length][]; //[0] -residual disparity, [1] - orthogonal (just for debugging) last 4 - max pixel differences
			  disparity_map = new double [ImageDtt.getDisparityTitles(getNumSensors(), isMonochrome()).length][]; //[0] -residual disparity, [1] - orthogonal (just for debugging)
		  }
		  // Includes all 3 colors - will have zeros in unused

		  double min_corr_selected = clt_parameters.min_corr;
		  double [][] shiftXY = new double [getNumSensors()][2];
		  if (!clt_parameters.fine_corr_ignore) {// invalid for AUX!
			  double [][] shiftXY0 = {
					  {clt_parameters.fine_corr_x_0,clt_parameters.fine_corr_y_0},
					  {clt_parameters.fine_corr_x_1,clt_parameters.fine_corr_y_1},
					  {clt_parameters.fine_corr_x_2,clt_parameters.fine_corr_y_2},
					  {clt_parameters.fine_corr_x_3,clt_parameters.fine_corr_y_3}};
			  // FIXME - only first 4 sensors have correction. And is it the same for aux and main? 
			  for (int i = 0; i < shiftXY0.length;i++) {
				  shiftXY[i] = shiftXY0[i];
			  }
		  }

		  double z_correction =  clt_parameters.z_correction;
		  if (clt_parameters.z_corr_map.containsKey(image_name)){
			  z_correction +=clt_parameters.z_corr_map.get(image_name);// not used in lwir
		  }
		  final double disparity_corr = (z_correction == 0) ? 0.0 : geometryCorrection.getDisparityFromZ(1.0/z_correction);
		  
		  // fix this.fine_corr
		  if (this.fine_corr.length != getNumSensors()) {
			  System.out.println ("**** this.fine_corr.length != getNumSensors(), fixing");
			  double [][][] fine_corr0 = this.fine_corr;
			  this.fine_corr = new double [getNumSensors()][2][6];
			  for (int i = 0; i < fine_corr0.length; i++) {
				  this.fine_corr[i] = fine_corr0[i];
			  }
		  }
		  //getNumSensors()
		  
		  int mcorr_sel = ImageDttParameters.corrSelEncode(clt_parameters.img_dtt, getNumSensors());
//		  if (debugLevel > 1000) texture_tiles = null; // FIXME: until texture generation for multi-cam is fixed
		  
		  double [][][][][][] clt_data = image_dtt.clt_aberrations_quad_corr(
				  clt_parameters.img_dtt,       // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
				  1,                            // final int  macro_scale, // to correlate tile data instead of the pixel data: 1 - pixels, 8 - tiles
				  tile_op,                      // per-tile operation bit codes
				  disparity_array,              // final double            disparity,
				  getImageData(), // double_stacks, // final double [][][]      imade_data, // first index - number of image in a quad
				  saturation_imp,               // boolean [][] saturation_imp, // (near) saturated pixels or null
				  // correlation results
				  clt_corr_out,   // final double [][][][]     clt_corr_out,   // sparse (by the first index) [type][tilesY][tilesX][(2*transform_size-1)*(2*transform_size-1)] or null
				  clt_combo_out,  // final double [][][][]     clt_combo_out,  // sparse (by the first index) [type][tilesY][tilesX][(combo_tile_size] or null
				  clt_combo_dbg,  // final double [][][][]     clt_combo_dbg,  // generate sparse  partial rotated/scaled pairs
				  disparity_map,                // [2][tp.tilesY * tp.tilesX]
	 			  texture_tiles,                // [tp.tilesY][tp.tilesX]["RGBA".length()][];
	 			  geometryCorrection.getSensorWH()[0], //  imp_quad[0].getWidth(),       // final int width,
				  clt_parameters.getFatZero(isMonochrome()),      // add to denominator to modify phase correlation (same units as data1, data2). <0 - pure sum
				  clt_parameters.corr_sym,
				  clt_parameters.corr_offset,
				  clt_parameters.corr_red,
				  clt_parameters.corr_blue,
				  clt_parameters.getCorrSigma(image_dtt.isMonochrome()),

				  clt_parameters.min_shot,       // 10.0;  // Do not adjust for shot noise if lower than
				  clt_parameters.scale_shot,     // 3.0;   // scale when dividing by sqrt ( <0 - disable correction)
				  clt_parameters.diff_sigma,     // 5.0;//RMS difference from average to reduce weights (~ 1.0 - 1/255 full scale image)
				  clt_parameters.diff_threshold, // 5.0;   // RMS difference from average to discard channel (~ 1.0 - 1/255 full scale image)
				  clt_parameters.diff_gauss,     // true;  // when averaging images, use gaussian around average as weight (false - sharp all/nothing)
				  clt_parameters.min_agree,      // 3.0;   // minimal number of channels to agree on a point (real number to work with fuzzy averages)
				  clt_parameters.dust_remove,    // Do not reduce average weight when only one image differes much from the average
				  clt_parameters.keep_weights,   // Add port weights to RGBA stack (debug feature)
				  geometryCorrection,            // final GeometryCorrection  geometryCorrection,
				  null,                          // final GeometryCorrection  geometryCorrection_main, // if not null correct this camera (aux) to the coordinates of the main
				  clt_kernels,                   // final double [][][][][][] clt_kernels, // [channel_in_quad][color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
//				  clt_parameters.kernel_step,
				  clt_parameters.clt_window,
				  shiftXY, //
				  disparity_corr, // final double              disparity_corr, // disparity at infinity
				  (clt_parameters.fcorr_ignore? null: this.fine_corr),
				  clt_parameters.corr_magic_scale, // still not understood coefficient that reduces reported disparity value.  Seems to be around 0.85
				  clt_parameters.shift_x,       // final int               shiftX, // shift image horizontally (positive - right) - just for testing
				  clt_parameters.shift_y,       // final int               shiftY, // shift image vertically (positive - down)
				  mcorr_sel, // 	final int                 mcorr_sel,    // Which pairs to correlate // +1 - all, +2 - dia, +4 - sq, +8 - neibs, +16 - hor + 32 - vert
				  clt_parameters.img_dtt.mcorr_comb_width,					// final int                 mcorr_comb_width,  // combined correlation tile width
				  clt_parameters.img_dtt.mcorr_comb_height,					// final int                 mcorr_comb_height, // combined correlation tile full height
				  clt_parameters.img_dtt.mcorr_comb_offset,					// final int                 mcorr_comb_offset, // combined correlation tile height offset: 0 - centered (-height/2 to height/2), height/2 - only positive (0 to height)
				  clt_parameters.img_dtt.mcorr_comb_disp,					// final double              mcorr_comb_disp,   // Combined tile per-pixel disparity for baseline == side of a square
				  
				  clt_parameters.tileX, // -1234, // clt_parameters.tileX,         // final int               debug_tileX,
				  clt_parameters.tileY,         // final int               debug_tileY, -1234 will cause port coordinates debug images
				  (clt_parameters.dbg_mode & 64) != 0, // no fract shift
				  (clt_parameters.dbg_mode & 128) != 0, // no convolve
				  //				  (clt_parameters.dbg_mode & 256) != 0, // transpose convolve
				  threadsMax,
				  debugLevel);
		  int first_color = isMonochrome()? ImageDtt.MONO_CHN:0;
		  if (debugLevel > -1){
			  System.out.println("clt_data.length="+clt_data.length+" clt_data[0].length="+clt_data[0].length
					  +" clt_data[0]["+first_color+"].length="+clt_data[0][first_color].length+" clt_data[0]["+first_color+"][0].length="+
					  clt_data[0][first_color][0].length);
		  }
		  //clt_corr_out = null;
		  //clt_combo_out = null;
		  // visualize texture tiles as RGBA slices
		  double [][] texture_nonoverlap = null;
		  double [][] texture_overlap = null;
		  String [] rbga_titles = {"red","blue","green","alpha"};
//		  String [] rbga_weights_titles4 = {"red","blue","green","alpha","port0","port1","port2","port3","r-rms","b-rms","g-rms","w-rms"};
		  String [] rbga_weights_titles_pre =  {"red","blue","green","alpha"};
		  String [] rbga_weights_titles_post = {"r-rms","b-rms","g-rms","w-rms"};
		  String [] rbga_weights_titles = new String[rbga_weights_titles_pre.length + rbga_weights_titles_post.length + image_dtt.getNumSensors()];
		  int indx = 0;
		  for (int i = 0; i < rbga_weights_titles_pre.length; i++) {
			  rbga_weights_titles[indx++] = rbga_weights_titles_pre[i];
		  }
		  for (int i = 0; i < image_dtt.getNumSensors(); i++) {
			  rbga_weights_titles[indx++] = "port"+i;
		  }
		  for (int i = 0; i < rbga_weights_titles_post.length; i++) {
			  rbga_weights_titles[indx++] = rbga_weights_titles_post[i];
		  }	
		  
		  // In monochrome mode only G is used ImageDtt.MONO_CHN(==2), others are null
		  // visualize correlation results
		  if (disparity_map != null){
			  if (!batch_mode && clt_parameters.show_map &&  (debugLevel > -2)){
				  ShowDoubleFloatArrays.showArrays(
						  disparity_map,
						  tilesX,
						  tilesY,
						  true,
						  image_name+sAux()+"-DISP_MAP-D"+clt_parameters.disparity,
						  ImageDtt.getDisparityTitles(getNumSensors(), isMonochrome())); // ImageDtt.DISPARITY_TITLES);
			  }
		  }
		  // pairwise 2D correlations
		  if (!batch_mode && !infinity_corr && (clt_corr_out != null)){
			  if (debugLevel > -2){ // -1
				  String [] titles =  image_dtt.correlation2d.getCorrTitles();
				  double [][] corr_rslt = ImageDtt.corr_partial_dbg(
						  clt_corr_out, // final double [][][][] corr_data,
						  2 * image_dtt.transform_size - 1,	//final int             corr_width,
						  2 * image_dtt.transform_size - 1,	//final int             corr_height,
						  threadsMax,
						  debugLevel);
				  // titles.length = 15, corr_rslt_partial.length=16!
				  System.out.println("corr_rslt.length = "+corr_rslt.length+", titles.length = "+titles.length); // 120
				  ShowDoubleFloatArrays.showArrays( // out of boundary 15
						  corr_rslt,
						  tilesX*(2*image_dtt.transform_size),
						  tilesY*(2*image_dtt.transform_size),
						  true,
						  image_name+sAux()+"-CORR-D"+clt_parameters.disparity+"-FZ"+clt_parameters.getFatZero(isMonochrome()),
						  titles);
			  }
		  }

		  if (!batch_mode && !infinity_corr && (clt_combo_out != null)){
			  if (debugLevel > -2){ // -1
				  String [] titles =  image_dtt.correlation2d.getComboTitles();
				  double [][] combo_rslt = ImageDtt.corr_partial_dbg(
						  clt_combo_out, // final double [][][][] corr_data,
						  clt_parameters.img_dtt.mcorr_comb_width,	// final int             corr_width,
						  clt_parameters.img_dtt.mcorr_comb_height,	// final int             corr_height
						  threadsMax,
						  debugLevel);
				  System.out.println("combo_rslt.length = "+combo_rslt.length+", titles.length = "+titles.length);
				  ShowDoubleFloatArrays.showArrays(
						  combo_rslt,
						  tilesX * (clt_parameters.img_dtt.mcorr_comb_width + 1),
						  tilesY * (clt_parameters.img_dtt.mcorr_comb_height + 1),
						  true,
						  image_name+sAux()+"-COMBO-D"+clt_parameters.disparity+"-FZ"+clt_parameters.getFatZero(isMonochrome()),
						  titles);
			  }
		  }
		  
		  if (!batch_mode && !infinity_corr && (clt_combo_dbg != null)){
			  if (debugLevel > -2){ // -1
				  String [] titles =  image_dtt.correlation2d.getCorrTitles();
				  double [][] corr_rslt = ImageDtt.corr_partial_dbg(
						  clt_combo_dbg, // final double [][][][] corr_data,
						  clt_parameters.img_dtt.mcorr_comb_width,	// final int             corr_width,
						  clt_parameters.img_dtt.mcorr_comb_height,	// final int             corr_height
						  threadsMax,
						  debugLevel);
				  // titles.length = 15, corr_rslt_partial.length=16!
				  System.out.println("corr_rslt.length = "+corr_rslt.length+", titles.length = "+titles.length);
				  ShowDoubleFloatArrays.showArrays( // out of boundary 15
						  corr_rslt,
						  tilesX * (clt_parameters.img_dtt.mcorr_comb_width + 1),
						  tilesY * (clt_parameters.img_dtt.mcorr_comb_height + 1),
						  true,
						  image_name+sAux()+"-COMBO-DBG-D"+clt_parameters.disparity,
						  titles);
			  }
		  }

		  
		  double [][][] iclt_data = new double [clt_data.length][][];
		  if (!infinity_corr && (clt_parameters.gen_chn_img || clt_parameters.gen_4_img || clt_parameters.gen_chn_stacks)) {
			  for (int iQuad = 0; iQuad < clt_data.length; iQuad++){

				  if (clt_parameters.getCorrSigma(image_dtt.isMonochrome()) > 0){ // no filter at all
					  for (int chn = 0; chn < clt_data[iQuad].length; chn++) if (clt_data[iQuad][chn] != null){
						  image_dtt.clt_lpf(
								  clt_parameters.getCorrSigma(image_dtt.isMonochrome()),
								  clt_data[iQuad][chn],
								  threadsMax,
								  debugLevel);
					  }
				  }

				  if (debugLevel > 0){
					  System.out.println("--tilesX="+tilesX);
					  System.out.println("--tilesY="+tilesY);
				  }
				  if (!batch_mode && (debugLevel > 0)){ // FALSE
					  double [][] clt = new double [clt_data[iQuad].length*4][];
					  for (int chn = 0; chn < clt_data[iQuad].length; chn++) if (clt_data[iQuad][chn] != null){
						  double [][] clt_set = image_dtt.clt_dbg(
								  clt_data [iQuad][chn],
								  threadsMax,
								  debugLevel);
						  for (int ii = 0; ii < clt_set.length; ii++) clt[chn*4+ii] = clt_set[ii];
					  }
				  }
				  iclt_data[iQuad] = new double [clt_data[iQuad].length][];

				  for (int ncol=0; ncol<iclt_data[iQuad].length;ncol++) if (clt_data[iQuad][ncol] != null) {
					  iclt_data[iQuad][ncol] = image_dtt.iclt_2d(
							  clt_data[iQuad][ncol],           // scanline representation of dcd data, organized as dct_size x dct_size tiles
							  clt_parameters.clt_window,      // window_type
							  15,                             // clt_parameters.iclt_mask,       //which of 4 to transform back
							  0,                              // clt_parameters.dbg_mode,        //which of 4 to transform back
							  threadsMax,
							  debugLevel);

				  }
			  } // end of generating shifted channel images


			  // Use iclt_data here for LWIR autorange
			  //			  if (colorProcParameters.isLwir() && colorProcParameters.lwir_autorange) {
			  if (isLwir() && colorProcParameters.lwir_autorange) {
				  double rel_low =  colorProcParameters.lwir_low;
				  double rel_high = colorProcParameters.lwir_high;
				  if (!Double.isNaN(getLwirOffset())) {
					  rel_low -=  getLwirOffset();
					  rel_high -= getLwirOffset();
				  }
				  double [] cold_hot =  autorange(
						  iclt_data, // double [][][] iclt_data, //  [iQuad][ncol][i] - normally only [][2][] is non-null
						  rel_low, // double hard_cold,// matches data, DC (this.lwir_offset)  subtracted
						  rel_high, // double hard_hot, // matches data, DC (this.lwir_offset)  subtracted
						  colorProcParameters.lwir_too_cold, // double too_cold, // pixels per image
						  colorProcParameters.lwir_too_hot, // double too_hot,  // pixels per image
						  1024); // int num_bins)
				  if (cold_hot != null) {
					  if (!Double.isNaN(getLwirOffset())) {
						  cold_hot[0] += getLwirOffset();
						  cold_hot[1] += getLwirOffset();
					  }
				  }
				  setColdHot(cold_hot); // will be used for shifted images and for texture tiles
			  }
			  ImagePlus [] imps_RGB = new ImagePlus[clt_data.length]; // all 16 here
			  for (int iQuad = 0; iQuad < clt_data.length; iQuad++){
				  if (!clt_parameters.gen_chn_img) continue;
				  String title=String.format("%s%s-%02d",image_name, sAux(), iQuad);
				  imps_RGB[iQuad] = linearStackToColor(
						  clt_parameters,
						  colorProcParameters,
						  rgbParameters,
						  title, // String name,
						  "-D"+clt_parameters.disparity, //String suffix, // such as disparity=...
						  toRGB,
						  !this.correctionsParameters.jpeg, // boolean bpp16, // 16-bit per channel color mode for result
						  !batch_mode, // true, // boolean saveShowIntermediate, // save/show if set globally
						  false, // boolean saveShowFinal,        // save/show result (color image?)
						  iclt_data[iQuad],
						  tilesX *  image_dtt.transform_size,
						  tilesY *  image_dtt.transform_size,
						  (scaleExposures == null) ? 1.0 : scaleExposures[iQuad], // double scaleExposure, // is it needed?
								  debugLevel );
			  }


			  if (clt_parameters.gen_chn_img) {
				  // combine to a sliced color image
				  int [] slice_seq = {0,1,3,2}; //clockwise
				  if (imps_RGB.length > 4) {
					  slice_seq = new int [imps_RGB.length];
					  for (int i = 0; i < slice_seq.length; i++) {
						  slice_seq[i] = i;
					  }
				  }
				  int width = imps_RGB[0].getWidth();
				  int height = imps_RGB[0].getHeight();
				  ImageStack array_stack=new ImageStack(width,height);
				  for (int i = 0; i<slice_seq.length; i++){
					  if (imps_RGB[slice_seq[i]] != null) {
						  array_stack.addSlice("port_"+slice_seq[i], imps_RGB[slice_seq[i]].getProcessor().getPixels());
					  } else { // not used in lwir
						  ///						  array_stack.addSlice("port_"+slice_seq[i], results[slice_seq[i]].getProcessor().getPixels());
					  }
				  }
				  ImagePlus imp_stack = new ImagePlus(image_name+sAux()+"CPU-SHIFTED-D"+clt_parameters.disparity, array_stack);
				  imp_stack.getProcessor().resetMinAndMax();
				  if (!batch_mode) {
					  imp_stack.updateAndDraw(); // not used in lwir
				  }
				  //imp_stack.getProcessor().resetMinAndMax();
				  //imp_stack.show();
				  //				  eyesisCorrections.saveAndShow(imp_stack, this.correctionsParameters);
				  eyesisCorrections.saveAndShowEnable(
						  imp_stack,  // ImagePlus             imp,
						  this.correctionsParameters, // EyesisCorrectionParameters.CorrectionParameters  correctionsParameters,
						  true, // boolean               enableSave,
						  !batch_mode) ;// boolean               enableShow);
			  }
			  if (clt_parameters.gen_4_img) {
				  // Save as individual JPEG images in the model directory
				  String x3d_path = getX3dDirectory(true);
				  for (int sub_img = 0; sub_img < imps_RGB.length; sub_img++){
					  EyesisCorrections.saveAndShow(
							  imps_RGB[sub_img],
							  x3d_path,
							  correctionsParameters.png && !clt_parameters.black_back,
							  !batch_mode && clt_parameters.show_textures,
							  correctionsParameters.JPEG_quality, // jpegQuality); // jpegQuality){//  <0 - keep current, 0 - force Tiff, >0 use for JPEG
							  (debugLevel > 0) ? debugLevel : 1); // int debugLevel (print what it saves)
				  }

				  String model_path= correctionsParameters.selectX3dDirectory(
						  image_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
						  null,
						  true,  // smart,
						  true);  //newAllowed, // save

				  createThumbNailImage(
						  imps_RGB[0],
						  model_path,
						  "thumb"+sAux(),
						  debugLevel);


			  }

		  }
		  if (texture_tiles != null){
			  if (clt_parameters.show_nonoverlap){// not used in lwir
				  texture_nonoverlap = image_dtt.combineRBGATiles(
						  texture_tiles,                 // array [tp.tilesY][tp.tilesX][4][4*transform_size] or [tp.tilesY][tp.tilesX]{null}
						  false,                         // when false - output each tile as 16x16, true - overlap to make 8x8
						  clt_parameters.sharp_alpha,    // combining mode for alpha channel: false - treat as RGB, true - apply center 8x8 only
						  threadsMax,                    // maximal number of threads to launch
						  debugLevel);
				  ShowDoubleFloatArrays.showArrays(
						  texture_nonoverlap,
						  tilesX * (2 * image_dtt.transform_size),
						  tilesY * (2 * image_dtt.transform_size),
						  true,
						  image_name+sAux() + "-TXTNOL-D"+clt_parameters.disparity,
						  (clt_parameters.keep_weights?rbga_weights_titles:rbga_titles));

			  }
			  if (!infinity_corr && (clt_parameters.show_overlap || clt_parameters.show_rgba_color)){
				  int alpha_index = 3;
				  texture_overlap = image_dtt.combineRBGATiles(
						  texture_tiles,                 // array [tp.tilesY][tp.tilesX][4][4*transform_size] or [tp.tilesY][tp.tilesX]{null}
						  true,                         // when false - output each tile as 16x16, true - overlap to make 8x8
						  clt_parameters.sharp_alpha,    // combining mode for alpha channel: false - treat as RGB, true - apply center 8x8 only
						  threadsMax,                    // maximal number of threads to launch
						  debugLevel);
				  if (clt_parameters.alpha1 > 0){ // negative or 0 - keep alpha as it was
					  double scale = (clt_parameters.alpha1 > clt_parameters.alpha0) ? (1.0/(clt_parameters.alpha1 - clt_parameters.alpha0)) : 0.0;
					  for (int i = 0; i < texture_overlap[alpha_index].length; i++){
						  double d = texture_overlap[alpha_index][i];
						  if      (d >=clt_parameters.alpha1) d = 1.0;
						  else if (d <=clt_parameters.alpha0) d = 0.0;
						  else d = scale * (d- clt_parameters.alpha0);
						  texture_overlap[alpha_index][i] = d;
					  }
				  }

				  if (!batch_mode && clt_parameters.show_overlap) {// not used in lwir
					  ShowDoubleFloatArrays.showArrays( // all but r-rms, b-rms
							  texture_overlap,
							  tilesX * image_dtt.transform_size,
							  tilesY * image_dtt.transform_size,
							  true,
							  image_name+sAux() + "-TXTOL-D"+clt_parameters.disparity,
							  (clt_parameters.keep_weights?rbga_weights_titles:rbga_titles));
				  }
				  if (!batch_mode && clt_parameters.show_rgba_color) {// not used in lwir
					  // for now - use just RGB. Later add option for RGBA
					  double [][] texture_rgb = {texture_overlap[0],texture_overlap[1],texture_overlap[2]};
					  double [][] texture_rgba = {texture_overlap[0],texture_overlap[1],texture_overlap[2],texture_overlap[3]};
					  ImagePlus img_texture =
					  linearStackToColor(
							  clt_parameters,
							  colorProcParameters,
							  rgbParameters,
							  image_name+sAux()+"-texture", // String name,
							  "-D"+clt_parameters.disparity, //String suffix, // such as disparity=...
							  toRGB,
							  !this.correctionsParameters.jpeg, // boolean bpp16, // 16-bit per channel color mode for result
							  true, // boolean saveShowIntermediate, // save/show if set globally
							  true, // boolean saveShowFinal,        // save/show result (color image?)
							  ((clt_parameters.alpha1 > 0)? texture_rgba: texture_rgb),
							  tilesX *  image_dtt.transform_size,
							  tilesY *  image_dtt.transform_size,
							  1.0,         // double scaleExposure, // is it needed?
							  debugLevel );
					  img_texture.show();
				  }
			  }
		  }
//		  return results;
	  }
	  
	  public ImagePlus [] processCLTQuadCorrTestERS(
			  ImagePlus [] imp_quad, // should have properties "name"(base for saving results), "channel","path"
			  boolean [][] saturation_imp, // (near) saturated pixels or null
			  CLTParameters           clt_parameters,
			  ColorProcParameters                              colorProcParameters,
			  CorrectionColorProc.ColorGainsParameters         channelGainParameters,
			  EyesisCorrectionParameters.RGBParameters             rgbParameters,
			  double []	       scaleExposures, // probably not needed here
			  final boolean    apply_corr, // calculate and apply additional fine geometry correction
			  final boolean    infinity_corr, // calculate and apply geometry correction at infinity
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  int              debugLevel){

		  if (debugLevel > -2) { // -1
			  debugLevel = clt_parameters.ly_debug_level;
		  }

		  String path= (String) imp_quad[0].getProperty("path");

		  ImagePlus [] results = new ImagePlus[imp_quad.length];
		  for (int i = 0; i < results.length; i++) {
			  results[i] = imp_quad[i];
			  results[i].setTitle(results[i].getTitle()+"RAW");
		  }
		  if (debugLevel>1) System.out.println("processing: "+path);
// 08/12/2020 Moved to condifuinImageSet - remove setTiles?
/*
   	     setTiles (imp_quad[0], // set global tp.tilesX, tp.tilesY
 				  clt_parameters,
				  threadsMax);
*/
		  final int tilesX=tp.getTilesX(); // imp_quad[0].getWidth()/clt_parameters.transform_size;
		  final int tilesY=tp.getTilesY(); // imp_quad[0].getHeight()/clt_parameters.transform_size;

		  final int clustersX= (tilesX + clt_parameters.tileStep - 1) / clt_parameters.tileStep;
		  final int clustersY= (tilesY + clt_parameters.tileStep - 1) / clt_parameters.tileStep;

		  double [][] dsxy = new double[clustersX * clustersY][];
		  ImagePlus imp_sel = WindowManager.getCurrentImage();
		  if ((imp_sel == null) || (imp_sel.getStackSize() != ExtrinsicAdjustment.get_INDX_LENGTH(getNumSensors()))) {
			  System.out.println("No image / wrong image selected, bailing out");
			  return null;
		  } else {
			  System.out.println("Image: "+imp_sel.getTitle());
				int width = imp_sel.getWidth();
				int height = imp_sel.getHeight();
				if ((width != clustersX) || (height != clustersY)) {
					  System.out.println(String.format("Image size mismatch: width=%d (%d), height=%d(%d)",
							  width, clustersX, height, clustersY));
					  return null;
				}
				ImageStack stack_sel = imp_sel.getStack();
				float [][] fpixels = new float [ExtrinsicAdjustment.get_INDX_LENGTH(getNumSensors())][];
				for (int i = 0; i < fpixels.length; i++) {
					fpixels[i] = (float[]) stack_sel.getPixels(i+1);
				}
				for (int tile = 0; tile < dsxy.length; tile ++) {
					if (fpixels[1][tile] > 0.0) {
						dsxy[tile] = new double[fpixels.length];
						for (int i = 0; i < fpixels.length; i++) {
							dsxy[tile][i] = fpixels[i][tile];
						}
					}
				}
		  }
		  ExtrinsicAdjustment ea = new ExtrinsicAdjustment(
				  geometryCorrection, // GeometryCorrection gc,
				  clt_parameters.tileStep,   // int         clusterSize,
				  clustersX, // 	int         clustersX,
				  clustersY); // int         clustersY);

		  double [] old_new_rms = new double[2];
		  boolean apply_extrinsic = (clt_parameters.ly_corr_scale != 0.0);
		  double      inf_min_disparity = clt_parameters.ly_inf_force_fine? clt_parameters.ly_inf_min_narrow :clt_parameters.ly_inf_min_broad; 
		  double      inf_max_disparity = clt_parameters.ly_inf_force_fine? clt_parameters.ly_inf_max_narrow :clt_parameters.ly_inf_max_broad; 
		  CorrVector corr_vector =   ea.solveCorr (
				  clt_parameters.ly_marg_fract, 	// double      marg_fract,        // part of half-width, and half-height to reduce weights
				  clt_parameters.ly_inf_en,      // boolean     use_disparity,     // adjust disparity-related extrinsics
				  // 1.0 - to skip filtering infinity
				  inf_min_disparity,             // -0.5, // -1.0, //double      inf_min_disparity, // minimal disparity for infinity 
				  inf_max_disparity,             // 0.05, // 1.0, // double      inf_max_disparity, // minimal disparity for infinity
				  clt_parameters.ly_inf_min_broad, // inf_min_disp_abs,  // minimal disparity for infinity (absolute) 
				  clt_parameters.ly_inf_max_broad, // maximal disparity for infinity (absolute)
				  clt_parameters.ly_inf_tilt,      //   boolean     en_infinity_tilt,  // select infinity tiles form right/left tilted (false - from average)  
				  clt_parameters.ly_right_left,    //   boolean     infinity_right_left, // balance weights between right and left halves of infinity
				  clt_parameters.ly_aztilt_en,   // boolean     use_aztilts,       // Adjust azimuths and tilts excluding disparity
				  clt_parameters.ly_diff_roll_en,//boolean     use_diff_rolls,    // Adjust differential rolls (3 of 4 angles)
				  clt_parameters.ly_min_forced,  //	int         min_num_forced,    // minimal number of clusters with forced disparity to use it
				  // data, using just radial distortions
				  clt_parameters.ly_com_roll,    //boolean     common_roll,       // Enable common roll (valid for high disparity range only)
				  clt_parameters.ly_focalLength, //boolean     corr_focalLength,  // Correct scales (focal length temperature? variations)
				  clt_parameters.ly_ers_rot,     //	boolean     ers_rot,           // Enable ERS correction of the camera rotation
				  clt_parameters.getErsForw(),        // boolean     ers_forw,          // Enable ERS correction of the camera linear movement in z direction
				  clt_parameters.getErsSide(),        // boolean     ers_side,          // Enable ERS correction of the camera linear movement in x direction
				  clt_parameters.getErsVert(),        // boolean     ers_vert,          // Enable ERS correction of the camera linear movement in y direction
				  // add balancing-related here?
				  clt_parameters.ly_par_sel, // 	int         manual_par_sel,    // Manually select the parameter mask bit 0 - sym0, bit1 - sym1, ... (0 - use boolean flags, != 0 - ignore boolean flags)
				  clt_parameters.ly_weight_infinity,     //0.3, // double      weight_infinity,     // 0.3, total weight of infinity tiles fraction (0.0 - 1.0) 
				  clt_parameters.ly_weight_disparity,    //0.0, // double      weight_disparity,    // 0.0 disparity weight relative to the sum of 8 lazy eye values of the same tile 
				  clt_parameters.ly_weight_disparity_inf,//0.5, // double      weight_disparity_inf,// 0.5 disparity weight relative to the sum of 8 lazy eye values of the same tile for infinity 
				  clt_parameters.ly_max_disparity_far,   //5.0, // double      max_disparity_far,   // 5.0 reduce weights of near tiles proportional to sqrt(max_disparity_far/disparity) 
				  clt_parameters.ly_max_disparity_use,   //5.0, // double      max_disparity_use,   // 5.0 (default 1000)disable near objects completely - use to avoid ERS

				  clt_parameters.ly_inf_min_dfe,         //1.75,// double      min_dfe, // = 1.75;
				  clt_parameters.ly_inf_max_dfe,         //5.0, // double      max_dfe, // = 5.0; // <=0 - disable feature

				  // moving objects filtering
				  clt_parameters.ly_moving_en,  // 	boolean     moving_en,         // enable filtering areas with potentially moving objects 
				  clt_parameters.ly_moving_apply,  // 	boolean     moving_apply,      // apply filtering areas with potentially moving objects 
				  clt_parameters.ly_moving_sigma,   // 	double      moving_sigma,      // blurring sigma for moving objects = 1.0;
				  clt_parameters.ly_max_mov_disparity,  //		double      max_mov_disparity, // disparity limit for moving objects detection = 75.0;
				  clt_parameters.ly_rad_to_hdiag_mov,   // 	double      rad_to_hdiag_mov,  // radius to half-diagonal ratio to remove high-distortion corners = 0.7 ; // 0.8
				  clt_parameters.ly_max_mov_average,   //		double      max_mov_average,   // do not attempt to detect moving objects if ERS is not accurate for terrain = .25;
				  clt_parameters.ly_mov_min_L2,  // 	double      mov_min_L2,        // threshold for moving objects = 0.75;
				  
				  dsxy, // double [][] measured_dsxy,
				  null, //	boolean [] force_disparity,    // boolean [] force_disparity,
				  false, // 	boolean     use_main, // corr_rots_aux != null;
				  geometryCorrection.getCorrVector(), // CorrVector corr_vector,
				  old_new_rms, // double [] old_new_rms, // should be double[2]
				  debugLevel); //  + 5);// int debugLevel)

		  if (debugLevel > -2){
			  System.out.println("Old extrinsic corrections:");
			  System.out.println(geometryCorrection.getCorrVector().toString());
		  }
		  if (corr_vector != null) {
			  CorrVector diff_corr = corr_vector.diffFromVector(geometryCorrection.getCorrVector());
			  if (debugLevel > -2){
					  System.out.println("New extrinsic corrections:");
					  System.out.println(corr_vector.toString());

					  System.out.println("Increment extrinsic corrections:");
					  System.out.println(diff_corr.toString());
					  // System.out.println("Correction scale = "+clt_parameters.ly_corr_scale);

			  }

			  if (apply_extrinsic){
				  geometryCorrection.setCorrVector(corr_vector) ;
/*
				  geometryCorrection.getCorrVector().incrementVector(diff_corr, clt_parameters.ly_corr_scale);
				  System.out.println("New (with correction scale applied) extrinsic corrections:");
				  System.out.println(geometryCorrection.getCorrVector().toString());
*/
				  System.out.println("Extrinsic correction updated (can be disabled by setting clt_parameters.ly_corr_scale = 0.0) ");

			  } else {
				  System.out.println("Correction is not applied according clt_parameters.ly_corr_scale == 0.0) ");
			  }
		  } else {
			  if (debugLevel > -3){
				  System.out.println("LMA failed");
			  }
		  }
		  double [][][] mismatch_corr_coefficients = new double [1][2][];
		  mismatch_corr_coefficients[0][0] = geometryCorrection.getCorrVector().toSymArray(null);
		  mismatch_corr_coefficients[0][1] = old_new_rms;
		  return null;
	  }

	  public ImagePlus [] processCLTQuadCorrTest(
			  ImagePlus [] imp_quad, // should have properties "name"(base for saving results), "channel","path"
			  boolean [][] saturation_imp, // (near) saturated pixels or null
			  CLTParameters           clt_parameters,
			  ColorProcParameters                              colorProcParameters,
			  CorrectionColorProc.ColorGainsParameters         channelGainParameters,
			  EyesisCorrectionParameters.RGBParameters             rgbParameters,
			  double []	       scaleExposures, // probably not needed here
			  final boolean    apply_corr, // calculate and apply additional fine geometry correction
			  final boolean    infinity_corr, // calculate and apply geometry correction at infinity
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel){
		  final boolean      batch_mode = clt_parameters.batch_run; //disable any debug images
		  String name=this.correctionsParameters.getModelName((String) imp_quad[0].getProperty("name"));
		  //		int channel= Integer.parseInt((String) imp_src.getProperty("channel"));
		  String path= (String) imp_quad[0].getProperty("path");

		  ImagePlus [] results = new ImagePlus[imp_quad.length];
		  for (int i = 0; i < results.length; i++) {
			  results[i] = imp_quad[i];
			  results[i].setTitle(results[i].getTitle()+"RAW");
		  }
		  if (debugLevel>1) System.out.println("processing: "+path);
		  ImageDtt image_dtt = new ImageDtt(
				  getNumSensors(),
				  clt_parameters.transform_size,
				  clt_parameters.img_dtt,
				  isMonochrome(),
				  isAux(),
				  isLwir(),
				  clt_parameters.getScaleStrength(isAux()));
		  image_dtt.getCorrelation2d(); // initiate image_dtt.correlation2d, needed if disparity_map != null  
		  double [][] disparity_array = tp.setSameDisparity(clt_parameters.disparity); // 0.0); // [tp.tilesY][tp.tilesX] - individual per-tile expected disparity
		  ImagePlus imp_sel = WindowManager.getCurrentImage();
		  if ((imp_sel == null) || (imp_sel.getStackSize() != 3)) {
			  System.out.println("No image / wrong image selected, using infinity");
		  } else {
			  System.out.println("Image: "+imp_sel.getTitle());
				int width = imp_sel.getWidth();
				int height = imp_sel.getHeight();
				if ((width != tp.getTilesX()) || (height != tp.getTilesY())) {
					  System.out.println(String.format("Image size mismatch: width=%d (%d), height=%d(%d)",
							  width, tp.getTilesX(), height, tp.getTilesY()));
					  return null;
				}
				ImageStack stack_sel = imp_sel.getStack();
				float [] img_disparity = (float[]) stack_sel.getPixels(1); // first stack is disparity
				int indx = 0;
				for (int i = 0; i< disparity_array.length; i++){
					for (int j = 0; j< disparity_array[0].length; j++){
						double d = img_disparity[indx++];
						if (!Double.isNaN(d)) { // treat NaN as 0
							disparity_array[i][j] += d;
						}
					}
				}
		  }


		  // temporary setting up tile task file (one integer per tile, bitmask
		  // for testing defined for a window, later the tiles to process will be calculated based on previous passes results

		  int [][]    tile_op = tp.setSameTileOp(clt_parameters,  clt_parameters.tile_task_op, debugLevel);
		  //TODO: Add array of default disparity - use for combining images in force disparity mode (no correlation), when disparity is predicted from other tiles
		  double [][][][]     clt_corr_combo =   null;
		  double [][][][][]   clt_corr_partial = null; // [tp.tilesY][tp.tilesX][pair][color][(2*transform_size-1)*(2*transform_size-1)]
		  double [][][][]     texture_tiles =    null; // [tp.tilesY][tp.tilesX]["RGBA".length()][]; // tiles will be 16x16, 2 visualization mode full 16 or overlapped
		  // undecided, so 2 modes of combining alpha - same as rgb, or use center tile only
		  final int tilesX = tp.getTilesX();
		  final int tilesY = tp.getTilesY();

		  if (clt_parameters.correlate){ // true
			  clt_corr_combo =    new double [ImageDtt.TCORR_TITLES.length][tilesY][tilesX][];
			  texture_tiles =     new double [tilesY][tilesX][][]; // ["RGBA".length()][];
			  for (int i = 0; i < tilesY; i++){
				  for (int j = 0; j < tilesX; j++){
					  for (int k = 0; k<clt_corr_combo.length; k++){
						  clt_corr_combo[k][i][j] = null;
					  }
					  texture_tiles[i][j] = null;
				  }
			  }
			  if (!infinity_corr && clt_parameters.corr_keep){ // true
				  clt_corr_partial = new double [tilesY][tilesX][][][];
				  for (int i = 0; i < tilesY; i++){
					  for (int j = 0; j < tilesX; j++){
						  clt_corr_partial[i][j] = null;
					  }
				  }
			  } // clt_parameters.corr_mismatch = false
///			  if (clt_parameters.corr_mismatch || apply_corr || infinity_corr){ // added infinity_corr
///				  clt_mismatch = new double [12][]; // What is 12?// not used in lwir
///			  }
		  }
		  // Includes all 3 colors - will have zeros in unused
//		  double [][] disparity_map = new double [ImageDtt.DISPARITY_TITLES.length][]; //[0] -residual disparity, [1] - orthogonal (just for debugging) last 4 - max pixel differences
		  double [][] disparity_map = new double [ImageDtt.getDisparityTitles(getNumSensors(), isMonochrome()).length][]; //[0] -residual disparity, [1] - orthogonal (just for debugging)

		  double min_corr_selected = clt_parameters.min_corr;
		  double [][] shiftXY = new double [getNumSensors()][2];
		  if (!clt_parameters.fine_corr_ignore) {// invalid for AUX!
			  double [][] shiftXY0 = {
					  {clt_parameters.fine_corr_x_0,clt_parameters.fine_corr_y_0},
					  {clt_parameters.fine_corr_x_1,clt_parameters.fine_corr_y_1},
					  {clt_parameters.fine_corr_x_2,clt_parameters.fine_corr_y_2},
					  {clt_parameters.fine_corr_x_3,clt_parameters.fine_corr_y_3}};
			  // FIXME - only first 4 sensors have correction. And is it the same for aux and main? 
			  for (int i = 0; i < shiftXY0.length;i++) {
				  shiftXY[i] = shiftXY0[i];
			  }
		  }

		  double z_correction =  clt_parameters.z_correction;
		  if (clt_parameters.z_corr_map.containsKey(name)){
			  z_correction +=clt_parameters.z_corr_map.get(name);// not used in lwir
		  }
		  final double disparity_corr = (z_correction == 0) ? 0.0 : geometryCorrection.getDisparityFromZ(1.0/z_correction);
		  double [][] lazy_eye_data = image_dtt.cltMeasureLazyEye ( // returns d,s lazy eye parameters 
				  clt_parameters.img_dtt,       // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
				  tile_op,                      // final int [][]            tile_op,         // [tilesY][tilesX] - what to do - 0 - nothing for this tile
				  disparity_array,              // final double [][]         disparity_array, // [tilesY][tilesX] - individual per-tile expected disparity
				  getImageData(),                   // final double [][][]      imade_data, // first index - number of image in a quad
				  saturation_imp,               // final boolean [][]        saturation_imp, // (near) saturated pixels or null
				  geometryCorrection.getSensorWH()[0], // 	final int                 width,
				  clt_parameters.getFatZero(isMonochrome()),      // final double              corr_fat_zero,    // add to denominator to modify phase correlation (same units as data1, data2). <0 - pure sum
				  clt_parameters.corr_red,      // final double              corr_red,
				  clt_parameters.corr_blue,     // final double              corr_blue,
				  clt_parameters.getCorrSigma(image_dtt.isMonochrome()), // final double              corr_sigma,
				  min_corr_selected, // 0.0001; //final double              min_corr,        // 0.02; // minimal correlation value to consider valid
				  geometryCorrection,            // final GeometryCorrection  geometryCorrection,
				  null,                          // final GeometryCorrection  geometryCorrection_main, // if not null correct this camera (aux) to the coordinates of the main
				  clt_kernels,                   // final double [][][][][][] clt_kernels, // [channel_in_quad][color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
				  clt_parameters.clt_window,     // final int                 window_type,
				  shiftXY,                       // final double [][]         shiftXY, // [port]{shiftX,shiftY}
				  disparity_corr,                // final double              disparity_corr, // disparity at infinity
				  clt_parameters.shift_x,        // final double              shiftX, // shift image horizontally (positive - right) - just for testing
				  clt_parameters.shift_y,        // final double              shiftY, // shift image vertically (positive - down)
				  clt_parameters.tileStep,       // final int                 tileStep, // process tileStep x tileStep cluster of tiles when adjusting lazy eye parameters
				  clt_parameters.img_dtt.getMcorrSelLY(getNumSensors()), //    final int                 mcorr_sel, // +1 - all, +2 - dia, +4 - sq, +8 - neibs, +16 - hor + 32 - vert

				  clt_parameters.img_dtt.mcorr_comb_width,					// final int                 mcorr_comb_width,  // combined correlation tile width
				  clt_parameters.img_dtt.mcorr_comb_height,					// final int                 mcorr_comb_height, // combined correlation tile full height
				  clt_parameters.img_dtt.mcorr_comb_offset,					// final int                 mcorr_comb_offset, // combined correlation tile height offset: 0 - centered (-height/2 to height/2), height/2 - only positive (0 to height)
				  clt_parameters.img_dtt.mcorr_comb_disp,					// final double              mcorr_comb_disp,   // Combined tile per-pixel disparity for baseline == side of a square
				  
				  clt_parameters.tileX,        // final int                 debug_tileX,
				  clt_parameters.tileY,         // final int                 debug_tileY,
				  threadsMax, // final int                 threadsMax,  // maximal number of threads to launch
				  debugLevel - 2); // final int                 globalDebugLevel)

		  if (lazy_eye_data != null) {
				  int clustersX= (tilesX + clt_parameters.tileStep - 1) / clt_parameters.tileStep;
				  int clustersY= (tilesY + clt_parameters.tileStep - 1) / clt_parameters.tileStep;
				  ExtrinsicAdjustment ea_dbg = new ExtrinsicAdjustment (
						  geometryCorrection, // GeometryCorrection gc,
						  clt_parameters.tileStep, // int         clusterSize,
						  clustersX, // int         clustersX,
						  clustersY); // int         clustersY)

				  double [][] dbg_cluster = new double [ExtrinsicAdjustment.get_INDX_LENGTH(getNumSensors())][clustersY * clustersX];
				  for (int n = 0; n < lazy_eye_data.length; n++) {
					  if ((lazy_eye_data[n] != null) && (lazy_eye_data[n][ExtrinsicAdjustment.INDX_STRENGTH] >= clt_parameters.img_dtt.lma_diff_minw)) {
						  for (int i = 0; i < ExtrinsicAdjustment.get_INDX_LENGTH(getNumSensors()); i++ ) {
							  if (i == ExtrinsicAdjustment.INDX_STRENGTH) {
								  dbg_cluster[i][n] = lazy_eye_data[n][i] - clt_parameters.img_dtt.lma_diff_minw; // strength
							  } else {
								  dbg_cluster[i][n] = lazy_eye_data[n][i];
							  }
						  }
					  } else {
						  for (int i = 0; i < ExtrinsicAdjustment.get_INDX_LENGTH(getNumSensors()); i++ ) {
							  if (i == ExtrinsicAdjustment.INDX_STRENGTH) {
								  dbg_cluster[i][n] = 0.0; // strength
							  } else {
								  dbg_cluster[i][n] = Double.NaN;
							  }
						  }
					  }
				  }
				  //clt_parameters.img_dtt.lma_diff_minw
				  ShowDoubleFloatArrays.showArrays(
						  dbg_cluster,
						  clustersX,
						  clustersY,
						  true,
						  name+sAux()+"-CLT_MISMATCH-D"+clt_parameters.disparity+"_"+clt_parameters.tileStep+"x"+clt_parameters.tileStep,
						  ea_dbg.data_titles); //  ExtrinsicAdjustment.DATA_TITLES);

				  if (disparity_map != null){
					  int target_index = ImageDtt.DISPARITY_INDEX_INT;
					  int cm_index = ImageDtt.DISPARITY_INDEX_INT+1;
					  int lma_index = ImageDtt.DISPARITY_INDEX_VERT;
					  int strength_index = ImageDtt.DISPARITY_STRENGTH_INDEX;
					  double [][] scan_maps = new double[3][tilesX*tilesY];
					  for (int i = 0; i < scan_maps[0].length; i++) {
						  scan_maps[1][i] = disparity_map[lma_index][i];
						  scan_maps[2][i] = disparity_map[strength_index][i];
						  if (Double.isNaN(disparity_map[lma_index][i])) {
							  if (Double.isNaN(disparity_map[cm_index][i])) {
								  scan_maps[0][i] = disparity_map[target_index][i]; // TODO: add offset calculated from neighbours
							  } else {
								  scan_maps[0][i] = disparity_map[cm_index][i];
							  }

						  } else {
							  scan_maps[0][i] = disparity_map[lma_index][i];
						  }
					  }
					  String [] titles3 = {"combo", "lma", "strength"};
					  ShowDoubleFloatArrays.showArrays(
							  scan_maps,
							  tilesX,
							  tilesY,
							  true,
							  name+sAux()+"-DISP_MAP-D"+clt_parameters.disparity+"-CLT",
							  titles3);

				  }
		  }

		  if (disparity_map != null){
			  if (!batch_mode && clt_parameters.show_map &&  (debugLevel > -2)){
				  ShowDoubleFloatArrays.showArrays(
						  disparity_map,
						  tilesX,
						  tilesY,
						  true,
						  name+sAux()+"-DISP_MAP-D"+clt_parameters.disparity,
						  ImageDtt.getDisparityTitles(getNumSensors(), isMonochrome()));// ImageDtt.DISPARITY_TITLES);
			  }
		  }

		  return results;
	  }

	  static double [][] resizeGridTexture( // USED in lwir
			  double [][] imgData,
			  int tileSize,
			  int tilesX,
			  int tilesY,
			  Rectangle bounds){
		  int width =   tileSize*bounds.width;
		  int height =  tileSize*bounds.height;
		  // Adding row/column of all 0, assuming java zeroes arrays
		  int numSlices = imgData.length;
		  double [][] rslt = new double [numSlices][width*height];
		  int offset = (tileSize*bounds.y)*tileSize*tilesX + (tileSize*bounds.x);
		  for (int y = 0; y < height; y ++){
			  for (int x = 0; x < width; x ++){
				  int indx = width * y + x;
				  int indx_in = indx + offset;
				  for (int i = 0; i < numSlices; i++) {
					  rslt[i][indx] = Double.isNaN(imgData[i][indx_in])? 0.0:imgData[i][indx_in];
				  }
			  }
			  offset += tilesX * tileSize - width;

		  }
		  return rslt;
	  }
	  public static int [] getLwirHistogram( // USED in lwir
			  double [] data,
			  double    hard_cold,
			  double    hard_hot,
			  int       num_bins) {
		  int [] hist = new int [num_bins];
		  double k = num_bins / (hard_hot - hard_cold);
		  for (double d:data) {
			  int bin = (int) ((d - hard_cold)*k);
			  if (bin < 0) bin = 0;
			  else if (bin >= num_bins) bin = (num_bins -1);
			  hist[bin]++;
		  }
		  return hist;
	  }
	  
	  public static double [] getLwirHistogramSliceAlpha(
			  double [][] data,
			  double    hard_cold,
			  double    hard_hot,
			  int       num_bins) {
		  int chn_y =     0;
		  int chn_alpha = 1;
		  double [] hist = new double [num_bins];
		  double k = num_bins / (hard_hot - hard_cold);
		  for (int i = 0; i < data[chn_y].length; i++) {
			  double d = data[chn_y][i] ; 
			  int bin = (int) ((d - hard_cold)*k);
			  if (bin < 0) bin = 0;
			  else if (bin >= num_bins) bin = (num_bins -1);
			  hist[bin] += data[chn_alpha][i];
		  }
		  return hist;
	  }
	  
	  
	  public static int [] getLwirHistogram( // USED in lwir
			  float [] data,
			  double    hard_cold,
			  double    hard_hot,
			  int       num_bins) {
		  int [] hist = new int [num_bins];
		  double k = num_bins / (hard_hot - hard_cold);
		  for (double d:data) if (!Double.isNaN(d)){
			  int bin = (int) ((d - hard_cold)*k);
			  if (bin < 0) bin = 0;
			  else if (bin >= num_bins) bin = (num_bins -1);
			  hist[bin]++;
		  }
		  return hist;
	  }
	  
	  public static int [] addHist( // USED in lwir
			  int [] this_hist,
			  int [] other_hist) {
		  for (int i = 0; i < this_hist.length; i++) {
			  this_hist[i] += other_hist[i];
		  }
		  return this_hist;
	  }

	  public static double [] addHist( // USED in lwir
			  double [] this_hist,
			  double [] other_hist) {
		  for (int i = 0; i < this_hist.length; i++) {
			  this_hist[i] += other_hist[i];
		  }
		  return this_hist;
	  }
	  
	  
	  // get low/high (soft min/max) from the histogram
	  // returns value between 0.0 (low histogram limit and 1.0 - high histgram limit
	  public static double getMarginFromHist( // USED in lwir
			  int [] hist, // histogram
			  double cumul_val, // cumulative number of items to be ignored
			  boolean high_marg) { // false - find low margin(output ~0.0) , true - find high margin (output ~1.0)
		  int n = 0;
		  int n_prev = 0;
		  int bin;
		  double s = 1.0 / hist.length;
		  double v;
		  if (high_marg) {
			  for (bin = hist.length -1; bin >= 0; bin--) {
				  n_prev = n;
				  n+= hist[bin];
				  if (n > cumul_val) break;
			  }
			  if (n <= cumul_val) { // not used in lwir
				  v =  0.0; // cumul_val > total number of samples
			  } else {
				  v = s* (bin + 1 - (cumul_val - n_prev)/(n - n_prev));
			  }

		  } else {
			  for (bin = 0; bin < hist.length; bin++) {
				  n_prev = n;
				  n+= hist[bin];
				  if (n > cumul_val) break;
			  }
			  if (n <= cumul_val) { // not used in lwir
				  v =  1.0; // cumul_val > total number of samples
			  } else {
				  v = s * (bin + (cumul_val - n_prev)/(n - n_prev));
			  }
		  }
		  return v;
	  }

	  public static double getMarginFromHist( // USED in lwir
			  double [] hist, // histogram
			  double cumul_val, // cumulative number of items to be ignored
			  boolean high_marg) { // false - find low margin(output ~0.0) , true - find high margin (output ~1.0)
		  double n = 0;
		  double n_prev = 0;
		  int bin;
		  double s = 1.0 / hist.length;
		  double v;
		  if (high_marg) {
			  for (bin = hist.length -1; bin >= 0; bin--) {
				  n_prev = n;
				  n+= hist[bin];
				  if (n > cumul_val) break;
			  }
			  if (n <= cumul_val) { // not used in lwir
				  v =  0.0; // cumul_val > total number of samples
			  } else {
				  v = s* (bin + 1 - (cumul_val - n_prev)/(n - n_prev));
			  }

		  } else {
			  for (bin = 0; bin < hist.length; bin++) {
				  n_prev = n;
				  n+= hist[bin];
				  if (n > cumul_val) break;
			  }
			  if (n <= cumul_val) { // not used in lwir
				  v =  1.0; // cumul_val > total number of samples
			  } else {
				  v = s * (bin + (cumul_val - n_prev)/(n - n_prev));
			  }
		  }
		  return v;
	  }
	  
	  
	  
	  public static double [] autorange( // USED in lwir (double input)
			  double [][][] iclt_data, //  [iQuad][ncol][i] - normally only [][2][] is non-null
			  double hard_cold,// matches data, DC (this.lwir_offset)  subtracted
			  double hard_hot, // matches data, DC (this.lwir_offset)  subtracted
			  double too_cold, // pixels per image
			  double too_hot,  // pixels per image
			  int num_bins) {
		  int num_chn = 0;
		  int ncol=0;
		  for (int nsens = 0; nsens < iclt_data.length; nsens++) if (iclt_data[nsens] != null) {
			  num_chn++;
			  if (ncol==0) {
				  for (ncol = 0; ncol < iclt_data[nsens].length; ncol++) {
					  if (iclt_data[nsens][ncol] != null) break;
				  }
			  }
		  }
		  too_cold *= num_chn; // iclt_data.length;
		  too_hot *= num_chn; // iclt_data.length;
		  int [] hist = null;
		  for (int iQuad = 0; iQuad < iclt_data.length; iQuad++) {
			  int [] this_hist = getLwirHistogram(
					  iclt_data[iQuad][ncol], // double [] data,
					  hard_cold,
					  hard_hot,
					  num_bins);
			  if (hist == null) {
				  hist = this_hist;
			  } else {
				  addHist(
						  hist,
						  this_hist);
			  }
		  }
		  double [] rel_lim = {
				  getMarginFromHist(
						  hist, // histogram
						  too_cold, // double cumul_val, // cummulative number of items to be ignored
						  false), // boolean high_marg)
				  getMarginFromHist(
						  hist, // histogram
						  too_hot, // double cumul_val, // cummulative number of items to be ignored
						  true)}; // boolean high_marg)
		  double [] abs_lim = {
				  rel_lim[0] * (hard_hot - hard_cold) + hard_cold,
				  rel_lim[1] * (hard_hot - hard_cold) + hard_cold,
		  };
		  return abs_lim;
	  }

	  public static double [] autorange( // USED in lwir (float input)
			  float [][][] iclt_data, //  [iQuad][ncol][i] - normally only [][2][] is non-null
			  double hard_cold,// matches data, DC (this.lwir_offset)  subtracted
			  double hard_hot, // matches data, DC (this.lwir_offset)  subtracted
			  double too_cold, // pixels per image
			  double too_hot,  // pixels per image
			  int num_bins) {
		  int ncol;
		  for (ncol = 0; ncol < iclt_data[0].length; ncol++) {
			  if (iclt_data[0][ncol] != null) break;
		  }
		  too_cold *= iclt_data.length;
		  too_hot *= iclt_data.length;
		  int [] hist = null;
		  for (int iQuad = 0; iQuad < iclt_data.length; iQuad++) if (iclt_data[iQuad] != null){
			  int [] this_hist = getLwirHistogram(
					  iclt_data[iQuad][ncol], // double [] data,
					  hard_cold,
					  hard_hot,
					  num_bins);
			  if (hist == null) {
				  hist = this_hist;
			  } else {
				  addHist(
						  hist,
						  this_hist);
			  }
		  }
		  double [] rel_lim = {
				  getMarginFromHist(
						  hist, // histogram
						  too_cold, // double cumul_val, // cumulative number of items to be ignored
						  false), // boolean high_marg)
				  getMarginFromHist(
						  hist, // histogram
						  too_hot, // double cumul_val, // cumulative number of items to be ignored
						  true)}; // boolean high_marg)
		  double [] abs_lim = {
				  rel_lim[0] * (hard_hot - hard_cold) + hard_cold,
				  rel_lim[1] * (hard_hot - hard_cold) + hard_cold,
		  };
		  return abs_lim;
	  }

	  /**
	   * Collectively autorange LWIR texture with alpha(0.0...1.0) - multiply by it
	   * @param textures  multiple texture slices - use all [nslices][nchn:2/4][pixel]
	   * @param hard_cold matches data, DC (this.lwir_offset)  subtracted
	   * @param hard_hot  matches data, DC (this.lwir_offset)  subtracted
	   * @param too_cold  total pixels number
	   * @param too_hot   total pixels number
	   * @param num_bins  number of the histogram bins
	   * @return
	   */
	  public static double [] autorangeTextures( // USED in lwir using double
			  double [][][] textures, //  [nslices][nchn][i]
			  double hard_cold,// matches data, DC (this.lwir_offset)  subtracted
			  double hard_hot, // matches data, DC (this.lwir_offset)  subtracted
			  double too_cold, // pixels per image
			  double too_hot,  // pixels per image
			  int num_bins) {
		  double [] hist = null;
		  for (int nslice = 0; nslice < textures.length; nslice++) {
			  double [] this_hist = getLwirHistogramSliceAlpha(
					  textures[nslice], // double [][][] data,
					  hard_cold,
					  hard_hot,
					  num_bins);
			  if (hist == null) {
				  hist = this_hist;
			  } else {
				  addHist(
						  hist,
						  this_hist);
			  }
		  }
		  double [] rel_lim = {
				  getMarginFromHist(
						  hist, // histogram
						  too_cold, // double cumul_val, // cumulative number of items to be ignored
						  false), // boolean high_marg)
				  getMarginFromHist(
						  hist, // histogram
						  too_hot, // double cumul_val, // cumulative number of items to be ignored
						  true)}; // boolean high_marg)
		  double [] abs_lim = {
				  rel_lim[0] * (hard_hot - hard_cold) + hard_cold,
				  rel_lim[1] * (hard_hot - hard_cold) + hard_cold,
		  };
		  return abs_lim;
	  }
	  
	  public static double [] getMinMaxTextures(
			  double [][][] textures //  [nslices][nchn][i]
			  ) {
		  if ((textures == null) || (textures.length==0)) {
			  throw new IllegalArgumentException ("getMinMaxTextures(): Empty textures");
		  }
		  final boolean is_mono = textures[0].length <= 2;
		  final int alpha_chn = is_mono? 1 : 3;
		  final boolean has_alpha = textures[0].length > alpha_chn;
		  final double alpha_min = 0.9; // only consider pixels with higher alpha
		  final int num_um_chn = alpha_chn * textures.length; // number of images to UM
		  final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
		  final AtomicInteger ai =  new AtomicInteger(0);
		  final AtomicInteger ati = new AtomicInteger(0);
		  final double [][] tminmax = new double [threads.length][];
		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  public void run() {
					  int thread_num = ati.getAndIncrement();
					  tminmax[thread_num] = new double [] {Double.NaN,Double.NaN};
					  for (int nimg = ai.getAndIncrement(); nimg < num_um_chn; nimg = ai.getAndIncrement()) {
						  int nslice = nimg / alpha_chn;
						  int nchn = nimg % alpha_chn;
						  double [] alpha = has_alpha ? textures[nslice][alpha_chn] : null;
						  double [] texture = textures[nslice][nchn];
						  for (int i = 0; i < texture.length; i++) if (!has_alpha || (alpha[i] > alpha_min)) {
							  if (!(texture[i] >= tminmax[thread_num][0])) {
								  tminmax[thread_num][0] =  texture[i];
							  }
							  if (!(texture[i] <= tminmax[thread_num][1])) {
								  tminmax[thread_num][1] =  texture[i];
							  }
						  }
					  }
				  }
			  };
		  }		      
		  ImageDtt.startAndJoin(threads);
		  double [] minmax =  {Double.NaN,Double.NaN};
		  for (int i = 0; i < tminmax.length; i++) if ((tminmax[i] != null) && !Double.isNaN(tminmax[i][0]) &&!Double.isNaN(tminmax[i][1])) {
			  if (!(tminmax[i][0] >= minmax[0])) {
				  minmax[0] = tminmax[i][0];
			  }
			  if (!(tminmax[i][1] <= minmax[1])) {
				  minmax[1] = tminmax[i][1];
			  }
		  }
		  return minmax;
	  }
	  
	  public static void umTextures(
			  final double [][][] textures, //  [nslices][nchn][i]
			  final int    width,
			  final boolean ignore_alpha,
			  final double um_sigma,
			  final double um_weight){
		  if ((textures == null) || (textures.length==0)) {
			  throw new IllegalArgumentException ("umTextures(): Empty textures");
		  }
		  final int height = textures[0][0].length / width;
		  final boolean is_mono = textures[0].length <= 2;
		  final int alpha_chn = is_mono? 1 : 3;
		  final boolean has_alpha = textures[0].length > alpha_chn;
		  final int num_um_chn = alpha_chn * textures.length; // number of images to UM
		  final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
		  final AtomicInteger ai = new AtomicInteger(0);
		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  public void run() {
					  double [] texture_orig = new double [width * height];
					  for (int nimg = ai.getAndIncrement(); nimg < num_um_chn; nimg = ai.getAndIncrement()) {
						  int nslice = nimg / alpha_chn;
						  int nchn = nimg % alpha_chn; 
						  double [] texture = textures[nslice][nchn];
						  double [] texture_alpha = has_alpha? textures[nslice][alpha_chn]:null;
						  System.arraycopy(texture, 0, texture_orig,0,texture.length);
						  (new DoubleGaussianBlur()).blurDouble(
								  texture,       // FloatProcessor ip,
								  width,
								  height,
								  um_sigma,              // double sigmaX,
								  um_sigma,              // double sigmaY,
								  0.01);                 // double accuracy)
						  for (int i = 0; i < texture.length; i++) {
							  texture[i] = texture_orig[i] - um_weight * texture[i];
						  }
						  if (has_alpha && !ignore_alpha) {
							  for (int i = 0; i < texture.length; i++) if (texture_alpha[i] <= 0.0){
								  texture[i] = 0.0;
							  }
						  }
					  }
				  }
			  };
		  }		      
		  ImageDtt.startAndJoin(threads);
	  }

	  public static double [] getHistogramNormalization(
			  double [][][] textures, //  [nslices][nchn][i]
			  double [] minmax,
			  int       num_bins,
			  int       num_nodes,
			  double    hist_normalize_amount // 1.0 - full
			  ) {
		  double [] hist = null;
		  for (int nslice = 0; nslice < textures.length; nslice++) {
			  double [] this_hist = getLwirHistogramSliceAlpha(
					  textures[nslice], // double [][][] data,
					  minmax[0],
					  minmax[1],
					  num_bins);
			  if (hist == null) {
				  hist = this_hist;
			  } else {
				  addHist(
						  hist,
						  this_hist);
			  }
		  }
		  for (int i = 1; i < hist.length; i++) {
			  hist[i] += hist[i - 1];  
		  }
		  if (hist_normalize_amount < 1.0) {
			  double full = hist[hist.length-1];
			  for (int i = 0; i < hist.length; i++) {
				  hist[i] = hist_normalize_amount * hist[i] + i* (1.0-hist_normalize_amount) * full / (hist.length-1);  
			  }
		  }
		  double [] norm_table = new double  [num_nodes];
		  int indx = 0;
//		  norm_table[num_nodes - 1] = 1.0;
		  for (int n = 0; n < num_nodes; n++) {
			  double thresh =  hist[hist.length - 1] * (n + 1) /(num_nodes + 1);
			  if (thresh > hist[hist.length - 1]) { // full histogram
				  thresh = hist[hist.length - 1];
			  }
			  for (; ((indx < hist.length) && (hist[indx] <= thresh)); indx++);
			  double hist_prev = (indx == 0) ? 0.0: hist[indx-1];
			  if (indx >= hist.length) {
				  norm_table[n] = 1.0; // not normal?
			  } else {
				  norm_table[n] = (1.0 * indx)/hist.length + 
						  (thresh - hist_prev)/(hist[indx] - hist_prev)/hist.length;
			  }
		  }
		  return norm_table;
	  }
	  
	  /**
	   * Calculate long inverted table from short normalization one for fast application
	   * @param direct_table
	   * @param num_bins
	   * @return
	   */
	  public static double [] invertHistogramNormalization(
			  double [] direct_table, // last is <1.0, first > 0
			  int       num_bins) {
		  int       num_nodes = direct_table.length;
		  double out_scale = num_bins - 1; // 1023, last is 1.0
		  double out_step = 1.0 / out_scale;
		  double [] inverted_table = new double [num_bins];
		  int i_last = -1;
		  double inv_step = 1.0/(direct_table.length+1);
		  for (int indx = 0; indx <= direct_table.length; indx++) {
			  double frac_l = (indx > 0) ? direct_table[indx-1] : 0.0;
			  double frac_h = (indx == direct_table.length) ? 1.0 : direct_table[indx]; 
			  int i_l = (int) Math.floor(frac_l*out_scale);
			  int i_h = (int) Math.ceil (frac_h*out_scale);
			  if (i_h > num_bins)  i_h = num_bins;
			  if (i_l <= i_last)    i_l++;
			  i_last = i_h - 1;
			  double out_range = frac_h - frac_l;
			  if (out_range <= 0) {
				  continue; // may happen at the beginning/end?
			  }
			  double inv_l = inv_step * indx;
			  for (int i = i_l; i < i_h; i++) {
				double out_diff = i * out_step - frac_l;
				inverted_table[i] = inv_l + out_diff * inv_step / out_range;
			  }
		  }
		  inverted_table[inverted_table.length-1] = 1.0;
		  return inverted_table;
	  }
	 
	  public static void applyTexturesNormHist(
			  final boolean       ignore_alpha,
			  final double [][][] textures, //  [nslices][nchn][i]
			  final double []     min_max,
			  final double []     inv_table)
	  {
		  final double range = min_max[1] - min_max[0];
		  final boolean is_mono = textures[0].length <= 2;
		  final int alpha_chn = is_mono? 1 : 3;
		  final boolean has_alpha = textures[0].length > alpha_chn;
		  final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
		  final AtomicInteger ai = new AtomicInteger(0);
		  for (int nslice = 0; nslice < textures.length; nslice++) {
			  final double [] texture_alpha = (has_alpha) ? textures[nslice][alpha_chn] : null;
			  for (int nchn = 0; nchn < alpha_chn; nchn ++) {
				  final double [] texture = textures[nslice][nchn];
				  for (int ithread = 0; ithread < threads.length; ithread++) {
					  threads[ithread] = new Thread() {
						  public void run() {
							  for (int npix = ai.getAndIncrement(); npix < texture.length; npix = ai.getAndIncrement()) {
								  if (!Double.isNaN(texture[npix]) && (ignore_alpha || !has_alpha || (texture_alpha[npix] > 0.0))) {
									  double rel_in = (texture[npix] - min_max[0]) / range;
									  if (rel_in < 0.0) {
										  rel_in = 0.0;
									  } else if (rel_in > 1.0) {
										  rel_in = 1.0;
									  }
									  // interpolate by table
									  double srel_in = rel_in * inv_table.length;
									  int il = (int) Math.floor(srel_in);
									  if (il > (inv_table.length-1)) il = inv_table.length -1;
									  double dl = inv_table[il];
									  double dh = (il < (inv_table.length-1)) ? inv_table[il+1] : 1.0;
									  texture[npix] = min_max[0]+ (dl + (srel_in - il) * (dh-dl))*range; 
								  }
							  }
						  }
					  };
				  }		      
				  ImageDtt.startAndJoin(threads);
				  ai.set(0);
			  }
		  }
		  return;
	  }
	  
	  // float, for GPU
	  public ImagePlus linearStackToColor( // not used in lwir
			  CLTParameters         clt_parameters,
			  ColorProcParameters   colorProcParameters,
			  EyesisCorrectionParameters.RGBParameters         rgbParameters,
			  String name,
			  String suffix, // such as disparity=...
			  boolean toRGB,
			  boolean bpp16, // 16-bit per channel color mode for result
			  boolean saveShowIntermediate, // save/show if set globally
			  boolean saveShowFinal,        // save/show result (color image?)
			  float [][] iclt_data,
			  int width, // int tilesX,
			  int height, // int tilesY,
			  double scaleExposure,
			  int debugLevel
			  )
	  {
		  // convert to ImageStack of 3 slices
		  String [] sliceNames = isMonochrome()? new String[]{"mono"}: new String[]{"red", "blue", "green"};
		  int main_color_index = isMonochrome()? 0 : 2;
//		  int green_index = 2;
		  float [][] rbg_in  = isMonochrome()?
				  new float [][] {iclt_data[0]} :
					  new float[][] {iclt_data[0],iclt_data[1],iclt_data[2]};
/*					  
		  if (iclt_data.length >= 3) {
			  rbg_in = new float [][] {iclt_data[0],iclt_data[1],iclt_data[2]}; // RBG or LWIR CPU
		  } else {
			  rbg_in = new float [][] {iclt_data[0],iclt_data[0],iclt_data[0]}; // after LWIR/GPU
			  green_index = 0;
		  }
*/		  
		  float []   alpha_pixels = null; // (0..1.0)
		  if (iclt_data.length > rbg_in.length) {
			  alpha_pixels = iclt_data[rbg_in.length];
		  }
		  if (isLwir()) {
			  if (!toRGB) { // Double [][] uses 
///			  if (!colorProcParameters.lwir_pseudocolor) {
				  ImagePlus imp;
				  if (alpha_pixels != null) {
					  String [] titles = {"Y","alpha"};
					  ImageStack stack = ShowDoubleFloatArrays.makeStack(
							  new float[][] {rbg_in[0],alpha_pixels},       // iclt_data,
							  width,      // (tilesX + 0) * clt_parameters.transform_size,
							  height,     // (tilesY + 0) * clt_parameters.transform_size,
							  titles,     // or use null to get chn-nn slice names
							  true);      // replace NaN with 0.0
					  imp =  new ImagePlus(name+suffix, stack);
					  imp.getProcessor().resetMinAndMax();
				  } else {
					  ImageProcessor ip= new FloatProcessor(width,height);
					  ip.setPixels(rbg_in[0]);
					  ip.resetMinAndMax();
					  imp =  new ImagePlus(name+suffix, ip);
				  }
				  return imp;
			  }
			  String [] rgb_titles =  {"red","green","blue"};
			  String [] rgba_titles = {"red","green","blue","alpha"};
			  String [] titles = (alpha_pixels == null) ? rgb_titles : rgba_titles;
			  int num_slices = (alpha_pixels == null) ? 3 : 4;
			  double mn = colorProcParameters.lwir_low;
			  double mx = colorProcParameters.lwir_high;
			  double [] cold_hot = getColdHot();
			  if (cold_hot != null) {
				  mn = cold_hot[0];
				  mx = cold_hot[1];
			  }

			  double offset = getLwirOffset();
			  if (!Double.isNaN(offset)) {
				  mn -=  offset;
				  mx -=  offset;
			  }

			  ThermalColor tc = new ThermalColor(
					  colorProcParameters.lwir_palette,
					  mn,
					  mx,
					  255.0);
			  float [][] rgba = new float [num_slices][];
			  for (int i = 0; i < 3; i++) rgba[i] = new float [iclt_data[main_color_index].length];
			  for (int i = 0; i < rbg_in[main_color_index].length; i++) {
//				  if (i == 700) {
//					  System.out.println("linearStackToColor(): i="+i);
//				  }
				  float [] rgb = tc.getRGB(iclt_data[main_color_index][i]);
				  rgba[0][i] = rgb[0]; // red
				  rgba[1][i] = rgb[1]; // green
				  rgba[2][i] = rgb[2]; // blue
			  }
			  if (alpha_pixels != null) {
				  rgba[3] = alpha_pixels; // 0..1
			  }
			  ImageStack stack = ShowDoubleFloatArrays.makeStack(
					  rgba,       // iclt_data,
					  width,      // (tilesX + 0) * clt_parameters.transform_size,
					  height,     // (tilesY + 0) * clt_parameters.transform_size,
					  titles,     // or use null to get chn-nn slice names
					  true);      // replace NaN with 0.0
			  ImagePlus imp_rgba =  EyesisCorrections.convertRGBAFloatToRGBA32(
					  stack,   // ImageStack stackFloat, //r,g,b,a
					  //						name+"ARGB"+suffix, // String title,
					  name+suffix, // String title,
					  0.0,   // double r_min,
					  255.0, // double r_max,
					  0.0,   // double g_min,
					  255.0, // double g_max,
					  0.0,   // double b_min,
					  255.0, // double b_max,
					  0.0,   // double alpha_min,
					  1.0);  // double alpha_max)
			  return imp_rgba;
		  }

		  ImageStack stack = ShowDoubleFloatArrays.makeStack(
//				  rgb_in, // iclt_data,
				  rbg_in, // iclt_data,
				  width,  // (tilesX + 0) * clt_parameters.transform_size,
				  height, // (tilesY + 0) * clt_parameters.transform_size,
				  sliceNames,  // or use null to get chn-nn slice names
				  true); // replace NaN with 0.0
		  return linearStackToColor(
				  clt_parameters, // EyesisCorrectionParameters.CLTParameters         clt_parameters,
				  colorProcParameters, // EyesisCorrectionParameters.ColorProcParameters   colorProcParameters,
				  rgbParameters, // EyesisCorrectionParameters.RGBParameters         rgbParameters,
				  name,   // String name,
				  suffix, // String suffix, // such as disparity=...
				  toRGB, // boolean toRGB,
				  bpp16, // boolean bpp16, // 16-bit per channel color mode for result
				  saveShowIntermediate, // boolean saveShowIntermediate, // save/show if set globally
				  saveShowFinal, // boolean saveShowFinal,        // save/show result (color image?)
				  stack, // ImageStack stack,
				  alpha_pixels, // float [] alpha_pixels,
				  width, // int width, // int tilesX,
				  height, // int height, // int tilesY,
				  scaleExposure, // double scaleExposure,
				  debugLevel); //int debugLevel
	  }
	  // double data


	  public ImagePlus linearStackToColor( // USED in lwir
			  CLTParameters         clt_parameters,
			  ColorProcParameters   colorProcParameters,
			  EyesisCorrectionParameters.RGBParameters         rgbParameters,
			  String name,
			  String suffix, // such as disparity=...
			  boolean toRGB,
			  boolean bpp16, // 16-bit per channel color mode for result
			  boolean saveShowIntermediate, // save/show if set globally
			  boolean saveShowFinal,        // save/show result (color image?)
			  double [][] iclt_data,
			  int width, // int tilesX,
			  int height, // int tilesY,
			  double scaleExposure,
			  int debugLevel
			  )
	  {
		  // convert to ImageStack of 3 slices
		  String [] sliceNames = isMonochrome()? new String[]{"mono"}: new String[]{"red", "blue", "green"};
		  int main_color_index = isMonochrome()? 0 : 2;
		  double []   alpha = null; // (0..1.0)
		  double [][] rbg_in = isMonochrome()?
				  new double [][] {iclt_data[0]} :
					  new double[][] {iclt_data[0],iclt_data[1],iclt_data[2]};
		  float [] alpha_pixels = null;
		  if (iclt_data.length > rbg_in.length) {
			  alpha = iclt_data[rbg_in.length]; // last slice
			  if (alpha != null){
				  alpha_pixels = new float [alpha.length];
				  for (int i = 0; i <alpha.length; i++){
					  alpha_pixels[i] = (float) alpha[i];
				  }
			  }
		  }
		  if (isLwir()) {
			  if (!colorProcParameters.lwir_pseudocolor) {
				  float [] pixels = new float [iclt_data[0].length];
				  for (int i = 0; i < pixels.length; i++) {
					  pixels[i] = (float) iclt_data[0][i];
				  }
				  ImagePlus imp;
				  if (alpha_pixels != null) {
					  String [] titles = {"Y","alpha"};
					  ImageStack stack = ShowDoubleFloatArrays.makeStack(
							  new float[][] {pixels,alpha_pixels},       // iclt_data,
							  width,      // (tilesX + 0) * clt_parameters.transform_size,
							  height,     // (tilesY + 0) * clt_parameters.transform_size,
							  titles,     // or use null to get chn-nn slice names
							  true);      // replace NaN with 0.0
					  imp =  new ImagePlus(name+suffix, stack);
					  imp.getProcessor().resetMinAndMax();
				  } else {
					  ImageProcessor ip= new FloatProcessor(width,height);
					  ip.setPixels(pixels);
					  ip.resetMinAndMax();
					  imp =  new ImagePlus(name+suffix, ip);
				  }
				  return imp;
			  }
			  String [] rgb_titles =  {"red","green","blue"};
			  String [] rgba_titles = {"red","green","blue","alpha"};
			  String [] titles = (alpha == null) ? rgb_titles : rgba_titles;
			  int num_slices = (alpha == null) ? 3 : 4;
			  double mn = colorProcParameters.lwir_low;
			  double mx = colorProcParameters.lwir_high;
			  double [] cold_hot = getColdHot();
			  if (cold_hot != null) {
				  mn = cold_hot[0];
				  mx = cold_hot[1];
			  }

			  double offset = getLwirOffset();
			  if (!Double.isNaN(offset)) {
				  mn -=  offset;
				  mx -=  offset;
			  }

			  ThermalColor tc = new ThermalColor(
					  colorProcParameters.lwir_palette,
					  mn,
					  mx,
					  255.0);
			  double [][] rgba = new double [num_slices][];
			  for (int i = 0; i < 3; i++) rgba[i] = new double [iclt_data[main_color_index].length];
			  for (int i = 0; i < rbg_in[main_color_index].length; i++) {
				  double [] rgb = tc.getRGB(iclt_data[main_color_index][i]);
				  rgba[0][i] = rgb[0]; // red
				  rgba[1][i] = rgb[1]; // green
				  rgba[2][i] = rgb[2]; // blue
			  }
			  if (alpha != null) {
				  rgba[3] = alpha; // 0..1
			  }
			  ImageStack stack = ShowDoubleFloatArrays.makeStack(
					  rgba,       // iclt_data,
					  width,      // (tilesX + 0) * clt_parameters.transform_size,
					  height,     // (tilesY + 0) * clt_parameters.transform_size,
					  titles,     // or use null to get chn-nn slice names
					  true);      // replace NaN with 0.0
			  ImagePlus imp_rgba =  EyesisCorrections.convertRGBAFloatToRGBA32(
					  stack,   // ImageStack stackFloat, //r,g,b,a
					  //						name+"ARGB"+suffix, // String title,
					  name+suffix, // String title,
					  0.0,   // double r_min,
					  255.0, // double r_max,
					  0.0,   // double g_min,
					  255.0, // double g_max,
					  0.0,   // double b_min,
					  255.0, // double b_max,
					  0.0,   // double alpha_min,
					  1.0);  // double alpha_max)
			  return imp_rgba;
		  }

		  ImageStack stack = ShowDoubleFloatArrays.makeStack(
				  rbg_in, // iclt_data,
				  width,  // (tilesX + 0) * clt_parameters.transform_size,
				  height, // (tilesY + 0) * clt_parameters.transform_size,
				  sliceNames,  // or use null to get chn-nn slice names
				  true); // replace NaN with 0.0

		  return linearStackToColor(
				  clt_parameters, // EyesisCorrectionParameters.CLTParameters         clt_parameters,
				  // gamma should be 1.0 for LWIR
				  colorProcParameters, // EyesisCorrectionParameters.ColorProcParameters   colorProcParameters,
				  rgbParameters, // EyesisCorrectionParameters.RGBParameters         rgbParameters,
				  name,   // String name,
				  suffix, // String suffix, // such as disparity=...
				  toRGB, // boolean toRGB,
				  bpp16, // boolean bpp16, // 16-bit per channel color mode for result
				  saveShowIntermediate, // boolean saveShowIntermediate, // save/show if set globally
				  saveShowFinal, // boolean saveShowFinal,        // save/show result (color image?)
				  stack, // ImageStack stack,
				  alpha_pixels, // float [] alpha_pixels,
				  width, // int width, // int tilesX,
				  height, // int height, // int tilesY,
				  scaleExposure, // double scaleExposure,
				  debugLevel); //int debugLevel
	  }

	  // float, for GPU
	  public static ImagePlus linearStackToColorLWIR(
			  CLTParameters  clt_parameters,
			  int            lwir_palette, // <0 - do not convert
			  double []      minmax,
			  String         name,
			  String         suffix, // such as disparity=...
			  boolean        toRGB,
			  double [][]    texture_data,
			  int            width, // int tilesX,
			  int            height, // int tilesY,
			  int            debugLevel )
	  {
		  final boolean is_mono = texture_data.length <= 2;
		  final int alpha_chn = is_mono? 1 : 3;
//		  final boolean has_alpha = texture_data.length > alpha_chn;
		  // convert to ImageStack of 3 slices
//		  String [] sliceNames = is_mono? new String[]{"mono"}: new String[]{"red", "blue", "green"};
		  int main_color_index = is_mono? 0 : 2;
		  double [][] rbg_in  = is_mono?  new double [][] {texture_data[0]} :  new double[][] {texture_data[0],texture_data[1],texture_data[2]};
		  double []   alpha_pixels = null; // (0..1.0)
		  if (texture_data.length > rbg_in.length) {
			  alpha_pixels = texture_data[rbg_in.length];
		  } else {
			  alpha_pixels = new double[texture_data[0].length];
			  for (int i = 0; i < alpha_pixels.length; i++) {
				  alpha_pixels[i] = Double.isNaN(rbg_in[0][i]) ? 0.0 : 1.0;
			  }
		  }
		  if (!toRGB) { // Double [][] uses 
			  ImagePlus imp;
			  if (alpha_pixels != null) {
				  String [] titles = {"Y","alpha"};
				  ImageStack stack = ShowDoubleFloatArrays.makeStack(
						  new double[][] {rbg_in[0],alpha_pixels},       // iclt_data,
						  width,      // (tilesX + 0) * clt_parameters.transform_size,
						  height,     // (tilesY + 0) * clt_parameters.transform_size,
						  titles,     // or use null to get chn-nn slice names
						  true);      // replace NaN with 0.0
				  imp =  new ImagePlus(name+suffix, stack);
				  imp.getProcessor().resetMinAndMax();
			  } else {
				  ImageProcessor ip= new FloatProcessor(width,height);
				  ip.setPixels(rbg_in[0]);
				  ip.resetMinAndMax();
				  imp =  new ImagePlus(name+suffix, ip);
			  }
			  return imp;
		  }
		  String [] rgb_titles =  {"red","green","blue"};
		  String [] rgba_titles = {"red","green","blue","alpha"};
		  String [] titles = (alpha_pixels == null) ? rgb_titles : rgba_titles;
		  int num_slices = (alpha_pixels == null) ? 3 : 4;
		  if (minmax == null) {
			  minmax = new double [] {Double.NaN, Double.NaN};
			  for (int i = 0; i < rbg_in[0].length; i++) if (!Double.isNaN(rbg_in[0][i])){
				  if (!(rbg_in[0][i] >= minmax[0])) minmax[0] = rbg_in[0][i];
				  if (!(rbg_in[0][i] <= minmax[1])) minmax[1] = rbg_in[0][i];
			  }			  
		  }
		  double mn = minmax[0]; // colorProcParameters.lwir_low;
		  double mx = minmax[1]; // colorProcParameters.lwir_high;
		  ThermalColor tc = new ThermalColor(
				  lwir_palette, // colorProcParameters.lwir_palette,
				  mn,
				  mx,
				  255.0);
		  double [][] rgba = new double [num_slices][];
		  for (int i = 0; i < 3; i++) rgba[i] = new double [texture_data[main_color_index].length];
		  for (int i = 0; i < rbg_in[main_color_index].length; i++) {
			  if (i==160000) {
				  System.out.println ("i="+i);
			  }
			  double [] rgb = tc.getRGB(texture_data[main_color_index][i]);
			  rgba[0][i] = rgb[0]; // red
			  rgba[1][i] = rgb[1]; // green
			  rgba[2][i] = rgb[2]; // blue
		  }
		  if (alpha_pixels != null) {
			  rgba[3] = alpha_pixels; // 0..1
		  }
		  ImageStack stack = ShowDoubleFloatArrays.makeStack(
				  rgba,       // iclt_data,
				  width,      // (tilesX + 0) * clt_parameters.transform_size,
				  height,     // (tilesY + 0) * clt_parameters.transform_size,
				  titles,     // or use null to get chn-nn slice names
				  true);      // replace NaN with 0.0
		  ImagePlus imp_rgba =  EyesisCorrections.convertRGBAFloatToRGBA32(
				  stack,   // ImageStack stackFloat, //r,g,b,a
				  //						name+"ARGB"+suffix, // String title,
				  name+suffix, // String title,
				  0.0,   // double r_min,
				  255.0, // double r_max,
				  0.0,   // double g_min,
				  255.0, // double g_max,
				  0.0,   // double b_min,
				  255.0, // double b_max,
				  0.0,   // double alpha_min,
				  1.0);  // double alpha_max)
		  return imp_rgba;
	  }


	  
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  


	  // Convert a single value pixels to color (r,b,g) values to be processed instead of the normal colors


	  public ImagePlus linearStackToColor(
			  CLTParameters         clt_parameters,
			  ColorProcParameters   colorProcParameters,
			  EyesisCorrectionParameters.RGBParameters         rgbParameters,
			  String name,
			  String suffix, // such as disparity=...
			  boolean toRGB,
			  boolean bpp16, // 16-bit per channel color mode for result
			  boolean saveShowIntermediate, // save/show if set globally
			  boolean saveShowFinal,        // save/show result (color image?)
			  ImageStack stack,
			  float [] alpha_pixels,
			  int width, // int tilesX,
			  int height, // int tilesY,
			  double scaleExposure,
			  int debugLevel
			  )
	  {
		  if (debugLevel > -1) { // 0){
			  double [] chn_avg = {0.0,0.0,0.0};
			  float [] pixels;
			  for (int c = 0; c <3; c++){
				  pixels = (float[]) stack.getPixels(c+1);
				  for (int i = 0; i<pixels.length; i++){
					  chn_avg[c] += pixels[i];
				  }
			  }
			  chn_avg[0] /= width*height;
			  chn_avg[1] /= width*height;
			  chn_avg[2] /= width*height;
			  System.out.println("Processed channels averages: R="+chn_avg[0]+", G="+chn_avg[2]+", B="+chn_avg[1]);
		  }

		  if (debugLevel > 1) System.out.println("before colors.1");
		  //Processing colors - changing stack sequence to r-g-b (was r-b-g)
		  if (!eyesisCorrections.fixSliceSequence(
				  stack,
				  debugLevel)){
			  if (debugLevel > -1) System.out.println("fixSliceSequence() returned false");
			  return null;// not used in lwir
		  }
		  if (debugLevel > 1) System.out.println("before colors.2");
		  if (saveShowIntermediate && (debugLevel > 1)){
			  ImagePlus imp_dbg=new ImagePlus(name+sAux()+"-preColors",stack);
			  eyesisCorrections.saveAndShow(
					  imp_dbg,
					  this.correctionsParameters);
		  }
		  if (debugLevel > -1) System.out.println("before colors.3, scaleExposure="+scaleExposure+" scale = "+(255.0/eyesisCorrections.psfSubpixelShouldBe4/eyesisCorrections.psfSubpixelShouldBe4/scaleExposure));
		  CorrectionColorProc correctionColorProc=new CorrectionColorProc(eyesisCorrections.stopRequested);
		  double [][] yPrPb=new double [3][];
		  //			if (dct_parameters.color_DCT){
		  // need to get YPbPr - not RGB here
		  //			} else {
		  correctionColorProc.processColorsWeights(stack, // just gamma convert? TODO: Cleanup? Convert directly form the linear YPrPb
				  //					  255.0/this.psfSubpixelShouldBe4/this.psfSubpixelShouldBe4, //  double scale,     // initial maximal pixel value (16))
				  //					  255.0/eyesisCorrections.psfSubpixelShouldBe4/eyesisCorrections.psfSubpixelShouldBe4/scaleExposure, //  double scale,     // initial maximal pixel value (16))
				  //					  255.0/2/2/scaleExposure, //  double scale,     // initial maximal pixel value (16))
				  255.0/scaleExposure, //  double scale,     // initial maximal pixel value (16))
				  colorProcParameters,
				  null, // channelGainParameters,
				  -1, // channel,
				  null, //correctionDenoise.getDenoiseMask(),
				  this.correctionsParameters.blueProc,
				  debugLevel);
		  if (debugLevel > 1) System.out.println("Processed colors to YPbPr, total number of slices="+stack.getSize());
		  if (saveShowIntermediate && (stack != null) && (debugLevel > 1)) {
			  ImagePlus imp_dbg=new ImagePlus("procColors",stack);  // null
			  eyesisCorrections.saveAndShow(
					  imp_dbg,
					  this.correctionsParameters);
		  }
		  float [] fpixels;
		  int [] slices_YPrPb = {8,6,7};
		  yPrPb=new double [3][];
		  for (int n = 0; n < slices_YPrPb.length; n++){
			  fpixels = (float[]) stack.getPixels(slices_YPrPb[n]);
			  yPrPb[n] = new double [fpixels.length];
			  for (int i = 0; i < fpixels.length; i++) yPrPb[n][i] = fpixels[i];
		  }
		  String titleFull = "";
		  if (toRGB) {
			  if (debugLevel > 0){
				  System.out.println("correctionColorProc.YPrPbToRGB");
			  }
			  stack =  YPrPbToRGB(yPrPb,
					  colorProcParameters.kr,        // 0.299;
					  colorProcParameters.kb,        // 0.114;
					  stack.getWidth());
			  titleFull=name+sAux()+"-RGB-float"+suffix;
			  //Trim stack to just first 3 slices
			  if (saveShowIntermediate && (debugLevel > 1)){ // 2){
				  ImagePlus imp_dbg=new ImagePlus("YPrPbToRGB",stack);
				  eyesisCorrections.saveAndShow(
						  imp_dbg,
						  this.correctionsParameters);
			  }
			  while (stack.getSize() > 3) stack.deleteLastSlice();
			  if (debugLevel > 1) System.out.println("Trimming color stack");
		  } else {// not used in lwir
			  titleFull=name+sAux()+"-YPrPb"+suffix;
			  if (debugLevel > 1) System.out.println("Using full stack, including YPbPr");
		  }

		  if (alpha_pixels != null){
			  stack.addSlice("alpha",alpha_pixels);
		  }

		  ImagePlus result= new ImagePlus(titleFull, stack);
		  if (debugLevel> 1){
			  result.show();
		  }

		  if (!toRGB && !this.correctionsParameters.jpeg){ // toRGB set for equirectangular// not used in lwir
			  if (debugLevel > 1) System.out.println("!toRGB && !this.correctionsParameters.jpeg");
			  if (saveShowIntermediate) eyesisCorrections.saveAndShow(result, this.correctionsParameters);
			  return result;
		  } else { // that's not the end result, save if required
			  if (debugLevel > 1) System.out.println("!toRGB && !this.correctionsParameters.jpeg - else");


			  if (saveShowIntermediate) eyesisCorrections.saveAndShow(result, // saves OK with alpha - 32-bit float
					  eyesisCorrections.correctionsParameters,
					  eyesisCorrections.correctionsParameters.save32,
					  false, // true, // false,
					  eyesisCorrections.correctionsParameters.JPEG_quality); // save, no show
		  }
		  stack=eyesisCorrections.convertRGB32toRGB16Stack(
				  stack,
				  rgbParameters);

		  titleFull=name+sAux()+"-RGB48"+suffix;
		  result= new ImagePlus(titleFull, stack);
		  result.updateAndDraw();
		  if (debugLevel > 1) {
//		  if (debugLevel > -1) {
			  System.out.println("result.updateAndDraw(), "+titleFull+"-RGB48");
			  result.show();
		  }

		  CompositeImage compositeImage=eyesisCorrections.convertToComposite(result);

		  if (!this.correctionsParameters.jpeg && bpp16){ // RGB48 was the end result // not used in lwir
			  if (debugLevel > 1) System.out.println("if (!this.correctionsParameters.jpeg && !advanced)");
			  if (saveShowIntermediate) eyesisCorrections.saveAndShow(compositeImage, this.correctionsParameters);
			  return compositeImage; // return result;
		  } else { // that's not the end result, save if required
			  if (debugLevel > 1) System.out.println("if (!this.correctionsParameters.jpeg && !advanced) - else");
			  if (saveShowIntermediate) eyesisCorrections.saveAndShow(compositeImage, this.correctionsParameters, this.correctionsParameters.save16, false); // save, no show
		  }
		  result = eyesisCorrections.convertRGB48toRGB24(
				  stack,
//				  name+"-RGB24"+suffix,
				  name+suffix,
				  0, 65536, // r range 0->0, 65536->256
				  0, 65536, // g range
				  0, 65536,// b range
				  0, 65536);// alpha range
		  // next will save either JPEG (if no alpha) or RGBA tiff (if alpha is present). ImageJ shows just RGB (no alpha)
		  if (saveShowFinal) eyesisCorrections.saveAndShow(result, this.correctionsParameters);
		  return result;

	  }



	  public void apply_fine_corr( // not used in lwir
			  double [][][] corr,
			  int debugLevel)
	  {
		  if (debugLevel > 1){
			  if (debugLevel > 1){
				  show_fine_corr( this.fine_corr, "  was");
			  }
		  }
		  if (corr==null) {
			  System.out.println("New correction is null (only non-null for poly, not for infinity");
			  return;
		  } else {
			  if (debugLevel > 1){
				  show_fine_corr(corr, "added");
			  }
			  for (int n = 0; n< corr.length; n++){
				  for (int i = 0; i< corr[n].length; i++){
					  for (int j = 0; j< corr[n][i].length; j++){
						  this.fine_corr[n][i][j]+=corr[n][i][j];
					  }
				  }
			  }
			  if (debugLevel > 0){
				  show_fine_corr( this.fine_corr, "");
			  }
		  }
	  }
	  public void show_fine_corr() // not used in lwir
	  {
		  show_fine_corr("");
	  }
	  public void show_fine_corr(String prefix) // not used in lwir
	  {
		  show_fine_corr( this.fine_corr, prefix);
	  }

	  public void show_fine_corr( // not used in lwir
			  double [][][] corr,
			  String prefix)
	  {
		  String sadd = (prefix.length() > 0)?(prefix+" "):"";

		  for (int n = 0; n< corr.length; n++){
			  for (int i = 0; i< corr[n].length; i++){
				  System.out.print(sadd+"port"+n+": "+fine_corr_dir_names[i]+": ");
				  for (int j = 0; j< corr[n][i].length; j++){
					  System.out.print(fine_corr_coeff_names[j]+"="+corr[n][i][j]+" ");
				  }
				  System.out.println();
			  }
		  }
		  System.out.println();
		  String name = (sadd.length() == 0)?"":("("+sadd+")");
		  showExtrinsicCorr(name);
	  }


	  public void reset_fine_corr() // not used in lwir
	  {
		  this.fine_corr = new double [4][2][6]; // reset all coefficients to 0
	  }

	  public void showExtrinsicCorr(String name) // not used in lwir
	  {
		  System.out.println("Extrinsic corrections "+name);
		  if (geometryCorrection == null){
			  System.out.println("are not set, will be:");
			  System.out.println(new GeometryCorrection(this.extrinsic_vect).getCorrVector().toString());
		  } else {
			  System.out.println(geometryCorrection.getCorrVector().toString());
		  }
	  }
	  
	  
	  public void showQuatCorr() {
		  showQuatCorr(getQuatCorr(),getENUCorrMetric());
	  }
	  public static void showQuatCorr(double [] quat_corr, double [] enu_corr_metric) {
		  if (quat_corr != null) {
				System.out.println("Correction quaternion = ["+quat_corr[0]+", "+
						quat_corr[1]+", "+quat_corr[2]+", "+quat_corr[3]+"]");
				double [] angles = Imx5.quatToCamAtr(quat_corr);
				System.out.println("Correction angles: "+
				(angles[0]*180/Math.PI)+"deg, "+
				(angles[1]*180/Math.PI)+"deg, "+
				(angles[2]*180/Math.PI)+"deg");
		  } else {
			  System.out.println("No IMS orientation correction is defined");
		  }
		  if (enu_corr_metric != null) {
				System.out.println("Correction to ENU (meters) = ["+enu_corr_metric[0]+", "+
						enu_corr_metric[1]+", "+enu_corr_metric[2]+"]");
		  } else {
			  System.out.println("No ENU correction is defined");
		  }
	  }
	  
	  public ImagePlus show_pXpYD(
			  double [][] pXpYD,
			  String      suffix,
			  boolean     show) {
		  if (suffix == null) {
			  suffix = "";
		  }
		  String [] titles = {"pX","pY","Disparity"};
			double [][] dbg_img = new double [titles.length][pXpYD.length];
			for (int i = 0; i < dbg_img.length; i++) {
				Arrays.fill(dbg_img[i], Double.NaN);
			}
			for (int nTile = 0; nTile < pXpYD.length; nTile++){
				if (pXpYD[nTile] != null) {
					for (int i = 0; i < pXpYD[nTile].length; i++) {
						dbg_img[i][nTile] = pXpYD[nTile][i];
					}
				}
			}
			ImagePlus imp = ShowDoubleFloatArrays.makeArrays(
					dbg_img,
					getTilesX(), // int width,
					getTilesY(), // int height,
					getImageName()+"-pXpYD"+suffix,
					titles);
			if (show) {
				imp.show();
			}
			return imp;
	  }

	  public static void showPimuOffsets(CLTParameters clt_parameters) {
		  showPimuOffsets( clt_parameters.imp.get_pimu_offs());
	  }

	  public static void showPimuOffsets(double [][] pimu_offsets) {
		  if (pimu_offsets != null) {
				System.out.println("Linear velocities scales (from 1.0) = ["+pimu_offsets[0][0]+", "+
						pimu_offsets[0][1]+", "+pimu_offsets[0][2]+"]");
				System.out.println("Angular velocities offsets (rad/s) =  ["+
						pimu_offsets[1][0]+", " + pimu_offsets[1][1]+", "+ + pimu_offsets[1][2]+"]");
		  } else {
			  System.out.println("No PIMU offsets are defined");
		  }
	  }
	  
	  
	  
	  public boolean editExtrinsicCorr() // not used in lwir
	  {
		  if (geometryCorrection == null){
			  System.out.println("are not set, will be:");
			  return new GeometryCorrection(this.extrinsic_vect).getCorrVector().editVector(); // editIMU();
		  } else {
			  return geometryCorrection.getCorrVector().editVector(); // .editIMU();
		  }
	  }



	  public boolean editRig() // not used in lwir
	  {
		  if (!is_aux) {
			  System.out.println("Rig offsets can only be edited for the auxiliary camera, not for the main one");
			  return false;
		  }
//		  GeometryCorrection gc = this.geometryCorrection;
		  if (this.geometryCorrection == null){
			  System.out.println("geometryCorrection is not set, creating one");
			  this.geometryCorrection = new GeometryCorrection(this.extrinsic_vect);
		  }
		  boolean edited = this.geometryCorrection.editRig();
//		  if (edited) {
//			  gc.rigOffset.setProperties(prefix,properties);
//		  }
		  return edited;
	  }
/*
		if (is_aux && (gc.rigOffset != null)) {
			gc.rigOffset.setProperties(prefix,properties);
		}

 */


	  public void resetExtrinsicCorr( // not used in lwir // only manual commands
			  CLTParameters           clt_parameters)
	  {
		  if (extrinsic_vect != null) {
			  for (int i = 0; i < extrinsic_vect.length; i++) {
				  extrinsic_vect [i] = 0.0;
			  }
			  /*
			  int imu_index = extrinsic_vect.getIMUIndex();
			  extrinsic_vect [CorrVector.IMU_INDEX + 0] = 0.0;
			  extrinsic_vect [CorrVector.IMU_INDEX + 1] = 0.0;
			  extrinsic_vect [CorrVector.IMU_INDEX + 2] = 0.0;
			  extrinsic_vect [CorrVector.IMU_INDEX + 3] = 0.0;
			  extrinsic_vect [CorrVector.IMU_INDEX + 4] = 0.0;
			  extrinsic_vect [CorrVector.IMU_INDEX + 5] = 0.0;
			  */
		  }
		  if (geometryCorrection != null){
			  geometryCorrection.resetCorrVectorERS();
		  }
		  if (clt_parameters.fine_corr_apply){
			  clt_parameters.fine_corr_ignore = false;
		  }
		  gpuResetCorrVector();
	  }
	  
	  public void process_infinity_corr( //from existing image // not used in lwir
			  CLTParameters clt_parameters,
			  int debugLevel
			  ) {
	        ImagePlus imp_src = WindowManager.getCurrentImage();
	        if (imp_src==null){
	            IJ.showMessage("Error","14*n-layer file with disparities/strengthspairs measured at infinity is required");
	            return;
	        }
	        ImageStack disp_strength_stack= imp_src.getStack();
		    final int tilesX = disp_strength_stack.getWidth(); // tp.getTilesX();
		    final int tilesY = disp_strength_stack.getHeight(); // tp.getTilesY();
		    final int nTiles =tilesX * tilesY;
		    final int num_scans =  disp_strength_stack.getSize()/AlignmentCorrection.NUM_ALL_SLICES;
		    final double [][] inf_disp_strength = new double [AlignmentCorrection.NUM_SLICES * num_scans][nTiles];
		    for (int n = 0; n <  disp_strength_stack.getSize(); n++){
	    		float [] fpixels = (float[]) disp_strength_stack.getPixels(n +1);
	    		for (int i = 0; i < nTiles; i++){
	    			inf_disp_strength[n][i] = fpixels[i];
	    		}
		    }
		    if (debugLevel > -1){
		    	System.out.println("process_infinity_corr(): proocessing "+num_scans+" disparity/strength pairs");
		    }
		    AlignmentCorrection ac = new AlignmentCorrection(this);
		    // includes both infinity correction and mismatch correction for the same infinity tiles

		    //FIXME: Here disparity now should be restored in dxy...


		    double [][][] new_corr = ac.infinityCorrection(
		    		clt_parameters.ly_poly,        // final boolean use_poly,
		    		clt_parameters.fcorr_inf_strength, //  final double min_strenth,
		    		clt_parameters.fcorr_inf_diff,     // final double max_diff,
		    		clt_parameters.inf_iters,          // 20, // 0, // final int max_iterations,
		    		clt_parameters.inf_final_diff,     // 0.0001, // final double max_coeff_diff,
		    		clt_parameters.inf_far_pull,       // 0.0, // 0.25, //   final double far_pull, //  = 0.2; // 1; //  0.5;
		    		clt_parameters.inf_str_pow,        // 1.0, //   final double     strength_pow,
		    		clt_parameters.inf_smpl_side,      // 3, //   final int        smplSide, //        = 2;      // Sample size (side of a square)
		    		clt_parameters.inf_smpl_num,       // 5, //   final int        smplNum,  //         = 3;      // Number after removing worst (should be >1)
		    		clt_parameters.inf_smpl_rms,       // 0.1, // 0.05, //  final double     smplRms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
					// histogram parameters
		    		clt_parameters.ih_smpl_step,       // 8,    // final int        hist_smpl_side, // 8 x8 masked, 16x16 sampled
		    		clt_parameters.ih_disp_min,        // -1.0, // final double     hist_disp_min,
		    		clt_parameters.ih_disp_step,       // 0.05, // final double     hist_disp_step,
		    		clt_parameters.ih_num_bins,        // 40,   // final int        hist_num_bins,
		    		clt_parameters.ih_sigma,           // 0.1,  // final double     hist_sigma,
		    		clt_parameters.ih_max_diff,        // 0.1,  // final double     hist_max_diff,
		    		clt_parameters.ih_min_samples,     // 10,   // final int        hist_min_samples,
		    		clt_parameters.ih_norm_center,     // true, // final boolean    hist_norm_center, // if there are more tiles that fit than min_samples, replace with
					clt_parameters,  // EyesisCorrectionParameters.CLTParameters           clt_parameters,
		    		inf_disp_strength,   // double [][] disp_strength,
		    		tilesX, // int         tilesX,
		    		clt_parameters.corr_magic_scale, // double      magic_coeff, // still not understood coefficent that reduces reported disparity value.  Seems to be around 8.5
		    		debugLevel + 1); // int debugLevel)




		    if (debugLevel > -1){
		    	System.out.println("process_infinity_corr(): ready to apply infinity correction");
		    	show_fine_corr(
		    			new_corr, // double [][][] corr,
		    			"");// String prefix)

		    }

		    if (debugLevel > -100){
		    	apply_fine_corr(
		    			new_corr,
		    			debugLevel + 2);
		    }
	  }


	  public void processLazyEye( // not used in lwir
			  boolean dry_run,
			  CLTParameters clt_parameters,
			  int debugLevel
			  ) {
	        ImagePlus imp_src = WindowManager.getCurrentImage();
	        if (imp_src==null){
	            IJ.showMessage("Error","2*n-layer file with disparities/strengthspairs measured at infinity is required");
	            return;
	        }
	        ImageStack disp_strength_stack= imp_src.getStack();
		    final int tilesX = disp_strength_stack.getWidth(); // tp.getTilesX();

		    AlignmentCorrection ac = new AlignmentCorrection(this);



			double [][] scans = ac.getDoubleFromImage(
					imp_src,
					debugLevel);
			double [][] disp_strength = ac.getFineCorrFromDoubleArray(
					scans,  // double [][] data,
					tilesX, // int         tilesX,
					debugLevel); // int debugLevel)

			int num_tiles = disp_strength[0].length;

		    double [][][] new_corr = ac.lazyEyeCorrection(
		    		clt_parameters.ly_poly,        // final boolean use_poly,
					true, // final boolean    restore_disp_inf, // Restore subtracted disparity for scan #0 (infinity)
				    clt_parameters.fcorr_radius,       // 	final double fcorr_radius,
		    		clt_parameters.fcorr_inf_strength, //  final double min_strenth,
		    		clt_parameters.fcorr_inf_diff,     // final double max_diff,
		    		clt_parameters.inf_iters,          // 20, // 0, // final int max_iterations,
		    		clt_parameters.inf_final_diff,     // 0.0001, // final double max_coeff_diff,
		    		clt_parameters.inf_far_pull,       // 0.0, // 0.25, //   final double far_pull, //  = 0.2; // 1; //  0.5;
		    		clt_parameters.inf_str_pow,        // 1.0, //   final double     strength_pow,
		    		0.8*clt_parameters.disp_scan_step, // clt_parameters.ly_meas_disp,       // 1.5, // final double     lazyEyeCompDiff, // clt_parameters.fcorr_disp_diff
					clt_parameters.ly_smpl_side,       // 3,   // final int        lazyEyeSmplSide, //        = 2;      // Sample size (side of a square)
					clt_parameters.ly_smpl_num,        // 5,   // final int        lazyEyeSmplNum, //         = 3;      // Number after removing worst (should be >1)
					clt_parameters.ly_smpl_rms,        // 0.1, // final double     lazyEyeSmplRms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
					clt_parameters.ly_disp_var,        // 0.2, // final double     lazyEyeDispVariation, // 0.2, maximal full disparity difference between tgh tile and 8 neighbors
					clt_parameters.ly_disp_rvar,       // 0.2, // final double     lazyEyeDispRelVariation, // 0.02 Maximal relative full disparity difference to 8 neighbors
					clt_parameters.ly_norm_disp,       //	final double     ly_norm_disp, //  =    5.0;     // Reduce weight of higher disparity tiles
		    		clt_parameters.inf_smpl_side,      // 3, //   final int        smplSide, //        = 2;      // Sample size (side of a square)
		    		clt_parameters.inf_smpl_num,       // 5, //   final int        smplNum,  //         = 3;      // Number after removing worst (should be >1)
		    		clt_parameters.inf_smpl_rms,       // 0.1, // 0.05, //  final double     smplRms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
					// histogram parameters
		    		clt_parameters.ih_smpl_step,       // 8,    // final int        hist_smpl_side, // 8 x8 masked, 16x16 sampled
		    		clt_parameters.ih_disp_min,        // -1.0, // final double     hist_disp_min,
		    		clt_parameters.ih_disp_step,       // 0.05, // final double     hist_disp_step,
		    		clt_parameters.ih_num_bins,        // 40,   // final int        hist_num_bins,
		    		clt_parameters.ih_sigma,           // 0.1,  // final double     hist_sigma,
		    		clt_parameters.ih_max_diff,        // 0.1,  // final double     hist_max_diff,
		    		clt_parameters.ih_min_samples,     // 10,   // final int        hist_min_samples,
		    		clt_parameters.ih_norm_center,     // true, // final boolean    hist_norm_center, // if there are more tiles that fit than min_samples, replace with
		    		clt_parameters.ly_inf_frac,        // 0.5, // final double     inf_fraction,    // fraction of the weight for the infinity tiles
		    		clt_parameters.getLyPerQuad(num_tiles), // final int        min_per_quadrant, // minimal tiles per quadrant (not counting the worst) tp proceed
		    		clt_parameters.getLyInf(num_tiles),	    // final int        min_inf,          // minimal number of tiles at infinity to proceed
		    		clt_parameters.getLyInfScale(num_tiles),// final int        min_inf_to_scale, // minimal number of tiles at infinity to apply weight scaling
		    		clt_parameters.ly_right_left,      // false // equalize weights of right/left FoV (use with horizon in both halves and gross infinity correction)
					clt_parameters,  // EyesisCorrectionParameters.CLTParameters           clt_parameters,
					disp_strength, // scans,   // double [][] disp_strength,
					null,          // double [][]      target_disparity, // null or programmed disparity (1 per each 14 entries of scans_14)
		    		tilesX, // int         tilesX,
		    		clt_parameters.corr_magic_scale, // double      magic_coeff, // still not understood coefficent that reduces reported disparity value.  Seems to be around 8.5
		    		debugLevel + (clt_parameters.fine_dbg ? 1:0)); // int debugLevel)

		    if (!dry_run  && clt_parameters.ly_poly && (new_corr != null)){
				  apply_fine_corr(
						  new_corr,
						  debugLevel + 2);

		    }
	  }

	  public void showCLTPlanes( // not used in lwir
			  CLTParameters           clt_parameters,
			  final int          threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel)
	  {
		  this.startStepTime=System.nanoTime();

		  if (tp == null){
			  System.out.println("showCLTPlanes(): tp is null");
			  return;
		  }
		  if (tp.clt_3d_passes == null){
			  System.out.println("showCLTPlanes(): tp.clt_3d_passes is null");
			  return;
		  }
		  tp.showPlanes(
				  clt_parameters,
				  geometryCorrection,
				  threadsMax,
				  updateStatus,
//				  false, // batch_mode
				  debugLevel);
		  Runtime.getRuntime().gc();
	      System.out.println("showCLTPlanes(): processing  finished at "+
			  IJ.d2s(0.000000001*(System.nanoTime()-this.startStepTime),3)+" sec, --- Free memory18="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
	  }

	  public double [][]  assignCLTPlanes( // not used in lwir
			  CLTParameters           clt_parameters,
			  final int          threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel)
	  {
		  	if (tp == null){
		  		System.out.println("showCLTPlanes(): tp is null");
		  		return null;
		  	}
		  	if (tp.clt_3d_passes == null){
		  		System.out.println("showCLTPlanes(): tp.clt_3d_passes is null");
		  		return null;
		  	}
		  	this.startStepTime=System.nanoTime();
			setPassAvgRBGA(                      // get image from a single pass, return relative path for x3d // USED in lwir
					clt_parameters,                           // CLTParameters           clt_parameters,
					tp.clt_3d_passes.size() - 1, // int        scanIndex,
					threadsMax,                               // int        threadsMax,  // maximal number of threads to launch
					updateStatus,                             // boolean    updateStatus,
					debugLevel);                         // int        debugLevel)
		  	
		  	double [][] assign_dbg = tp.assignTilesToSurfaces(
		  			clt_parameters,
		  			geometryCorrection,
		  			threadsMax,
		  			updateStatus,
//		  			false, //  boolean batch_mode,
		  			debugLevel);
		  	Runtime.getRuntime().gc();
		  	System.out.println("assignCLTPlanes(): processing  finished at "+
		  			IJ.d2s(0.000000001*(System.nanoTime()-this.startStepTime),3)+" sec, --- Free memory19="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
		  	return assign_dbg;

	  }


	  public void out3d_old( // not used in lwir
			  CLTParameters           clt_parameters,
			  final int          threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel)
	  {
		  	if (tp == null){
		  		System.out.println("showCLTPlanes(): tp is null");
		  		return;
		  	}
		  	if (tp.clt_3d_passes == null){
		  		System.out.println("showCLTPlanes(): tp.clt_3d_passes is null");
		  		return;
		  	}
		  	tp.showPlanes(
		  			clt_parameters,
		  			geometryCorrection,
		  			threadsMax,
		  			updateStatus,
//		  			false, // batch_mode
		  			debugLevel);
//		  	CLTPass3d last_scan = tp.clt_3d_passes.get(tp.clt_3d_passes.size() -1); // get last one

	  }


	  @Deprecated
	  public void processCLTQuads3d( // not used in lwir
			  boolean                                              adjust_extrinsics,
			  boolean                                              adjust_poly,
			  TwoQuadCLT                                           twoQuadCLT, //maybe null in no-rig mode, otherwise may contain rig measurements to be used as infinity ground truth
			  CLTParameters                                        clt_parameters,
			  ColorProcParameters                                  colorProcParameters,
			  CorrectionColorProc.ColorGainsParameters             channelGainParameters,
			  EyesisCorrectionParameters.RGBParameters             rgbParameters,
			  EyesisCorrectionParameters.EquirectangularParameters equirectangularParameters,
			  final int                                            threadsMax,  // maximal number of threads to launch
			  final boolean                                        updateStatus,
			  final int                                            debugLevel)
	  {
		  this.startTime=System.nanoTime();
		  String [] sourceFiles=correctionsParameters.getSourcePaths();
		  SetChannels [] set_channels=setChannels(debugLevel);
		  if ((set_channels == null) || (set_channels.length==0)) {
			  System.out.println("No files to process (of "+sourceFiles.length+")");
			  return;
		  }
		  double [] referenceExposures=eyesisCorrections.calcReferenceExposures(debugLevel); // multiply each image by this and divide by individual (if not NaN)
		  for (int nSet = 0; nSet < set_channels.length; nSet++){
			  int [] channelFiles = set_channels[nSet].fileNumber();
//			  boolean [][] 
					  saturation_imp = (clt_parameters.sat_level > 0.0)? new boolean[channelFiles.length][] : null;
			  double [] scaleExposures = new double[channelFiles.length];

			  ImagePlus [] imp_srcs = conditionImageSet(
					  clt_parameters,             // EyesisCorrectionParameters.CLTParameters  clt_parameters,
					  colorProcParameters,        // ColorProcParameters                       colorProcParameters,
					  sourceFiles,                // String []                                 sourceFiles,
					  set_channels[nSet].name(),  // String                                    set_name,
					  referenceExposures,         // double []                                 referenceExposures,
					  channelFiles,               // int []                                    channelFiles,
					  scaleExposures,   //output  // double [] scaleExposures
					  saturation_imp,   //output  // boolean [][]                              saturation_imp,
					  threadsMax,                 // int                                       threadsMax,
					  debugLevel); // int                                       debugLevel);
			  boolean use_rig = (twoQuadCLT != null) && (twoQuadCLT.getBiScan(0) != null);
			  if (!adjust_extrinsics || !use_rig) {
				  // Difficult to fix: adjust extrinsics for aux - when it is adjusted alone, it will not match tiles to those of a rig!
				  // can use only far tiles with small gradients?

				  // once per quad here

// need to replace for low-res?
				  preExpandCLTQuad3d( // returns ImagePlus, but it already should be saved/shown
						  clt_parameters,
						  // adding these parameters for more flexibility in accuracy/speed
						  clt_parameters.gr_max_clust_radius, //int      gr_max_clust_radius,			  
						  clt_parameters.disp_scan_start,     // double  disp_scan_start,
						  clt_parameters.disp_scan_step,      // double  disp_scan_step,
						  clt_parameters.disp_scan_count,     // double  disp_scan_count,
						  false,                              // boolean no_bg_generate,			  
						  false,                              // boolean no_lma,			  
						  colorProcParameters,
						  rgbParameters,
						  threadsMax,  // maximal number of threads to launch
						  updateStatus,
						  debugLevel);
// Add here composite scans and show FG and BG images
				  // adjust extrinsics here

// Only here it uses 2 planes
				  ArrayList<CLTPass3d> combo_pass_list = tp.compositeScan(
						  2, // just FG and BG
						  tp.clt_3d_passes, // final ArrayList <CLTPass3d> passes,
						  0, // bg_pass, //  final int                   firstPass,
						  tp.clt_3d_passes.size(),                          //  final int                   lastPassPlus1,
						  tp.getTrustedCorrelation(),                       // 	 final double                trustedCorrelation,
						  tp.getMaxOverexposure(),                          //  final double                max_overexposure,
						  0.0, // clt_parameters.bgnd_range,                //	 final double                disp_far,   // limit results to the disparity range
						  clt_parameters.grow_disp_max,                     // final double                disp_near,
						  clt_parameters.combine_min_strength,              // final double                minStrength,
						  clt_parameters.combine_min_hor,                   // final double                minStrengthHor,
						  clt_parameters.combine_min_vert,                  // final double                minStrengthVert,
						  false,                                            // final boolean               no_weak,
						  false,                                            // final boolean               use_last,   //
						  // TODO: when useCombo - pay attention to borders (disregard)
//						  false,                                            // final boolean               usePoly)  // use polynomial method to find max), valid if useCombo == false
						  true,                                             // 	 final boolean               copyDebug)
						  debugLevel);

				  tp.ShowScansSFB(
						  combo_pass_list, // ArrayList<CLTPass3d> scans, // list of composite scans
						  this.image_name+sAux()+"-SFB0"); // String               title);

			  }
			  if (adjust_extrinsics) {
				  // temporarily
				  if (ds_from_main != null) {
					  System.out.println("Adjust AUX extrinsics using main camera measurements");
					  extrinsicsCLTfromGT(
//							  twoQuadCLT,   // TwoQuadCLT       twoQuadCLT, //maybe null in no-rig mode, otherwise may contain rig measurements to be used as infinity ground truth
							  null,
							  ds_from_main, // gt_disp_strength,
							  clt_parameters, // EyesisCorrectionParameters.CLTParameters           clt_parameters,
							  adjust_poly,
							  threadsMax,  //final int        threadsMax,  // maximal number of threads to launch
							  updateStatus,// final boolean    updateStatus,
							  debugLevel + 2); // final int        debugLevel)

				  } else  if (use_rig) {
					  System.out.println("Adjust extrinsics using rig data here");
					  double [][] gt_disp_strength = getRigDSFromTwoQuadCL(
							  twoQuadCLT, //maybe null in no-rig mode, otherwise may contain rig measurements to be used as infinity ground truth
							  clt_parameters,
							  debugLevel + 2); // final int        debugLevel)
					  GeometryCorrection geometryCorrection_main = null;
					  if (geometryCorrection.getRotMatrix(true) != null) {
						  geometryCorrection_main = twoQuadCLT.quadCLT_main.getGeometryCorrection();
					  }
					  extrinsicsCLTfromGT(
//							  twoQuadCLT,   // TwoQuadCLT       twoQuadCLT, //maybe null in no-rig mode, otherwise may contain rig measurements to be used as infinity ground truth
							  geometryCorrection_main,
							  gt_disp_strength,
							  clt_parameters, // EyesisCorrectionParameters.CLTParameters           clt_parameters,
							  adjust_poly,
							  threadsMax,  //final int        threadsMax,  // maximal number of threads to launch
							  updateStatus,// final boolean    updateStatus,
							  debugLevel + 2); // final int        debugLevel)

				  } else {
					  System.out.println("Adjust extrinsics here");
					  extrinsicsCLT(
							  //						  twoQuadCLT,   // TwoQuadCLT       twoQuadCLT, //maybe null in no-rig mode, otherwise may contain rig measurements to be used as infinity ground truth
							  clt_parameters, // EyesisCorrectionParameters.CLTParameters           clt_parameters,
							  adjust_poly,
							  -1.0, // double inf_min,
							  1.0,  // double inf_max,
							  threadsMax,  //final int        threadsMax,  // maximal number of threads to launch
							  updateStatus,// final boolean    updateStatus,
							  debugLevel); // final int        debugLevel)
				  }

			  } else {
				  expandCLTQuad3d( // returns ImagePlus, but it already should be saved/shown
						  clt_parameters,
						  colorProcParameters,
						  channelGainParameters,
						  rgbParameters,
						  threadsMax,  // maximal number of threads to launch
						  updateStatus,
						  debugLevel);
			  }

			  ArrayList<CLTPass3d> combo_pass_list = tp.compositeScan(
					  2, // just FG and BG
					  tp.clt_3d_passes, // final ArrayList <CLTPass3d> passes,
					  0, // bg_pass, //  final int                   firstPass,
					  tp.clt_3d_passes.size(),                          //  final int                   lastPassPlus1,
					  tp.getTrustedCorrelation(),                       // 	 final double                trustedCorrelation,
					  tp.getMaxOverexposure(),                          //  final double                max_overexposure,
					  0.0, // clt_parameters.bgnd_range,                //	 final double                disp_far,   // limit results to the disparity range
					  clt_parameters.grow_disp_max,                     // final double                disp_near,
					  clt_parameters.combine_min_strength,              // final double                minStrength,
					  clt_parameters.combine_min_hor,                   // final double                minStrengthHor,
					  clt_parameters.combine_min_vert,                  // final double                minStrengthVert,
					  false,                                            // final boolean               no_weak,
					  false,                                            // final boolean               use_last,   //
					  // TODO: when useCombo - pay attention to borders (disregard)
//					  false,                                            // final boolean               usePoly)  // use polynomial method to find max), valid if useCombo == false
					  true,                                             // 	 final boolean               copyDebug)
					  debugLevel);

			  tp.ShowScansSFB(
					  combo_pass_list, // ArrayList<CLTPass3d> scans, // list of composite scans
					  this.image_name+sAux()+"-SFB1"); // String               title);


			  Runtime.getRuntime().gc();

//			  if (debugLevel >-1) System.out.println("Processing set "+(nSet+1)+" (of "+setNames.size()+") finished at "+
//					  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
			  if (debugLevel >-1) System.out.println("Processing set "+(nSet+1)+" (of "+set_channels.length+") finished at "+
					  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory20="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
			  if (eyesisCorrections.stopRequested.get()>0) {
				  System.out.println("User requested stop");
				  return;
			  }
		  }
//		  System.out.println("Processing "+fileIndices.length+" files finished at "+
//				  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory21="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
		  System.out.println("Processing "+getTotalFiles(set_channels)+" files finished at "+
				  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory22="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
	  }

	public double [][] depthMapMainToAux(// USED in lwir
			double [][]        ds,
			GeometryCorrection geometryCorrection_main,
			GeometryCorrection geometryCorrection_aux,
			CLTParameters      clt_Parameters,
//			double             min_strength,
//			boolean            use_wnd,
			boolean            split_fg_bg,
//			double             split_fbg_rms,
			boolean            for_adjust, // for LY adjustment: only keep d,s and remove samples with high variations
			int                debug_level
			){
		class DS{// USED in lwir
			double disparity;  // gt disparity
			double strength;   // gt strength
			int tx;            // gt tile x
			int ty;            // gt tile x
			double fx;         // fractional aux tile X (0.0..1.0) for optional window
			double fy;         // fractional aux tile Y (0.0..1.0) for optional window
			DS (double disparity, double strength, int tx, int ty, double fx, double fy){
				this.disparity = disparity;
				this.strength =  strength;
				this.tx =        tx;
				this.ty =        ty;
				this.fx =        fx;
				this.fy =        fy;

			}
			@Override
			public String toString() { // not used in lwir
				return String.format("Disparity (str) = % 6f (%5f), tx=%d ty=%d fx=%5f fy=%5f\n", disparity, strength,tx,ty,fx,fy);
			}
		}
		int tile_size =  clt_Parameters.transform_size;
		int [] wh_main = geometryCorrection_main.getSensorWH();
		int [] wh_aux =  geometryCorrection_aux.getSensorWH();
		int tilesX_main = wh_main[0] / tile_size;
		int tilesY_main = wh_main[1] / tile_size;
		int tilesX_aux = wh_aux[0] / tile_size;
		int tilesY_aux = wh_aux[1] / tile_size;

		ArrayList<ArrayList<DS>> ds_list = new ArrayList<ArrayList<DS>>();
		for (int nt = 0; nt < tilesX_aux * tilesY_aux; nt++) {
			ds_list.add(new ArrayList<DS>());
		}
		for (int ty = 0; ty < tilesY_main; ty++) {
			double centerY = ty * tile_size + tile_size/2;
			for (int tx = 0; tx < tilesX_main; tx++) {
				int nt = ty*tilesX_main + tx;
				double centerX = tx * tile_size + tile_size/2;
				double disparity = ds[0][nt];
				double strength =  ds[1][nt];
				if ((strength >= clt_Parameters.ly_gt_strength) && !Double.isNaN(disparity)) {
					double [] dpxpy_aux =  geometryCorrection_aux.getFromOther(
							geometryCorrection_main, // GeometryCorrection other_gc,
							centerX,                 // double other_px,
							centerY,                 // double other_py,
							disparity);              // double other_disparity)
					double fx = dpxpy_aux[1]/tile_size;
					double fy = dpxpy_aux[2]/tile_size;
					int tx_aux = (int) Math.floor(fx);
					int ty_aux = (int) Math.floor(fy);
					fx -= tx_aux;
					fy -= ty_aux;
					if ((ty_aux >= 0) && (ty_aux < tilesY_aux) && (tx_aux >= 0) && (tx_aux < tilesX_aux)) {
						int nt_aux = ty_aux * tilesX_aux + tx_aux;
						ds_list.get(nt_aux).add(new DS(dpxpy_aux[0], strength, tx, ty, fx, fy));
					}
				}
			}
		}

		// simple average (ignoring below minimal)
		int num_slices = split_fg_bg? FGBG_TITLES_AUX.length:FGBG_TITLES_ADJ.length;
		double [][] ds_aux_avg = new double [num_slices][tilesX_aux * tilesY_aux];
		for (int ty = 0; ty < tilesY_aux; ty++) {
			for (int tx = 0; tx < tilesX_aux; tx++) {
//				if ((ty == 3) && (tx == 12)) {
//					System.out.println("tx = "+tx+", ty = "+ty);
//				}
				int nt = ty * tilesX_aux + tx;
				ds_aux_avg[FGBG_DISPARITY][nt] = Double.NaN;
				ds_aux_avg[FGBG_STRENGTH][nt] = 0.0;
				if(ds_list.get(nt).isEmpty()) continue;
	    		Collections.sort(ds_list.get(nt), new Comparator<DS>() {
	    		    @Override
	    		    public int compare(DS lhs, DS rhs) { // ascending
	    		        return rhs.disparity > lhs.disparity  ? -1 : (rhs.disparity  < lhs.disparity ) ? 1 : 0;
	    		    }
	    		});

				double sw = 0.0, swd = 0.0, swd2 = 0.0;
	    		for (DS dsi: ds_list.get(nt)) {
	    			double w = dsi.strength;
	    			if (clt_Parameters.ly_gt_use_wnd) {
	    				w *= Math.sin(Math.PI * dsi.fx) * Math.sin(Math.PI * dsi.fy);
	    			}
	    			sw +=  w;
	    			double wd = w * dsi.disparity;
	    			swd += wd;
	    			swd2 += wd * dsi.disparity;

	    		}
	    		ds_aux_avg[FGBG_DISPARITY][nt] = swd/sw;
	    		ds_aux_avg[FGBG_STRENGTH][nt] = sw/ds_list.get(nt).size();
	    		double rms = Math.sqrt( (swd2 * sw - swd * swd) / (sw * sw));
    			if (for_adjust && (rms >= clt_Parameters.ly_gt_rms)) { // remove ambiguous tiles
    	    		ds_aux_avg[FGBG_DISPARITY][nt] = Double.NaN;
    	    		ds_aux_avg[FGBG_STRENGTH][nt] = 0;

    			}
	    		if (split_fg_bg) {

	    			ds_aux_avg[FGBG_RMS ][nt] =      rms;
	    			ds_aux_avg[FGBG_RMS_SPLIT][nt] = ds_aux_avg[2][nt]; // rms
	    			ds_aux_avg[FGBG_FG_DISP][nt] =   ds_aux_avg[0][nt]; // fg disp
	    			ds_aux_avg[FGBG_FG_STR][nt] =    ds_aux_avg[1][nt]; // fg strength
	    			ds_aux_avg[FGBG_BG_DISP][nt] =   ds_aux_avg[0][nt]; // bg disp
	    			ds_aux_avg[FGBG_BG_STR][nt] =    ds_aux_avg[1][nt]; // bg strength
	    			if (rms >= clt_Parameters.ly_gt_rms) {
	    				// splitting while minimizing sum of 2 squared errors
	    	    		double [][] swfb =  new double [2][ds_list.get(nt).size() -1];
	    	    		double [][] swdfb = new double [2][ds_list.get(nt).size() -1];
	    	    		double []   s2fb =  new double [ds_list.get(nt).size() -1];
	    	    		for (int n = 0; n < s2fb.length; n++) { // split position
	    	    			double [] s2 = new double[2];
	    	    			for (int i = 0; i <= s2fb.length; i++) {
	    	    				int fg = (i > n)? 1 : 0; // 0 - bg, 1 - fg
	    	    				DS dsi = ds_list.get(nt).get(i);
	    		    			double w = dsi.strength;
	    		    			if (clt_Parameters.ly_gt_use_wnd) {
	    		    				w *= Math.sin(Math.PI * dsi.fx) * Math.sin(Math.PI * dsi.fy);
	    		    			}
	    		    			swfb[fg][n] +=  w;
	    		    			double wd =      w * dsi.disparity;
	    		    			swdfb[fg][n] += wd;
	    		    			s2[fg] +=        wd * dsi.disparity;
	    	    			}
	    	    			s2fb[n] =  ((s2[0] * swfb[0][n] - swdfb[0][n] * swdfb[0][n]) / swfb[0][n] +
	    	    					(s2[1] * swfb[1][n] - swdfb[1][n] * swdfb[1][n]) / swfb[1][n]) / (swfb[0][n] + swfb[1][n]);
	    	    		}
		    			// now find the n with lowest s2fb and use it to split fg/bg. Could be done in a single pass, but with saved arrays
		    			// it is easier to verify
		    			int nsplit = 0;
		    			for (int i = 1; i < s2fb.length; i++) if (s2fb[i] < s2fb[nsplit]) {
		    				nsplit = i;
		    			}
		    			ds_aux_avg[FGBG_RMS_SPLIT][nt] = s2fb[nsplit]; // rms split
		    			ds_aux_avg[FGBG_FG_DISP][nt] =   swdfb[1][nsplit] / swfb[1][nsplit] ;      // fg disp
		    			ds_aux_avg[FGBG_FG_STR][nt] =    swfb[1][nsplit]/ (s2fb.length - nsplit) ; // fg strength

		    			ds_aux_avg[FGBG_BG_DISP][nt] =   swdfb[0][nsplit] / swfb[0][nsplit] ;      // bg disp
		    			ds_aux_avg[FGBG_BG_STR][nt] =    swfb[0][nsplit]/ (nsplit + 1) ;           // bg strength
	    			}
	    		}
			}
		}
		return ds_aux_avg;

	}

	  public boolean preExpandCLTQuad3d( // USED in lwir
			  CLTParameters                                    clt_parameters,
			  // adding these parameters for more flexibility in accuracy/speed
			  int                                              gr_max_clust_radius,			  
			  double                                           disp_scan_start,
			  double                                           disp_scan_step,
			  int                                              disp_scan_count,
			  boolean                                          no_bg_generate,
			  boolean                                          no_lma,
			  ColorProcParameters                              colorProcParameters,
			  EyesisCorrectionParameters.RGBParameters         rgbParameters,
			  final int                                        threadsMax,  // maximal number of threads to launch
			  final boolean                                    updateStatus,
			  final int                                        debugLevel)
	  {
		  final boolean    show_init_refine =  clt_parameters.show_init_refine; // change to true?
    	  boolean lwir2022 = true;
    	  boolean debug_graphic = false; // true;

		  boolean no_macro = isLwir(); // make it a separate configurable parameter?
		  // change debugLevel to 0
		  this.startStepTime=System.nanoTime()+0;
		  final double     max_mismatch =      clt_parameters.getMaxChannelMismatch(isLwir());
		  final double     mismatch_override = clt_parameters.mismatch_override;
		  final double     fom_min_strength =  clt_parameters.fom_min_strength;
		  final double     fom_adisp =         clt_parameters.getFomWAdisp(isLwir()); // 0.5;
		  final double     fom_cdiff =         clt_parameters.getFomCDiff(isLwir()); // 0.02;
		  final double     fom_inf_bonus  =    clt_parameters.fom_inf_bonus;  // 0.2; // add this to infinity FOM (if it is closer than fom_inf_range)
		  final double     fom_inf_range  =    clt_parameters.fom_inf_range;  // 0.5;
		  
//		  final boolean no_lma = true;
		  //max_expand
//		  String name = (String) imp_quad[0].getProperty("name");
		  String name = getImageName();
		  // should create data for the macro! (diff, rgb) make sure .texture_tiles is measured correctly
		  CLTPass3d bgnd_data_lma = null;
		  if (!no_lma) {
			  bgnd_data_lma = CLTBackgroundMeas( // measure background - both CPU and GPU (remove textures from GPU)
					  clt_parameters,
					  true,              //  final boolean     run_lma, //			  
					  max_mismatch,      // max_chn_diff, //final double      max_chn_diff, // filter correlation results by maximum difference between channels
					  mismatch_override, // final double      mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
					  threadsMax,        // maximal number of threads to launch
					  updateStatus,
					  debugLevel);
			  if (clt_parameters.img_dtt.lmamask_dbg) {
				  System.out.println("Remove me - QCC8257");
				  return false;
			  }
			  tp.clt_3d_passes.add(bgnd_data_lma);
		  }
		  CLTPass3d bgnd_data_nolma = CLTBackgroundMeas( // measure background - both CPU and GPU (remove textures from GPU)
				  clt_parameters,
				  false,              //  final boolean     run_lma, //			  
				  max_mismatch,      // max_chn_diff, //final double      max_chn_diff, // filter correlation results by maximum difference between channels
				  mismatch_override, // final double      mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
				  threadsMax,        // maximal number of threads to launch
				  updateStatus,
				  debugLevel);
		  tp.clt_3d_passes.add(bgnd_data_nolma);
		  if (no_lma) {
			  bgnd_data_lma = bgnd_data_nolma; // use for bg generation
		  }
		  if (!no_bg_generate) {
			  //    	  if (show_init_refine)
			  //		  if ((debugLevel > -2) && clt_parameters.show_first_bg) {
			  if ((debugLevel > -2) && clt_parameters.show_first_bg) {
				  tp.showScan(
						  tp.clt_3d_passes.get(0), // CLTPass3d   scan,
						  "bgnd_data_lma-"+tp.clt_3d_passes.size());
				  tp.showScan(
						  tp.clt_3d_passes.get(1), // CLTPass3d   scan,
						  "bgnd_data_nolma-"+tp.clt_3d_passes.size());
			  }

			  //TODO: Move away from here?
			  boolean no_image_save = false; // Restore? true;

			  boolean [] bgmask = getBackgroundImageMasks(
					  clt_parameters,
					  bgnd_data_lma,   // CLTPass3d               bgnd_data,
					  name,        // .getTitle(), //String name=(String) imp_src.getProperty("name");
					  threadsMax,  // maximal number of threads to launch
					  updateStatus,
					  debugLevel);

			  bgnd_data_nolma.setSelected(bgnd_data_lma.getSelected());

			  if ((debugLevel > -2)  && (bgmask != null)) {
				  double [][] dbg_img = new double[1][tp.getTilesY() * tp.getTilesX()];
				  for (int i = 0; i<dbg_img[0].length;i++){
					  dbg_img[0][i] =  bgmask[i]?1:0;
				  }
				  ShowDoubleFloatArrays.showArrays(
						  dbg_img,
						  tp.getTilesX(),
						  tp.getTilesY(),
						  true, "bgmask");
			  }
			  ImagePlus imp_bgnd = null; 
			  if ((colorProcParameters != null) && (rgbParameters != null)){

				  ImagePlus imp_bgnd_int = getBackgroundImage( // null pointer
						  bgmask, // boolean []                                bgnd_tiles, 
						  bgnd_data_lma,   // CLTPass3d               bgnd_data,
						  clt_parameters,
						  colorProcParameters,
						  rgbParameters,
						  name,               // .getTitle(), //String name=(String) imp_src.getProperty("name");
						  threadsMax,  // maximal number of threads to launch
						  updateStatus,
						  debugLevel);

				  if (debugLevel > -2) {
					  imp_bgnd_int.show(); /// OK 
				  }

				  //		  if (debugLevel > -100) {
				  //			  return null;
				  //		  }
				  // resize for backdrop here! check imp_bgnd_int here !

				  imp_bgnd = finalizeBackgroundImage( // USED in lwir - output all 0?
						  imp_bgnd_int,   //  ImagePlus imp_texture_bgnd, null pointer
						  no_image_save,  // boolean    no_image_save,
						  clt_parameters, // CLTParameters           clt_parameters,
						  name,           // String     name,
						  debugLevel);    // int        debugLevel)
			  }
			  bgnd_data_lma.texture = (imp_bgnd == null)? null: ( imp_bgnd.getTitle()+ (clt_parameters.black_back? ".jpeg" : ".png"));
		  }

		  // create x3d file
		  X3dOutput x3dOutput = new X3dOutput(
				  clt_parameters,
				  correctionsParameters,
				  geometryCorrection,
				  tp.clt_3d_passes);

		  x3dOutput.generateBackground(clt_parameters.infinityDistance <= 0.0); // needs just first (background) scan

    	  // refine first measurement
    	  int bg_pass = tp.clt_3d_passes.size() - 1; // 0
    	  int refine_pass = tp.clt_3d_passes.size(); // 1
    	  if (debugLevel > -1) {
    		  tp.showScan(
    				  tp.clt_3d_passes.get(bg_pass), // CLTPass3d   scan,
    				  "after_bg-"+tp.clt_3d_passes.size());
    	  }


    	  // TODO: Make double pass - with only 	weight_var (thin wires) and weight_Y, weight_RBmG - larger objects
    	  // just use two instances of MacroCorrelation, run one after another (move code to MacroCorrelation class)
    	  // and then join
    	  ArrayList <CLTPass3d> new_meas = null; // filled either from macro correlation, or just from plain disparity scan
    	  if (no_macro) {
    		  new_meas = prepareDisparityScan(
    				  disp_scan_start,  // double scan_start,
    				  disp_scan_step,   // 	double scan_step,
    				  disp_scan_count); // 	int    scan_count)
    	  } else {
    		  MacroCorrelation mc = new MacroCorrelation(
    				  tp,
    				  clt_parameters.mc_disp8_trust,
    				  clt_parameters.mc_weight_var,   // final double     weight_var,   // weight of variance data (old, detects thin wires?)
    				  clt_parameters.mc_weight_Y,     // final double     weight_Y,     // weight of average intensity
    				  clt_parameters.mc_weight_RBmG   // final double     weight_RBmG,  // weight of average color difference (0.5*(R+B)-G), shoukld be ~5*weight_Y
    				  );


    		  double [][][] input_data = mc.CLTMacroSetData( // perform single pass according to prepared tiles operations and disparity
    				  bgnd_data_nolma);           // final CLTPass3d      src_scan, // results of the normal correlations (now expecting infinity)

    		  TileProcessor mtp =  mc.CLTMacroScan( // perform single pass according to prepared tiles operations and disparity
    				  bgnd_data_nolma,          // final CLTPass3d                            src_scan, // results of the normal correlations (now expecting infinity)
    				  clt_parameters,     // EyesisCorrectionParameters.CLTParameters clt_parameters,
    				  geometryCorrection, // GeometryCorrection geometryCorrection,
    				  0.0,                // 	final double                             macro_disparity_low,
    				  clt_parameters.grow_disp_max / tp.getTileSize(), // final double                             macro_disparity_high,
    				  clt_parameters.mc_disp8_step, // final double                             macro_disparity_step,
    				  debugLevel); // 1); // 	final int                                debugLevel){

    		  CLTPass3d macro_combo = mtp.compositeScan(
    				  mtp.clt_3d_passes, // final ArrayList <CLTPass3d> passes,
    				  0, //  final int                   firstPass,
    				  mtp.clt_3d_passes.size(),                         //  final int                   lastPassPlus1,
    				  mtp.getTrustedCorrelation(),                      // 	final double                trustedCorrelation,
    				  mtp.getMaxOverexposure(),                         //  final double                max_overexposure,
    				  0.0, // clt_parameters.bgnd_range,                //	final double                disp_far,   // limit results to the disparity range
    				  clt_parameters.grow_disp_max / tp.getTileSize(),  //  final double                disp_near,
    				  clt_parameters.mc_strength,                       // final double                minStrength,
    				  Double.NaN, // clt_parameters.combine_min_hor,                   // final double                minStrengthHor,
    				  Double.NaN, // clt_parameters.combine_min_vert,                  // final double                minStrengthVert,
    				  // maybe temporarily? later keep weak?
    				  true,      // final boolean               no_weak,
    				  false, // final boolean               use_last,   //
    				  // TODO: when useCombo - pay attention to borders (disregard)
    				  false, // final boolean               usePoly)  // use polynomial method to find max), valid if useCombo == false
    				  true, // 	 final boolean               copyDebug)
    				  debugLevel);
    		  mtp.clt_3d_passes.add(macro_combo);
    		  if (clt_parameters.show_macro) {
    			  mtp.showScan(
    					  macro_combo, // CLTPass3d   scan,
    					  "macro_combo-"+mtp.clt_3d_passes.size());
    		  }

    		  for (int num_try = 0; num_try < 100; num_try++) {
    			  CLTPass3d refined_macro = mc.refineMacro(
    					  input_data,                                        // final double [][][]                      input_data,
    					  clt_parameters,                                    // EyesisCorrectionParameters.CLTParameters clt_parameters,
    					  geometryCorrection,                                // GeometryCorrection                       geometryCorrection,
    					  clt_parameters.mc_disp8_trust,                     // final double                             trustedCorrelation,
    					  0, //  final double                             disp_far,   // limit results to the disparity range
    					  clt_parameters.grow_disp_max / tp.getTileSize(), // final double                             disp_near,
    					  clt_parameters.mc_strength, // final double                             minStrength,
    					  clt_parameters.mc_unique_tol, // final double                             unique_tolerance,
    					  1); // final int                                debugLevel)
    			  if (refined_macro == null) break;
    			  mtp.clt_3d_passes.add(refined_macro);
    			  if (clt_parameters.show_macro) {
    				  mtp.showScan(
    						  refined_macro, // CLTPass3d   scan,
    						  "refined_macro-"+mtp.clt_3d_passes.size());
    			  }

    		  }
    		  CLTPass3d macro_combo1 = mtp.compositeScan(
    				  mtp.clt_3d_passes, // final ArrayList <CLTPass3d> passes,
    				  0, //  final int                   firstPass,
    				  mtp.clt_3d_passes.size(),                         //  final int                   lastPassPlus1,
    				  mtp.getTrustedCorrelation(),                       // 	 final double                trustedCorrelation,
    				  mtp.getMaxOverexposure(),                         //  final double                max_overexposure,
    				  0.0, // clt_parameters.bgnd_range,                //	 final double                disp_far,   // limit results to the disparity range
    				  clt_parameters.grow_disp_max / tp.getTileSize(),  //  final double                disp_near,
    				  clt_parameters.mc_strength,                       // final double                minStrength,
    				  Double.NaN, // clt_parameters.combine_min_hor,                   // final double                minStrengthHor,
    				  Double.NaN, // clt_parameters.combine_min_vert,                  // final double                minStrengthVert,
    				  // maybe temporarily? later keep weak?
    				  true,      // final boolean               no_weak,
    				  false, // final boolean               use_last,   //
    				  // TODO: when useCombo - pay attention to borders (disregard)
    				  false, // final boolean               usePoly)  // use polynomial method to find max), valid if useCombo == false
    				  true, // 	 final boolean               copyDebug)
    				  debugLevel);
    		  mtp.clt_3d_passes.add(macro_combo1);
    		  if (clt_parameters.show_macro) {
    			  mtp.showScan(
    					  macro_combo1, // CLTPass3d   scan,
    					  "macro_combo-"+mtp.clt_3d_passes.size());
    		  }

    		  //    	  ArrayList <CLTPass3d>
    		  new_meas = mc.prepareMeasurementsFromMacro(
    				  mtp.clt_3d_passes, // final ArrayList <CLTPass3d> macro_passes, // macro correlation measurements
    				  // in pixels
    				  3.0, // final double                disp_far,   // limit results to the disparity range
    				  clt_parameters.grow_disp_max, // final double                disp_near,
    				  clt_parameters.mc_strength, // final double                minStrength,
    				  clt_parameters.mc_strength, //final double                mc_trust_fin, //          =   0.3;   // When consolidating macro results, exclude high residual disparity
    				  clt_parameters.mc_strength, //final double                mc_trust_sigma, //        =   0.2;   // Gaussian sigma to reduce weight of large residual disparity
    				  clt_parameters.mc_strength, //final double                mc_ortho_weight, //       =   0.5;   // Weight from ortho neighbor supertiles
    				  clt_parameters.mc_strength, //final double                mc_diag_weight, //        =   0.25;  // Weight from diagonal neighbor supertiles
    				  clt_parameters.mc_strength, //final double                mc_gap, //                =   0.4;   // Do not remove measurements farther from the kept ones
    				  false, // final boolean               usePoly,  // use polynomial method to find max), valid if useCombo == false
    				  true, // final boolean               sort_disparity,  // sort results for increasing disparity (false - decreasing strength)
    				  clt_parameters.tileX, // final int                   dbg_x,
    				  clt_parameters.tileY, // final int                   dbg_y,
    				  debugLevel); // final int                   debugLevel);
    	  }  // if (no_macro) {} else

    	  System.out.println("new_meas.size()="+new_meas.size());
    	  int indx = 0;
    	  if ((debugLevel > -1) && clt_parameters.show_macro) {
    		  for (CLTPass3d pass: new_meas) {
    			  tp.showScan(
    					  pass, // CLTPass3d   scan,
    					  "meas-"+(indx++));

    		  }
    	  }
//		  boolean reduce_pairs_multi = true;
//		  boolean last_iter_all = true;
    	  // Save pair selection and minimize them for scanning, then restore;
    	  int save_pairs_selection = clt_parameters.img_dtt.getMcorr(getNumSensors());
    	  clt_parameters.img_dtt.setMcorr(getNumSensors(), 0 ); // remove all
    	  clt_parameters.img_dtt.setMcorrNeib(getNumSensors(),true);
    	  clt_parameters.img_dtt.setMcorrSq  (getNumSensors(),true); // remove even more?
    	  clt_parameters.img_dtt.setMcorrDia (getNumSensors(),true); // remove even more?
//    	  boolean save_run_lma = clt_parameters.correlate_lma; 
//    	  clt_parameters.correlate_lma = false;
    	  
    	  int dbg_num_new = 0; // only BG in the list
		  if (show_init_refine) tp.showScan(
				  tp.clt_3d_passes.get(tp.clt_3d_passes.size()-1), // CLTPass3d   scan,
				  "before adding-"+tp.clt_3d_passes.size());
    	  for (CLTPass3d from_macro_pass: new_meas) {
    		  if (debugLevel > -3) {
    			  System.out.println("Next from new_meas: "+dbg_num_new);
    		  }
    		  dbg_num_new++;
    		  // First refine from latest in the list, add new later
    		  for (int nnn = 0; nnn < clt_parameters.gr_num_refines; nnn ++){ //
    			  refine_pass = tp.clt_3d_passes.size(); // 1
    			  // refinePassSetup uses last scan in list
    			  CLTPass3d refined = tp.refinePassSetup( // prepare tile tasks for the refine pass (re-measure disparities)
    					  //				  final double [][][]       image_data, // first index - number of image in a quad
    					  clt_parameters,
    					  0, // int               clust_radius,     // 0 - initial single-tile, 1 - 1x1 (same), 2 - 3x3, 3 5x5
    					  clt_parameters.stUseRefine, // use supertiles
    					  bg_pass, /// does it have .selected
    					  // disparity range - differences from
    					  clt_parameters.bgnd_range, // double            disparity_far,
    					  clt_parameters.grow_disp_max, // other_range, //double            disparity_near,   //
    					  clt_parameters.ex_strength,  // double            this_sure,        // minimal strength to be considered definitely good
    					  clt_parameters.ex_nstrength, // double            ex_nstrength, // minimal 4-corr strength divided by channel diff for new (border) tiles
    					  clt_parameters.bgnd_maybe, // double            this_maybe,       // maximal strength to ignore as non-background
    					  clt_parameters.sure_smth, // sure_smth,        // if 2-nd worst image difference (noise-normalized) exceeds this - do not propagate bgnd
    					  clt_parameters.pt_super_trust, //  final double      super_trust,      // If strength exceeds ex_strength * super_trust, do not apply ex_nstrength and plate_ds
    					  // using plates disparity/strength - averaged for small square sets of tiles. If null - just use raw tiles
    					  null, // 		final double [][] plate_ds,  // disparity/strength last time measured for the multi-tile squares. Strength =-1 - not measured. May be null
    					  true, // 		final boolean     keep_raw_fg,  // do not replace raw tiles by the plates, if raw is closer (like poles)
    					  0.0, // final double      scale_filtered_strength_pre, // scale plate_ds[1] before comparing to raw strength
    					  0.0, // final double      scale_filtered_strength_post,// scale plate_ds[1] when replacing raw (generally plate_ds is more reliable if it exists)
    					  geometryCorrection,
    					  threadsMax,  // maximal number of threads to launch
    					  updateStatus,
    					  debugLevel); //2);
    			  tp.clt_3d_passes.add(refined);
       			  if ((debugLevel > -2) && clt_parameters.show_first_bg)
    				  tp.showScan(
    						  tp.clt_3d_passes.get(refine_pass), // CLTPass3d   scan,
    						  "before_makeUnique-"+refine_pass);
    			  int [] numLeftRemoved = tp.makeUnique(
    					  tp.clt_3d_passes,                      // final ArrayList <CLTPass3d> passes,
    					  0,                                     //  final int                   firstPass,
    					  refine_pass, // - 1,                   //  final int                   lastPassPlus1,
    					  0, // 	 final int                   clust_radius,     // 0 - initial single-tile, 1 - 1x1 (same), 2 - 3x3, 3 5x5
    					  tp.clt_3d_passes.get(refine_pass),     //  final CLTPass3d             new_scan,
    					  clt_parameters.grow_disp_max,          // final double                grow_disp_max,
    					  clt_parameters.gr_unique_tol,          //  final double                unique_tolerance,
    					  clt_parameters.show_unique);           // final boolean               show_unique)
    			  if (debugLevel > -3){ // -1
    				  System.out.println("cycle makeUnique("+refine_pass+") -> left: "+numLeftRemoved[0]+", removed:" + numLeftRemoved[1]);
    			  }
    			  if (show_init_refine && (debugLevel>0)) tp.showScan(
    					  tp.clt_3d_passes.get(refine_pass), // CLTPass3d   scan,
    					  "after_refinePassSetup-"+tp.clt_3d_passes.size());
    			  // Adding filtering for large difference only here (high-disparity ghosts in the sky)
    			  // In other places may harm FG/BG borders
     			  CLTMeasCorr( // perform single pass according to prepared tiles operations and disparity // CUDA_ERROR_INVALID_VALUE on lowres
    					  clt_parameters,
    					  refine_pass,
    					  false, // true,    // final boolean     save_textures,
    					  0,                 // final int         clust_radius,
    					  false, // clt_parameters.correlate_lma, // final boolean       run_lma, //
    					  max_mismatch,      // final double      max_chn_diff, // filter correlation results by maximum difference between channels
    					  mismatch_override, //final double      mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
    					  threadsMax,        // maximal number of threads to launch
    					  updateStatus,
    					  debugLevel);

    			  if (debugLevel > -2){
    				  System.out.println("CLTMeasCorr("+refine_pass+")-*");
    			  }
    			  if ((debugLevel > 0) && show_init_refine) tp.showScan( // remove by debug
    					  tp.clt_3d_passes.get(refine_pass), // CLTPass3d   scan,
    					  "after_measure-"+tp.clt_3d_passes.size());
    			  if (nnn < (clt_parameters.gr_num_refines-1)) { // all but last, because after last the next fresh one will be used
    				  CLTPass3d combo_pass;
    				  if (lwir2022) {
    					  combo_pass = tp.compositeScan(
    							  tp.clt_3d_passes,           // final ArrayList <CLTPass3d> passes,
    							  bg_pass,                    // final int                   firstPass,
    							  tp.clt_3d_passes.size(),    // final int                   lastPassPlus1,
    							  fom_min_strength,           // final double                fom_min_strength,
    							  false,                      // final boolean               fom_use_lma,
    							  fom_adisp,                  // final double                fom_adisp,     // 0.5
    							  fom_cdiff,                  // final double                fom_cdiff,     // 0.02
    							  fom_inf_bonus,              // final double                fom_inf_bonus, // 0.2; // add this to infinity FOM (if it is closer than fom_inf_range)
    							  fom_inf_range,              // final double                fom_inf_range, // 0.5;
    							  true,                       // final boolean               copyDebug)
    							  debugLevel);
    				  } else {
    					  combo_pass = tp.compositeScan(
    							  tp.clt_3d_passes, // final ArrayList <CLTPass3d> passes,
    							  bg_pass, //  final int                   firstPass,
    							  tp.clt_3d_passes.size(),                          //  final int                   lastPassPlus1,
    							  tp.getTrustedCorrelation(),                       // 	 final double                trustedCorrelation,
    							  tp.getMaxOverexposure(),                          //  final double                max_overexposure,
    							  0.0, // clt_parameters.bgnd_range,                //	 final double                disp_far,   // limit results to the disparity range
    							  clt_parameters.grow_disp_max,                     // final double                disp_near,
    							  clt_parameters.combine_min_strength,              // final double                minStrength,
    							  clt_parameters.combine_min_hor,                   // final double                minStrengthHor,
    							  clt_parameters.combine_min_vert,                  // final double                minStrengthVert,
    							  false,                                            // final boolean               no_weak,
    							  false,                                            // final boolean               use_last,   //
    							  // TODO: when useCombo - pay attention to borders (disregard)
    							  false, // final boolean               usePoly)  // use polynomial method to find max), valid if useCombo == false
    							  true, // 	 final boolean               copyDebug)
    							  debugLevel);
    				  }
    				  if (show_init_refine && (debugLevel>0)) tp.showScan(
    						  combo_pass, // CLTPass3d   scan,
    						  "after_compositeScan-"+tp.clt_3d_passes.size());
    				  tp.clt_3d_passes.add(combo_pass);

    			  }
    		  }
    		  // add new scan from macro
    		  tp.clt_3d_passes.add(from_macro_pass);
      		  CLTMeasCorr( // perform single pass according to prepared tiles operations and disparity
					  clt_parameters,
					  tp.clt_3d_passes.size() -1, // new, refine_pass+1, - just added from macro - VERIFY
					  true, // final boolean     save_textures,
					  0,                 // final int         clust_radius,
					  false, // clt_parameters.correlate_lma, // final boolean       run_lma, //
					  max_mismatch,      // final double      max_chn_diff, // filter correlation results by maximum difference between channels
					  mismatch_override, //final double      mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
					  threadsMax,        // maximal number of threads to launch
					  updateStatus,
					  debugLevel);
			  if (show_init_refine && (debugLevel >-2)) tp.showScan(
					  tp.clt_3d_passes.get(tp.clt_3d_passes.size() -1), // CLTPass3d   scan,
					  "after_measure_macro-"+tp.clt_3d_passes.size());
			  if (debugLevel > -1){
				  System.out.println("CLTMeasure("+(tp.clt_3d_passes.size() -1)+")");
			  }
    	  }

    	  // Restore pair selection and minimize them for scanning, then restore;
    	  if (!clt_parameters.gr_reduce_sngl) {
    		  clt_parameters.img_dtt.setMcorr(getNumSensors(), save_pairs_selection); // restore
    	  }
//    	  clt_parameters.correlate_lma = save_run_lma; // restore *********
    	  
    	  
// Manually debug here for various tiles
    	  int dbg_tileX = 58; // 42;
    	  int dbg_tileY = 48; // 28;
    	  boolean break_now = true;
    	  while (!break_now && debug_graphic) {
    		  tp.printScans(
    				  tp.clt_3d_passes,        // final ArrayList <CLTPass3d> passes,
    				  0, // bg_pass,           // final int                   firstPass,
    				  tp.clt_3d_passes.size(), // final int                   lastPassPlus1,
    				  false,                   // 	final boolean               use_lma,
    				  fom_min_strength,         // final double                min_strength,
    				  fom_adisp,               // final double     fom_adisp,     // 0.5
    				  fom_cdiff,               // final double     fom_cdiff,     // 0.02
    				  fom_inf_bonus,           // final double     fom_inf_bonus, // 0.2; // add this to infinity FOM (if it is closer than fom_inf_range)
    				  fom_inf_range,           // final double     fom_inf_range, // 0.5;
    				  dbg_tileX,               // 	 final int                   tileX,
    				  dbg_tileY);              //     					 final int                   tileY
    		  CLTPass3d combo_pass = tp.compositeScan(
					  tp.clt_3d_passes,           // final ArrayList <CLTPass3d> passes,
					  bg_pass,                    // final int                   firstPass,
					  tp.clt_3d_passes.size(),    // final int                   lastPassPlus1,
 //					  tp.getTrustedCorrelation(), // final double                trustedCorrelation,
					  fom_min_strength,           // final double                fom_min_strength,
					  false,                      // final boolean               fom_use_lma,
					  fom_adisp,                  // final double                fom_adisp,     // 0.5
					  fom_cdiff,                  // final double                fom_cdiff,     // 0.02
					  fom_inf_bonus,              // final double                fom_inf_bonus, // 0.2; // add this to infinity FOM (if it is closer than fom_inf_range)
					  fom_inf_range,              // final double                fom_inf_range, // 0.5;
					  true,                       // final boolean               copyDebug)
					  debugLevel);
    		  tp.showScan( combo_pass, "combo_after_all_measured-"+tp.clt_3d_passes.size());
    		  if (break_now) {
    			  break;
    		  }
    	  }
    	  
/// Refining after all added
		  if (debugLevel > -3){
			  System.out.println("---- Refining after all added , combining all previous measurements ----");
		  }
		  // first - combine all measured before (that was missing)
		  CLTPass3d combo_pass = null;
		  if (lwir2022) {
			  combo_pass = tp.compositeScan(
					  tp.clt_3d_passes,           // final ArrayList <CLTPass3d> passes,
					  bg_pass,                    // final int                   firstPass,
					  tp.clt_3d_passes.size(),    // final int                   lastPassPlus1,
					  fom_min_strength,           // final double                fom_min_strength,
					  false,                      // final boolean               fom_use_lma,
					  fom_adisp,                  // final double                fom_adisp,     // 0.5
					  fom_cdiff,                  // final double                fom_cdiff,     // 0.02
					  fom_inf_bonus,              // final double                fom_inf_bonus, // 0.2; // add this to infinity FOM (if it is closer than fom_inf_range)
					  fom_inf_range,              // final double                fom_inf_range, // 0.5;
					  true,                       // final boolean               copyDebug)
					  debugLevel);
		  } else {
			  combo_pass = tp.compositeScan(
					  tp.clt_3d_passes, // final ArrayList <CLTPass3d> passes,
					  bg_pass, //  final int                   firstPass,
					  tp.clt_3d_passes.size(),                          //  final int                   lastPassPlus1,
					  tp.getTrustedCorrelation(),                       // 	 final double                trustedCorrelation,
					  tp.getMaxOverexposure(),                          //  final double                max_overexposure,
					  0.0, // clt_parameters.bgnd_range,                //	 final double                disp_far,   // limit results to the disparity range
					  clt_parameters.grow_disp_max,                     // final double                disp_near,
					  clt_parameters.combine_min_strength,              // final double                minStrength,
					  clt_parameters.combine_min_hor,                   // final double                minStrengthHor,
					  clt_parameters.combine_min_vert,                  // final double                minStrengthVert,
					  false,                                            // final boolean               no_weak,
					  false,                                            // final boolean               use_last,   //
					  // TODO: when useCombo - pay attention to borders (disregard)
					  false,                                            // final boolean               usePoly)  // use polynomial method to find max), valid if useCombo == false
					  true,                                             // 	 final boolean               copyDebug)
					  debugLevel);
		  }
		  tp.clt_3d_passes.add(combo_pass);
		  if (debugLevel > -3){
			  System.out.println("---- Refining after all added , combined all previous mesurements ----");
		  }
		  if (show_init_refine) tp.showScan(
				  tp.clt_3d_passes.get(tp.clt_3d_passes.size() -1), // last scan (combo)
				  "combo_after_all_measured-"+tp.clt_3d_passes.size());
		  // now measure with LMA everything w/o filtering
		  // next adds to the list !
		  refine_pass = tp.clt_3d_passes.size(); // 1
		  CLTPass3d refined0 = tp.refinePassSetup( // prepare tile tasks for the refine pass (re-measure disparities)
				  combo_pass, // CLTPass3d         combo_pass,
				  debugLevel); // final int         debugLevel);
		  tp.clt_3d_passes.add(refined0);
		  if (show_init_refine && (debugLevel > -2)) tp.showScan(
				  tp.clt_3d_passes.get(tp.clt_3d_passes.size() -1), // last scan (combo)
				  "first_refined-"+tp.clt_3d_passes.size());

		  CLTMeasCorr( // perform single pass according to prepared tiles operations and disparity
				  clt_parameters,
				  tp.clt_3d_passes.size() - 1,// last one, combo
				  true, // final boolean     save_textures,
				  0,                 // final int         clust_radius,
				  clt_parameters.correlate_lma, // final boolean       run_lma, //
				  max_mismatch,      // final double      max_chn_diff, // filter correlation results by maximum difference between channels
				  mismatch_override, //final double      mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
				  threadsMax,        // maximal number of threads to launch
				  updateStatus,
				  debugLevel);
//		  if (debugLevel > -1){
		  if (debugLevel > -3){
			  System.out.println("After first LMA measure ("+(tp.clt_3d_passes.size() - 1)+")");
		  }
		  if (show_init_refine) {
			  tp.showScan(
				  tp.clt_3d_passes.get(tp.clt_3d_passes.size() - 1), // CLTPass3d   scan,
				  "after_first_lma_measure-"+tp.clt_3d_passes.size());
		  }
		  combo_pass = tp.compositeScan(
				  tp.clt_3d_passes,           // final ArrayList <CLTPass3d> passes,
				  bg_pass,                    // final int                   firstPass,
				  tp.clt_3d_passes.size(),    // final int                   lastPassPlus1,
				  fom_min_strength,           // final double                fom_min_strength,
				  true,                      // final boolean               fom_use_lma,
				  fom_adisp,                  // final double                fom_adisp,     // 0.5
				  fom_cdiff,                  // final double                fom_cdiff,     // 0.02
				  fom_inf_bonus,              // final double                fom_inf_bonus, // 0.2; // add this to infinity FOM (if it is closer than fom_inf_range)
				  fom_inf_range,              // final double                fom_inf_range, // 0.5;
				  true,                       // final boolean               copyDebug)
				  debugLevel);
		  tp.clt_3d_passes.add(combo_pass);
		  if (show_init_refine && (debugLevel >-2)) {
			  tp.showScan(
				  tp.clt_3d_passes.get(tp.clt_3d_passes.size() - 1), // CLTPass3d   scan,
				  "combo_after_first_lma-"+tp.clt_3d_passes.size());
		  }

		  
		  
		  // find out - why first pass corresponds to last scan step?
    	  // first ("before_makeUnique-41-" was empty)
		  for (int nnn = 0; nnn < clt_parameters.gr_num_refines; nnn ++){ //
	    	  if ((nnn == (clt_parameters.gr_num_refines - 1)) && clt_parameters.gr_all_last) {
	    		  clt_parameters.img_dtt.setMcorr(getNumSensors(), save_pairs_selection); // restore
	    	  }
			  refine_pass = tp.clt_3d_passes.size(); // 1
// refinePassSetup() - old one - try to update
			  CLTPass3d refined = tp.refinePassSetup( // prepare tile tasks for the refine pass (re-measure disparities)
					  //				  final double [][][]       image_data, // first index - number of image in a quad
					  clt_parameters,
					  0, // int               clust_radius,     // 0 - initial single-tile, 1 - 1x1 (same), 2 - 3x3, 3 5x5
					  clt_parameters.stUseRefine, // use supertiles
					  bg_pass,
					  // disparity range - differences from
					  clt_parameters.bgnd_range, // double            disparity_far,
					  clt_parameters.grow_disp_max, // other_range, //double            disparity_near,   //
					  clt_parameters.ex_strength,  // double            this_sure,        // minimal strength to be considered definitely good
					  clt_parameters.ex_nstrength, // double            ex_nstrength, // minimal 4-corr strength divided by channel diff for new (border) tiles
					  clt_parameters.bgnd_maybe, // double            this_maybe,       // maximal strength to ignore as non-background
					  clt_parameters.sure_smth, // sure_smth,        // if 2-nd worst image difference (noise-normalized) exceeds this - do not propagate bgnd
					  clt_parameters.pt_super_trust, //  final double      super_trust,      // If strength exceeds ex_strength * super_trust, do not apply ex_nstrength and plate_ds
					  // using plates disparity/strength - averaged for small square sets of tiles. If null - just use raw tiles
					  null, // 		final double [][] plate_ds,  // disparity/strength last time measured for the multi-tile squares. Strength =-1 - not measured. May be null
					  true, // 		final boolean     keep_raw_fg,  // do not replace raw tiles by the plates, if raw is closer (like poles)
					  0.0, // final double      scale_filtered_strength_pre, // scale plate_ds[1] before comparing to raw strength
					  0.0, // final double      scale_filtered_strength_post,// scale plate_ds[1] when replacing raw (generally plate_ds is more reliable if it exists)
//					  ImageDtt.DISPARITY_INDEX_CM,  // index of disparity value in disparity_map == 2 (0,2 or 4)
					  geometryCorrection,
					  threadsMax,  // maximal number of threads to launch
					  updateStatus,
					  debugLevel); //2);
			  tp.clt_3d_passes.add(refined);
			  

			  ///    		  if (debugLevel > 1)
			  if (debug_graphic && (debugLevel > -2)) { // &&  (debugLevel > 0)
				  tp.showScan(
						  tp.clt_3d_passes.get(refine_pass), // CLTPass3d   scan,
						  "before_makeUnique-"+refine_pass);
			  }
			  int [] numLeftRemoved = tp.makeUnique(
					  tp.clt_3d_passes,                  // final ArrayList <CLTPass3d> passes,
					  0,                                 //  final int                   firstPass,
					  refine_pass, // - 1,                   //  final int                   lastPassPlus1,
					  0, // 	 final int                   clust_radius,     // 0 - initial single-tile, 1 - 1x1 (same), 2 - 3x3, 3 5x5
					  tp.clt_3d_passes.get(refine_pass), //  final CLTPass3d             new_scan,
					  clt_parameters.grow_disp_max,       // final double                grow_disp_max,
					  clt_parameters.gr_unique_tol,   //  final double                unique_tolerance,
					  clt_parameters.show_unique);      // final boolean               show_unique)
// temporary - set all to measure:
//			  refined.setTileOp(511);
			  
			  if (debugLevel > -1){
				  System.out.println("cycle makeUnique("+refine_pass+") -> left: "+numLeftRemoved[0]+", removed:" + numLeftRemoved[1]);
			  }
			  if (debug_graphic && (debugLevel > -3)) { // (show_init_refine && (debugLevel >0)) {
				  tp.showScan(
					  tp.clt_3d_passes.get(refine_pass), // CLTPass3d   scan,
					  "after_refinePassSetup-"+tp.clt_3d_passes.size());
			  }
			  
// first time - last scan step????
			  if (lwir2022) {
				  CLTMeasCorr( // perform single pass according to prepared tiles operations and disparity
						  clt_parameters,
						  refine_pass,
						  true, // final boolean     save_textures,
						  0,                   // final int         clust_radius,
						  clt_parameters.correlate_lma, // final boolean       run_lma, //
						  max_mismatch,        // final double      max_chn_diff, // filter correlation results by maximum difference between channels
						  mismatch_override,   //final double      mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
						  threadsMax,          // maximal number of threads to launch
						  updateStatus,
						  debugLevel);
			  } else {
				  CLTMeasCorr( // perform single pass according to prepared tiles operations and disparity
						  clt_parameters,
						  refine_pass,
						  true, // final boolean     save_textures,
						  0,                 // final int         clust_radius,
						  clt_parameters.correlate_lma, // final boolean       run_lma, //
						  0,              // final double      max_chn_diff, // filter correlation results by maximum difference between channels
						  -1,                //final double      mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
						  threadsMax,  // maximal number of threads to launch
						  updateStatus,
						  debugLevel);
			  }
//			  if (debugLevel > -1){
			  if (debugLevel > -2){
				  System.out.println("?.CLTMeasure("+refine_pass+")");
			  }
			  if (show_init_refine) tp.showScan(
					  tp.clt_3d_passes.get(refine_pass), // CLTPass3d   scan,
					  "after_measure-"+tp.clt_3d_passes.size());
//
			  if (lwir2022) {
				  combo_pass = tp.compositeScan(
						  tp.clt_3d_passes,           // final ArrayList <CLTPass3d> passes,
						  bg_pass,                    // final int                   firstPass,
						  tp.clt_3d_passes.size(),    // final int                   lastPassPlus1,
						  fom_min_strength,           // final double                fom_min_strength,
						  true,                       // final boolean               fom_use_lma,
						  fom_adisp,                  // final double                fom_adisp,     // 0.5
						  fom_cdiff,                  // final double                fom_cdiff,     // 0.02
						  fom_inf_bonus,              // final double                fom_inf_bonus, // 0.2; // add this to infinity FOM (if it is closer than fom_inf_range)
						  fom_inf_range,              // final double                fom_inf_range, // 0.5;
						  true,                       // final boolean               copyDebug)
						  debugLevel);
			  } else {
				  combo_pass = tp.compositeScan(
						  tp.clt_3d_passes, // final ArrayList <CLTPass3d> passes,
						  bg_pass, //  final int                   firstPass,
						  tp.clt_3d_passes.size(),                          //  final int                   lastPassPlus1,
						  tp.getTrustedCorrelation(),                       // 	 final double                trustedCorrelation,
						  tp.getMaxOverexposure(),                          //  final double                max_overexposure,
						  0.0, // clt_parameters.bgnd_range,                //	 final double                disp_far,   // limit results to the disparity range
						  clt_parameters.grow_disp_max,                     // final double                disp_near,
						  clt_parameters.combine_min_strength,              // final double                minStrength,
						  clt_parameters.combine_min_hor,                   // final double                minStrengthHor,
						  clt_parameters.combine_min_vert,                  // final double                minStrengthVert,
						  false,                                            // final boolean               no_weak,
						  false,                                            // final boolean               use_last,   //
						  // TODO: when useCombo - pay attention to borders (disregard)
						  false,                                            // final boolean               usePoly)  // use polynomial method to find max), valid if useCombo == false
						  true,                                             // 	 final boolean               copyDebug)
						  debugLevel);
			  }
			  tp.clt_3d_passes.add(combo_pass);
		  }
		  if (show_init_refine && (debugLevel >-2)) {
			  tp.showScan(
				  tp.clt_3d_passes.get(tp.clt_3d_passes.size() - 1), // CLTPass3d   scan,
				  "before multi-tile-"+tp.clt_3d_passes.size());
		  }
		  
		  // create and measure several variable-cluster scans from the same single-tile combo_pass
//		  CLTPass3d 
		  combo_pass = tp.clt_3d_passes.get(tp.clt_3d_passes.size() - 1); // last pass created by tp.compositeScan
    	  break_now = true;
    	  while (!break_now && debug_graphic) {
    		  tp.printScans(
    				  tp.clt_3d_passes,        // final ArrayList <CLTPass3d> passes,
    				  0, // bg_pass,           // final int                   firstPass,
    				  tp.clt_3d_passes.size(), // final int                   lastPassPlus1,
    				  true,                    // final boolean               use_lma,
    				  fom_min_strength,        // final double                min_strength,
    				  fom_adisp,               // final double     fom_adisp,     // 0.5
    				  fom_cdiff,               // final double     fom_cdiff,     // 0.02
    				  fom_inf_bonus,           // final double     fom_inf_bonus, // 0.2; // add this to infinity FOM (if it is closer than fom_inf_range)
    				  fom_inf_range,           // final double     fom_inf_range, // 0.5;
    				  dbg_tileX,               // 	 final int                   tileX,
    				  dbg_tileY);              //     					 final int                   tileY
    	  }  
		  
		  
		  
//		  int max_clust_radius =  4; // 7x7
		  CLTPass3d [] combo_multi = new CLTPass3d[gr_max_clust_radius+1];
		  combo_multi[0] = combo_pass;
		  int max_expand_radius = 0; // max_clust_radius;
		  for (int clust_radius = 2; clust_radius <= gr_max_clust_radius; clust_radius++) {
	    	  if (clt_parameters.gr_reduce_multi) {
		    	  clt_parameters.img_dtt.setMcorr(getNumSensors(), 0 ); // remove all
		    	  clt_parameters.img_dtt.setMcorrNeib(getNumSensors(),true);
		    	  clt_parameters.img_dtt.setMcorrSq  (getNumSensors(),true); // remove even more?
		    	  clt_parameters.img_dtt.setMcorrDia (getNumSensors(),true); // remove even more?
	    	  }
			  
			  // using combo_pass (latest)
			  int num_added = 0;
			  if (clust_radius < max_expand_radius) { // disabled currently by int max_expand_radius = 0;
				  num_added = tp.expandCertainMulti ( 
					  combo_pass, // CLTPass3d             combo_pass, // modify
					  tp.clt_3d_passes, //ArrayList <CLTPass3d> passes,
					  bg_pass, //  int                   firstPass,
					  tp.clt_3d_passes.size(), //	 int                   lastPassPlus1,
					  0.5, //  double                disp_avg_arange1, // average neighbors with disparity not more than that from the lowest 
					  0.1, // double                disp_avg_rrange1, // same, relative to disparity
					  1.0, // double                disp_avg_arange2, // average neighbors with disparity not more than that from the lowest 
					  0.2, // double                disp_avg_rrange2, // same, relative to disparity
					  1.0, //                 disp_arange,     // look for a fit within range from the neighbor 
					  0.1, // double                disp_rrange,     // same, relative to disparity
					  "single-", // String                title_prefix,
					  0); // debugLevl = 1 for low number of images (6) 
			  }
			  System.out.println("Added "+num_added+" tiles before cluster radius "+clust_radius);
			  boolean [] has_lma = combo_pass.getLMA();
			  double [] disparity = combo_pass.getDisparity(1); // calc_disparity (skip NaN!)
			  for (int nnn = 0; nnn < clt_parameters.gr_num_refines; nnn ++){ //
		    	  if ((nnn == (clt_parameters.gr_num_refines - 1)) && clt_parameters.gr_all_last) {
		    		  clt_parameters.img_dtt.setMcorr(getNumSensors(), save_pairs_selection); // restore
		    	  }
				  refine_pass = tp.clt_3d_passes.size();
				  CLTPass3d refined_multi = tp.refinePassSetupMulti( // prepare tile tasks for the second pass based on the previous one(s)
						  combo_pass, // CLTPass3d         combo_pass,
						  clust_radius, // int               clust_radius,     // 0 - initial single-tile, 1 - 1x1 (same), 2 - 3x3, 3 5x5
						  0, // clust_radius - 1, // 	int               shrink_from_defined,

						  threadsMax,  // maximal number of threads to launch
						  updateStatus,
						  debugLevel);
				  tp.clt_3d_passes.add(refined_multi);

				  if (debugLevel > -1)
					  tp.showScan(
							  tp.clt_3d_passes.get(refine_pass), // CLTPass3d   scan,
							  "refined_multi_"+clust_radius+"before_makeUnique"); //OK
				  int [] numLeftRemoved = tp.makeUnique( // 
						  tp.clt_3d_passes,                  // final ArrayList <CLTPass3d> passes,
						  0,                                 //  final int                   firstPass,
						  refine_pass, // - 1,               //  final int                   lastPassPlus1,
						  clust_radius,                      //	 final int                   clust_radius,     // 0 - initial single-tile, 1 - 1x1 (same), 2 - 3x3, 3 5x5
						  tp.clt_3d_passes.get(refine_pass), //  final CLTPass3d             new_scan,
						  clt_parameters.grow_disp_max,       // final double                grow_disp_max,
						  clt_parameters.gr_unique_tol,   //  final double                unique_tolerance,
						  clt_parameters.show_unique);      // final boolean               show_unique)
				  
				  if (debugLevel > -2){
					  System.out.println("makeUnique("+refine_pass+") -> left: "+numLeftRemoved[0]+", removed: " + numLeftRemoved[1]);
				  }
				  if (show_init_refine) tp.showScan(
						  tp.clt_3d_passes.get(refine_pass), // CLTPass3d   scan,
						  "after_refinePassSetup-"+tp.clt_3d_passes.size());
				  CLTMeasCorr( // perform single pass according to prepared tiles operations and disparity
							  clt_parameters,
							  refine_pass,
							  false,          // true,           // final boolean     save_textures,
							  clust_radius,   // final int         clust_radius,
	    					  clt_parameters.correlate_lma, // final boolean       run_lma, //
	    					  0,              // final double      max_chn_diff, // filter correlation results by maximum difference between channels
	    					  -1,                //final double      mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
							  threadsMax,
							  updateStatus,
							  debugLevel);
				  if (show_init_refine && (debugLevel > -2)) tp.showScan(
						  tp.clt_3d_passes.get(refine_pass), // CLTPass3d   scan,
						  "after_measure-"+tp.clt_3d_passes.size());
				  
				  if (lwir2022) {
					  combo_multi[clust_radius-1] = tp.compositeScan(
							  tp.clt_3d_passes,           // final ArrayList <CLTPass3d> passes,
							  bg_pass,                    // final int                   firstPass,
							  tp.clt_3d_passes.size(),    // final int                   lastPassPlus1,
							  fom_min_strength,           // final double                fom_min_strength,
							  true,                       // final boolean               fom_use_lma,
							  fom_adisp,                  // final double                fom_adisp,     // 0.5
							  fom_cdiff,                  // final double                fom_cdiff,     // 0.02
							  fom_inf_bonus,              // final double                fom_inf_bonus, // 0.2; // add this to infinity FOM (if it is closer than fom_inf_range)
							  fom_inf_range,              // final double                fom_inf_range, // 0.5;
							  true,                       // final boolean               copyDebug)
							  debugLevel);
				  } else {
					  combo_multi[clust_radius-1] = tp.compositeScan(
							  tp.clt_3d_passes, // final ArrayList <CLTPass3d> passes,
							  bg_pass, //  final int                   firstPass,
							  tp.clt_3d_passes.size(),                          //  final int                   lastPassPlus1,
							  tp.getTrustedCorrelation(),                       // 	 final double                trustedCorrelation,
							  tp.getMaxOverexposure(),                          //  final double                max_overexposure,
							  -0.5, // 0.0, // clt_parameters.bgnd_range,                //	 final double                disp_far,   // limit results to the disparity range
							  clt_parameters.grow_disp_max,                     // final double                disp_near,
							  clt_parameters.combine_min_strength,              // final double                minStrength,
							  clt_parameters.combine_min_hor,                   // final double                minStrengthHor,
							  clt_parameters.combine_min_vert,                  // final double                minStrengthVert,
							  false,                                            // final boolean               no_weak,
							  false,                                            // final boolean               use_last,   //
							  // TODO: when useCombo - pay attention to borders (disregard)
							  false,                                            // final boolean               usePoly)  // use polynomial method to find max), valid if useCombo == false
							  true,                                             // 	 final boolean               copyDebug)
							  debugLevel);
				  }
				  double [] disparity_LMA = combo_multi[clust_radius-1].getDisparityLMA();
				  for (int i = 0; i< disparity.length; i++) {
					  if (!Double.isNaN(disparity_LMA[i])) {
						  disparity[i] = disparity_LMA[i]; // update only measured, others will be removed by makeUnique
					  }
				  }
				  if (show_init_refine && (debugLevel > -2)) tp.showScan(
						  combo_multi[clust_radius-1], // CLTPass3d   scan,
						  "combo_multi-"+clust_radius+"-pass"+nnn);
				  
				  tp.clt_3d_passes.add(combo_multi[clust_radius-1] );
				  combo_pass = combo_multi[clust_radius-1];
				  // combine tasks from (original) combo_pass and combo_pass_multi (use its combo_disparity, but saved getLMA from original combo_pass ?
				  // when done, grow to fill gaps from 3x3 first (by 1 step) and from 5x5 - second?(by 2 steps), ...
			  }
		  }
		  // Restore pairs selection
		  clt_parameters.img_dtt.setMcorr(getNumSensors(), save_pairs_selection); // restore
		  if (clt_parameters.gr_nan_bg) {
			  if (debugLevel > -2) {
				  tp.showScan(
						  tp.clt_3d_passes.get(bg_pass), // CLTPass3d   scan,
						  "bg_passs-"+tp.clt_3d_passes.size());
				  tp.showScan(
						  tp.clt_3d_passes.get(tp.clt_3d_passes.size()-1), // CLTPass3d   scan,
						  "last_pass-"+tp.clt_3d_passes.size());
				  tp.showScan(
						  tp.clt_3d_passes.get(tp.clt_3d_passes.size()-2), // CLTPass3d   scan,
						  "pre-last_pass-"+tp.clt_3d_passes.size());
			  }
		  }
    	  break_now = true;
    	  while (!break_now && debug_graphic) {
    		  tp.printScans(
    				  tp.clt_3d_passes,        // final ArrayList <CLTPass3d> passes,
    				  0, // bg_pass,           // final int                   firstPass,
    				  tp.clt_3d_passes.size(), // final int                   lastPassPlus1,
    				  true,                    // final boolean               use_lma,
    				  fom_min_strength,        // final double                min_strength,
    				  fom_adisp,               // final double     fom_adisp,     // 0.5
    				  fom_cdiff,               // final double     fom_cdiff,     // 0.02
    				  fom_inf_bonus,           // final double     fom_inf_bonus, // 0.2; // add this to infinity FOM (if it is closer than fom_inf_range)
    				  fom_inf_range,           // final double     fom_inf_range, // 0.5;
    				  dbg_tileX,               // 	 final int                   tileX,
    				  dbg_tileY);              //     					 final int                   tileY
    	  }  
		  
		  if (clt_parameters.gr_exp_certain) {
			  int num_added = tp.expandCertainMulti ( 
					  combo_pass, // CLTPass3d             combo_pass, // modify
					  tp.clt_3d_passes, //ArrayList <CLTPass3d> passes,
					  bg_pass, //  int                   firstPass,
					  tp.clt_3d_passes.size(), //	 int                   lastPassPlus1,
					  0.5, //  double                disp_avg_arange1, // average neighbors with disparity not more than that from the lowest 
					  0.1, // double                disp_avg_rrange1, // same, relative to disparity
					  1.0, // double                disp_avg_arange2, // average neighbors with disparity not more than that from the lowest 
					  0.2, // double                disp_avg_rrange2, // same, relative to disparity
					  1.0, //                 disp_arange,     // look for a fit within range from the neighbor 
					  0.1, // double                disp_rrange,     // same, relative to disparity
					  "single-", // String                title_prefix,
					  0); // int                   debugLevel) // 1 - show results 2 - show stages 3 - show all 
			  System.out.println("Added "+num_added+" tiles after cluster radius scan");
			  if (debugLevel > -2) tp.showScan(
					  combo_pass, // CLTPass3d   scan,
					  "after_multi-tile_disparity_extension");
		  }
		  // copy second_max from the BG pass to the last one (to be used)
		  tp.clt_3d_passes.get(tp.clt_3d_passes.size()-1).setSecondMax(tp.clt_3d_passes.get(bg_pass).getSecondMax());
		  tp.clt_3d_passes.get(tp.clt_3d_passes.size()-1).setAvgVal(tp.clt_3d_passes.get(bg_pass).getAvgVal());
 ///// Refining after all added   - end
		  Runtime.getRuntime().gc();
	      System.out.println("preExpandCLTQuad3d(): processing scene "+getImageName()+" finished at "+
			  IJ.d2s(0.000000001*(System.nanoTime()-this.startStepTime),3)+" sec, --- Free memory23="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
		  return true;
	  }

	  
	  
	  ArrayList <CLTPass3d> prepareDisparityScan( // USED in lwir
			  	double scan_start,
			  	double scan_step,
			  	int    scan_count){
		  ArrayList <CLTPass3d> measurements = new ArrayList <CLTPass3d>();
		  for (int si = 0; si < scan_count; si++ ) {
			  double disparity = scan_start + scan_step * si;
			  CLTPass3d pass = new CLTPass3d(tp, 0 );
			  int op = ImageDtt.setImgMask(0, 0xf);
			  op =     ImageDtt.setPairMask(op,0xf);
			  op =     ImageDtt.setForcedDisparity(op,true);
			  pass.disparity = tp.setSameDisparity(disparity);
			  pass.tile_op = tp.setSameTileOp(op);
			  measurements.add(pass);
		  }
		  return measurements;
	  }
	
	  public void extrinsics_prepare( // USED in lwir TODO: provide boolean 
			  CLTParameters           clt_parameters,
			  double inf_min, //  = -1.0;
			  double inf_max, //  =  1.0;
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel)
	  {
		  final boolean    batch_mode = clt_parameters.batch_run;
		  int debugLevelInner =  batch_mode ? -5: debugLevel;
		  int bg_scan = 0;
		  int combo_scan= tp.clt_3d_passes.size()-1;


		  if (!batch_mode && clt_parameters.show_extrinsic && (debugLevel >-1)) {
			  //		  if (!batch_mode && (debugLevel >-1)) {
			  tp.showScan(
					  tp.clt_3d_passes.get(bg_scan),   // CLTPass3d   scan,
					  "bg_scan"); //String title)
			  tp.showScan( // selected is null
					  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
					  "combo_scan-"+combo_scan); //String title)
		  }
		  
/*		  tp.showScan(
				  tp.clt_3d_passes.get(bg_scan),   // CLTPass3d   scan,
				  "bg_scan"); //String title)
*/		  
// combo_scan: normStrength - junk. Is it used?
		  boolean [] bg_sel = null;
		  boolean [] bg_use = null;
		  double [] combo_disp = null;
		  double [] combo_str = null;
		  boolean [] combo_use = null;
		  double [] combo_overexp = null;
		  int num_combo = 0 ;
		  double [][] filtered_bgnd_disp_strength = tp.getFilteredDisparityStrength(
				  tp.clt_3d_passes, // final ArrayList <CLTPass3d> passes,// List, first, last - to search for the already tried disparity
				  bg_scan,          // final int        measured_scan_index, // will not look at higher scans
				  0,                  //	final int        start_scan_index,
				  null , // final boolean [] bg_tiles,          // get from selected in clt_3d_passes.get(0);
				  0.0,   // whatever as null above //  clt_parameters.ex_min_over,// final double     ex_min_over,       // when expanding over previously detected (by error) background, disregard far tiles

				  ImageDtt.DISPARITY_INDEX_CM,       // final int        disp_index,
				  ImageDtt.DISPARITY_STRENGTH_INDEX, // final int        str_index,
				  null,                    // final double []  tiltXY,    // null - free with limit on both absolute (2.0?) and relative (0.2) values
				  0.5, // clt_parameters.fcorr_inf_diff, // tp.getTrustedCorrelation(),//	final double     trustedCorrelation,
				  clt_parameters.fcorr_inf_strength,     //	final double     strength_floor, 0.12
				  clt_parameters.inf_str_pow,       // final double     strength_pow,
				  clt_parameters.ly_smpl_side,           // final int        smplSide, //        = 2;      // Sample size (side of a square)
				  clt_parameters.ly_smpl_num,            // final int        smplNum, //         = 3;      // Number after removing worst (should be >1)
				  clt_parameters.ly_smpl_rms,            //	final double     smplRms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
				  0.0,                                   // smplRelRms,         // final double     smplRelRms, //      = 0.005;  // Maximal RMS/disparity in addition to smplRms
				  clt_parameters.fds_smpl_wnd,            //	final boolean    smplWnd, //
				  clt_parameters.fds_abs_tilt,       //	final double     max_abs_tilt, //  = 2.0; // pix per tile
				  clt_parameters.fds_rel_tilt,       //	final double     max_rel_tilt, //  = 0.2; // (pix / disparity) per tile
				  clt_parameters.tileX, //   dbg_x,              //	final int        dbg_x,
				  clt_parameters.tileX, //  dbg_y,              // final int        dbg_y,
				  debugLevelInner);        //	final int        debugLevel)

		  // prepare re-measurements of background
		  bg_sel = tp.clt_3d_passes.get(bg_scan).getSelected();
		  bg_use = new boolean [bg_sel.length];
		  //		  double  [] bg_disp = tp.clt_3d_passes.get(bg_scan).getDisparity(0);
		  double [] bg_str =  tp.clt_3d_passes.get(bg_scan).getStrength();
		  double [] bg_overexp = tp.clt_3d_passes.get(bg_scan).getOverexposedFraction();
		  for (int  nTile = 0 ; nTile < bg_use.length; nTile++) {
			  if (bg_sel[nTile] &&
////					  ((filtered_bgnd_disp_strength[1][nTile] > 0.0) || (bg_str[nTile] > 1.25 * clt_parameters.fcorr_inf_strength)) &&
					  (filtered_bgnd_disp_strength[1][nTile] > 0.0) &&
					  (bg_str[nTile] > clt_parameters.fcorr_inf_strength) && // 0.13
					  ((bg_overexp == null) || (bg_overexp[nTile] < clt_parameters.lym_overexp)) //1e-4
					  ){
				  bg_use[nTile] = true;
			  }
		  }
		  if (!batch_mode && clt_parameters.show_extrinsic && (debugLevel >-1)) { // if (true) 
			  String [] dbg_titles = {"fdisp", "fstr", "disp", "str", "overexp","sel","use"};
			  double [][] ddd = {filtered_bgnd_disp_strength[0],filtered_bgnd_disp_strength[1],null,bg_str,bg_overexp, null, null};
			  ddd[5] = new double [bg_sel.length];
			  ddd[6] = new double [bg_sel.length];
			  for (int  nTile = 0 ; nTile < bg_use.length; nTile++) {
				  ddd[5][nTile] = bg_sel[nTile]?1.0:0.0;
				  ddd[6][nTile] = bg_use[nTile]?1.0:0.0;
			  }
			  ShowDoubleFloatArrays.showArrays(
				  ddd,
				  tp.getTilesX(),
				  tp.getTilesY(),
				  true,
				  "filtered_bgnd_disp_strength",
				  dbg_titles);
		  }
		  int num_bg = tp.clt_3d_passes.get(bg_scan).setTileOpDisparity( // other minimal strength?
				  bg_sel, // bg_use, // bg_sel, // bg_use, // boolean [] selection, measure all that can be bg
				  null); // double []  disparity); // null for 0

		  // Prepare measurement of combo-scan - remove low strength and what was used for background
		  combo_disp = tp.clt_3d_passes.get(combo_scan).getDisparity(0);
		  combo_str =  tp.clt_3d_passes.get(combo_scan).getStrength();
		  combo_use = new boolean [bg_sel.length];
		  combo_overexp = tp.clt_3d_passes.get(combo_scan).getOverexposedFraction();
		  for (int  nTile = 0 ; nTile < bg_use.length; nTile++) {
			  if (!bg_use[nTile] &&
					  (combo_str[nTile] > clt_parameters.fcorr_inf_strength) && // same fcorr_inf_strength
					  ((combo_overexp == null) || (combo_overexp[nTile] < clt_parameters.lym_overexp))
					  ){ // other minimal strength?
				  combo_use[nTile] = true;
			  }
		  }
		  num_combo = tp.clt_3d_passes.get(combo_scan).setTileOpDisparity(
				  combo_use, // boolean [] selection,
				  combo_disp); // double []  disparity);
		  if (debugLevel > -3) { // -1
			  System.out.println("Number of background tiles = " + num_bg+", number of lazy eye tiles = " + num_combo);
		  }

		  CLTMeasureCorr( // perform single pass according to prepared tiles operations and disparity
				  clt_parameters,
				  combo_scan,
				  false, // final boolean     save_textures,
				  0,                 // final int         clust_radius,
				  tp.threadsMax,  // maximal number of threads to launch
				  false, // updateStatus,
				  debugLevelInner - 1);
		  if (!batch_mode && clt_parameters.show_extrinsic && (debugLevel >-3))  {
			  tp.showScan(
					  tp.clt_3d_passes.get(bg_scan),   // CLTPass3d   scan, badly filtered?
					  "bg_scan_post"); //String title)
			  tp.showScan(
					  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
					  "combo_scan-"+combo_scan+"_post"); //String title)
			  tp.showScan(
					  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
					  "combo_measured_scan-"+combo_scan+"_post", //String title)
					  true); // measured only
		  }

		  double [][] filtered_combo_scand_isp_strength = tp.getFilteredDisparityStrength(
				  tp.clt_3d_passes, // final ArrayList <CLTPass3d> passes,// List, first, last - to search for the already tried disparity
				  combo_scan,          // final int        measured_scan_index, // will not look at higher scans
				  0,                  //	final int        start_scan_index,
				  null , // final boolean [] bg_tiles,          // get from selected in clt_3d_passes.get(0);
				  0.02,   // whatever as null above //  clt_parameters.ex_min_over,// final double     ex_min_over,       // when expanding over previously detected (by error) background, disregard far tiles

				  ImageDtt.DISPARITY_INDEX_CM,       // final int        disp_index,
				  ImageDtt.DISPARITY_STRENGTH_INDEX, // final int        str_index,
				  null,                    // final double []  tiltXY,    // null - free with limit on both absolute (2.0?) and relative (0.2) values
				  tp.getTrustedCorrelation(),//	final double     trustedCorrelation,
				  clt_parameters.fcorr_inf_strength,     //	final double     strength_floor,
				  clt_parameters.inf_str_pow,       // final double     strength_pow,
				  clt_parameters.ly_smpl_side,           // final int        smplSide, //        = 2;      // Sample size (side of a square)
				  clt_parameters.ly_smpl_num,            // final int        smplNum, //         = 3;      // Number after removing worst (should be >1)
				  clt_parameters.ly_smpl_rms,            //	final double     smplRms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
				  0.0,                                   // smplRelRms,         // final double     smplRelRms, //      = 0.005;  // Maximal RMS/disparity in addition to smplRms
				  clt_parameters.fds_smpl_wnd,            //	final boolean    smplWnd, //
				  clt_parameters.fds_abs_tilt,       //	final double     max_abs_tilt, //  = 2.0; // pix per tile
				  clt_parameters.fds_rel_tilt,       //	final double     max_rel_tilt, //  = 0.2; // (pix / disparity) per tile
				  clt_parameters.tileX, //   dbg_x,              //	final int        dbg_x,
				  clt_parameters.tileX, //  dbg_y,              // final int        dbg_y,
				  debugLevelInner);        //	final int        debugLevel)
		  // update selection after filtering

		  combo_disp = tp.clt_3d_passes.get(combo_scan).getDisparity(0);
		  combo_str =  tp.clt_3d_passes.get(combo_scan).getStrength();
		  combo_use = new boolean [bg_sel.length];
		  combo_overexp = tp.clt_3d_passes.get(combo_scan).getOverexposedFraction();
		  for (int  nTile = 0 ; nTile < bg_use.length; nTile++) {
			  if (!bg_sel[nTile] && // bg_use[nTile] &&  disable where may be BG
					  (combo_str[nTile] > clt_parameters.fcorr_inf_strength) &&
					  (filtered_combo_scand_isp_strength[1][nTile] > 0) &&
					  ((combo_overexp == null) || (combo_overexp[nTile] < clt_parameters.lym_overexp))
					  ){ // other minimal strength?
				  combo_use[nTile] = true;
			  }
		  }
		  if ( !batch_mode && clt_parameters.show_extrinsic && (debugLevel >-1+0)) { // true
			  String [] dbg_titles = {"fdisp", "fstr", "disp", "str", "overexp","sel","use"};
			  double [][] ddd = {filtered_combo_scand_isp_strength[0],filtered_combo_scand_isp_strength[1],combo_disp,combo_str,combo_overexp, null, null};
			  ddd[5] = new double [combo_use.length];
			  ddd[6] = new double [combo_use.length];
			  for (int  nTile = 0 ; nTile < combo_use.length; nTile++) {
				  //ddd[5][nTile] = bg_sel[nTile]?1.0:0.0;
				  ddd[6][nTile] = combo_use[nTile]?1.0:0.0;
			  }
			  ShowDoubleFloatArrays.showArrays(
					  ddd,
					  tp.getTilesX(),
					  tp.getTilesY(),
					  true,
					  "filtered_combo_scand_isp_strength",dbg_titles);
		  }
		  
		  
		  int num_combo1 = tp.clt_3d_passes.get(combo_scan).setTileOpDisparity( // GPU ==0 !
				  combo_use, // boolean [] selection,
				  combo_disp); // double []  disparity);
		  if (debugLevel > -3) { // -1
			  System.out.println("Updated number of lazy eye tiles = " + num_combo1+" (was "+num_combo+")");
		  }
		  if (!batch_mode && clt_parameters.show_extrinsic && (debugLevel >-1)) {
////		  if (clt_parameters.show_extrinsic && (debugLevel >-3)) {
			  String [] titles = {"bgnd_disp","bgnd_str","combo_disp","combo_str","bg_sel","bg_use","combo_use"};
			  double [] dbg_bg_sel = new double [bg_sel.length];
			  double [] dbg_bg_use =   new double [bg_sel.length];
			  double [] dbg_combo_use = new double [bg_sel.length];
			  for (int i= 0; i < bg_sel.length; i++) {
				  dbg_bg_sel[i] =    bg_sel[i]? 1.0:0.0; //only sky, no far mountains (too high disparity!)
				  dbg_bg_use[i] =    bg_use[i]? 1.0:0.0;
				  dbg_combo_use[i] = combo_use[i]? 1.0:0.0;
			  }
			  double [][]dbg_img = { // bg_use - all 0? (never assigned)?
					  filtered_bgnd_disp_strength[0],
					  filtered_bgnd_disp_strength[1],
					  filtered_combo_scand_isp_strength[0],
					  filtered_combo_scand_isp_strength[1],
					  dbg_bg_sel,
					  dbg_bg_use, // too few
					  dbg_combo_use};
			  ShowDoubleFloatArrays.showArrays(dbg_img,  tp.getTilesX(), tp.getTilesY(), true, "extrinsics_bgnd_combo",titles);
		  }
	  }	  
	  
	  
	  
	  /**
	   *
	   * @param clt_parameters
	   * @param adjust_poly
	   * @param threadsMax
	   * @param updateStatus
	   * @param debugLevel
	   * @return true on success, false - on failure
	   */
	  public boolean extrinsicsCLT( 
			  CLTParameters           clt_parameters,
			  boolean 		   adjust_poly,
			  double inf_min, //  = -1.0;
			  double inf_max, //  =  1.0;
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel) {
		  return extrinsicsCLT( 
				  clt_parameters,
				  null, // String           dbg_path, // if not null - read extrinsics_bgnd_combo file instead of extrinsics_prepare
				  adjust_poly,
				  inf_min, //  = -1.0;
				  inf_max, //  =  1.0;
				  threadsMax,  // maximal number of threads to launch
				  updateStatus,
				  debugLevel);
	  }
	  
	  public boolean getPreparedExtrinsics(String path) {
		  String [] titles =  {"bgnd_disp","bgnd_str","combo_disp","combo_str","bg_sel","bg_use","combo_use"};
		  int exp_slices = titles.length;
		  if (path == null) {return false;}
		  ImagePlus img_extrinsics_bgnd_combo = new ImagePlus(path);
		  ImageStack stack_extrinsics_bgnd_combo = img_extrinsics_bgnd_combo.getStack();
		  int nSlices=stack_extrinsics_bgnd_combo.getSize();
		  int width = img_extrinsics_bgnd_combo.getWidth();
		  int height = img_extrinsics_bgnd_combo.getHeight();
		  if (nSlices != exp_slices) {
			  throw new IllegalArgumentException ("getPreparedExtrinsics(): Expected "+exp_slices+" in "+path+", got "+nSlices);
		  }
		  double [][] data = new double [nSlices][width*height];
		  for (int slice = 0; slice < nSlices; slice ++) {
			  float [] pixels = (float []) stack_extrinsics_bgnd_combo.getPixels(slice+1);
			  for (int i = 0; i < pixels.length; i++) {
				  data[slice][i] = pixels[i];
			  }
		  }
		  
		  
		  
		  boolean [] bg_sel =    new boolean [width*height];
		  boolean [] bg_use =    new boolean [width*height];
		  boolean [] combo_use = new boolean [width*height];
		  for (int i = 0; i < bg_sel.length; i++) {
			  bg_sel[i] = data[4][i] > 0; // NaN OK
			  bg_use[i] = data[5][i] > 0; // NaN OK
			  combo_use[i] = data[6][i] > 0; // NaN OK
		  }
		  
		  CLTPass3d bg_scan =    new CLTPass3d(tp, 0 );
		  CLTPass3d combo_scan = new CLTPass3d(tp, 0 );
		  int op = ImageDtt.setImgMask(0, 0xf);
		  op =     ImageDtt.setPairMask(op,0xf);
		  op =     ImageDtt.setForcedDisparity(op,true);
		  bg_scan.setSelected(bg_sel);
		  combo_scan.setSelected(combo_use);
		  bg_scan.setStrength(data[1]); // will not be used
		  combo_scan.setStrength(data[3]);
		  for (int ty = 0; ty < height; ty++) {
			  for (int tx = 0; tx < width; tx++) {
				  int indx = ty*width+tx;
				  bg_scan.tile_op[ty][tx] =      bg_use[indx]? op: 0;
				  combo_scan.tile_op[ty][tx] =   combo_use[indx]? op: 0;
//				  bg_scan.disparity[ty][tx] =    bg_use[indx]? data[0][indx]: Double.NaN;
				  bg_scan.disparity[ty][tx] =    bg_use[indx]? 0.0: Double.NaN;
				  combo_scan.disparity[ty][tx] = combo_use[indx]? data[2][indx]: Double.NaN;
			  }
		  }
		  tp.clt_3d_passes.add(bg_scan);
		  tp.clt_3d_passes.add(combo_scan);
		  return true;
	  }
	  
	  // for now works only from file (using
	  public void updateScansForLY(
			  int bg_scan_indx,
			  int combo_scan_indx,
			  boolean top_bg,                  // all above bg is bg
			  int fill_gaps_bg,                // 1 - in 4 directions by 1, 2 - in 8 directions by 1,
			  int fill_gaps_combo,             // 1 - in 4 directions by 1, 2 - in 8 directions by 1,
			  boolean use_strength,            // weight average disparity by strength
			  double scale_derivative_strength // 1.0 - new strength - average of neibs, 0.5 - only 1/2 of neibs
		      ) {
		  int op = ImageDtt.setImgMask(0, 0xf);
		  op =     ImageDtt.setPairMask(op,0xf);
		  op =     ImageDtt.setForcedDisparity(op,true);
		  int width =  tp.getTilesX();
		  int height = tp.getTilesY();
		  CLTPass3d bg_scan =    tp.clt_3d_passes.get(bg_scan_indx);
		  CLTPass3d combo_scan = tp.clt_3d_passes.get(combo_scan_indx);
		  TileNeibs tn = new TileNeibs(width,height);
//		  int [][] bg_tile_op =    bg_scan.tile_op;
//		  int [][] combo_tile_op = combo_scan.tile_op;
		  boolean [] bg_sel =    bg_scan.getSelected(); 
		  boolean [] combo_sel = combo_scan.getLMA();
		  if (combo_sel == null) {
			  System.out.println("No has_lma is available");
			  combo_sel = combo_scan.getSelected();
			  if (combo_sel == null) {
				  System.out.println("No selected is available");
				  combo_sel = combo_scan.getSelectedOrTileOp();
			  }
		  }
		  double [] combo_disparity = new double [width*height];
		  {
			  int indx = 0;
			  for (int ty = 0; ty < height; ty++) {
				  for (int tx = 0; tx < width; tx++) {
					  combo_disparity[indx++] = combo_scan.disparity[ty][tx];
				  }
			  }
		  }
		  double [] combo_strength = null;
		  if (use_strength) {
			  combo_strength = combo_scan.getStrength();
		  }
		  
		  if (fill_gaps_bg > 0) {
			  tp.growTiles(
					  fill_gaps_bg,       // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
					  bg_sel,
					  null); // prohibit
		  }
		  if (top_bg) {
			  for (int indx = width; indx < bg_sel.length; indx++) {
				  if (bg_sel[indx]) {
					  int indx_up = tn.getNeibIndex(indx, TileNeibs.DIR_N);
					  if ((indx_up >= 0) && !bg_sel[indx_up]) {
						  while (indx_up >= 0) {
							  bg_sel[indx_up] = true;
							  indx_up = tn.getNeibIndex(indx_up, TileNeibs.DIR_N);
						  }
					  }
				  }
			  }
		  }
		  bg_scan.setSelected(bg_sel); // maybe not needed, as it is already the same array
		  // remove from combo all bg
		  for (int indx = 0; indx < bg_sel.length; indx++) {
			  combo_sel[indx] &= !bg_sel[indx]; // null pointer
		  }
		  // Fill gaps
		  for (; fill_gaps_combo > 0; fill_gaps_combo--) {
			  boolean [] sel_new = combo_sel.clone();
			  int num_sel =0;
			  for (int i = 0; i < sel_new.length; i++) if (sel_new[i]) num_sel++;
			  System.out.println("num_sel = "+num_sel);
			  tp.growTiles(
					  1,       // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
					  sel_new,
					  bg_sel); // prohibit
			  num_sel =0;
			  for (int i = 0; i < sel_new.length; i++) if (sel_new[i]) num_sel++;
			  System.out.println("num_sel grown = "+num_sel);
			  
			  for (int indx = 0; indx < combo_sel.length; indx++) {
				  if (!combo_sel[indx] && sel_new[indx]) {
					  double sum_w = 0.0;
					  double sum_wd = 0.0;
					  double sum_d = 0.0;
					  int num_neibs = 0;
					  for (int dir = 0; dir< 8; dir++) {
						  int indx1 = tn.getNeibIndex(indx, dir);
						  if ((indx1 >= 0) && combo_sel[indx1]) {
							  double w = (combo_strength != null) ? combo_strength[indx1]:1.0;
							  sum_w += w;
							  sum_d +=  combo_disparity[indx1];
							  sum_wd += w * combo_disparity[indx1];
							  num_neibs++;
						  }
					  }
					  // num_neibs should be > 0;
					  if (combo_strength != null) {
						  combo_strength[indx] = scale_derivative_strength * sum_w/num_neibs;
					  }
					  if (sum_w > 0) {
						  combo_disparity[indx] = sum_wd/sum_w;
					  } else {
						  combo_disparity[indx] = sum_d/num_neibs;
					  }
				  }
			  }
			  combo_sel = sel_new;
		  }
		  combo_scan.setSelected(combo_sel); // maybe not needed, as it is already the same array
		  // prepare tile_op and disparity for
		  for (int ty = 0; ty < height; ty++) {
			  for (int tx = 0; tx <  width; tx++) {
				  int indx = ty * width + tx;
				  bg_scan.tile_op[ty][tx] =      bg_sel[indx] ?    op: 0;
				  combo_scan.tile_op[ty][tx] =   combo_sel[indx] ? op: 0;
				  bg_scan.disparity[ty][tx] =    bg_sel[indx]?     0.0: Double.NaN;
				  combo_scan.disparity[ty][tx] = combo_sel[indx]?  combo_disparity[indx]: Double.NaN;
			  }
		  }
	  }
	  
	  public boolean extrinsicsCLT( 
			  CLTParameters           clt_parameters,
			  String           dbg_path, // if not null - read extrinsics_bgnd_combo file instead of extrinsics_prepare
			  boolean 		   adjust_poly,
			  double inf_min, //  = -1.0;
			  double inf_max, //  =  1.0;
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel)
	  {
		  boolean got_saved = getPreparedExtrinsics(dbg_path);
		  if (!got_saved) {
			  extrinsics_prepare( 
					  clt_parameters,
					  inf_min, //  = -1.0;
					  inf_max, //  =  1.0;
					  threadsMax,  // maximal number of threads to launch
					  updateStatus,
					  debugLevel);
		  }
		  
		  
		  final boolean    batch_mode = false; // clt_parameters.batch_run;
		  int debugLevelInner =  batch_mode ? -5: debugLevel;
		  boolean update_disp_from_latest = clt_parameters.lym_update_disp ; // true;
		  int max_tries =                   clt_parameters.lym_iter; // 25;
		  double min_sym_update =           clt_parameters.getLymChange(is_aux); //  4e-6; // stop iterations if no angle changes more than this
		  double min_poly_update =          clt_parameters.lym_poly_change; //  Parameter vector difference to exit from polynomial correction
		  int bg_scan = 0+0;
		  int combo_scan= tp.clt_3d_passes.size()-1;
	  
		  AlignmentCorrection ac = null;
		  if (!clt_parameters.ly_lma_ers ) {
			  ac = new AlignmentCorrection(this);
		  }
		  // iteration steps
		  if (!batch_mode && clt_parameters.show_extrinsic && (debugLevel >-1)) {
		  //if (clt_parameters.show_extrinsic && (debugLevel > -1)) { // temporary
			  tp.showScan(
					  tp.clt_3d_passes.get(bg_scan),   // CLTPass3d   scan,
					  "bg_scan_post"); //String title)
			  tp.showScan(
					  tp.clt_3d_passes.get(combo_scan+0),   // CLTPass3d   scan,
					  "combo_scan-"+combo_scan+"_post"); //String title)
//			  tp.showScan(
//					  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
//					  "combo_measured_scan-"+combo_scan+"_post"); //String title)
		  }
		  // Increase density before LY
		  if (clt_parameters.lym_mod_map) { // may not work when running directly, w/o getPreparedExtrinsics(dbg_path)
			  updateScansForLY(
					  bg_scan,                             // int bg_scan_indx,
					  combo_scan,                          // int combo_scan_indx,
					  clt_parameters.lym_top_bg ,          // boolean top_bg, // all above bg is bg
					  clt_parameters.lym_fill_gaps_bg ,    // int fill_gaps_combo, // 1 - in 4 directions by 1, 2 - in 8 directions by 1,
					  clt_parameters.lym_fill_gaps_combo , // int fill_gaps_bg,    // 1 - in 4 directions by 1, 2 - in 8 directions by 1,
					  clt_parameters.lym_use_strength ,    // boolean use_strength,
					  clt_parameters.lym_scale_deriv_str); // double scale_derivative_strength, // 1.0 - new strength - average of neibs, 0.5 - only 1/2 of neibs
		  }

		  if (!batch_mode && clt_parameters.show_extrinsic && (debugLevel >-1)) {
		  //if (clt_parameters.show_extrinsic && (debugLevel > -1)) { // temporary
			  tp.showScan(
					  tp.clt_3d_passes.get(bg_scan),   // CLTPass3d   scan,
					  "bg_scan_post_mod"); //String title)
			  tp.showScan(
					  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
					  "combo_scan-"+combo_scan+"_post_mod"); //String title)
//			  tp.showScan(
//					  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
//					  "combo_measured_scan-"+combo_scan+"_post_mod"); //String title)
		  }
		  
		  
		  
		  double comp_diff = min_sym_update + 1; // (> min_sym_update)
		  
		  for (int num_iter = 0; num_iter < max_tries; num_iter++){
			  if (update_disp_from_latest) {
				  tp.clt_3d_passes.get(combo_scan).updateDisparity();
			  }
			  if (clt_parameters.ly_lma_ers) { // next is implemented in both QuadCLTCPU and QuadCLT !
				  CLTMeasureLY( // perform single pass according to prepared tiles operations and disparity // USED in lwir
						  clt_parameters,
						  combo_scan,     // final int           scanIndex,
						  // only combine and calculate once, next passes keep
						  // remeasure each pass - target disparity is the same, but vector changes
						  bg_scan, // (num_iter >0)? -1: bg_scan,        // final int           bgIndex, // combine, if >=0
						  tp.threadsMax,  // maximal number of threads to launch
						  false, // updateStatus,
						  debugLevelInner -1); // - 1); // -5-1
///				  if (debugLevel > -2){
				  if (!batch_mode && clt_parameters.show_extrinsic && (debugLevel >-2)) {
					  tp.showScan(
							  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
							  "LY_combo_scan-"+combo_scan+"_post"); //String title)
					  tp.showScan(
							  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
							  "LY_measured_combo_scan-"+combo_scan+"_post", //String title)
							  true);
				  }

				  int tilesX = tp.getTilesX();
				  int tilesY = tp.getTilesY();
				  int cluster_size =clt_parameters.tileStep;
				  int clustersX= (tilesX + cluster_size - 1) / cluster_size;
				  int clustersY= (tilesY + cluster_size - 1) / cluster_size;


				  ExtrinsicAdjustment ea = new ExtrinsicAdjustment(
						  geometryCorrection, // GeometryCorrection gc,
						  clt_parameters.tileStep,   // int         clusterSize,
						  clustersX, // 	int         clustersX,
						  clustersY); // int         clustersY);

				  double [] old_new_rms = new double[2];
				  boolean apply_extrinsic = (clt_parameters.ly_corr_scale != 0.0);
				  CLTPass3d   scan = tp.clt_3d_passes.get(combo_scan);
				  // for the second half of runs (always for single run) - limit infinity min/max
				  double min_strength = 0.1; // 0.23;
				  int [] pfmt = {8, 3};
				  if (!batch_mode && clt_parameters.show_extrinsic && (debugLevel >-1)) {
					  ea.showInput(scan.getLazyEyeData(),"first_data");
					  System.out.println(ea.stringWeightedLY(
							  scan.getLazyEyeData(), // double [][] data,
							  null,                  // double [][] ref_data,
							  min_strength,          // double min_strength,
							  pfmt,                  // int [] format,
			                  "_00"));               // String suffix))
				  }
				  
				  boolean debug_actual_LY_derivs =  debugLevel > 9; // true
				  boolean                 use_tarz = false;
				  if (debug_actual_LY_derivs) {
					  debugLYDerivatives(
							  ea, // ExtrinsicAdjustment     ea,
							  combo_scan, //  int                     scanIndex,
							  clt_parameters, // CLTParameters           clt_parameters,
							  false, // boolean                 update_disparity, // re-measure disparity before measuring LY
							  threadsMax, // final int        threadsMax,  // maximal number of threads to launch
							  updateStatus, //final boolean    updateStatus,
							  0.01, // 0.001, // 3.3333E-3, // double           delta,
							  use_tarz, // boolean                 use_tarz,  // derivatives by tarz, notg symmetrical vectors
							  debugLevel); // final int        debugLevel)				  
				  }
				  
				  CorrVector corr_vector =   ea.solveCorr (
						  clt_parameters.ly_marg_fract, 	  // double      marg_fract,        // part of half-width, and half-height to reduce weights
						  clt_parameters.ly_inf_en,           // boolean     use_disparity,     // adjust disparity-related extrinsics
						  // 1.0 - to skip filtering infinity
						  inf_min, //double      inf_min_disparity, // minimal disparity for infinity 
						  inf_max, // double      inf_max_disparity, // minimal disparity for infinity
						  clt_parameters.ly_inf_min_broad, // inf_min_disp_abs,  // minimal disparity for infinity (absolute) 
						  clt_parameters.ly_inf_max_broad, // maximal disparity for infinity (absolute)
						  clt_parameters.ly_inf_tilt,      //   boolean     en_infinity_tilt,  // select infinity tiles form right/left tilted (false - from average)  
						  clt_parameters.ly_right_left,    //   boolean     infinity_right_left, // balance weights between right and left halves of infinity
						  
						  clt_parameters.ly_aztilt_en,        // boolean     use_aztilts,       // Adjust azimuths and tilts excluding disparity
						  clt_parameters.ly_diff_roll_en,     // boolean     use_diff_rolls,    // Adjust differential rolls (3 of 4 angles)
//						  clt_parameters.ly_inf_force,        // boolean     force_convergence, // if true try to adjust convergence (disparity, symmetrical parameter 0) even with no disparity
						  clt_parameters.ly_min_forced,       // int         min_num_forced,    // minimal number of clusters with forced disparity to use it
						  // data, using just radial distortions
						  clt_parameters.ly_com_roll,         // boolean     common_roll,       // Enable common roll (valid for high disparity range only)
						  clt_parameters.ly_focalLength ,     // boolean     corr_focalLength,  // Correct scales (focal length temperature? variations)
						  clt_parameters.ly_ers_rot,          // boolean     ers_rot,           // Enable ERS correction of the camera rotation
						  clt_parameters.getErsForw(),        // boolean     ers_forw,          // Enable ERS correction of the camera linear movement in z direction
						  clt_parameters.getErsSide(),        // boolean     ers_side,          // Enable ERS correction of the camera linear movement in x direction
						  clt_parameters.getErsVert(),        // boolean     ers_vert,          // Enable ERS correction of the camera linear movement in y direction
						  // add balancing-related here?
						  clt_parameters.ly_par_sel,          // 	int         manual_par_sel,    // Manually select the parameter mask bit 0 - sym0, bit1 - sym1, ... (0 - use boolean flags, != 0 - ignore boolean flags)
						  clt_parameters.ly_weight_infinity,     //0.3, // double      weight_infinity,     // 0.3, total weight of infinity tiles fraction (0.0 - 1.0) 
						  clt_parameters.ly_weight_disparity,    //0.0, // double      weight_disparity,    // 0.0 disparity weight relative to the sum of 8 lazy eye values of the same tile 
						  clt_parameters.ly_weight_disparity_inf,//0.5, // double      weight_disparity_inf,// 0.5 disparity weight relative to the sum of 8 lazy eye values of the same tile for infinity 
						  clt_parameters.ly_max_disparity_far,   //5.0, // double      max_disparity_far,   // 5.0 reduce weights of near tiles proportional to sqrt(max_disparity_far/disparity) 
						  clt_parameters.ly_max_disparity_use,   //5.0, // double      max_disparity_use,   // 5.0 (default 1000)disable near objects completely - use to avoid ERS
						  clt_parameters.ly_inf_min_dfe,         //1.75,// double      min_dfe, // = 1.75;
						  clt_parameters.ly_inf_max_dfe,         //5.0, // double      max_dfe, // = 5.0; // <=0 - disable feature

						  // moving objects filtering
						  clt_parameters.ly_moving_en,  // 	boolean     moving_en,         // enable filtering areas with potentially moving objects 
						  clt_parameters.ly_moving_apply,  // 	boolean     moving_apply,      // apply filtering areas with potentially moving objects 
						  clt_parameters.ly_moving_sigma,   // 	double      moving_sigma,      // blurring sigma for moving objects = 1.0;
						  clt_parameters.ly_max_mov_disparity,  //		double      max_mov_disparity, // disparity limit for moving objects detection = 75.0;
						  clt_parameters.ly_rad_to_hdiag_mov,   // 	double      rad_to_hdiag_mov,  // radius to half-diagonal ratio to remove high-distortion corners = 0.7 ; // 0.8
						  clt_parameters.ly_max_mov_average,   //		double      max_mov_average,   // do not attempt to detect moving objects if ERS is not accurate for terrain = .25;
						  clt_parameters.ly_mov_min_L2,  // 	double      mov_min_L2,        // threshold for moving objects = 0.75;

						  scan.getLazyEyeData(),              // dsxy, // double [][] measured_dsxy,
						  scan.getLazyEyeForceDisparity(),    // null, //	boolean [] force_disparity,    // boolean [] force_disparity,
						  false, // 	boolean     use_main, // corr_rots_aux != null;
						  geometryCorrection.getCorrVector(), // CorrVector corr_vector,
						  old_new_rms, // double [] old_new_rms, // should be double[2]
						  debugLevel); //  + 5);// int debugLevel) >=2 to show images

				  if (debugLevel > -2){
					  System.out.println("Old extrinsic corrections:");
					  System.out.println(geometryCorrection.getCorrVector().toString());
				  }
				  if (corr_vector != null) {
					  CorrVector diff_corr = corr_vector.diffFromVector(geometryCorrection.getCorrVector());
					  comp_diff = diff_corr.getNorm();

					  if (debugLevel > -2){
							  System.out.println("New extrinsic corrections:");
							  System.out.println(corr_vector.toString());
					  }
					  if (debugLevel > -3){
							  System.out.println("Increment extrinsic corrections:");
							  System.out.println(diff_corr.toString());
							  // System.out.println("Correction scale = "+clt_parameters.ly_corr_scale);

					  }
					  gpuResetCorrVector(); // next time GPU will need to set correction vector (and re-calculate offsets?)
					  if (apply_extrinsic){
						  geometryCorrection.setCorrVector(corr_vector) ;
						  System.out.println("Extrinsic correction updated (can be disabled by setting clt_parameters.ly_corr_scale = 0.0) ");
					  } else {
						  System.out.println("Correction is not applied according clt_parameters.ly_corr_scale == 0.0) ");
					  }
				  } else {
					  if (debugLevel > -3){
						  System.out.println("LMA failed");
					  }
				  }

				  boolean done = (comp_diff < min_sym_update) || (num_iter == (max_tries - 1));
				  //				  System.out.println("done="+done);
				  if (debugLevel > -10) { // should work even in batch mode
					  System.out.println("#### extrinsicsCLT(): iteration step = "+(num_iter + 1) + " ( of "+max_tries+") change = "+
							  comp_diff + " ("+min_sym_update+"), previous RMS = " + old_new_rms[0]+
							  " final RMS = " + old_new_rms[1]+ " (debugLevel = "+debugLevel+")");
				  }
				  if (debugLevel > -10) {
					  if ((debugLevel > -3) || done) {
						  //						  System.out.println("#### extrinsicsCLT(): iteration step = "+(num_iter + 1) + " ( of "+max_tries+") change = "+
						  //								  comp_diff + " ("+min_sym_update+"), previous RMS = " + new_corr[0][1][0]);
						  System.out.println("New extrinsic corrections:");
						  System.out.println(geometryCorrection.getCorrVector().toString());
					  }
				  }

				  if (comp_diff < min_sym_update) {
					  break;
				  }
				  if (update_disp_from_latest) { // true
/**/					  
					  CLTMeasure( // perform single pass according to prepared tiles operations and disparity
							  clt_parameters,
							  combo_scan,
							  false,             // final boolean     save_textures,
							  true,              // final boolean     save_corr,
							  0,           // clust_radius,
							  tp.threadsMax,     // maximal number of threads to launch
							  false,             // updateStatus,
							  debugLevelInner - 1);
/**/		
					  // Why twice?
					  CLTMeasureCorr( // perform single pass according to prepared tiles operations and disparity
							  clt_parameters,
							  combo_scan,
							  false,             // final boolean     save_textures,
							  0,                 // clust_radius,
							  tp.threadsMax,     // maximal number of threads to launch
							  false,             // updateStatus,
							  debugLevelInner - 1);
				  }

			  } else { // Old, no-GPU
					throw new IllegalArgumentException ("clt_parameters.ly_lma_ers should be true, false is not supported anymore!");
				/*	
				  double [][] bg_mismatch = new double[12][];
				  double [][] combo_mismatch = new double[12][];
				  CLTMeasure( // perform single pass according to prepared tiles operations and disparity
						  clt_parameters,
						  bg_scan,
						  false,             // final boolean     save_textures,
						  true,              // final boolean     save_corr,
						  bg_mismatch,    // final double [][] mismatch,    // null or double [12][]
						  tp.threadsMax,  // maximal number of threads to launch
						  false, // updateStatus,
						  debugLevelInner - 1);
				  CLTMeasure( // perform single pass according to prepared tiles operations and disparity
						  clt_parameters,
						  combo_scan,
						  false,             // final boolean     save_textures,
						  true,              // final boolean     save_corr,
						  combo_mismatch,    // final double [][] mismatch,    // null or double [12][]
						  tp.threadsMax,  // maximal number of threads to launch
						  false, // updateStatus,
						  debugLevelInner - 1);
				  double [][] scans14 = new double [28][];
				  scans14[14 * 0 + 0] =  tp.clt_3d_passes.get(bg_scan).disparity_map[ImageDtt.DISPARITY_INDEX_CM]; // .getDisparity(0);
				  scans14[14 * 0 + 1] =  tp.clt_3d_passes.get(bg_scan).getStrength();
				  scans14[14 * 1 + 0] =  tp.clt_3d_passes.get(combo_scan).disparity_map[ImageDtt.DISPARITY_INDEX_CM];
				  scans14[14 * 1 + 1] =  tp.clt_3d_passes.get(combo_scan).getStrength();
				  for (int i = 0; i < bg_mismatch.length; i++) {
					  scans14[14 * 0 + 2 + i] =  bg_mismatch[i];
					  scans14[14 * 1 + 2 + i] =  combo_mismatch[i];
				  }
				  if (debugLevelInner > 0) {
					  ShowDoubleFloatArrays.showArrays(scans14, tp.getTilesX(), tp.getTilesY(), true, "scans_14"); //  , titles);
				  }

				  if (!batch_mode && clt_parameters.show_extrinsic && (debugLevel > 1)) {
					  tp.showScan(
							  tp.clt_3d_passes.get(bg_scan),   // CLTPass3d   scan,
							  "bg_scan_iter"); //String title)
					  tp.showScan(
							  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
							  "combo_scan-"+combo_scan+"_iter"); //String title)
				  }

				  double [][] target_disparity = {tp.clt_3d_passes.get(bg_scan).getDisparity(0), tp.clt_3d_passes.get(combo_scan).getDisparity(0)};

				  int num_tiles = tp.clt_3d_passes.get(combo_scan).getStrength().length;


				  // TODO: fix above for using GT
				  // use 		lazyEyeCorrectionFromGT(..) when ground truth data is available
				  double [][][] new_corr = ac.lazyEyeCorrection(
						  adjust_poly,                       // final boolean use_poly,
						  true, // final boolean    restore_disp_inf, // Restore subtracted disparity for scan #0 (infinity)
						  clt_parameters.fcorr_radius,       // 	final double fcorr_radius,
						  clt_parameters.fcorr_inf_strength, //  final double min_strenth,
						  clt_parameters.fcorr_inf_diff,     // final double max_diff,
						  //								1.3, // final double comp_strength_var,
						  clt_parameters.inf_iters,          // 20, // 0, // final int max_iterations,
						  clt_parameters.inf_final_diff,     // 0.0001, // final double max_coeff_diff,
						  clt_parameters.inf_far_pull,       // 0.0, // 0.25, //   final double far_pull, //  = 0.2; // 1; //  0.5;
						  clt_parameters.inf_str_pow,        // 1.0, //   final double     strength_pow,
						  0.8*clt_parameters.disp_scan_step, // 1.5, // final double     lazyEyeCompDiff, // clt_parameters.fcorr_disp_diff
						  clt_parameters.ly_smpl_side,       // 3,   // final int        lazyEyeSmplSide, //        = 2;      // Sample size (side of a square)
						  clt_parameters.ly_smpl_num,        // 5,   // final int        lazyEyeSmplNum, //         = 3;      // Number after removing worst (should be >1)
						  clt_parameters.ly_smpl_rms,        // 0.1, // final double     lazyEyeSmplRms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
						  clt_parameters.ly_disp_var,        // 0.2, // final double     lazyEyeDispVariation, // 0.2, maximal full disparity difference between tgh tile and 8 neighborxs
						  clt_parameters.ly_disp_rvar,       // 0.2, // final double     lazyEyeDispRelVariation, // 0.02 Maximal relative full disparity difference to 8 neighbors
						  clt_parameters.ly_norm_disp,       //	final double     ly_norm_disp, //  =    5.0;     // Reduce weight of higher disparity tiles
						  clt_parameters.inf_smpl_side,      // 3, //   final int        smplSide, //        = 2;      // Sample size (side of a square)
						  clt_parameters.inf_smpl_num,       // 5, //   final int        smplNum,  //         = 3;      // Number after removing worst (should be >1)
						  clt_parameters.inf_smpl_rms,       // 0.1, // 0.05, //  final double     smplRms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
						  // histogram parameters
						  clt_parameters.ih_smpl_step,       // 8,    // final int        hist_smpl_side, // 8 x8 masked, 16x16 sampled
						  clt_parameters.ih_disp_min,        // -1.0, // final double     hist_disp_min,
						  clt_parameters.ih_disp_step,       // 0.05, // final double     hist_disp_step,
						  clt_parameters.ih_num_bins,        // 40,   // final int        hist_num_bins,
						  clt_parameters.ih_sigma,           // 0.1,  // final double     hist_sigma,
						  clt_parameters.ih_max_diff,        // 0.1,  // final double     hist_max_diff,
						  clt_parameters.ih_min_samples,     // 10,   // final int        hist_min_samples,
						  clt_parameters.ih_norm_center,     // true, // final boolean    hist_norm_center, // if there are more tiles that fit than min_samples, replace with
						  clt_parameters.ly_inf_frac,        // 0.5, // final double     inf_fraction,    // fraction of the weight for the infinity tiles

						  clt_parameters.getLyPerQuad(num_tiles), // final int        min_per_quadrant, // minimal tiles per quadrant (not counting the worst) tp proceed
						  clt_parameters.getLyInf(num_tiles),	  // final int        min_inf,          // minimal number of tiles at infinity to proceed
						  clt_parameters.getLyInfScale(num_tiles),// final int        min_inf_to_scale, // minimal number of tiles at infinity to apply weight scaling

						  clt_parameters.ly_right_left,      // false // equalize weights of right/left FoV (use with horizon in both halves and gross infinity correction)
						  clt_parameters,  // EyesisCorrectionParameters.CLTParameters           clt_parameters,
						  scans14, // disp_strength, // scans,   // double [][] disp_strength,
						  target_disparity,          // double [][]      target_disparity, // null or programmed disparity (1 per each 14 entries of scans_14)
						  tp.getTilesX(), // int         tilesX,
						  clt_parameters.corr_magic_scale, // double      magic_coeff, // still not understood coefficent that reduces reported disparity value.  Seems to be around 8.5
						  debugLevelInner - 1); //  + (clt_parameters.fine_dbg ? 1:0)); // int debugLevel)
				  if (new_corr == null) {
					  return false;
				  }
//				  gpuResetCorrVector(); // next time GPU will need to set correction vector (and re-calculate offsets?)
				  comp_diff = 0.0;
				  int num_pars = 0;
				  if (adjust_poly) {
					  apply_fine_corr(
							  new_corr,
							  debugLevelInner + 2);
					  for (int n = 0; n < new_corr.length; n++){
						  for (int d = 0; d < new_corr[n].length; d++){
							  for (int i = 0; i < new_corr[n][d].length; i++){
								  comp_diff += new_corr[n][d][i] * new_corr[n][d][i];
								  num_pars++;
							  }
						  }
					  }
					  comp_diff = Math.sqrt(comp_diff/num_pars);
					  if (debugLevel > -2) {
						  if ((debugLevel > -1) || (comp_diff < min_poly_update)) {
							  System.out.println("#### fine correction iteration step = "+(num_iter + 1) + " ( of "+max_tries+") change = "+
									  comp_diff + " ("+min_poly_update+")");
						  }
					  }

					  if (comp_diff < min_poly_update) { // add other parameter to exit from poly
						  break;
					  }
				  } else {
					  for (int i = 0; i < new_corr[0][0].length; i++){
						  comp_diff += new_corr[0][0][i] * new_corr[0][0][i];
					  }
					  comp_diff = Math.sqrt(comp_diff);
					  boolean done = (comp_diff < min_sym_update) || (num_iter == (max_tries - 1));
					  //				  System.out.println("done="+done);
					  if (debugLevel > -10) { // should work even in batch mode
						  System.out.println("#### extrinsicsCLT(): iteration step = "+(num_iter + 1) + " ( of "+max_tries+") change = "+
								  comp_diff + " ("+min_sym_update+"), previous RMS = " + new_corr[0][1][0]+ " (debugLevel = "+debugLevel+")");
					  }
					  if (debugLevel > -10) {
						  if ((debugLevel > -1) || done) {
							  //						  System.out.println("#### extrinsicsCLT(): iteration step = "+(num_iter + 1) + " ( of "+max_tries+") change = "+
							  //								  comp_diff + " ("+min_sym_update+"), previous RMS = " + new_corr[0][1][0]);
							  System.out.println("New extrinsic corrections:");
							  System.out.println(geometryCorrection.getCorrVector().toString());
						  }
					  }

					  if (comp_diff < min_sym_update) {
						  break;
					  }
				  }
*/			  
			  
			  }
		  }
		  if (geometryCorrection instanceof ErsCorrection) {
			  ((ErsCorrection) geometryCorrection).setupERSfromExtrinsics();
		  }

		  return true; // (comp_diff < (adjust_poly ? min_poly_update : min_sym_update));
	  }


	  
	  public double [][] filterByLY( 
			  CLTParameters           clt_parameters,
			  double inf_min, //  = -1.0;
			  double inf_max, //  =  1.0;
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel)
	  {
		  extrinsics_prepare( 
				  clt_parameters,
				  inf_min, //  = -1.0;
				  inf_max, //  =  1.0;
				  threadsMax,  // maximal number of threads to launch
				  updateStatus,
				  debugLevel);
		  // FIXME: Cleanup - reduce number of measurements (when using neighbors, there are many duplicates.
		  // Use existing multi-pass refinement (now unconditionally two times after calculated)
		  // Other cleanup to match older code
		  // Implement dedicated flexible multi-tile consolidation similar to infinity in LY
		  
		  final boolean    batch_mode = clt_parameters.batch_run;
		  int debugLevelInner =  batch_mode ? -5: debugLevel;
		  boolean update_disp_from_latest = clt_parameters.lym_update_disp ; // true;
		  int bg_scan = 0 ;
		  int combo_scan= tp.clt_3d_passes.size()-1;
		  // iteration steps
//		  if (true) { // !batch_mode && clt_parameters.show_extrinsic && (debugLevel >-1)) {
		  if (!batch_mode && clt_parameters.show_filter_ly && (debugLevel >-1)) {
			  tp.showScan(
					  tp.clt_3d_passes.get(bg_scan),   // CLTPass3d   scan,
					  "bg_scan_post"); //String title)
			  tp.showScan(
					  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
					  "combo_scan-"+combo_scan+"_post"); //String title)
			  tp.showScan(
					  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
					  "combo_measured_scan-"+combo_scan+"_post"); //String title)
		  }


		  if (update_disp_from_latest) {
			  tp.clt_3d_passes.get(combo_scan).updateDisparity(); // tile_op from disparity 
		  }
		  CLTPass3d   scan = tp.clt_3d_passes.get(combo_scan);
		  // before CLTMeasureLY that assignes clusters wityh BG to BG
		  double [] disparity =          scan.getDisparity().clone();
		  double [] strength =           scan.getStrength().clone();
		  
		  CLTMeasureLY( // perform single pass according to prepared tiles operations and disparity // USED in lwir
				  clt_parameters,
				  combo_scan,     // final int           scanIndex,
				  // only combine and calculate once, next passes keep
				  // remeasure each pass - target disparity is the same, but vector changes
				  bg_scan, // (num_iter >0)? -1: bg_scan,        // final int           bgIndex, // combine, if >=0
				  tp.threadsMax,  // maximal number of threads to launch
				  false, // updateStatus,
				  debugLevelInner -1); // - 1); // -5-1
		  if (!batch_mode && clt_parameters.show_filter_ly && (debugLevel >-1)) {
//		  if (debugLevel > -3){
			  tp.showScan(
					  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
					  "LY_combo_scan-"+combo_scan+"_post"); //String title)
			  tp.showScan(
					  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
					  "LY_measured_combo_scan-"+combo_scan+"_post", //String title)
					  true);
		  }
		  // temporarily:
		  final int tilesX =       tp.getTilesX();
		  final int tilesY =       tp.getTilesY();
		  int cluster_size =       clt_parameters.tileStep;
		  final int clustersX=     (tilesX + cluster_size - 1) / cluster_size;
		  final int clustersY=     (tilesY + cluster_size - 1) / cluster_size;
//		  if (true) {
		  if (!batch_mode && clt_parameters.show_filter_ly && (debugLevel >-1)) {
			  ExtrinsicAdjustment ea = new ExtrinsicAdjustment(
					  geometryCorrection, // GeometryCorrection gc,
					  clt_parameters.tileStep,   // int         clusterSize,
					  clustersX, // 	int         clustersX,
					  clustersY); // int         clustersY);
			  ea.showInput(scan.getLazyEyeData(),"first_data");
		  }
		  
		  int    [][][] tile_ops =       new int [9][][]; 
		  double [][][] disparity_maps = new double [9][][]; 
		  double [][] ly =               scan.getLazyEyeData();
		  int op = ImageDtt.setImgMask(0, 0xf);
		  op =     ImageDtt.setPairMask(op,0xf);
		  op =     ImageDtt.setForcedDisparity(op,true);
		  double [][] ds_orig = {disparity.clone(),strength.clone()};
		  int num_meas = remeasureFromLY( // now - always 9, last is center
				  clt_parameters.intra_keep_strength,                // final double        keep_strength,                // do not remeasure if stronger than
				  clt_parameters.intra_keep_conditional_strength,    // final double        keep_conditional_strength,    // keep if has a close neighbor in clusters
				  clt_parameters.intra_absolute_disparity_tolerance, // final double        absolute_disparity_tolerance, // 
				  clt_parameters.intra_relative_disparity_tolerance, // final double        relative_disparity_tolerance, // 
				  clt_parameters.intra_cluster_size,                 // final int           cluster_size,  // 4
				  ly,                     // final double [][]   lazy_eye_data, // measured by cltMeasureLazyEyeGPU
				  disparity,              // final double []     disparity, modified!
				  strength,               // final double []     strength, modified!
				  disparity_maps,         // final double [][][] disparity_map, // should be [9][][]
				  tile_ops,               // final int    [][][] tile_op,       // should be [9][][]
				  op,                     // final int           tile_op_value,
				  threadsMax,
				  updateStatus,
				  debugLevel);
		  if (debugLevel >-1) {
			  System.out.println("remeasureFromLY Done");
		  }

		  for (int nm = 0; nm < num_meas; nm++) {
			  CLTPass3d pass = new CLTPass3d(tp);
			  pass.tile_op =       tile_ops[nm];
			  pass.disparity =     disparity_maps[nm];
			  tp.clt_3d_passes.add(pass);
		  }
		  if (debugLevel >-1) {
			  System.out.println("Preparation Done");
		  }

		  // measure prepared:
		  for (int nm = 0; nm < num_meas; nm++) {
			  CLTMeasureCorr( // perform single pass according to prepared tiles operations and disparity
					  clt_parameters,
					  combo_scan + 1 + nm,
					  false, // true, // final boolean     save_textures,
					  0,                 // final int         clust_radius,
					  threadsMax,  // maximal number of threads to launch
					  updateStatus,
					  debugLevel);
		  }
		  if (debugLevel >-1) {
			  System.out.println("Measurements Done");
		  }
		  double [][] disparities = new double[num_meas+2][]; //  y =          scan.getDisparity();
		  double [][] strengths =   new double[num_meas+2][]; //         scan.getStrength(); // has NaN!
		  for (int nm = 0; nm < num_meas; nm++) {
			  disparities[nm+2] = tp.clt_3d_passes.get(combo_scan + 1 + nm).getDisparity();
			  strengths  [nm+2] = tp.clt_3d_passes.get(combo_scan + 1 + nm).getStrength();
		  }

		  disparities[0] =         ds_orig[0]; // scan.getDisparity().clone();
		  strengths[0] =           ds_orig[1]; // scan.getStrength().clone();
		  disparities[1] =         disparity; // modified by remeasureFromLY()
		  strengths[1] =           strength;  // modified by remeasureFromLY()
		  double [] disparity_combo = disparities[1];
		  double [] strength_combo =  strengths[1];
		  for (int nt = 0; nt < disparity_combo.length; nt++) {
///			  if (nt == 11764) {
///				  System.out.println("Debug1");
///			  }
			  for (int nm = 0; nm < num_meas; nm++) {
				  if (strengths[nm+2][nt] > strength_combo[nt]) {
					  strength_combo[nt] = strengths[nm+2][nt];
					  disparity_combo[nt] = disparities[nm+2][nt];
				  }
			  }
		  }
		  // FIXME: probably a wrong way to set
		  scan.calc_disparity_combo = disparity_combo;
		  scan.strength =      strength_combo;
		  scan.is_combo =      true;//?
		  int [][] tile_ops_combo = new int[tilesY][tilesX];
		  double [][] disparity_map_combo = new double[tilesY][tilesX];
		  for (int nm = 0; nm < num_meas; nm++) {
			  for (int ty = 0; ty < tilesY; ty++) {
				  for (int tx = 0; tx < tilesX; tx++) {
					  tile_ops_combo[ty][tx] |= tile_ops[nm][ty][tx];
					  disparity_map_combo[ty][tx] = disparity_combo[ty*tilesX + tx];
				  }
			  }
		  }
		  if (!batch_mode && clt_parameters.show_filter_ly && (debugLevel >-1)) {
//		  if (debugLevel >-10) {

			  /*
		  for (int nm = 0; nm < num_meas; nm++) {
			  tp.showScan(
					  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
					  "refine_from_LY-"+(combo_scan + 1 + nm)); //String title)
		  }
			   */		   
			  tp.showScan(
					  tp.clt_3d_passes.get(combo_scan),   // CLTPass3d   scan,
					  "refined_from_LY-"+combo_scan); //String title)


			  ShowDoubleFloatArrays.showArrays(
					  disparities,
					  tilesX,
					  tilesY,
					  true, "disparities"); //  , titles);
			  ShowDoubleFloatArrays.showArrays(
					  strengths,
					  tilesX,
					  tilesY,
					  true, "strengths"); //  , titles);
		  }
		  CLTPass3d pass = new CLTPass3d(tp);
		  pass.tile_op =       tile_ops_combo;
		  pass.disparity =     disparity_map_combo;
		  tp.clt_3d_passes.add(pass);
		  int last_scan = tp.clt_3d_passes.size()-1;
		  CLTMeasureCorr( // perform single pass according to prepared tiles operations and disparity
				  clt_parameters,
				  last_scan,
				  false, // true, // final boolean     save_textures,
				  0,                 // final int         clust_radius,
				  threadsMax,  // maximal number of threads to launch
				  updateStatus,
				  debugLevel);
		  double [] disparity2 = tp.clt_3d_passes.get(last_scan).getDisparity();
		  double [] strength2  = tp.clt_3d_passes.get(last_scan).getStrength();
		  
		  //strength_combo
		  // combine
		  for (int tl = 0; tl < strength2.length; tl++) {
			  if (strength2[tl] <= 0.0) {
				  strength2[tl] = strength_combo[tl];
			  }
		  }
		  if (!batch_mode && clt_parameters.show_filter_ly && (debugLevel >-1)) {
			  tp.showScan(
					  tp.clt_3d_passes.get(last_scan),   // CLTPass3d   scan,
					  "refined_twice-"+last_scan); //String title)
		  }
//----------------
		  double [][] disparity_map_combo2 = new double[tilesY][tilesX];
		  for (int nm = 0; nm < num_meas; nm++) {
			  for (int ty = 0; ty < tilesY; ty++) {
				  for (int tx = 0; tx < tilesX; tx++) {
					  disparity_map_combo2[ty][tx] = disparity2[ty*tilesX + tx];
				  }
			  }
		  }
		  
		  CLTPass3d pass2 = new CLTPass3d(tp);
		  pass2.tile_op =       tile_ops_combo;
		  pass2.disparity =     disparity_map_combo2;
		  tp.clt_3d_passes.add(pass);
		  int last_scan2 = tp.clt_3d_passes.size()-1;
		  CLTMeasureCorr( // perform single pass according to prepared tiles operations and disparity
				  clt_parameters,
				  last_scan2,
				  false, // true, // final boolean     save_textures,
				  0,                 // final int         clust_radius,
				  threadsMax,  // maximal number of threads to launch
				  updateStatus,
				  debugLevel);
		  double [] disparity3 = tp.clt_3d_passes.get(last_scan).getDisparity();
		  double [] strength3  = tp.clt_3d_passes.get(last_scan).getStrength();
		  // combine
		  for (int tl = 0; tl < strength3.length; tl++) {
			  if (strength3[tl] <= 0.0) {
				  strength3[tl] = strength2[tl];
			  }
		  }
//		  if (debugLevel >-10) {
		  if (!batch_mode && clt_parameters.show_filter_ly && (debugLevel >-1)) {
			  tp.showScan(
					  tp.clt_3d_passes.get(last_scan2),   // CLTPass3d   scan,
					  "refined_three-"+last_scan2); //String title)

		  }

		  if (debugLevel >-3) {
			  System.out.println("Done");
		  }
		  
		  double [][] enhanced_dsi = {disparity3, strength3};
		  return enhanced_dsi;
	  }
	  
//double [][]         lazy_eye_data_final = new double [clustersY*clustersX][]	  
	  int remeasureFromLY(
			  final double        keep_strength,                // do not remeasure if stronger than
			  final double        keep_conditional_strength,    // keep if has a close neighbor in clusters
			  final double        absolute_disparity_tolerance, // 
			  final double        relative_disparity_tolerance, // 
			  final int           cluster_size,  // 4
			  final double [][]   lazy_eye_data, // measured by cltMeasureLazyEyeGPU
			  final double []     disparity, // will be modified
			  final double []     strength,  // will be modified
			  final double [][][] disparity_map, // should be [9][][]
			  final int    [][][] tile_op,       // should be [9][][]
			  final int           tile_op_value,
			  final int           threadsMax,  // maximal number of threads to launch
			  final boolean       updateStatus,
			  final int           debugLevel)
	  {
		  final int tilesX =       tp.getTilesX();
		  final int tilesY =       tp.getTilesY();
		  final int clustersX=     (tilesX + cluster_size - 1) / cluster_size;
		  final int clustersY=     (tilesY + cluster_size - 1) / cluster_size;
		  final int numTiles =     tilesX*tilesY;
		  final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		  final AtomicInteger ai = new AtomicInteger(0);
		  for (int i = 0; i < disparity_map.length; i++) {
			  disparity_map[i] = new double [tilesY][tilesX];
			  tile_op[i] =       new int [tilesY][tilesX];
		  }

		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  @Override
				  public void run() {
					  TileNeibs tn = new TileNeibs(clustersX,clustersY);
					  double [] neibs_disp =    new double [9];
					  double [] neibs_ordered;
					  for (int indx_tile = ai.getAndIncrement(); indx_tile < numTiles; indx_tile = ai.getAndIncrement()) {
						  if (indx_tile == 11764) {
							  System.out.println("DEBUG");
						  }
						  if (strength[indx_tile] >= keep_strength) { // already strong enough
							  continue;
						  }
						  // get cluster disparities around this cluster
						  int tileX = indx_tile % tilesX;
						  int tileY = indx_tile / tilesX;
						  int clusterX = tileX / cluster_size;
						  int clusterY = tileY / cluster_size;
						  int cluster = clusterX+ clustersX*clusterY;
						  for (int dir = 0; dir < 9; dir++) {
							  neibs_disp[dir] = Double.NaN;
							  int icl = tn.getNeibIndex(cluster, dir);
							  if (icl >= 0) {
								  if (lazy_eye_data[icl] != null) {
									  neibs_disp[dir] = lazy_eye_data[icl][ExtrinsicAdjustment.INDX_DISP];
								  }
							  }
						  }
						  boolean keep = false;
						  if (strength[indx_tile] >= keep_conditional_strength) {// See if any of the cluster disparities is within range
							  for (int dir = 0; dir < 9; dir++) if (!Double.isNaN(neibs_disp[dir])){
								  double maxdiff = absolute_disparity_tolerance + Math.abs(neibs_disp[dir]) * relative_disparity_tolerance;
								  if (Math.abs(neibs_disp[dir] - disparity[indx_tile]) <= maxdiff) {
									  keep = true;
									  break;
								  }
							  }							  
						  }
						  if (keep) {
							  continue;
						  }
						  // Compact and order neighbors and
						  int nneibs = 0;
						  for (int dir = 0; dir < 9; dir++) if (!Double.isNaN(neibs_disp[dir])){
							  nneibs++;
						  }
						  
						  if (nneibs == 0) {
							  disparity[indx_tile] = Double.NaN;
							  strength[indx_tile] = 0.0;
							  continue;
						  }
						  for (int i = 0; i < neibs_disp.length; i++) if (!Double.isNaN(neibs_disp[i])){
							  disparity_map[i][tileY][tileX] = neibs_disp[i];
							  tile_op[i][tileY][tileX] = tile_op_value;
						  }
						  if (!Double.isNaN(neibs_disp[8])) { // center, where this tile is
							  disparity[indx_tile] = neibs_disp[8]; // if that tile original strength will win, use LY disparity 
						  } else {
							  // Maybe improve - 
							  disparity[indx_tile] = Double.NaN;
							  strength[indx_tile] = 0.0;
						  }
					  } // end of tile
				  }
			  };
		  }
		  ImageDtt.startAndJoin(threads);
		  return 9;
	  }

	  public double [][] getRigDSFromTwoQuadCL( // not used in lwir
			  TwoQuadCLT       twoQuadCLT, //maybe null in no-rig mode, otherwise may contain rig measurements to be used as infinity ground truth
			  CLTParameters           clt_parameters,
			  final int        debugLevel) {
		  if ((twoQuadCLT == null) || (twoQuadCLT.getBiScan(0) == null)){
			  System.out.println("Rig data is not available, aborting");
			  return null;
		  }

		  BiScan scan = twoQuadCLT.getBiScan(0);
		  double [][] rig_disp_strength = 		scan.getDisparityStrength(
		    		true,   // final boolean only_strong,
		    		true,   // final boolean only_trusted,
		    		true) ; // final boolean only_enabled);
		  GeometryCorrection geometryCorrection_main = null;
		  if (geometryCorrection.getRotMatrix(true) != null) {
			  geometryCorrection_main = twoQuadCLT.quadCLT_main.getGeometryCorrection();
			  double disparityScale =  geometryCorrection.getDisparityRadius()/geometryCorrection_main.getDisparityRadius();
			  for (int i = 0; i < rig_disp_strength[0].length; i++) {
				  rig_disp_strength[0][i] *= disparityScale;
			  }

			  if (debugLevel > -2) {
				  System.out.println("This is an AUX camera, using MAIN camera coordinates");
			  }
		  }
		  return rig_disp_strength;


	  }
/**
 *
 * @param geometryCorrection_main
 * @param rig_disp_strength
 * @param clt_parameters
 * @param adjust_poly
 * @param threadsMax
 * @param updateStatus
 * @param debugLevel
 * @return true on success, false on failure
 */
	  @Deprecated
	  // get rid of clt_mismatch
	  public boolean extrinsicsCLTfromGT( // USED in lwir
//			  TwoQuadCLT       twoQuadCLT, //maybe null in no-rig mode, otherwise may contain rig measurements to be used as infinity ground truth
			  GeometryCorrection geometryCorrection_main, // only used for aux camera if coordinates are for main (null for LWIR)
			  double [][] rig_disp_strength,
			  CLTParameters           clt_parameters,
			  boolean 		   adjust_poly,
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel)
	  {

		  final boolean    filter_ds = clt_parameters.lyr_filter_ds;  //  = false; // true;
		  final boolean    filter_lyf = clt_parameters.lyr_filter_lyf; //  = false; // ~clt_parameters.lyf_filter, but may be different, now off for a single cameras

		  final boolean    batch_mode = clt_parameters.batch_run;
		  int debugLevelInner =  batch_mode ? -5: debugLevel;
		  int max_tries =                   clt_parameters.lym_iter; // 25;
		  double min_sym_update =           clt_parameters.getLymChange(is_aux); //  4e-6; // stop iterations if no angle changes more than this
		  double min_poly_update =          clt_parameters.lym_poly_change; //  Parameter vector difference to exit from polynomial correction
		  if (debugLevel > 20) {
			  boolean tmp_exit = true;
			  System.out.println("extrinsicsCLTfromGT()");
			  if (tmp_exit) {
				  System.out.println("will now exit. To continue - change variable tmp_exit in debugger" );
				  if (tmp_exit) {
					  return false;
				  }
			  }
		  }

		  CLTPass3d comboScan = tp.compositeScan(
				  rig_disp_strength[0], // final double []             disparity,
				  rig_disp_strength[1], //  final double []             strength,
				  null, //  final boolean []            selected,
				  debugLevel); // final int                   debugLevel)
		  // comboScan will remain the same through iterations, no need to update disparity (maybe shrink selection?

		  AlignmentCorrection ac = new AlignmentCorrection(this);
		  // iteration steps
		  double comp_diff = min_sym_update + 1; // (> min_sym_update)
		  for (int num_iter = 0; num_iter < max_tries; num_iter++){
			  double [][] combo_mismatch = new double[12][];
			  if (combo_mismatch != null) {
				  throw new IllegalArgumentException ("combo_mismatch is not supported anymore!");
			  }
			  CLTMeasure( // perform single pass according to prepared tiles operations and disparity
					  clt_parameters,
					  comboScan,               // final CLTPass3d     scan,
					  false,                   // final boolean     save_textures,
					  true,                    // final boolean     save_corr,
//					  combo_mismatch,          // final double [][] mismatch,    // null or double [12][]
					  geometryCorrection_main, // final GeometryCorrection geometryCorrection_main, // If not null - covert to main camera coordinates
					  tp.threadsMax,           // maximal number of threads to launch
					  false,                   // updateStatus,
					  debugLevelInner - 1);

			  double [][] scans14 = new double [14][];
			  scans14[14 * 0 + 0] =  comboScan.disparity_map[ImageDtt.DISPARITY_INDEX_CM]; // .getDisparity(0);
			  scans14[14 * 0 + 1] =  comboScan.getStrength();
			  

			  
			  for (int i = 0; i < combo_mismatch.length; i++) {
				  scans14[14 * 0 + 2 + i] =  combo_mismatch[i];
			  }
			  if (debugLevelInner > 0) {
				  ShowDoubleFloatArrays.showArrays(scans14, tp.getTilesX(), tp.getTilesY(), true, "scans_14"); //  , titles);
			  }

			  if (!batch_mode && clt_parameters.show_extrinsic && (debugLevel > 1)) {
				  tp.showScan(
						  comboScan,   // CLTPass3d   scan,
						  "combo_scan-"+num_iter+"_iter"); //String title)
			  }

			  double [][][] new_corr;
			  double [][][]    gt_disparity_strength = {rig_disp_strength};
			  int num_tiles = comboScan.getStrength().length;

			  new_corr = ac.lazyEyeCorrectionFromGT(
///					  geometryCorrection_main, //final GeometryCorrection geometryCorrection_main, // if not null - this is an AUX camera of a rig
					  adjust_poly,                       // final boolean use_poly,
					  true, // final boolean    restore_disp_inf, // Restore subtracted disparity for scan #0 (infinity)
					  clt_parameters.fcorr_radius,       // 	final double fcorr_radius,
					  clt_parameters.fcorr_inf_strength, //  final double min_strenth,

					  clt_parameters.inf_str_pow,        // 1.0, //   final double     strength_pow,
					  0.8*clt_parameters.disp_scan_step, // 1.5, // final double     lazyEyeCompDiff, // clt_parameters.fcorr_disp_diff
					  clt_parameters.ly_smpl_side,       // 3,   // final int        lazyEyeSmplSide, //        = 2;      // Sample size (side of a square)
					  clt_parameters.ly_smpl_num,        // 5,   // final int        lazyEyeSmplNum, //         = 3;      // Number after removing worst (should be >1)
					  clt_parameters.ly_smpl_rms,        // 0.1, // final double     lazyEyeSmplRms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
					  clt_parameters.ly_disp_var_gt,        // 0.2, // final double     lazyEyeDispVariation, // 0.2, maximal full disparity difference between tgh tile and 8 neighborxs
					  clt_parameters.ly_disp_rvar_gt,       // 0.2, // final double     lazyEyeDispRelVariation, // 0.02 Maximal relative full disparity difference to 8 neighbors
					  clt_parameters.ly_norm_disp,       //	final double     ly_norm_disp, //  =    5.0;     // Reduce weight of higher disparity tiles
					  clt_parameters.inf_smpl_side,      // 3, //   final int        smplSide, //        = 2;      // Sample size (side of a square)
					  clt_parameters.inf_smpl_num,       // 5, //   final int        smplNum,  //         = 3;      // Number after removing worst (should be >1)
					  clt_parameters.inf_smpl_rms,       // 0.1, // 0.05, //  final double     smplRms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
					  // histogram parameters
					  clt_parameters.ih_smpl_step,       // 8,    // final int        hist_smpl_side, // 8 x8 masked, 16x16 sampled
					  clt_parameters.ih_disp_min,        // -1.0, // final double     hist_disp_min,
					  clt_parameters.ih_disp_step,       // 0.05, // final double     hist_disp_step,
					  clt_parameters.ih_num_bins,        // 40,   // final int        hist_num_bins,
					  clt_parameters.ih_sigma,           // 0.1,  // final double     hist_sigma,
					  clt_parameters.ih_max_diff,        // 0.1,  // final double     hist_max_diff,
					  clt_parameters.ih_min_samples,     // 10,   // final int        hist_min_samples,
					  clt_parameters.ih_norm_center,     // true, // final boolean    hist_norm_center, // if there are more tiles that fit than min_samples, replace with
					  clt_parameters.ly_inf_frac,        // 0.5, // final double     inf_fraction,    // fraction of the weight for the infinity tiles
					  clt_parameters.getLyPerQuad(num_tiles), // final int        min_per_quadrant, // minimal tiles per quadrant (not counting the worst) tp proceed
					  0, // clt_parameters.getLyInf(num_tiles),	  // final int        min_inf,          // minimal number of tiles at infinity to proceed
					  clt_parameters.getLyInfScale(num_tiles),// final int        min_inf_to_scale, // minimal number of tiles at infinity to apply weight scaling

					  clt_parameters.ly_inf_max_disparity, // inf_max_disparity, // final double     inf_max_disparity, // use all smaller disparities as inf_fraction
					  clt_parameters,  // EyesisCorrectionParameters.CLTParameters           clt_parameters,
					  scans14, // disp_strength, // scans,   // double [][] disp_strength,
					  gt_disparity_strength, // double [][][]    gt_disparity_strength, // 1 pair for each 14 entries of scans_14 (normally - just 1 scan
					  filter_ds,   // final boolean    filter_ds, //
					  filter_lyf,   // final boolean    filter_lyf, // ~clt_parameters.lyf_filter, but may be different, now off for a single cameras
					  tp.getTilesX(), // int         tilesX,
					  clt_parameters.corr_magic_scale, // double      magic_coeff, // still not understood coefficent that reduces reported disparity value.  Seems to be around 8.5
					  debugLevelInner - 1); //  + (clt_parameters.fine_dbg ? 1:0)); // int debugLevel)
			  if (new_corr == null) {
				  return false; // not used in lwir
			  }
			  comp_diff = 0.0;
			  int num_pars = 0;
			  if (adjust_poly) { // not used in lwir
				  apply_fine_corr(
						  new_corr,
						  debugLevelInner + 2);
				  for (int n = 0; n < new_corr.length; n++){
					  for (int d = 0; d < new_corr[n].length; d++){
						  for (int i = 0; i < new_corr[n][d].length; i++){
							  comp_diff += new_corr[n][d][i] * new_corr[n][d][i];
							  num_pars++;
						  }
					  }
				  }
				  comp_diff = Math.sqrt(comp_diff/num_pars);
				  if (debugLevel > -2) {
					  if ((debugLevel > -1) || (comp_diff < min_poly_update)) {
						  System.out.println("#### fine correction iteration step = "+(num_iter + 1) + " ( of "+max_tries+") change = "+
								  comp_diff + " ("+min_poly_update+")");
					  }
				  }

				  if (comp_diff < min_poly_update) { // add other parameter to exit from poly
					  break;
				  }
			  } else { // USED in lwir
				  for (int i = 0; i < new_corr[0][0].length; i++){
					  comp_diff += new_corr[0][0][i] * new_corr[0][0][i];
				  }
				  comp_diff = Math.sqrt(comp_diff);
				  if (debugLevel > -10) { // should work even in batch mode
					  System.out.println("#### extrinsicsCLTfromGT(): iteration step = "+(num_iter + 1) + " ( of "+max_tries+") change = "+
							  comp_diff + " ("+min_sym_update+"), previous RMS = " + new_corr[0][1][0]+ " (debugLevel = "+debugLevel+")");
				  }

				  if (debugLevel > -2) {
					  if ((debugLevel > -1) || (comp_diff < min_sym_update)) {
//						  System.out.println("#### extrinsicsCLT(): iteration step = "+(num_iter + 1) + " ( of "+max_tries+") change = "+
//								  comp_diff + " ("+min_sym_update+"), previous RMS = " + new_corr[0][1][0]);
						  System.out.println("New extrinsic corrections:");
						  System.out.println(geometryCorrection.getCorrVector().toString());
					  }
				  }
				  if (comp_diff < min_sym_update) {
					  break;
				  }
			  }
		  }
		  return true; // (comp_diff < (adjust_poly ? min_poly_update : min_sym_update));
	  }






	  public boolean expandCLTQuad3d( // USED in lwir
		  CLTParameters           clt_parameters,
		  ColorProcParameters colorProcParameters,
		  CorrectionColorProc.ColorGainsParameters     channelGainParameters,
		  EyesisCorrectionParameters.RGBParameters             rgbParameters,
		  final int        threadsMax,  // maximal number of threads to launch
		  final boolean    updateStatus,
		  final int        debugLevel)
	  {
		  final boolean                       batch_mode = clt_parameters.batch_run; //disable any debug images
		  final int debugLevelInner =  batch_mode ? -3: debugLevel;
		  final double     trustedCorrelation = tp.getTrustedCorrelation();
		  final int        max_expand =  500; // 150; // 30;
  		  final boolean    show_retry_far =   clt_parameters.show_retry_far && false; // (max_expand <= 10);
		  boolean          show_expand =      false; //    clt_parameters.show_expand && (max_expand <= 10);

		  final int        disp_index =      ImageDtt.DISPARITY_INDEX_CM;
		  final int        str_index =       ImageDtt.DISPARITY_STRENGTH_INDEX;
		  final double     strength_floor =  clt_parameters.fds_str_floor;  // 0.6* clt_parameters.combine_min_strength;

		  // TODO: make parameters
		  final double     strength_pow    = clt_parameters.fds_str_pow ;   // 1.0;
		  final int        smplSide        = clt_parameters.fds_smpl_side ;       // 5; // 3;      // Sample size (side of a square)
		  final int        smplNum         = clt_parameters.fds_smpl_num ;        // 10; // 13; // 5;      // Number after removing worst (should be >1)
		  final double     smplRms         = clt_parameters.fds_smpl_rms ;        // 0.15; // Maximal RMS of the remaining tiles in a sample
		  final double     smplRelRms      = clt_parameters.fds_smpl_rel_rms ;     // 0.01; // 05;  // Maximal RMS/disparity in addition to smplRms
		  final boolean    smplWnd         = clt_parameters.fds_smpl_wnd ;        // true; //
		  final double     max_abs_tilt    = clt_parameters.fds_abs_tilt ;   // 2.0; // pix per tile
		  final double     max_rel_tilt    = clt_parameters.fds_rel_tilt ;   // 0.2; // (pix / disparity) per tile
		  final int        bg_pass = 0;

		  final int        dbg_x = 155;
		  final int        dbg_y = 207;

          int num_extended = -1;
// process once more to try combining of processed
          boolean last_pass = false;
//          for (int num_expand = 0; (num_expand < 4) && (num_extended != 0); num_expand++) {
          boolean over_infinity = false;
          int dbg_start_pass = 4; // 10; // 20;
          int dbg_end_pass =   -20; // 12; // -29;
//          final int        dbg_x0 = 73; //
//          final int        dbg_y0 = 195; //
  		  double [][] filtered_bgnd_disp_strength = tp.getFilteredDisparityStrength(
				  tp.clt_3d_passes, // final ArrayList <CLTPass3d> passes,// List, first, last - to search for the already tried disparity
				  bg_pass, // tp.clt_3d_passes.size() - 1,         // final int        measured_scan_index, // will not look at higher scans
  				  0,                  //	final int        start_scan_index,
  				  null , // final boolean [] bg_tiles,          // get from selected in clt_3d_passes.get(0);
				  0.0,   // whatever as null above //  clt_parameters.ex_min_over,// final double     ex_min_over,       // when expanding over previously detected (by error) background, disregard far tiles

  				  disp_index,         // final int        disp_index,
  				  str_index,          // final int        str_index,
  				  null,               // final double []  tiltXY,    // null - free with limit on both absolute (2.0?) and relative (0.2) values
  				  trustedCorrelation, //	final double     trustedCorrelation,
  				  strength_floor,     //	final double     strength_floor,
  				  strength_pow,       // final double     strength_pow,
  				  smplSide,           // final int        smplSide, //        = 2;      // Sample size (side of a square)
  				  smplNum,            // final int        smplNum, //         = 3;      // Number after removing worst (should be >1)
  				  smplRms,            //	final double     smplRms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
  				  smplRelRms,         // final double     smplRelRms, //      = 0.005;  // Maximal RMS/disparity in addition to smplRms

  				  smplWnd,            //	final boolean    smplWnd, //
  				  max_abs_tilt,       //	final double     max_abs_tilt, //  = 2.0; // pix per tile
  				  max_rel_tilt,       //	final double     max_rel_tilt, //  = 0.2; // (pix / disparity) per tile
  				  dbg_x,              //	final int        dbg_x,
  				  dbg_y,              // final int        dbg_y,
  				  debugLevelInner);   //	final int        debugLevel)



          for (int num_expand = 0; num_expand < max_expand; num_expand++) {
        	  boolean dbg_pass = (num_expand >= dbg_start_pass) && (num_expand <= dbg_end_pass);
        	  show_expand =      (clt_parameters.show_expand && (max_expand <= 10)) || dbg_pass;

        	  //        	  Runtime runtime = Runtime.getRuntime();
        	  //       	  runtime.gc();
        	  //       	  System.out.println("--- Free memory="+runtime.freeMemory()+" (of "+runtime.totalMemory()+")");

        	  num_extended = zMapExpansionStep(
        			  tp.clt_3d_passes,               // final ArrayList <CLTPass3d> passes,// List, first, last - to search for the already tried disparity
        			  clt_parameters,                 //final EyesisCorrectionParameters.CLTParameters           clt_parameters, // for refinePassSetup()
        			  0,                              // final int         firstPass,
        			  tp.clt_3d_passes.size(),        // final int         lastPassPlus1,
        			  bg_pass,                        // final int         bg_index,

        			  true,                           // final boolean     refine,         // now always should be true ?
        			  clt_parameters.gr_new_expand,   // final boolean     expand_neibs,   // expand from neighbors
        			  !clt_parameters.gr_new_expand,  // final boolean     expand_legacy,  // old mode of growing tiles (using max scanned). If both true, will try only if expand_neibs fails

        			  // Filtering by background data:
        			  filtered_bgnd_disp_strength,    // final double [][] filtered_bgnd_ds,       // if not null, will filter results not to have low disparity new tiles over supposed bgnd
        			  // for legacy expansion it is not used, but just checked for null, so double [0][] should work too
        			  clt_parameters.ex_min_over ,              // final double      ex_min_over,            // when expanding over previously detected (by error) background, disregard far tiles
        			  clt_parameters.gr_ovrbg_filtered ,  // final double      str_over_bg,            // Minimal filtered strength when replacing background data
        			  clt_parameters.gr_ovrbg_cmb ,     // final double      str_over_bg_combo,      // Minimal combined strength  when replacing background data
        			  clt_parameters.gr_ovrbg_cmb_hor , // final double      str_over_bg_combo_hor,  // Minimal combined strength  when replacing background data
        			  clt_parameters.gr_ovrbg_cmb_vert, // final double      str_over_bg_combo_vert, // Minimal combined strength  when replacing background data

        			  // Refine parameters use directly some clt_parameters, others below
        			  clt_parameters.stUseRefine,               // final boolean     stUseRefine, // use supertiles (now false)
        			  clt_parameters.bgnd_range,                //final double      bgnd_range,     // double            disparity_far,
        			  clt_parameters.ex_strength,               // final double      ex_strength,    // double            this_sure,        // minimal strength to be considered definitely good
        			  clt_parameters.ex_nstrength,              //final double      ex_nstrength,   // double            ex_nstrength, // minimal 4-corr strength divided by channel diff for new (border) tiles
        			  clt_parameters.bgnd_maybe,                // final double      bgnd_maybe,     // double            this_maybe,       // maximal strength to ignore as non-background
        			  clt_parameters.sure_smth,                 // final double      sure_smth,      // sure_smth,        // if 2-nd worst image difference (noise-normalized) exceeds this - do not propagate bgnd
        			  clt_parameters.pt_super_trust,            //final double      pt_super_trust, //  final double      super_trust,      // If strength exceeds ex_strength * super_trust, do not apply ex_nstrength and plate_ds
        			  // using plates disparity/strength - averaged for small square sets of tiles. If null - just use raw tiles
        			  clt_parameters.pt_keep_raw_fg,            // final boolean     pt_keep_raw_fg, // 		final boolean     keep_raw_fg,  // do not replace raw tiles by the plates, if raw is closer (like poles)
        			  clt_parameters.pt_scale_pre,              // final double      pt_scale_pre,   // final double      scale_filtered_strength_pre, // scale plate_ds[1] before comparing to raw strength
        			  clt_parameters.pt_scale_post,             // final double      pt_scale_post,  // final double      scale_filtered_strength_post,// scale plate_ds[1] when replacing raw (generally plate_ds is more reliable if it exists)

        			  // Composite scan parameters
        			  clt_parameters.combine_min_strength,      // final double      combine_min_strength,              // final double                minStrength,
        			  clt_parameters.combine_min_hor,           // final double      combine_min_hor,                   // final double                minStrengthHor,
        			  clt_parameters.combine_min_vert,          // final double      combine_min_vert,                  // final double                minStrengthVert,

        			  //getFilteredDisparityStrength parameters

        			  // Consider separate parameters for FDS?
        			  clt_parameters.fds_str_floor,        // final double      fds_str_floor,
        			  clt_parameters.fds_str_pow,          // final double      fds_str_pow,
        			  clt_parameters.fds_smpl_side,             // final int         fds_smpl_size, // == 5
        			  clt_parameters.fds_smpl_num,              // final int         fds_smpl_points, // == 3
        			  clt_parameters.fds_smpl_rms,              // final double      fds_abs_rms,
        			  clt_parameters.fds_smpl_rel_rms,          // final double      fds_rel_rms,
        			  clt_parameters.fds_smpl_wnd,              // final boolean     fds_use_wnd,   // use window function fro the neighbors
        			  0.001,                                    // final double      fds_tilt_damp,
        			  clt_parameters.fds_abs_tilt,              // final double      fds_abs_tilt, //   = 2.0; // pix per tile
        			  clt_parameters.fds_rel_tilt,              // final double      fds_rel_tilt, //   = 0.2; // (pix / disparity) per tile

        			  // expansion from neighbors parameters:
        			  clt_parameters.gr_min_new ,               // final int         gr_min_new,         // discard variant if there are less new tiles
        			  clt_parameters.gr_steps_over,             // final int         gr_steps_over,  // how far to extend
        			  clt_parameters.gr_var_new_sngl ,          // final boolean     gr_var_new_sngl,
        			  clt_parameters.gr_var_new_fg ,            // final boolean     gr_var_new_fg,
        			  clt_parameters.gr_var_all_fg ,            // final boolean     gr_var_all_fg,
        			  clt_parameters.gr_var_new_bg ,            // final boolean     gr_var_new_bg,
        			  clt_parameters.gr_var_all_bg ,            // final boolean     gr_var_all_bg,
        			  clt_parameters.gr_var_next ,              // final boolean     gr_var_next,

        			  clt_parameters.gr_smpl_size , // final int         gr_smpl_size ,      // 5,      // 	final int         smpl_size, // == 5
        			  clt_parameters.gr_min_pnts , // final int         gr_min_pnts ,       // 5, // 4, // 3,      //    final int        min_points, // == 3
        			  clt_parameters.gr_use_wnd , // final boolean     gr_use_wnd ,        // true,   // 	final boolean     use_wnd,   // use window function fro the neighbors
        			  clt_parameters.gr_tilt_damp , // final double      gr_tilt_damp ,      // 0.001,  // 	final double      tilt_cost,
        			  clt_parameters.gr_split_rng , // final double      gr_split_rng ,      // 5.0,    // 	final double      split_threshold, // if full range of the values around the cell higher, need separate fg, bg
        			  clt_parameters.gr_same_rng , // final double      gr_same_rng ,       // 3.0,    // 	final double      same_range,      // modify
        			  clt_parameters.gr_diff_cont , // final double      gr_diff_cont ,      // 2.0,    // 	final double      diff_continue,   // maximal difference from the old value (for previously defined tiles
        			  clt_parameters.gr_abs_tilt , // final double      gr_abs_tilt ,       // 2.0,    //    final double     max_abs_tilt, //   = 2.0; // pix per tile
        			  clt_parameters.gr_rel_tilt , // final double      gr_rel_tilt ,       // 0.2,    //    final double     max_rel_tilt, //   = 0.2; // (pix / disparity) per tile
        			  clt_parameters.gr_smooth , // final int         gr_smooth ,         // 50,     // 	final int         max_tries, // maximal number of smoothing steps
        			  clt_parameters.gr_fin_diff , // final double      gr_fin_diff ,       // 0.01,   // 	final double      final_diff, // maximal change to finish iterations
        			  clt_parameters.gr_unique_pretol , // final double      gr_unique_pretol ,  // 1.0,    // 	final double      unique_pre_tolerance, // usually larger than clt_parameters.unique_tolerance

        			  // legacy expansion
        			  clt_parameters.grow_min_diff ,  // final double      grow_min_diff,   // =    0.5; // Grow more only if at least one channel has higher variance from others for the tile
        			  clt_parameters.grow_retry_far , // final boolean     grow_retry_far,  // final boolean     grow_retry_far,  // Retry tiles around known foreground that have low max_tried_disparity
        			  clt_parameters.grow_pedantic ,  // final boolean     grow_pedantic,   // final boolean     grow_pedantic,   // Scan full range between max_tried_disparity of the background and known foreground
        			  clt_parameters.grow_retry_inf , // final boolean     grow_retry_inf,  // final boolean     grow_retry_inf,  // Retry border tiles that were identified as infinity earlier

        			  // common expansion
        			  clt_parameters.gr_num_steps,    // final int         gr_num_steps,    // how far to extend
        			  clt_parameters.gr_unique_tol ,  // final double      gr_unique_tol,
        			  0.0, // clt_parameters.grow_disp_min ,  // final double      grow_disp_min,   // final double                disp_near,
        			  clt_parameters.grow_disp_max ,  // final double      grow_disp_max,   // final double                disp_near,
        			  clt_parameters.grow_disp_step , // final double      grow_disp_step,  //  =   6.0; // Increase disparity (from maximal tried) if nothing found in that tile // TODO: handle enclosed dips?

        			  // for debug level > 1, just corresponding clt_parameters.* will work, otherwise following 3 can override
        			  last_pass,                      // final boolean     last_pass, // just for more debug?
        			  true,                           // final boolean     remove_non_measurement, // save memory, true is OK
        			  show_expand,                    // final boolean     show_expand,
        			  clt_parameters. show_unique,    // final boolean     show_unique,
        			  show_retry_far,                 // final boolean     show_retry_far,

        			  dbg_x,
        			  dbg_y,             // final int        dbg_y,
        			  debugLevelInner + 2);        //	final int        debugLevel)

        	  if (last_pass) {
        		  break;
        	  } else if (num_extended == 0){
        		  System.out.println("**** processCLTQuad3d(): nothing to expand ***");
        		  System.out.println("!clt_parameters.ex_over_bgnd="+clt_parameters.ex_over_bgnd+" over_infinity="+over_infinity);
        		  if (!clt_parameters.ex_over_bgnd || over_infinity)  last_pass = true;
        		  else { // not used in lwir
        			  over_infinity = true;
        			  if (debugLevel > -1){
        				  System.out.println("===== processCLTQuad3d(): trying to expand over previously identified background (may be by error)====");
        			  }
        		  }
        	  }

          }






          show_expand =      (clt_parameters.show_expand && (max_expand <= 10));
          if (debugLevelInner > -1) {
        	  tp.showScan(
        			  tp.clt_3d_passes.get(tp.clt_3d_passes.size()-2),   // CLTPass3d   scan,
        			  "after_pre_last_combo_pass-"+(tp.clt_3d_passes.size()-2)); //String title)
        	  tp.showScan(
        			  tp.clt_3d_passes.get(tp.clt_3d_passes.size()-1),   // CLTPass3d   scan,
        			  "after_last_combo_pass-"+(tp.clt_3d_passes.size()-1)); //String title)
          }

///          int refine_pass = tp.clt_3d_passes.size(); // refinePassSetup() will add one - not anymore!

///          int next_pass = tp.clt_3d_passes.size(); //
          tp.secondPassSetup( // prepare tile tasks for the second pass based on the previous one(s)
        		  clt_parameters,
        		  clt_parameters.stUsePass2, // use supertiles
        		  bg_pass,
				  // disparity range - differences from
				  clt_parameters.bgnd_range, // double            disparity_far,
//    				  -0.5, // 0.0, // clt_parameters.bgnd_range,                //	 final double                disp_far,   // limit results to the disparity range
				  clt_parameters.grow_disp_max, // other_range, //double            disparity_near,   //
				  clt_parameters.ex_strength,  // double            this_sure,        // minimal strength to be considered definitely good
				  clt_parameters.ex_nstrength, // double            ex_nstrength, // minimal 4-corr strength divided by channel diff for new (border) tiles
				  clt_parameters.bgnd_maybe, // double            this_maybe,       // maximal strength to ignore as non-background
				  clt_parameters.sure_smth, // sure_smth,        // if 2-nd worst image difference (noise-normalized) exceeds this - do not propagate bgnd
				  clt_parameters.pt_super_trust, // super_trust,      // If strength exceeds ex_strength * super_trust, do not apply ex_nstrength and plate_ds

				  ImageDtt.DISPARITY_INDEX_CM,  // index of disparity value in disparity_map == 2 (0,2 or 4)
				  geometryCorrection,
				  threadsMax,  // maximal number of threads to launch
				  updateStatus,
				  debugLevelInner);

// Save tp.clt_3d_passes.size() to roll back without restarting the program
          tp.saveCLTPasses(false); // not rig, and reset rig data
///          Runtime runtime = Runtime.getRuntime();
///          runtime.gc();
///          System.out.println("--- Free memory="+runtime.freeMemory()+" (of "+runtime.totalMemory()+")");
          return true; //  null;
	  }

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

// called after composite scan is added to the list? composite scan is inside

	  public int zMapExpansionStep( // USED in lwir
				final ArrayList <CLTPass3d> passes,// List, first, last - to search for the already tried disparity
				final CLTParameters           clt_parameters, // for refinePassSetup()
				final int         firstPass,
				final int         lastPassPlus1,
				final int         bg_index,


				final boolean     refine,         // now always should be true ?
				final boolean     expand_neibs,   // expand from neighbors
				final boolean     expand_legacy,  // old mode of growing tiles (using max scanned). If both true, will try only if expand_neibs fails

				// Filtering by background data:
				final double [][] filtered_bgnd_ds,       // if not null, will filter results not to have low disparity new tiles over supposed bgnd
				                                          // for legacy expansion it is not used, but just checked for null, so double [0][] should work too
				final double      ex_min_over,            // when expanding over previously detected (by error) background, disregard far tiles
				final double      str_over_bg,            // Minimal filtered strength when replacing background data
				final double      str_over_bg_combo,      // Minimal combined strength  when replacing background data
				final double      str_over_bg_combo_hor,  // Minimal combined strength  when replacing background data
				final double      str_over_bg_combo_vert, // Minimal combined strength  when replacing background data

				// Refine parameters use directly some clt_parameters, others below
				final boolean     stUseRefine, // use supertiles
				final double      bgnd_range,     // double            disparity_far,
				final double      ex_strength,    // double            this_sure,        // minimal strength to be considered definitely good
				final double      ex_nstrength,   // double            ex_nstrength, // minimal 4-corr strength divided by channel diff for new (border) tiles
				final double      bgnd_maybe,     // double            this_maybe,       // maximal strength to ignore as non-background
				final double      sure_smth,      // sure_smth,        // if 2-nd worst image difference (noise-normalized) exceeds this - do not propagate bgnd
				final double      pt_super_trust, //  final double      super_trust,      // If strength exceeds ex_strength * super_trust, do not apply ex_nstrength and plate_ds
				  // using plates disparity/strength - averaged for small square sets of tiles. If null - just use raw tiles
				final boolean     pt_keep_raw_fg, // 		final boolean     keep_raw_fg,  // do not replace raw tiles by the plates, if raw is closer (like poles)
				final double      pt_scale_pre,   // final double      scale_filtered_strength_pre, // scale plate_ds[1] before comparing to raw strength
				final double      pt_scale_post,  // final double      scale_filtered_strength_post,// scale plate_ds[1] when replacing raw (generally plate_ds is more reliable if it exists)

				// Composite scan parameters
				final double      combine_min_strength,              // final double                minStrength,
				final double      combine_min_hor,                   // final double                minStrengthHor,
				final double      combine_min_vert,                  // final double                minStrengthVert,

				//getFilteredDisparityStrength parameter
				final double      fds_str_floor,
				final double      fds_str_pow,
				final int         fds_smpl_size, // == 5
				final int         fds_smpl_points, // == 3
				final double      fds_abs_rms,
				final double      fds_rel_rms,
				final boolean     fds_use_wnd,   // use window function fro the neighbors
				final double      fds_tilt_damp,
				final double      fds_abs_tilt, //   = 2.0; // pix per tile
				final double      fds_rel_tilt, //   = 0.2; // (pix / disparity) per tile

				// expansion from neighbors parameters:
				final int         gr_min_new,         // discard variant if there are less new tiles
				final int         gr_steps_over,  // how far to extend
				final boolean     gr_var_new_sngl,
				final boolean     gr_var_new_fg,
				final boolean     gr_var_all_fg,
				final boolean     gr_var_new_bg,
				final boolean     gr_var_all_bg,
				final boolean     gr_var_next,

				final int         gr_smpl_size ,      // 5,      // 	final int         smpl_size, // == 5
				final int         gr_min_pnts ,       // 5, // 4, // 3,      //    final int        min_points, // == 3
				final boolean     gr_use_wnd ,        // true,   // 	final boolean     use_wnd,   // use window function fro the neighbors
				final double      gr_tilt_damp ,      // 0.001,  // 	final double      tilt_cost,
				final double      gr_split_rng ,      // 5.0,    // 	final double      split_threshold, // if full range of the values around the cell higher, need separate fg, bg
				final double      gr_same_rng ,       // 3.0,    // 	final double      same_range,      // modify
				final double      gr_diff_cont ,      // 2.0,    // 	final double      diff_continue,   // maximal difference from the old value (for previously defined tiles
				final double      gr_abs_tilt ,       // 2.0,    //    final double     max_abs_tilt, //   = 2.0; // pix per tile
				final double      gr_rel_tilt ,       // 0.2,    //    final double     max_rel_tilt, //   = 0.2; // (pix / disparity) per tile
				final int         gr_smooth ,         // 50,     // 	final int         max_tries, // maximal number of smoothing steps
				final double      gr_fin_diff ,       // 0.01,   // 	final double      final_diff, // maximal change to finish iterations
				final double      gr_unique_pretol ,  // 1.0,    // 	final double      unique_pre_tolerance, // usually larger than clt_parameters.unique_tolerance
				// legacy expansion

				final double      grow_min_diff,   // =    0.5; // Grow more only if at least one channel has higher variance from others for the tile
				final boolean     grow_retry_far,  // final boolean     grow_retry_far,  // Retry tiles around known foreground that have low max_tried_disparity
				final boolean     grow_pedantic,   // final boolean     grow_pedantic,   // Scan full range between max_tried_disparity of the background and known foreground
				final boolean     grow_retry_inf,  // final boolean     grow_retry_inf,  // Retry border tiles that were identified as infinity earlier

				// common expansion
				final int         gr_num_steps,    // how far to extend
				final double      gr_unique_tol,
				final double      grow_disp_min,   // final double                disp_near,
				final double      grow_disp_max,   // final double                disp_near,
				final double      grow_disp_step,  //  =   6.0; // Increase disparity (from maximal tried) if nothing found in that tile // TODO: handle enclosed dips?

				// for debug level > 1, just corresponding clt_parameters.* will work, otherwise following 3 can override
				final boolean     last_pass, // just for more debug?
				final boolean     remove_non_measurement, // save memory, true is OK
				final boolean     show_expand,
				final boolean     show_unique,
				final boolean     show_retry_far,

				final int         dbg_x,
				final int         dbg_y,
				final int         debugLevel)
	  {
    	  boolean dbg_pass = debugLevel > 1;
		  final CLTPass3d   bg_scan = passes.get(bg_index);         // background scan data, null - ignore background

    	  Runtime runtime = Runtime.getRuntime();
    	  runtime.gc();
    	  System.out.println("--- Free memory24="+runtime.freeMemory()+" (of "+runtime.totalMemory()+")");


    	  // Get filtered (by flexible "plates" that can tilt to accommodate tiles disparity/strength map, that uses data from all previous
    	  // disparity "measurements". This data will be combined with individual tiles, quad, hor and vert correlation results

    	  double [][] filtered_disp_strength = tp.getFilteredDisparityStrength( // disp all 0?, str -1/0
				  tp.clt_3d_passes, // final ArrayList <CLTPass3d> passes,// List, first, last - to search for the already tried disparity
    			  lastPassPlus1 - 1,                   // final int        measured_scan_index, // will not look at higher scans
    			  0,                                  // final int        start_scan_index,
    			  bg_scan.getSelected(),              // selected , // final boolean [] bg_tiles,          // get from selected in clt_3d_passes.get(0);
    			  ex_min_over,                        // final double     ex_min_over,       // when expanding over previously detected (by error) background, disregard far tiles
    			  ImageDtt.DISPARITY_INDEX_CM,        // final int        disp_index,
    			  ImageDtt.DISPARITY_STRENGTH_INDEX,  // final int        str_index,
    			  null,                               // final double []  tiltXY,    // null - free with limit on both absolute (2.0?) and relative (0.2) values
    			  tp.getTrustedCorrelation(),         // final double     trustedCorrelation,
    			  fds_str_floor,                 //	final double     strength_floor,
    			  fds_str_pow,                   // final double     strength_pow,
    			  fds_smpl_size,                      // final int        smplSide, //        = 2;      // Sample size (side of a square)
    			  fds_smpl_points,                    // final int        smplNum, //         = 3;      // Number after removing worst (should be >1)
    			  fds_abs_rms,                        //	final double     smplRms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
    			  fds_rel_rms,                        // final double     smplRelRms, //      = 0.005;  // Maximal RMS/disparity in addition to smplRms
    			  fds_use_wnd,                        //	final boolean    smplWnd, //
    			  fds_abs_tilt,                       //	final double     max_abs_tilt, //  = 2.0; // pix per tile
    			  fds_rel_tilt,                       //	final double     max_rel_tilt, //  = 0.2; // (pix / disparity) per tile
    			  dbg_x,                              //	final int        dbg_x,
    			  dbg_y,                              // final int        dbg_y,
    			  debugLevel+1);                      //	final int        debugLevel)

    	  // Optionally filter by background data to avoid far objects in the areas already identified as bgnd
    	  if (filtered_bgnd_ds != null) {
    		  tp.filterOverBackground(
    				  filtered_disp_strength,         // final double [][]           ds,
    				  filtered_bgnd_ds[1],            // final double []             bg_strength,
    				  bg_scan.getSelected(),          // final boolean []            bg_tiles,          // get from selected in clt_3d_passes.get(0);
    				  str_over_bg,                    // final double                minStrength,
    				  ex_min_over);                   // final double                ex_min_over        // when expanding over previously detected (by error) background, disregard far tiles
    	  }
    	  int refine_pass = passes.size() - 1; // last index plus 1

    	  // refine pass uses hor/vert thresholds inside
    	  // Now always start with refine - it will set disparity to improve previous results where residual disparity was significant
    	  if (refine) { // currently is broken if !refine
    		  CLTPass3d refined = tp.refinePassSetup( // prepare tile tasks for the refine pass (re-measure disparities), add it to the list
    				  clt_parameters,
					  0, // int               clust_radius,     // 0 - initial single-tile, 1 - 1x1 (same), 2 - 3x3, 3 5x5
    				  stUseRefine, // use supertiles
    				  bg_index, // bg_scan bg_pass,
    				  // disparity range - differences from
    				  bgnd_range,     // double            disparity_far,
    				  grow_disp_max,  // other_range, //double            disparity_near,   //
    				  ex_strength,    // double            this_sure,        // minimal strength to be considered definitely good
    				  ex_nstrength,   // double            ex_nstrength, // minimal 4-corr strength divided by channel diff for new (border) tiles
    				  bgnd_maybe,     // double            this_maybe,       // maximal strength to ignore as non-background
    				  sure_smth,      // sure_smth,        // if 2-nd worst image difference (noise-normalized) exceeds this - do not propagate bgnd
    				  pt_super_trust, //  final double      super_trust,      // If strength exceeds ex_strength * super_trust, do not apply ex_nstrength and plate_ds
    				  // using plates disparity/strength - averaged for small square sets of tiles. If null - just use raw tiles
    				  filtered_disp_strength,        // 		final double [][] plate_ds,  // disparity/strength last time measured for the multi-tile squares. Strength =-1 - not measured. May be null
    				  pt_keep_raw_fg, // 		final boolean     keep_raw_fg,  // do not replace raw tiles by the plates, if raw is closer (like poles)
    				  pt_scale_pre,   // final double      scale_filtered_strength_pre, // scale plate_ds[1] before comparing to raw strength
    				  pt_scale_post,  // final double      scale_filtered_strength_post,// scale plate_ds[1] when replacing raw (generally plate_ds is more reliable if it exists)
//    				  ImageDtt.DISPARITY_INDEX_CM,   // index of disparity value in disparity_map == 2 (0,2 or 4)
    				  geometryCorrection,
    				  tp.threadsMax,  // maximal number of threads to launch
    				  false, // updateStatus,
    				  debugLevel);
    		  passes.add(refined); // adding new scan, not yet measured
    		  refine_pass ++; // tp.refinePassSetup adds to the list
    		  if (show_expand || (clt_parameters.show_expand && dbg_pass)) {
    			  tp.showScan(
    					  passes.get(refine_pass), // CLTPass3d   scan,
    					  "after_refine-"+refine_pass);
    		  }
    	  }
    	  // Maybe it will not be used later, still calculate
    	  tp.calcMaxTried(
    			  passes, // final ArrayList <CLTPass3d> passes,
    			  bg_index, //  final int                   firstPass,
    			  refine_pass, // may add 1 to include current (for future?)     //  final int                   lastPassPlus1,
    			  passes.get(refine_pass)); //  final int                   lastPassPlus1,
    	  // repeated composite scan may be replaced by just a clone
		  if (last_pass){
			  System.out.println("+++++++++++ Last pass - pre  compositeScan() ++++++++++++");
		  }

    	  CLTPass3d extended_pass = tp.compositeScan(
    			  passes,             // final ArrayList <CLTPass3d> passes,
    			  bg_index,           //  final int                   firstPass,
    			  passes.size(),      //  final int                   lastPassPlus1,
    			  //      				  tp.clt_3d_passes.get(bg_pass).getSelected(), // selected , // final boolean [] bg_tiles,          // get from selected in clt_3d_passes.get(0);
    			  //    				  clt_parameters.ex_min_over,// final double     ex_min_over,       // when expanding over previously detected (by error) background, disregard far tiles
    			  tp.getTrustedCorrelation(),                       // 	 final double                trustedCorrelation,
    			  tp.getMaxOverexposure(),                          //  final double                max_overexposure,
    			  0.0, // clt_parameters.bgnd_range,                //	 final double                disp_far,   // limit results to the disparity range
    			  grow_disp_max,                     // final double                disp_near,
    			  combine_min_strength,              // final double                minStrength,
    			  combine_min_hor,                   // final double                minStrengthHor,
    			  combine_min_vert,                  // final double                minStrengthVert,
				  false,      // final boolean               no_weak,
    			  true, // false, // final boolean               use_last,   //
    			  // TODO: when useCombo - pay attention to borders (disregard)
    			  false, // final boolean               usePoly)  // use polynomial method to find max), valid if useCombo == false
    			  true, // 	 final boolean               copyDebug)
    			  debugLevel - 1);
    	  if (show_expand || (clt_parameters.show_expand && last_pass)) tp.showScan(
    			  passes.get(refine_pass), // CLTPass3d   scan,
    			  "after_refine-combine0-"+(passes.size() - 1));
    	  // TODO: Verify that IMG_DIFF0_INDEX has combined data
    	  if (filtered_bgnd_ds != null) {
    		  tp.filterOverBackground(
    				  extended_pass,           // final CLTPass3d            pass,
    				  str_over_bg_combo,       //	 final double             minStrength,
    				  str_over_bg_combo_hor,   //	 final double             minStrengthHor,
    				  str_over_bg_combo_vert,  //final double                minStrengthVert,
    				  bg_scan.getSelected(),   // selected , // final boolean [] bg_tiles,          // get from selected in clt_3d_passes.get(0);
    				  ex_min_over,             // final double     ex_min_over,       // when expanding over previously detected (by error) background, disregard far tiles
    				  filtered_disp_strength);
    	  }
    	  if (show_expand || (clt_parameters.show_expand && last_pass)) tp.showScan(
    			  passes.get(refine_pass), // CLTPass3d   scan,
    			  "after_refine-combine1-"+(passes.size() - 1));


    	  if (show_expand || (clt_parameters.show_expand && last_pass)) {
    		  final int tilesX = tp.getTilesX();
    		  final int tilesY = tp.getTilesY();
    		  String [] titles = {"disp","strength", "disp_combined","str_combined","max_tried","selected"};
    		  String title = "FDS_"+(passes.size() - 1);
    		  double [][] dbg_img = new double [titles.length][];
    		  dbg_img[0] = filtered_disp_strength[0];
    		  dbg_img[1] = filtered_disp_strength[1];
    		  dbg_img[2] = extended_pass.getDisparity();
    		  dbg_img[3] = extended_pass.getStrength();
    		  double [][] max_tried_disparity = extended_pass.getMaxTriedDisparity();
    		  if (max_tried_disparity != null){
    			  dbg_img[4] = new double [tilesX * tilesY];
    			  for (int ty = 0; ty < tilesY; ty++){
    				  for (int tx = 0; tx < tilesX; tx++){
    					  dbg_img[4][ty*tilesX + tx] = max_tried_disparity[ty][tx];
    				  }
    			  }
    		  }
    		  boolean [] s_selected =  extended_pass.getSelected(); // selected;
    		  boolean [] s_border =    extended_pass.getBorderTiles();
    		  if ((s_selected != null) && (s_border != null)){
    			  dbg_img[5] = new double [tilesX * tilesY];
    			  for (int i = 0; i < dbg_img[5].length; i++){
    				  dbg_img[5][i] = 1.0 * ((s_selected[i]?1:0) + (s_border[i]?2:0));
    			  }
    		  }

    		  ShowDoubleFloatArrays.showArrays(dbg_img,  tilesX, tilesY, true, title,titles);
    	  }
    	  int [] numLeftRemoved = {0, 0};
    	  int num_extended = 0;
    	  if (expand_neibs) { // show_expand){ // use new mode
    		  if (last_pass){
    			  System.out.println("+++++++++++ Last pass ++++++++++++");
    		  }

    		  //        			public boolean []
    		  tp.dbg_filtered_disp_strength = filtered_disp_strength; // to be shown inside
    		  boolean [] variants_flags = {
    				  gr_var_new_sngl,
    				  gr_var_new_fg,
    				  gr_var_all_fg,
    				  gr_var_new_bg,
    				  gr_var_all_bg,
    				  gr_var_next};
    		  numLeftRemoved = tp.prepareExpandVariant(
    				  extended_pass,                     // final CLTPass3d   scan,            // combined scan with max_tried_disparity, will be modified to re-scan
    				  passes.get(refine_pass), // final CLTPass3d   last_scan,       // last prepared tile - can use last_scan.disparity, .border_tiles and .selected
    				  /*null, // */ passes.get(bg_index),     // final CLTPass3d   bg_scan,         // background scan data
    				  passes,                  // final ArrayList <CLTPass3d> passes,// List, first, last - to search for the already tried disparity
    				  0,                                 // 	final int         firstPass,
    				  passes.size(),           //         					final int         lastPassPlus1,
    				  gr_min_new ,        // 20, // 	final int         min_new,         // discard variant if there are less new tiles
    				  variants_flags, // 0x1e, // 0x1f,                     // final int         variants_mask,
    				  gr_num_steps ,      // 8, // 	final int         num_steps,  // how far to extend
    				  gr_steps_over ,     // 8, // 4, // final int         num_steps_disc,  // how far to extend
    				  gr_smpl_size ,      // 5,      // 	final int         smpl_size, // == 5
    				  gr_min_pnts ,       // 5, // 4, // 3,      //    final int        min_points, // == 3
    				  gr_use_wnd ,        // true,   // 	final boolean     use_wnd,   // use window function fro the neighbors
    				  gr_tilt_damp ,      // 0.001,  // 	final double      tilt_cost,
    				  gr_split_rng ,      // 5.0,    // 	final double      split_threshold, // if full range of the values around the cell higher, need separate fg, bg
    				  gr_same_rng ,       // 3.0,    // 	final double      same_range,      // modify
    				  gr_diff_cont ,      // 2.0,    // 	final double      diff_continue,   // maximal difference from the old value (for previously defined tiles
    				  gr_abs_tilt ,       // 2.0,    //    final double     max_abs_tilt, //   = 2.0; // pix per tile
    				  gr_rel_tilt ,       // 0.2,    //    final double     max_rel_tilt, //   = 0.2; // (pix / disparity) per tile
    				  gr_smooth ,         // 50,     // 	final int         max_tries, // maximal number of smoothing steps
    				  gr_fin_diff ,       // 0.01,   // 	final double      final_diff, // maximal change to finish iterations
    				  grow_disp_min,      // final double                grow_disp_min,
    				  grow_disp_max,      // final double                grow_disp_max,
    				  grow_disp_step,      // final double                grow_disp_step,
    				  gr_unique_pretol ,  // 1.0,    // 	final double      unique_pre_tolerance, // usually larger than clt_parameters.unique_tolerance
//    				  last_pass?  gr_unique_tol : gr_unique_pretol, // 	final double      unique_tolerance,
    				  gr_unique_tol, // last_pass?  clt_parameters.gr_unique_tol : clt_parameters.gr_unique_pretol, // 	final double      unique_tolerance,
    				  show_expand,       // 	final boolean     show_expand,
    				  show_unique,       // 	final boolean     show_unique,
    				  dbg_x,
    				  dbg_y,
    				  debugLevel);
        	  num_extended = numLeftRemoved[0]; //0,0
			  if (clt_parameters.show_expand || (clt_parameters.show_variant && (numLeftRemoved[1] > 1 ))) tp.showScan( // not used in lwir
					  tp.clt_3d_passes.get(refine_pass), // CLTPass3d   scan,
					  "prepareExpandVariant-"+numLeftRemoved[1]+"-"+refine_pass); //String title)
    	  }
    	  if ((num_extended == 0) && expand_legacy) { // if both are on, will use legacy if neighbors failed // not used in lwir

    		  boolean show_ex_debug = show_retry_far || (clt_parameters.show_retry_far && last_pass) || dbg_pass;
    		  num_extended = tp.setupExtendDisparity(
    				  extended_pass,                              // final CLTPass3d   scan,            // combined scan with max_tried_disparity, will be modified to re-scan
    				  passes.get(refine_pass), // final CLTPass3d   last_scan,       // last prepared tile - can use last_scan.disparity, .border_tiles and .selected
    				  (filtered_bgnd_ds != null)? null: passes.get(bg_index), // final CLTPass3d   bg_scan,         // background scan data
    						  gr_num_steps, // clt_parameters.grow_sweep,      // 8; // Try these number of tiles around known ones
    						  grow_disp_max,   //  =   50.0; // Maximal disparity to try
    						  0.5 * tp.getTrustedCorrelation(), // clt_parameters.grow_disp_trust, //  =  4.0; // Trust measured disparity within +/- this value
    						  grow_disp_step,  //  =   6.0; // Increase disparity (from maximal tried) if nothing found in that tile // TODO: handle enclosed dips?
    						  grow_min_diff,   // =    0.5; // Grow more only if at least one channel has higher variance from others for the tile
    						  grow_retry_far,  // final boolean     grow_retry_far,  // Retry tiles around known foreground that have low max_tried_disparity
    						  grow_pedantic,   // final boolean     grow_pedantic,   // Scan full range between max_tried_disparity of the background and known foreground
    						  grow_retry_inf,  // final boolean     grow_retry_inf,  // Retry border tiles that were identified as infinity earlier
    						  clt_parameters, // EyesisCorrectionParameters.CLTParameters           clt_parameters,
    						  geometryCorrection, // GeometryCorrection geometryCorrection,
    						  show_ex_debug,     // true, // final boolean     show_debug,

    						  tp.threadsMax,  // maximal number of threads to launch
    						  false, // updateStatus,
    						  debugLevel);
    		  //TODO:  break if nothing wanted? - no, there are some left to be refined

    		  if (debugLevel > -1){
    			  System.out.println("=== setupExtendDisparity() added "+num_extended+" new tiles to scan");
    		  }

    		  numLeftRemoved = tp.makeUnique(
    				  passes,                  // final ArrayList <CLTPass3d> passes,
    				  0,                                 //  final int                   firstPass,
    				  refine_pass, // - 1,                   //  final int                   lastPassPlus1,
					  0, // 	 final int                   clust_radius,     // 0 - initial single-tile, 1 - 1x1 (same), 2 - 3x3, 3 5x5
    				  extended_pass, // tp.clt_3d_passes.get(refine_pass), //  final CLTPass3d             new_scan,
    				  clt_parameters.grow_disp_max,       // final double                grow_disp_max,
    				  clt_parameters.gr_unique_tol,   //  final double                unique_tolerance,
    				  clt_parameters.show_unique);      // final boolean               show_unique)

        	  num_extended = numLeftRemoved[0];

    		  if (debugLevel > -1){
    			  System.out.println("last makeUnique("+refine_pass+") -> left: "+numLeftRemoved[0]+", removed:" + numLeftRemoved[1]);
    		  }



    		  //        		  num_extended = numLeftRemoved[0];
    		  //TODO:  break if nothing wanted? - here yes, will make sense



    	  }

		  refine_pass = passes.size(); // 1

		  passes.add(extended_pass);

		  if (show_expand) tp.showScan(
				  passes.get(refine_pass), // CLTPass3d   scan,
				  "before_measure-"+refine_pass); //String title)

//    	  num_extended = numLeftRemoved[0];

		  CLTMeasureCorr( // perform single pass according to prepared tiles operations and disparity  BUG: gets with .disparity==null
//    			  image_data, // first index - number of image in a quad
//				  saturation_imp, //final boolean [][]  saturation_imp, // (near) saturated pixels or null
    			  clt_parameters,
    			  refine_pass,
    			  false, // final boolean     save_textures,
				  0,                 // final int         clust_radius,
    			  tp.threadsMax,  // maximal number of threads to launch
    			  false, // updateStatus,
    			  debugLevel - 1);
    	  if (remove_non_measurement) {
    		  if (debugLevel > 0){
    			  System.out.print("Removing non-measurement(derivative) scan data, current list size = "+passes.size());
    		  }
    		  tp.removeNonMeasurement();
    		  refine_pass = passes.size() - 1;
    		  if (debugLevel > 0){
    			  System.out.println(", new size = "+passes.size());
    		  }
    	  }


    	  if (show_expand || (clt_parameters.show_expand && last_pass)) {
    		  tp.showScan(
    				  passes.get(refine_pass), // CLTPass3d   scan,
    				  "after_measure-"+refine_pass); //String title)

    	  }
    	  if (debugLevel > -1){
    		  System.out.println("extending: CLTMeasure("+refine_pass+"),  num_extended = "+num_extended);
    	  }
    	  //num_expand == 9
    	  CLTPass3d combo_pass = tp.compositeScan(
    			  passes, // final ArrayList <CLTPass3d> passes,
    			  bg_index,                                         //  final int                   firstPass,
    			  passes.size(),                          //  final int                   lastPassPlus1,
    			  tp.getTrustedCorrelation(),                       // 	 final double                trustedCorrelation,
    			  tp.getMaxOverexposure(),                          //  final double                max_overexposure,
    			  -0.5, // 0.0, // clt_parameters.bgnd_range,                //	 final double                disp_far,   // limit results to the disparity range
    			  clt_parameters.grow_disp_max,                     // final double                disp_near,
    			  clt_parameters.combine_min_strength,              // final double                minStrength,
    			  clt_parameters.combine_min_hor,                   // final double                minStrengthHor,
    			  clt_parameters.combine_min_vert,                  // final double                minStrengthVert,
				  false,      // final boolean               no_weak,
    			  false, // final boolean               use_last,   //
    			  // TODO: when useCombo - pay attention to borders (disregard)
    			  false, // final boolean               usePoly)  // use polynomial method to find max), valid if useCombo == false
    			  true, // 	 final boolean               copyDebug)
    			  //    				  (num_expand == 9)? 2: debugLevel);
    			  debugLevel -1);
    	  passes.add(combo_pass);
    	  if (show_expand || (clt_parameters.show_expand && last_pass)) tp.showScan(
    			  passes.get(passes.size() -1), // refine_pass), // CLTPass3d   scan,
    			  "after_combo_pass-"+(passes.size() -1)); // (refine_pass)); //String title)
    	  return num_extended; // [0];
	  }
// Separate method to detect and remove periodic structures

	  public boolean showPeriodic( // not used in lwir
			  CLTParameters           clt_parameters,
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel) {
		  final boolean        usePoly =            false; // use polynomial method to find max), valid if useCombo == false

		  double [][] periodics = tp.getPeriodics(
				  tp.clt_3d_passes,                     // final ArrayList <CLTPass3d> passes,// List, first, last - to search for the already tried disparity
				  0,                                    // final int         firstPass,
				  tp.clt_3d_passes.size(),              // final int         lastPassPlus1,
				  clt_parameters.per_trustedCorrelation,// final double                trustedCorrelation,
				  clt_parameters.per_initial_diff,      // final double                initial_diff, // initial disparity difference to merge to maximum
				  clt_parameters.per_strength_floor,    // final double                strength_floor,
				  clt_parameters.per_strength_max_over, // final double                strength_max_over, // maximum should have strength by this more than the floor

				  clt_parameters.per_min_period,        // final double                min_period,
				  clt_parameters.per_min_num_periods,   // final int                   min_num_periods, // minimal number of periods
				  clt_parameters.per_disp_tolerance,    // final double                disp_tolerance, // maximal difference between the average of fundamental and 2-nd and first
				  // TODO: replace next parameter
				  clt_parameters.per_disp_tolerance,    // final double                disp_tol_center, // tolerance to match this (center) tile ds to that of the merged with neighbors - should be < min_period/2
				  clt_parameters.per_disp_match,        // final double                disp_match,     // disparity difference to match neighbors
				  clt_parameters.per_strong_match_inc,  // final double                strong_match_inc,   // extra strength to treat match as strong (for hysteresis)
				  usePoly,                              // final boolean               usePoly,  // use polynomial method to find max), valid if useCombo == false
				  clt_parameters.tileX,                 // final int               dbg_tileX,
				  clt_parameters.tileY,                 // final int               dbg_tileY,
				  threadsMax,                           // final int                   threadsMax,  // maximal number of threads to launch
				  updateStatus,                         // final boolean               updateStatus,
				  debugLevel+3);                        // final int                   debugLevel) // update status info

		  tp.periodics = periodics;

		  String [] dbg_titles= {"fundamental","period", "strength", "num_layers"};
			double [][] dbg_img = new double [4][tp.getTilesX()*tp.getTilesY()];
			for (int n = 0; n < periodics.length; n++) {
				for (int i = 0; i < dbg_img.length; i++) {
					dbg_img[i][n]= (periodics[n] == null) ? Double.NaN : periodics[n][i];
				}
			}

		  ShowDoubleFloatArrays.showArrays(
				  dbg_img,
				  tp.getTilesX(),
				  tp.getTilesY(),
				  true,
				  image_name+sAux()+"-PERIODIC",
				  dbg_titles);
		  return true;
	  }






//*****************************************************************

//	  public ImagePlus output3d(
	  public boolean output3d( // USED in lwir
			  CLTParameters                            clt_parameters,
			  ColorProcParameters                      colorProcParameters,
			  EyesisCorrectionParameters.RGBParameters rgbParameters,
			  final int                                threadsMax,  // maximal number of threads to launch
			  final boolean                            updateStatus,
			  final int                                debugLevel)
	  {
		  final boolean    batch_mode = clt_parameters.batch_run;
		  this.startStepTime=System.nanoTime();
		  final int tilesX = tp.getTilesX();
		  final int tilesY = tp.getTilesY();
		  if (getImageData() == null){
			  return false; // not used in lwir
		  }
		  double infinity_disparity = 	geometryCorrection.getDisparityFromZ(clt_parameters.infinityDistance);
		  X3dOutput x3dOutput = null;
		  WavefrontExport wfOutput = null;
		  ArrayList<TriMesh> tri_meshes = null; 
		  if (clt_parameters.remove_scans){
			  System.out.println("Removing all scans but the first(background) and the last to save memory");
			  System.out.println("Will need to re-start the program to be able to output differently");
			  CLTPass3d   latest_scan = tp.clt_3d_passes.get(tp.clt_3d_passes.size() - 1);
			  tp.trimCLTPasses(1);
			  tp.clt_3d_passes.add(latest_scan); // put it back
		  }
		  int next_pass = tp.clt_3d_passes.size(); //
		  // Create tasks to scan, have tasks, disparity and border tiles in tp.clt_3d_passes
		  tp.thirdPassSetupSurf( // prepare tile tasks for the second pass based on the previous one(s) // needs last scan
				  clt_parameters,
				  //FIXME: make a special parameter?
				  infinity_disparity, //0.25 * clt_parameters.bgnd_range, // double            disparity_far,
				  clt_parameters.grow_disp_max, // other_range, //double            disparity_near,   //
				  geometryCorrection,
				  threadsMax,  // maximal number of threads to launch
				  updateStatus,
				  0); // final int         debugLevel)
		  if (!batch_mode && (debugLevel > -1)) {
			  tp.showScan(
					  tp.clt_3d_passes.get(next_pass-1),   // CLTPass3d   scan,
					  "after_pass3-"+(next_pass-1)); //String title)
		  }
			String x3d_path = getX3dDirectory(true);
 			// create x3d file
		  if (clt_parameters.output_x3d) {
			  x3dOutput = new X3dOutput(
					clt_parameters,
					correctionsParameters,
					geometryCorrection,
					tp.clt_3d_passes);
		  }
		  if (clt_parameters.output_obj && (x3d_path != null)) {
			  try {
				  wfOutput = new WavefrontExport(
						  x3d_path,
						  correctionsParameters.getModelName(this.image_name),
						  clt_parameters,
						  correctionsParameters,
						  geometryCorrection,
						  tp.clt_3d_passes);
			  } catch (IOException e) {
				  System.out.println("Failed to open Wavefront files for writing");
				  // TODO Auto-generated catch block
				  e.printStackTrace();
				  // do nothing, just keep
			  }
		  }
		  if (clt_parameters.output_glTF && (x3d_path != null)) {
			  tri_meshes = new ArrayList<TriMesh>();
		  }		

		  if (x3dOutput != null) {
			  x3dOutput.generateBackground(clt_parameters.infinityDistance <= 0.0); // needs just first (background) scan
		  }

		  int bgndIndex = 0; // it already exists?
		  CLTPass3d bgndScan = tp.clt_3d_passes.get(bgndIndex);
//		  boolean [] bgnd_sel = bgndScan.getSelected().clone();
//		  int num_bgnd = 0;
//		  for (int i = 0; i < bgnd_sel.length; i++) if (bgnd_sel[i]) num_bgnd++;
//		  if (num_bgnd >= clt_parameters.min_bgnd_tiles) { // TODO: same for the backdrop too
//		  double infinity_disparity = 	geometryCorrection.getDisparityFromZ(clt_parameters.infinityDistance);
//TODO make it w/o need for  bgndScan.texture as GPU will calculate texture right before output
//use selection? or texture_selection instead?		  
		  if (bgndScan.texture != null) { // TODO: same for the backdrop too
			  if (clt_parameters.infinityDistance > 0.0){ // generate background as a billboard
				  // grow selection, then grow once more and create border_tiles
				  // create/rstore, probably not needed
				  boolean [] bg_sel_backup = bgndScan.getSelected().clone();
				  boolean [] bg_border_backup = (bgndScan.getBorderTiles() == null) ? null: bgndScan.getBorderTiles().clone();
				  boolean [] bg_selected = bgndScan.getSelected();
				  boolean [] border_tiles = bg_selected.clone();
				  tp.growTiles(
						  2,                   // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
						  bg_selected,
						  null); // prohibit

				  for (int i = 0; i < border_tiles.length; i++){
					  border_tiles[i] = !border_tiles[i] && bg_selected[i];
				  }
				  // update texture_tiles (remove what is known not to be background
				  if (bgndScan.texture_tiles != null) { // for CPU
					  for (int ty = 0; ty < tilesY; ty++){
						  for (int tx = 0; tx < tilesX; tx++){
							  if (!bg_selected[tx + tilesX*ty]){
								  bgndScan.texture_tiles[ty][tx] = null; //
							  }

						  }
					  }
				  } else {//  for GPU
					  for (int i = 0; i < bg_selected.length; i++) {
						  if (!bg_selected[i]) {
							  bgndScan.setTextureSelection(i,false);
						  }
					  }
				  }
				  
//TODO2020: set texture_selection
				  bgndScan.setBorderTiles(border_tiles);
				  // limit tile_op to selection?
				  // updates selection from non-null texture tiles
				  String texturePath = getPassImage( // get image from a single pass - both CPU and GPU
						  clt_parameters,
						  colorProcParameters,
						  rgbParameters,
						  correctionsParameters.getModelName(this.image_name)+"-img_infinity", // +scanIndex,
						  bgndIndex,
						  threadsMax,  // maximal number of threads to launch
						  updateStatus,
						  batch_mode ? -5: debugLevel);
				  if (texturePath != null) { // null if empty image
					  double [] scan_disparity = new double [tilesX * tilesY];
					  int indx = 0;
					  //		  boolean [] scan_selected = scan.getSelected();
					  for (int ty = 0; ty < tilesY; ty ++) for (int tx = 0; tx < tilesX; tx ++){
						  scan_disparity[indx++] = infinity_disparity;
					  }
					  //			  tp.showScan(
					  //    				  scan, // CLTPass3d   scan,
					  //    				  "infinityDistance");

					  boolean showTri = false; // ((scanIndex < next_pass + 1) && clt_parameters.show_triangles) ||((scanIndex - next_pass) == 73);
					  try {
						  TriMesh.generateClusterX3d(
								  false,     //   boolean         full_texture, // true - full size image, false - bounds only
								  0,         //   int             subdivide_mesh, // 0,1 - full tiles only, 2 - 2x2 pixels, 4 - 2x2 pixels
//								  null,      //   boolean []      alpha,     // boolean alpha - true - opaque, false - transparent. Full/bounds
								  x3dOutput,
								  wfOutput,  // output WSavefront if not null
								  tri_meshes, // ArrayList<TriMesh> tri_meshes,
								  texturePath,
								  "INFINITY", // id (scanIndex - next_pass), // id
								  "INFINITY", // class
								  bgndScan.getTextureBounds(),
								  bgndScan.getSelected(), // selected,
								  scan_disparity, // scan.disparity_map[ImageDtt.DISPARITY_INDEX_CM],
								  clt_parameters.transform_size,
								  tp.getTilesX(), // int             tilesX,
								  tp.getTilesY(), // int             tilesY,
								  getGeometryCorrection(), // GeometryCorrection geometryCorrection,
								  clt_parameters.correct_distortions, // requires backdrop image to be corrected also
								  showTri, // (scanIndex < next_pass + 1) && clt_parameters.show_triangles,
								  infinity_disparity,  // 0.3
								  clt_parameters.grow_disp_max, // other_range, // 2.0 'other_range - difference from the specified (*_CM)
								  clt_parameters.maxDispTriangle,
								  clt_parameters.maxZtoXY,      // double          maxZtoXY,       // 10.0. <=0 - do not use
								  clt_parameters.maxZ,
								  clt_parameters.limitZ,
								  null, // dbg_disp_tri_slice, //   double [][]     dbg_disp_tri_slice,
								  debugLevel + 1); //   int             debug_level) > 0
					  } catch (IOException e) {
						  // TODO Auto-generated catch block
						  e.printStackTrace();
						  return false;
					  }
					  // maybe not needed
					  bgndScan.setBorderTiles(bg_border_backup);
					  bgndScan.setSelected(bg_sel_backup);
				  }
			  }
		  }

		  // With GPU - do nothing here or copy selected -> texture_selection?
		  for (int scanIndex = next_pass; scanIndex < tp.clt_3d_passes.size(); scanIndex++){
			  if (debugLevel > 0){
				  System.out.println("FPGA processing scan #"+scanIndex);
			  }
			  /*
			  CLTMeasure( // perform single pass according to prepared tiles operations and disparity
					  clt_parameters,
					  scanIndex,
					  true,  // final boolean     save_textures,
					  false, // final boolean     save_corr,
					  threadsMax,  // maximal number of threads to launch
					  updateStatus,
					  batch_mode ? -5: debugLevel);
		*/
			  CLTMeasureTextures( // has GPU version - will just copy selection
					  clt_parameters,
					  scanIndex,
					  threadsMax,  // maximal number of threads to launch
					  updateStatus,
					  batch_mode ? -5: debugLevel);

		  }

		  for (int scanIndex = next_pass; (scanIndex < tp.clt_3d_passes.size()) && (scanIndex < clt_parameters.max_clusters); scanIndex++){ // just temporary limiting
			  if (debugLevel > -1){
				  System.out.println("Generating cluster images (limit is set to "+clt_parameters.max_clusters+") largest, scan #"+scanIndex);
			  }
			  //		  ImagePlus cluster_image = getPassImage( // get image form a single pass
			  String texturePath = getPassImage( // get image from a single pass
					  clt_parameters,
					  colorProcParameters,
					  rgbParameters,
					  correctionsParameters.getModelName(this.image_name)+"-img"+scanIndex,
					  scanIndex,
					  threadsMax,  // maximal number of threads to launch
					  updateStatus,
					  batch_mode ? -5: debugLevel);
			  if (texturePath == null) { // not used in lwir
				  continue; // empty image
			  }
			  CLTPass3d scan = tp.clt_3d_passes.get(scanIndex);

			  // TODO: use new updated disparity, for now just what was forced for the picture
			  double [] scan_disparity = new double [tilesX * tilesY];
			  int indx = 0;
			  for (int ty = 0; ty < tilesY; ty ++) for (int tx = 0; tx < tilesX; tx ++){
				  scan_disparity[indx++] = scan.disparity[ty][tx];
			  }
			  if (clt_parameters.avg_cluster_disp){
				  double sw = 0.0, sdw = 0.0;
				  for (int i = 0; i< scan_disparity.length; i++){
//					  if (scan.selected[i] && !scan.border_tiles[i]){
					  if (scan.getSelected()[i] && !scan.getBorderTiles()[i]){
						  double w = scan.disparity_map[ImageDtt.DISPARITY_STRENGTH_INDEX][i];
						  sw +=w;
						  sdw += scan_disparity[i]*w;
					  }
				  }
				  sdw/=sw;
				  for (int i = 0; i< scan_disparity.length; i++){
					  scan_disparity[i] = sdw;
				  }
			  }
			  boolean showTri = !batch_mode && (debugLevel > -1) && (((scanIndex < next_pass + 1) && clt_parameters.show_triangles) ||((scanIndex - next_pass) == 73));
			  try {
				  TriMesh.generateClusterX3d(
						  false, //   boolean         full_texture, // true - full size image, false - bounds only
						  0, //   int             subdivide_mesh, // 0,1 - full tiles only, 2 - 2x2 pixels, 4 - 2x2 pixels
//						  null, //   boolean []      alpha,     // boolean alpha - true - opaque, false - transparent. Full/bounds
						  x3dOutput,
						  wfOutput,  // output WSavefront if not null
						  tri_meshes, // ArrayList<TriMesh> tri_meshes,
						  texturePath,
						  "shape_id-"+(scanIndex - next_pass), // id
						  null, // class
						  scan.getTextureBounds(),
						  scan.getSelected(),
						  scan_disparity, // scan.disparity_map[ImageDtt.DISPARITY_INDEX_CM],
						  clt_parameters.transform_size,
						  tp.getTilesX(), // int             tilesX,
						  tp.getTilesY(), // int             tilesY,
						  getGeometryCorrection(), // GeometryCorrection geometryCorrection,
						  clt_parameters.correct_distortions, // requires backdrop image to be corrected also
						  showTri, // (scanIndex < next_pass + 1) && clt_parameters.show_triangles,
						  // FIXME: make a separate parameter:
						  infinity_disparity, //  0.25 * clt_parameters.bgnd_range,  // 0.3
						  clt_parameters.grow_disp_max, // other_range, // 2.0 'other_range - difference from the specified (*_CM)
						  clt_parameters.maxDispTriangle,
						  clt_parameters.maxZtoXY,      // double          maxZtoXY,       // 10.0. <=0 - do not use
						  clt_parameters.maxZ,
						  clt_parameters.limitZ,
						  null, //   double [][]     dbg_disp_tri_slice,
						  debugLevel + 1); //   int             debug_level) > 0
			} catch (IOException e) {
				e.printStackTrace();
				return false;
			}
		  }

		  if ((x3d_path != null) && (x3dOutput != null)){
			  x3dOutput.generateX3D(x3d_path+Prefs.getFileSeparator()+correctionsParameters.getModelName(this.image_name)+".x3d");
		  }
		  if (wfOutput != null){
			  wfOutput.close();
			  System.out.println("Wavefront object file saved to "+wfOutput.obj_path);
			  System.out.println("Wavefront material file saved to "+wfOutput.mtl_path);
		  }

		  // Save KML and ratings files if they do not exist (so not to overwrite edited ones), make them world-writable
		  writeKml        (debugLevel);
///		  writeRatingFile (debugLevel); broke!


		  Runtime.getRuntime().gc();
		  System.out.println("output3d(): generating 3d output files  finished at "+
				  IJ.d2s(0.000000001*(System.nanoTime()-this.startStepTime),3)+" sec, --- Free memory25="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
		  return true;
	  }





//	  public ImagePlus getBackgroundImage( // USED in lwir
	  public boolean[] getBackgroundImageMasks( // USED in lwir
			  CLTParameters           clt_parameters,
			  CLTPass3d               bgnd_data,
			  String     name,
			  int        threadsMax,  // maximal number of threads to launch
			  boolean    updateStatus,
			  int        debugLevel
			  )
	  {
		  boolean dbg_gpu_transition = true;


		  final int tilesX = tp.getTilesX();
		  final int tilesY = tp.getTilesY();
		  boolean show_dbg = false;
		  if ((clt_parameters.debug_filters && (debugLevel > -1)) || dbg_gpu_transition)
			  if ((debugLevel > -1))
				  show_dbg = true;

		  boolean [] bgnd_tiles =   tp.getBackgroundMask( // which tiles do belong to the background
				  clt_parameters.bgnd_range,     // disparity range to be considered background
				  clt_parameters.bgnd_sure,      // minimal strength to be considered definitely background
				  clt_parameters.bgnd_maybe,     // maximal strength to ignore as non-background
				  clt_parameters.rsure_smth,
				  clt_parameters.cold_sky_above,
				  clt_parameters.min_clstr_seed, // number of tiles in a cluster to seed (just background?)
				  clt_parameters.min_clstr_block,// number of tiles in a cluster to block (just non-background?)
				  clt_parameters.show_bgnd_nonbgnd,
				  (clt_parameters.debug_filters ? (debugLevel) : -1));
		  boolean [] bgnd_tiles_new = null;//
		  if (!isLwir()) {
		  bgnd_tiles_new =   tp.getBackgroundMask_new( // which tiles do belong to the background
				  clt_parameters.bgnd_range,     // disparity range to be considered background
				  clt_parameters.bgnd_sure,      // minimal strength to be considered definitely background
				  clt_parameters.bgnd_maybe,     // maximal strength to ignore as non-background
				  clt_parameters.sure_smth,      // if 2-nd worst image difference (noise-normalized) exceeds this - do not propagate bgnd
				  clt_parameters.min_clstr_seed, // number of tiles in a cluster to seed (just background?)
				  clt_parameters.min_clstr_block,// number of tiles in a cluster to block (just non-background?)
				  clt_parameters.show_bgnd_nonbgnd,
				  (clt_parameters.debug_filters ? (debugLevel) : -1));
		  }
		  boolean [] bgnd_dbg =    bgnd_tiles.clone(); // only these have non 0 alpha
// TODO: fix mess - after modifying getBackgroundMask() to getBackgroundMask_new (before road with tractor was identified as a background because of a double
// tile glitch, the background was wrong. So temporarily both old/new are used and combined (new is grown twice)
// still does not work - using old variant for now

// background selections (slightly) influences the plane mertging / connections

		  for (int i = 0; i < bgnd_tiles.length; i++){
//			  bgnd_tiles[i] &= bgnd_tiles_new[i];
		  }
		  boolean [] bgnd_strict = bgnd_tiles.clone(); // only these have non 0 alpha
		  tp.growTiles(
				  clt_parameters.bgnd_grow,      // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
				  bgnd_tiles,
				  null); // prohibit
		  boolean [] bgnd_tiles_grown = bgnd_tiles.clone(); // only these have non 0 alpha
		  tp.growTiles(
				  2,      // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
				  bgnd_tiles,
				  null); // prohibit

// hacking - grow bgnd texture even more, without changing selection

		  bgnd_data.setSelected(bgnd_tiles_grown); // selected for background w/o extra transparent layer (not all false)
		  boolean [] bgnd_tiles_grown2 = bgnd_tiles_grown.clone(); // only these have non 0 alpha
		  tp.growTiles(
				  2,      // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
				  bgnd_tiles_grown2,
				  null); // prohibit
		  if ((debugLevel > 1000) && show_dbg){
			  double [][] dbg_img = new double[5][tilesY * tilesX];
			  String [] titles = {"old","new","strict","grown","more_grown"};
			  for (int i = 0; i<dbg_img[0].length;i++){
				  //
				  dbg_img[0][i] =  bgnd_dbg[i]?1:0;
				  dbg_img[1][i] =  bgnd_tiles_new[i]?1:0; // null
				  dbg_img[2][i] =  bgnd_strict[i]?1:0;
				  dbg_img[3][i] =  bgnd_tiles_grown[i]?1:0;
				  dbg_img[4][i] =  bgnd_tiles[i]?1:0;
			  }
			ShowDoubleFloatArrays.showArrays(dbg_img,  tilesX, tilesY, true, "strict_grown",titles);
		  }
		  // not here - will be set/calculated for GPU only
///		  bgnd_data.setTextureSelection(bgnd_tiles_grown2); // selected for background w/o extra transparent layer
		  return bgnd_tiles ;
	  }
	  
	  // Get BG image from already available non-overlapped texture_tiles in bg_scan
	  public ImagePlus getBackgroundImage( // USED in lwir
			  boolean []                                bgnd_tiles, 
			  CLTPass3d                                 bgnd_data,
			  CLTParameters                             clt_parameters,
			  ColorProcParameters                       colorProcParameters,
			  EyesisCorrectionParameters.RGBParameters  rgbParameters,
			  String     name,
			  int        threadsMax,  // maximal number of threads to launch
			  boolean    updateStatus,
			  int        debugLevel
			  ) {
		  final int tilesX = tp.getTilesX();
		  final int tilesY = tp.getTilesY();
		  final boolean new_mode = false;
		  int num_bgnd = 0;

		  double [][][][] texture_tiles = bgnd_data.texture_tiles;
		  double [][][][] texture_tiles_bgnd = new double[tilesY][tilesX][][];
		  double [] alpha_zero = new double [4*clt_parameters.transform_size*clt_parameters.transform_size];
		  int alpha_index = 3;
		  boolean [] bgnd_tiles_grown = bgnd_tiles.clone(); // only these have non 0 alpha
		  tp.growTiles(
				  2,      // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
				  bgnd_tiles,
				  null); // prohibit
		  boolean [] bgnd_tiles_grown2 = bgnd_tiles_grown.clone(); // only these have non 0 alpha
		  tp.growTiles(
				  2,      // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
				  bgnd_tiles_grown2,
				  null); // prohibit
		  
		  for (int i = 0; i < alpha_zero.length; i++) alpha_zero[i]=0.0;
		  // Seems to be wrong for the second?
		  
		  if (new_mode) { // not used
			  for (int tileY = 0; tileY < tilesY; tileY++){
				  for (int tileX = 0; tileX < tilesX; tileX++){
					  texture_tiles_bgnd[tileY][tileX]= null;
					  if ((texture_tiles[tileY][tileX] != null) &&
							  bgnd_tiles_grown2[tileY * tilesX + tileX]) {
						  if (bgnd_tiles[tileY * tilesX + tileX]) {
							  texture_tiles_bgnd[tileY][tileX]= texture_tiles[tileY][tileX];
							  num_bgnd++;
						  }else{
							  texture_tiles_bgnd[tileY][tileX]= texture_tiles[tileY][tileX].clone();
							  texture_tiles_bgnd[tileY][tileX][alpha_index] = alpha_zero;
						  }
					  }
				  }
			  }
		  } else {
			  for (int tileY = 0; tileY < tilesY; tileY++){
				  for (int tileX = 0; tileX < tilesX; tileX++){
					  texture_tiles_bgnd[tileY][tileX]= null;
					  if ((texture_tiles[tileY][tileX] != null) && // null pointer
							  bgnd_tiles[tileY * tilesX + tileX]) {
						  if (bgnd_tiles_grown2[tileY * tilesX + tileX]) {
							  texture_tiles_bgnd[tileY][tileX]= texture_tiles[tileY][tileX];
							  num_bgnd++;
						  }else{ // not used in lwir
							  texture_tiles_bgnd[tileY][tileX]= texture_tiles[tileY][tileX].clone();
							  texture_tiles_bgnd[tileY][tileX][alpha_index] = alpha_zero;
						  }
					  }
				  }
			  }
		  }

		  if (num_bgnd < clt_parameters.min_bgnd_tiles){
			  return null; // no background to generate // not used in lwir
		  }
		  
		  
		  ImageDtt image_dtt = new ImageDtt(
				  getNumSensors(),
				  clt_parameters.transform_size,
				  clt_parameters.img_dtt,
				  isAux(),
				  isMonochrome(),
				  isLwir(),
				  clt_parameters.getScaleStrength(isAux()));
		  double [][] texture_overlap = image_dtt.combineRBGATiles(
				  texture_tiles_bgnd, // texture_tiles,               // array [tp.tilesY][tp.tilesX][4][4*transform_size] or [tp.tilesY][tp.tilesX]{null}
///				  image_dtt.transform_size,
				  true,                         // when false - output each tile as 16x16, true - overlap to make 8x8
				  clt_parameters.sharp_alpha,    // combining mode for alpha channel: false - treat as RGB, true - apply center 8x8 only
				  threadsMax,                    // maximal number of threads to launch
				  debugLevel);
		  if (clt_parameters.alpha1 > 0){ // negative or 0 - keep alpha as it was
			  double scale = (clt_parameters.alpha1 > clt_parameters.alpha0) ? (1.0/(clt_parameters.alpha1 - clt_parameters.alpha0)) : 0.0;
			  for (int i = 0; i < texture_overlap[alpha_index].length; i++){
				  double d = texture_overlap[alpha_index][i];
				  if      (d >=clt_parameters.alpha1) d = 1.0;
				  else if (d <=clt_parameters.alpha0) d = 0.0;
				  else d = scale * (d- clt_parameters.alpha0);
				  texture_overlap[alpha_index][i] = d;
			  }
		  }
		  // for now - use just RGB. Later add oprion for RGBA
		  double [][] texture_rgb = isMonochrome() ? new double [][] {texture_overlap[0]} : new double [][] {texture_overlap[0],texture_overlap[1],texture_overlap[2]};
		  double [][] texture_rgba =isMonochrome() ? new double [][] {texture_overlap[0], texture_overlap[1]} : new double [][] {texture_overlap[0],texture_overlap[1],texture_overlap[2],texture_overlap[3]};

		  //			  ImagePlus img_texture =
		  ImagePlus imp_texture_bgnd = linearStackToColor(
				  clt_parameters,
				  colorProcParameters,
				  rgbParameters,
				  name+"-texture-bgnd", // String name,
				  "", //String suffix, // such as disparity=...
				  true, // toRGB,
				  !this.correctionsParameters.jpeg, // boolean bpp16, // 16-bit per channel color mode for result
				  true, // boolean saveShowIntermediate, // save/show if set globally
				  false, //true, // boolean saveShowFinal,        // save/show result (color image?)
				  ((clt_parameters.alpha1 > 0)? texture_rgba: texture_rgb),
				  tilesX *  image_dtt.transform_size,
				  tilesY *  image_dtt.transform_size,
				  1.0,         // double scaleExposure, // is it needed?
				  debugLevel);
		  // resize for backdrop here!
//	public double getFOVPix(){ // get ratio of 1 pixel X/Y to Z (distance to object)
		  return imp_texture_bgnd;

	  }
	  
	  
	  public ImagePlus finalizeBackgroundImage( // USED in lwir
			  ImagePlus imp_texture_bgnd,
			  boolean    no_image_save,
			  CLTParameters           clt_parameters,
			  String     name,
			  int        debugLevel) {
		  
		  ImagePlus imp_texture_bgnd_ext = resizeForBackdrop(
				  imp_texture_bgnd, // null pointer
				  clt_parameters.black_back, //  boolean fillBlack,
				  clt_parameters.black_back, //  boolean noalpha,
				  debugLevel);
//		  imp_texture_bgnd_ext.show();
		  String path= correctionsParameters.selectX3dDirectory(
				  //TODO: Which one to use - name or this.image_name ?
 				  correctionsParameters.getModelName(this.image_name), // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
 				  correctionsParameters.x3dModelVersion,
				  true,  // smart,
				  true);  //newAllowed, // save
		  // only show/save original size if debug or debug_filters)
		  int jpegQuality = clt_parameters.black_back ? 90: -1;
		  if (clt_parameters.debug_filters || (debugLevel > 0)) {
			  eyesisCorrections.saveAndShow(
					  imp_texture_bgnd,
					  path,
					  correctionsParameters.png && !clt_parameters.black_back,
					  clt_parameters.show_textures,
					  jpegQuality); // jpegQuality){//  <0 - keep current, 0 - force Tiff, >0 use for JPEG
		  }
		  if (!no_image_save) {
			  eyesisCorrections.saveAndShow(
					  imp_texture_bgnd_ext,
					  path,
					  correctionsParameters.png  && !clt_parameters.black_back,
					  clt_parameters.show_textures,
					  jpegQuality); // jpegQuality){//  <0 - keep current, 0 - force Tiff, >0 use for JPEG
		  }
		  return imp_texture_bgnd_ext;
	  }



	 public String getPassImage( // get image from a single pass, return relative path for x3d // USED in lwir
			  CLTParameters           clt_parameters,
			  ColorProcParameters colorProcParameters,
			  EyesisCorrectionParameters.RGBParameters             rgbParameters,
			  String     name,
			  int        scanIndex,
			  int        threadsMax,  // maximal number of threads to launch
			  boolean    updateStatus,
			  int        debugLevel)
	  {
		 final int tilesX = tp.getTilesX();
		 final int tilesY = tp.getTilesY();
		 final int tile_size = tp.getTileSize();

//		  ShowDoubleFloatArrays sdfa_instance = null;
    	  boolean show_dbg = (debugLevel > -1);
		  CLTPass3d scan = tp.clt_3d_passes.get(scanIndex);
		  boolean [] borderTiles = scan.getBorderTiles();
		  double [][][][] texture_tiles = scan.texture_tiles;
		  // only place that uses updateSelection()
		  scan.updateSelection(); // update .selected field (all selected, including border) and Rectangle bounds
		  if (scan.getTextureBounds() == null) { // not used in lwir
			  System.out.println("getPassImage(): Empty image!");
			  return null;
		  }
		  ImageDtt image_dtt = new ImageDtt(
				  getNumSensors(),
				  clt_parameters.transform_size,
				  clt_parameters.img_dtt,
				  isAux(),
				  isMonochrome(),
				  isLwir(),
				  clt_parameters.getScaleStrength(isAux()));
		  double [][]alphaFade = TileProcessor.getAlphaFade(tile_size);
		  if ((debugLevel > 0) && (scanIndex == 1)) { // not used in lwir
			  String [] titles = new String[16];
			  for (int i = 0; i<titles.length;i++)  titles[i]=""+i;
			  ShowDoubleFloatArrays.showArrays(alphaFade, 2*tile_size,2*tile_size,true,"alphaFade",titles);
		  }
		  double [][][][] texture_tiles_cluster = new double[tilesY][tilesX][][];
		  double [] alpha_zero = new double [4*tile_size*tile_size];
		  int alpha_index = 3;
		  for (int i = 0; i < alpha_zero.length; i++) alpha_zero[i]=0.0;
		  // border tiles are copied, alpha from alphaFade (not multiplied?)
		  for (int tileY = 0; tileY < tilesY; tileY++){
			  for (int tileX = 0; tileX < tilesX; tileX++){
				  texture_tiles_cluster[tileY][tileX]= null;
				  if (texture_tiles[tileY][tileX] != null) {
					  if (borderTiles[tileY * tilesX + tileX]) {
						  texture_tiles_cluster[tileY][tileX]= texture_tiles[tileY][tileX].clone();
						  if (clt_parameters.shAggrFade) { // not used in lwir
							  texture_tiles_cluster[tileY][tileX][alpha_index] = alpha_zero;
						  } else {
							  if ((debugLevel > -1) && (scanIndex == 1)) {
								  System.out.println("getPassImage(): tileY="+tileY+", tileX = "+tileX+", tileY="+tileY);
							  }
							  int fade_mode=0;
							  if ((tileY > 0) &&              (texture_tiles[tileY - 1][tileX] != null) && !borderTiles[(tileY - 1) * tilesX + tileX]) fade_mode |= 1;
							  if ((tileX < (tilesX -1)) && (texture_tiles[tileY][tileX + 1] != null) && !borderTiles[tileY * tilesX + tileX + 1])   fade_mode |= 2;
							  if ((tileY < (tilesY -1)) && (texture_tiles[tileY + 1][tileX] != null) && !borderTiles[(tileY + 1) * tilesX + tileX]) fade_mode |= 4;
							  if ((tileX > 0) &&              (texture_tiles[tileY][tileX - 1] != null) && !borderTiles[tileY * tilesX + tileX - 1])   fade_mode |= 8;
							  texture_tiles_cluster[tileY][tileX][alpha_index] = alphaFade[fade_mode]; // alpha_zero;
						  }
					  }else{
						  texture_tiles_cluster[tileY][tileX]= texture_tiles[tileY][tileX];
					  }
				  }
			  }
		  }
		  // create 8*tilesX * 8*tilesY RBGA (?) image. input for mono (Y,,,A), output [null,null, Y, A]?
		  double [][] texture_overlap = image_dtt.combineRBGATiles(
				  texture_tiles_cluster, // texture_tiles,               // array [tp.tilesY][tp.tilesX][4][4*transform_size] or [tp.tilesY][tp.tilesX]{null}
				  true,                         // when false - output each tile as 16x16, true - overlap to make 8x8
				  clt_parameters.sharp_alpha,    // combining mode for alpha channel: false - treat as RGB, true - apply center 8x8 only
				  threadsMax,                    // maximal number of threads to launch
				  debugLevel);

		  // Update alpha to sharpen "tree branches"
		  if (clt_parameters.alpha1 > 0){ // negative or 0 - keep alpha as it was
			  double scale = (clt_parameters.alpha1 > clt_parameters.alpha0) ? (1.0/(clt_parameters.alpha1 - clt_parameters.alpha0)) : 0.0;
			  for (int i = 0; i < texture_overlap[alpha_index].length; i++){
				  double d = texture_overlap[alpha_index][i];
				  if      (d >=clt_parameters.alpha1) d = 1.0;
				  else if (d <=clt_parameters.alpha0) d = 0.0;
				  else d = scale * (d- clt_parameters.alpha0);
				  texture_overlap[alpha_index][i] = d;
			  }
		  }
		  // for now - use just RGB. Later add option for RGBA (?)
		  double [][] texture_rgb = {texture_overlap[0],texture_overlap[1],texture_overlap[2]};
		  double [][] texture_rgba = {texture_overlap[0],texture_overlap[1],texture_overlap[2],texture_overlap[3]};
		  double [][] texture_rgbx = ((clt_parameters.alpha1 > 0)? texture_rgba: texture_rgb);

		  // Resize was extracting rectangle from the full size texture. With consolidated texture (9.12.2022) multiple
		  // rectangles can be extracted from a single texture array
		  boolean resize = true;
		  if (resize) {
		  texture_rgbx = resizeGridTexture(
				  texture_rgbx,
				  image_dtt.transform_size,
				  tilesX,
				  tilesY,
				  scan.getTextureBounds());
		  }

		  int width = resize ? (image_dtt.transform_size * scan.getTextureBounds().width): (image_dtt.transform_size * tilesX);
		  int height = resize ? (image_dtt.transform_size * scan.getTextureBounds().height): (image_dtt.transform_size * tilesY);
		  if ((width <= 0) || (height <= 0)) {
			  System.out.println("***** BUG in getPassImage(): width="+width+", height="+height+", resize="+resize+" ****"); // not used in lwir
		  }
		  // 9.12.2022: should be done for each cluster
		  ImagePlus imp_texture_cluster = linearStackToColor(
				  clt_parameters,
				  colorProcParameters,
				  rgbParameters,
				  name+"-texture", // String name,
				  "", //String suffix, // such as disparity=...
				  true, // toRGB,
				  !this.correctionsParameters.jpeg, // boolean bpp16, // 16-bit per channel color mode for result
				  true, // boolean saveShowIntermediate, // save/show if set globally
				  false, //true, // boolean saveShowFinal,        // save/show result (color image?)
				  texture_rgbx,
				  width, //tp.tilesX *  image_dtt.transform_size,
				  height, //tp.tilesY *  image_dtt.transform_size,
				  1.0,         // double scaleExposure, // is it needed?
				  debugLevel);


		  String path= correctionsParameters.selectX3dDirectory(
				  //TODO: Which one to use - name or this.image_name ?
 				  correctionsParameters.getModelName(this.image_name), // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
 				  correctionsParameters.x3dModelVersion,
				  true,  // smart,
				  true);  //newAllowed, // save
		  // only show/save original size if debug or debug_filters)
			  eyesisCorrections.saveAndShow(
					  imp_texture_cluster,
					  path,
					  correctionsParameters.png,
					  clt_parameters.show_textures,
					  -1); // jpegQuality){//  <0 - keep current, 0 - force Tiff, >0 use for JPEG
		  return imp_texture_cluster.getTitle()+".png"; // imp_texture_cluster;
	  }



	  public ImagePlus resizeForBackdrop( // USED in lwir
			  ImagePlus imp,
			  boolean fillBlack,
			  boolean noalpha, // only with fillBlack, otherwise ignored
			  int debugLevel)
	  {
		  double backdropPixels = 2.0/geometryCorrection.getIFOV();
		  if (debugLevel > -1) {
			  System.out.println("backdropPixels = "+backdropPixels);
		  }
		  // TODO: currently - just adding pixels, no rescaling (add later). Alternatively - just modify geometry earlier
		  int width = imp.getWidth();
		  int height = imp.getHeight();
		  int h_margin = (int) Math.round((backdropPixels - width)/2);
		  int v_margin = (int) Math.round((backdropPixels - height)/2);
		  int width2 =  width +  2 * h_margin;
		  int height2 = height + 2 * v_margin;
		  if (debugLevel > -1) {
			  System.out.println("backdropPixels = "+backdropPixels+" h_margin = "+h_margin+" v_margin = "+v_margin);
		  }
		  int [] src_pixels = (int []) imp.getProcessor().convertToColorProcessor().getPixels();
		  int [] pixels = new int [width2* height2];
		  int black = noalpha ? 0 :        0xff000000;
		  int mask =  noalpha ? 0xffffff : 0xffffffff;
		  if (fillBlack) {
			  for (int i = 0; i < pixels.length; i++){
				  pixels[i] = black;
			  }
		  }
		  int indx = 0;
		  int offset = v_margin *  width2 + h_margin;
		  if (fillBlack) {
			  for (int i = 0; i < height; i++){
				  for (int j = 0; j < width; j++){
					  int a =  (src_pixels[indx] >> 24) & 0xff;
					  if ((a == 255) || noalpha) {
						  pixels[offset+ i * width2 + j] = src_pixels[indx] & mask;
					  } else if (a == 0) {
						  pixels[offset+ i * width2 + j] = black;
					  } else  {
						  int r = (src_pixels[indx] >> 16) & 0xff; //' maybe - swap with b?
						  int g = (src_pixels[indx] >>  8) & 0xff;
						  int b = (src_pixels[indx] >>  0) & 0xff;
						  r = (r * a) / 255;
						  g = (g * a) / 255;
						  b = (b * a) / 255;
						  pixels[offset+ i * width2 + j] = black | (r << 16) | (g << 8) | b;
					  }
					  indx++;
				  }
			  }
		  } else { // not used in lwir
			  for (int i = 0; i < height; i++){
				  for (int j = 0; j < width; j++){
					  pixels[offset+ i * width2 + j] = src_pixels[indx++];
				  }
			  }
		  }
		  ColorProcessor cp=new ColorProcessor(width2,height2);
		  cp.setPixels(pixels);
		  ImagePlus imp_ext=new ImagePlus(imp.getTitle()+"-ext",cp);
		  return imp_ext;
	  }

	  
	  
	  public static ImagePlus resizeToFull(
			  int       out_width,
			  int       out_height,
			  int       x0, // image offset-x pixels
			  int       y0, // image offset-y pixels
			  ImagePlus imp,
			  boolean   fillBlack,
			  boolean noalpha, // only with fillBlack, otherwise ignored
			  int debugLevel)
	  {
		  int width = imp.getWidth();
		  int height = imp.getHeight();
//		  int h_margin = (int) Math.round((backdropPixels - width)/2);
//		  int v_margin = (int) Math.round((backdropPixels - height)/2);
//		  int width2 =  width +  2 * h_margin;
//		  int height2 = height + 2 * v_margin;
		  int [] src_pixels = (int []) imp.getProcessor().convertToColorProcessor().getPixels();
		  int [] pixels = new int [out_width * out_height];
		  int black = noalpha ? 0 :        0xff000000;
		  int mask =  noalpha ? 0xffffff : 0xffffffff;
		  if (fillBlack) {
			  for (int i = 0; i < pixels.length; i++){
				  pixels[i] = black;
			  }
		  }
		  int indx = 0;
		  int offset = y0 * out_width + x0; // v_margin *  width2 + h_margin;
		  if (fillBlack) {
			  for (int i = 0; i < height; i++){
				  for (int j = 0; j < width; j++){
					  int a =  (src_pixels[indx] >> 24) & 0xff;
					  if (a == 255) {
						  pixels[offset+ i * out_width + j] = src_pixels[indx] & mask;
					  } else if (a == 0) {
						  pixels[offset+ i * out_width + j] = black;
					  } else  {
						  int r = (src_pixels[indx] >> 16) & 0xff; //' maybe - swap with b?
						  int g = (src_pixels[indx] >>  8) & 0xff;
						  int b = (src_pixels[indx] >>  0) & 0xff;
						  r = (r * a) / 255;
						  g = (g * a) / 255;
						  b = (b * a) / 255;
						  pixels[offset+ i * out_width + j] = black | (r << 16) | (g << 8) | b;
					  }
					  indx++;
				  }
			  }
		  } else { // not used in lwir
			  for (int i = 0; i < height; i++){
				  for (int j = 0; j < width; j++){
					  pixels[offset+ i * out_width + j] = src_pixels[indx++];
				  }
			  }
		  }
		  ColorProcessor cp=new ColorProcessor(out_width,out_height);
		  cp.setPixels(pixels);
		  ImagePlus imp_ext=new ImagePlus(imp.getTitle()+"-ext",cp);
		  return imp_ext;
	  }
	  

	  public CLTPass3d CLTBackgroundMeas( // measure background // USED in lwir
			  CLTParameters       clt_parameters,
			  final boolean       run_lma, //			  
			  final double        max_chn_diff, // filter correlation results by maximum difference between channels
			  final double        mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
			  final int           threadsMax,  // maximal number of threads to launch
			  final boolean       updateStatus,
			  final int           debugLevel)
	  {
		  CLTPass3d scan = new CLTPass3d(tp);
		  int d = ImageDtt.setImgMask(0, 0xf);
		  d =     ImageDtt.setPairMask(d,0xf);
		  d =     ImageDtt.setForcedDisparity(d,true);
		  int [][]     tile_op =         tp.setSameTileOp(clt_parameters,  d, debugLevel);
		  double disparity0 = 0.0;
		  double [][]  disparity_array = tp.setSameDisparity(disparity0); // [tp.tilesY][tp.tilesX] - individual per-tile expected disparity
		  scan.disparity = disparity_array;
		  scan.tile_op = tile_op;
		  CLTPass3d scan_rslt =  CLTMeas( // perform single pass according to prepared tiles operations and disparity // USED in lwir
				  clt_parameters,  // final CLTParameters clt_parameters,
				  scan,            // final CLTPass3d     scan,
				  true,            // final boolean       save_textures, // ignored for radius > 0
				  true,            // final boolean       need_diffs,     // calculate diffs even if textures are not needed. Also calculates low-res 
				  0,               // final int           clust_radius,
			      true,            // final boolean       save_corr,
			      run_lma,          // true,            // final boolean       run_lma, // =    true;
			      max_chn_diff,     // 0.0,            // final double        max_chn_diff, // filter correlation results by maximum difference between channels
			      mismatch_override,// -1.0,           // final double        mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
				  threadsMax,      // final int           threadsMax,  // maximal number of threads to launch
				  updateStatus,    // final boolean       updateStatus,
				  debugLevel);     // final int           debugLevel);
		  return scan_rslt;
	  }
	  
	  public CLTPass3d  CLTMeasureTextures( // perform single pass according to prepared tiles operations and disparity // not used in lwir
			  CLTParameters           clt_parameters,
			  final int         scanIndex,
			  final int         threadsMax,  // maximal number of threads to launch
			  final boolean     updateStatus,
			  final int         debugLevel)
	  {
		  CLTPass3d scan = tp.clt_3d_passes.get(scanIndex);
		  CLTMeasure( // measure background // USED in lwir
				  clt_parameters,
				  scan,           // final CLTPass3d   scan,
				  true,        // save_textures,
				  false,       // save_corr,
				  0,           // clust_radius,
				  threadsMax,  // maximal number of threads to launch
				  updateStatus,
				  debugLevel);
		  return scan;
	  }

	  public void  CLTMeasureTextures( // perform single pass according to prepared tiles operations and disparity // not used in lwir
			  CLTParameters           clt_parameters,
			  final CLTPass3d   scan,
			  final int         threadsMax,  // maximal number of threads to launch
			  final boolean     updateStatus,
			  final int         debugLevel)
	  {
		  CLTMeasure( // measure background // USED in lwir
				  clt_parameters,
				  scan,
				  true,        // save_textures,
				  false,       // save_corr,
				  0,           // clust_radius,
				  threadsMax,  // maximal number of threads to launch
				  updateStatus,
				  debugLevel);
		  if (debugLevel > 10) {
			  tp.showScan(
					  scan,   // CLTPass3d   scan,
					  "CLTMeasureTextures->");
		  }
		  
	  }
	  
	  @Deprecated
	  public CLTPass3d  CLTMeasureCorr( // perform single pass according to prepared tiles operations and disparity // not used in lwir
			  CLTParameters    clt_parameters,
			  final int         scanIndex,
			  final boolean     save_textures,
			  final int         clust_radius,
			  final int         threadsMax,  // maximal number of threads to launch
			  final boolean     updateStatus,
			  final int         debugLevel)
	  {
		  boolean run_lma = clt_parameters.correlate_lma; // true;
		  CLTPass3d scan = tp.clt_3d_passes.get(scanIndex);
		  CLTMeas( // perform single pass according to prepared tiles operations and disparity
				  clt_parameters, // EyesisCorrectionParameters.CLTParameters           clt_parameters,
				  scan,           // final CLTPass3d   scan,
				  save_textures,  // final boolean     save_textures,
				  true, // need_diffs,     // final boolean       need_diffs,     // calculate diffs even if textures are not needed 
				  clust_radius,   // final int         clust_radius,
				  true,           // final boolean     save_corr,
				  run_lma,        // final boolean     run_lma, // =    true;
				  0.0,            // final double        max_chn_diff, // filter correlation results by maximum difference between channels
				  -1.0,           // final double        mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
				  threadsMax,     // final int         threadsMax,  // maximal number of threads to launch
				  updateStatus,   // final boolean     updateStatus,
				  debugLevel);    // final int         debugLevel);
		  return scan;
	  }

	  public CLTPass3d  CLTMeasure( // perform single pass according to prepared tiles operations and disparity // USED in lwir
			  final CLTParameters clt_parameters,
			  final int           scanIndex,
			  final boolean       save_textures,
			  final boolean       save_corr,
			  final int           clust_radius,
			  final int           threadsMax,  // maximal number of threads to launch
			  final boolean       updateStatus,
			  final int           debugLevel) {
		  CLTPass3d scan = tp.clt_3d_passes.get(scanIndex);
		  CLTMeasure( // perform single pass according to prepared tiles operations and disparity // USED in lwir
				  clt_parameters,
				  scan,
				  save_textures,
				  save_corr,
				  clust_radius,
				  threadsMax,  // maximal number of threads to launch
				  updateStatus,
				  debugLevel);
		  return scan;
	  }
	  
	  
	  @Deprecated
	  public void CLTMeasure( // perform single pass according to prepared tiles operations and disparity // USED in lwir
			  final CLTParameters clt_parameters,
			  final CLTPass3d     scan,
			  final boolean       save_textures,
			  final boolean       save_corr,
			  final int           clust_radius,
			  final int           threadsMax,  // maximal number of threads to launch
			  final boolean       updateStatus,
			  final int           debugLevel)
	  {
		  // TODO: Move to clt_parameters
		  final double  arange =    1.0;  //absolute disparity range to consolidate
		  final double  rrange =    0.1;  // relative disparity range to consolidate
		  final double  no_tilt =   0.5;  // no tilt if center disparity is lower
		  final double  damp_tilt = 0.01; // 0.1?
		  
		  final boolean use_tilted = true; // Pass it (and clust_radius?) through scan properties?
//		  final double [][]   mismatch = null;    // null or double [12][] or [numClusters][] for new LMA
		  final int dbg_x = -295-debugLevel;
		  final int dbg_y = -160-debugLevel;
		  final int tilesX = tp.getTilesX();
		  final int tilesY = tp.getTilesY();
//		  CLTPass3d scan = tp.clt_3d_passes.get(scanIndex);
		  int [][]     tile_op =         scan.tile_op;
// Should not happen !
		  double [][]  disparity_array = scan.disparity;
		  if (scan.disparity == null) { // not used in lwir
			  System.out.println ("** BUG: should not happen - scan.disparity == null ! **");
			  System.out.println ("Trying to recover");
			  double [] backup_disparity = scan.getDisparity(0);
			  if (backup_disparity == null) {
				  System.out.println ("** BUG: no disparity at all !");
				  backup_disparity = new double[tilesX*tilesY];
			  }
			  scan.disparity = new double[tilesY][tilesX];
			  for (int ty = 0; ty < tilesY; ty++) {
				  for (int tx = 0; tx < tilesX; tx++) {
					  scan.disparity[ty][tx] = backup_disparity[ty*tilesX + tx];
					  if (Double.isNaN(scan.disparity[ty][tx])) {
						  scan.disparity[ty][tx] = 0;
						  tile_op[ty][tx] = 0;
					  }
				  }
			  }
			  disparity_array = scan.disparity;

		  }

		  // undecided, so 2 modes of combining alpha - same as rgb, or use center tile only
//		  double [][][][]     clt_corr_combo = null; //    new double [ImageDtt.TCORR_TITLES.length][tilesY][tilesX][]; // will only be used inside?
		  // broken clt_aberrations_quad_corr_new
		  if (debugLevel > -1){
			  int numTiles = 0;
			  for (int ty = 0; ty < tile_op.length; ty ++) for (int tx = 0; tx < tile_op[ty].length; tx ++){
				  if (tile_op[ty][tx] != 0) numTiles ++;
			  }
//			  System.out.println("CLTMeasure("+scanIndex+"): numTiles = "+numTiles);
			  System.out.println("CLTMeasure(): numTiles = "+numTiles);
			  if ((dbg_y >= 0) && (dbg_x >= 0) && (tile_op[dbg_y][dbg_x] != 0)){
//				  System.out.println("CLTMeasure("+scanIndex+"): tile_op["+dbg_y+"]["+dbg_x+"] = "+tile_op[dbg_y][dbg_x]);
				  System.out.println("CLTMeasure(): tile_op["+dbg_y+"]["+dbg_x+"] = "+tile_op[dbg_y][dbg_x]);
			  }
		  }
//		  double min_corr_selected = clt_parameters.min_corr;

		  double [][] disparity_map = save_corr ? new double [ImageDtt.getDisparityTitles(getNumSensors(), isMonochrome()).length][] : null; //[0] -residual disparity, [1] - orthogonal (just for debugging)

		  double [][] shiftXY = new double [getNumSensors()][2];
		  if (!clt_parameters.fine_corr_ignore) {// invalid for AUX!
			  double [][] shiftXY0 = {
					  {clt_parameters.fine_corr_x_0,clt_parameters.fine_corr_y_0},
					  {clt_parameters.fine_corr_x_1,clt_parameters.fine_corr_y_1},
					  {clt_parameters.fine_corr_x_2,clt_parameters.fine_corr_y_2},
					  {clt_parameters.fine_corr_x_3,clt_parameters.fine_corr_y_3}};
			  // FIXME - only first 4 sensors have correction. And is it the same for aux and main? 
			  for (int i = 0; i < shiftXY0.length;i++) {
				  shiftXY[i] = shiftXY0[i];
			  }
		  }

		  double [][][][] texture_tiles =   save_textures ? new double [tilesY][tilesX][][] : null; // ["RGBA".length()][];
		  ImageDtt image_dtt = new ImageDtt(
				  getNumSensors(),
				  clt_parameters.transform_size,
				  clt_parameters.img_dtt,
				  isAux(),
				  isMonochrome(),
				  isLwir(),
				  clt_parameters.getScaleStrength(isAux()));
		  image_dtt.getCorrelation2d(); // initiate image_dtt.correlation2d, needed if disparity_map != null  
  
		  double z_correction =  clt_parameters.z_correction;
		  if (clt_parameters.z_corr_map.containsKey(image_name)){ // not used in lwir
			  z_correction +=clt_parameters.z_corr_map.get(image_name);
		  }
		  final double disparity_corr = (z_correction == 0) ? 0.0 : geometryCorrection.getDisparityFromZ(1.0/z_correction);
		  // fix this.fine_corr
		  if (this.fine_corr.length != getNumSensors()) {
			  System.out.println ("**** this.fine_corr.length != getNumSensors(), fixing");
			  double [][][] fine_corr0 = this.fine_corr;
			  this.fine_corr = new double [getNumSensors()][2][6];
			  for (int i = 0; i < fine_corr0.length; i++) {
				  this.fine_corr[i] = fine_corr0[i];
			  }
		  }
		  double [][][] fine_corr =  (clt_parameters.fcorr_ignore? null: this.fine_corr);
		  int mcorr_sel = ImageDttParameters.corrSelEncode(clt_parameters.img_dtt, getNumSensors());
		  if (clust_radius > 0) {
			  if (use_tilted) { // always
				  image_dtt.clt_aberrations_quad_corr_tilted(
						  clt_parameters.img_dtt,       // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
						  1,                            // final int  macro_scale, // to correlate tile data instead of the pixel data: 1 - pixels, 8 - tiles
						  tile_op,                      // per-tile operation bit codes
						  disparity_array,              // clt_parameters.disparity,     // final double            disparity,
						  getImageData(),               // final double [][][]      imade_data, // first index - number of image in a quad
						  saturation_imp,               // boolean [][] saturation_imp, // (near) saturated pixels or null
						  // correlation results - final and partial
						  // 2021 replaced next 3
						  null,                         // final double [][][][]     clt_corr_out,   // sparse (by the first index) [type][tilesY][tilesX][(2*transform_size-1)*(2*transform_size-1)] or null
						  null,                         // final double [][][][]     clt_combo_out,  // sparse (by the first index) [type][tilesY][tilesX][(combo_tile_size] or null
						  null,                         // final double [][][][]     clt_combo_dbg,  // generate sparse  partial rotated/scaled pairs
						  //	Use it with disparity_maps[scan_step]?		  clt_mismatch,    // [tp.tilesY][tp.tilesX][pair]{dx,dy,weight}[(2*transform_size-1)*(2*transform_size-1)] // transpose unapplied. null - do not calculate
						  disparity_map,    // [12][tp.tilesY * tp.tilesX]
						  geometryCorrection.getSensorWH()[0], // 	final int                 width,
						  clt_parameters.getFatZero(isMonochrome()),      // add to denominator to modify phase correlation (same units as data1, data2). <0 - pure sum
						  clt_parameters.corr_sym,
						  clt_parameters.corr_offset,
						  clt_parameters.corr_red,
						  clt_parameters.corr_blue,
						  clt_parameters.getCorrSigma(image_dtt.isMonochrome()),

						  geometryCorrection,            // final GeometryCorrection  geometryCorrection,
						  null,                          // final GeometryCorrection  geometryCorrection_main, // if not null correct this camera (aux) to the coordinates of the main
						  clt_kernels,                   // final double [][][][][][] clt_kernels, // [channel_in_quad][color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
//						  clt_parameters.kernel_step,
						  clt_parameters.clt_window,
						  shiftXY, //
						  disparity_corr, // final double              disparity_corr, // disparity at infinity
						  fine_corr,                     // null, // (clt_parameters.fcorr_ignore? null: this.fine_corr),
						  clt_parameters.shift_x,        // final int               shiftX, // shift image horizontally (positive - right) - just for testing
						  clt_parameters.shift_y,        // final int               shiftY, // shift image vertically (positive - down)
						  clust_radius,                  // final int                 clustRadius,  // 1 - single tile, 2 - 3x3, 3 - 5x5, ... 
						  
						  arange,    // 1.0,  // final double arange, // absolute disparity range to consolidate
						  rrange,    // 0.1,  // final double rrange, // relative disparity range to consolidate
						  no_tilt,   // 0.5,  // final double no_tilt, // no tilt if center disparity is lower
						  damp_tilt, // 0.01, // final double damp_tilt,    // 0.1?

						  // 2021 new next 5
						  mcorr_sel, // 	final int                 mcorr_sel,    // Which pairs to correlate // +1 - all, +2 - dia, +4 - sq, +8 - neibs, +16 - hor + 32 - vert
						  clt_parameters.img_dtt.mcorr_comb_width,					// final int                 mcorr_comb_width,  // combined correlation tile width
						  clt_parameters.img_dtt.mcorr_comb_height,					// final int                 mcorr_comb_height, // combined correlation tile full height
						  clt_parameters.img_dtt.mcorr_comb_offset,					// final int                 mcorr_comb_offset, // combined correlation tile height offset: 0 - centered (-height/2 to height/2), height/2 - only positive (0 to height)
						  clt_parameters.img_dtt.mcorr_comb_disp,					// final double              mcorr_comb_disp,   // Combined tile per-pixel disparity for baseline == side of a square
						  clt_parameters.tileX,         // final int               debug_tileX,
						  clt_parameters.tileY,         // final int               debug_tileY,
						  (clt_parameters.dbg_mode & 64) != 0, // no fract shift
						  (clt_parameters.dbg_mode & 128) != 0, // no convolve
						  threadsMax,
						  debugLevel);			  
				  
			  } else { // never
				  image_dtt.clt_aberrations_quad_corr_multi(
						  clt_parameters.img_dtt,       // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
						  1,                            // final int  macro_scale, // to correlate tile data instead of the pixel data: 1 - pixels, 8 - tiles
						  tile_op,                      // per-tile operation bit codes
						  disparity_array,              // clt_parameters.disparity,     // final double            disparity,
						  getImageData(),               // final double [][][]      imade_data, // first index - number of image in a quad
						  saturation_imp,               // boolean [][] saturation_imp, // (near) saturated pixels or null
						  // correlation results - final and partial
						  // 2021 replaced next 3
						  null,                         // final double [][][][]     clt_corr_out,   // sparse (by the first index) [type][tilesY][tilesX][(2*transform_size-1)*(2*transform_size-1)] or null
						  null,                         // final double [][][][]     clt_combo_out,  // sparse (by the first index) [type][tilesY][tilesX][(combo_tile_size] or null
						  null,                         // final double [][][][]     clt_combo_dbg,  // generate sparse  partial rotated/scaled pairs
						  //	Use it with disparity_maps[scan_step]?		  clt_mismatch,    // [tp.tilesY][tp.tilesX][pair]{dx,dy,weight}[(2*transform_size-1)*(2*transform_size-1)] // transpose unapplied. null - do not calculate
						  disparity_map,    // [12][tp.tilesY * tp.tilesX]

						  //					  tilesX * image_dtt.transform_size, // imp_quad[0].getWidth(),       // final int width,
						  geometryCorrection.getSensorWH()[0], // 	final int                 width,
						  clt_parameters.getFatZero(isMonochrome()),      // add to denominator to modify phase correlation (same units as data1, data2). <0 - pure sum
						  clt_parameters.corr_sym,
						  clt_parameters.corr_offset,
						  clt_parameters.corr_red,
						  clt_parameters.corr_blue,
						  clt_parameters.getCorrSigma(image_dtt.isMonochrome()),

						  geometryCorrection,            // final GeometryCorrection  geometryCorrection,
						  null,                          // final GeometryCorrection  geometryCorrection_main, // if not null correct this camera (aux) to the coordinates of the main
						  clt_kernels,                   // final double [][][][][][] clt_kernels, // [channel_in_quad][color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
//						  clt_parameters.kernel_step,
						  clt_parameters.clt_window,
						  shiftXY, //
						  disparity_corr, // final double              disparity_corr, // disparity at infinity
						  fine_corr,                     // null, // (clt_parameters.fcorr_ignore? null: this.fine_corr),
						  //					  clt_parameters.corr_magic_scale, // still not understood coefficient that reduces reported disparity value.  Seems to be around 0.85
						  clt_parameters.shift_x,        // final int               shiftX, // shift image horizontally (positive - right) - just for testing
						  clt_parameters.shift_y,        // final int               shiftY, // shift image vertically (positive - down)
						  clust_radius,                  // final int                 clustRadius,  // 1 - single tile, 2 - 3x3, 3 - 5x5, ... 

						  1.0, // 	final double              arange, // absolute disparity range to consolidate
						  0.1, // 	final double              rrange, // relative disparity range to consolidate

						  // 2021 new next 5
						  mcorr_sel, // 	final int                 mcorr_sel,    // Which pairs to correlate // +1 - all, +2 - dia, +4 - sq, +8 - neibs, +16 - hor + 32 - vert
						  clt_parameters.img_dtt.mcorr_comb_width,					// final int                 mcorr_comb_width,  // combined correlation tile width
						  clt_parameters.img_dtt.mcorr_comb_height,					// final int                 mcorr_comb_height, // combined correlation tile full height
						  clt_parameters.img_dtt.mcorr_comb_offset,					// final int                 mcorr_comb_offset, // combined correlation tile height offset: 0 - centered (-height/2 to height/2), height/2 - only positive (0 to height)
						  clt_parameters.img_dtt.mcorr_comb_disp,					// final double              mcorr_comb_disp,   // Combined tile per-pixel disparity for baseline == side of a square
						  clt_parameters.tileX,         // final int               debug_tileX,
						  clt_parameters.tileY,         // final int               debug_tileY,
						  (clt_parameters.dbg_mode & 64) != 0, // no fract shift
						  (clt_parameters.dbg_mode & 128) != 0, // no convolve
						  threadsMax,
						  debugLevel);			  
			  }
		  } else { // clust_radius
			  image_dtt.clt_aberrations_quad_corr(
					  clt_parameters.img_dtt,       // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
					  1,                            // final int  macro_scale, // to correlate tile data instead of the pixel data: 1 - pixels, 8 - tiles
					  tile_op,                      // per-tile operation bit codes
					  disparity_array,              // clt_parameters.disparity,     // final double            disparity,
					  getImageData(),               // final double [][][]      imade_data, // first index - number of image in a quad
					  saturation_imp,               // boolean [][] saturation_imp, // (near) saturated pixels or null
					  // correlation results - final and partial
					  // 2021 replaced next 3
					  null,                         // final double [][][][]     clt_corr_out,   // sparse (by the first index) [type][tilesY][tilesX][(2*transform_size-1)*(2*transform_size-1)] or null
					  null,                         // final double [][][][]     clt_combo_out,  // sparse (by the first index) [type][tilesY][tilesX][(combo_tile_size] or null
					  null,                         // final double [][][][]     clt_combo_dbg,  // generate sparse  partial rotated/scaled pairs
					  //	Use it with disparity_maps[scan_step]?		  clt_mismatch,    // [tp.tilesY][tp.tilesX][pair]{dx,dy,weight}[(2*transform_size-1)*(2*transform_size-1)] // transpose unapplied. null - do not calculate
					  disparity_map,    // [12][tp.tilesY * tp.tilesX]
					  texture_tiles,        // [tp.tilesY][tp.tilesX]["RGBA".length()][];
					  //					  tilesX * image_dtt.transform_size, // imp_quad[0].getWidth(),       // final int width,
					  geometryCorrection.getSensorWH()[0], // 	final int                 width,
					  clt_parameters.getFatZero(isMonochrome()),      // add to denominator to modify phase correlation (same units as data1, data2). <0 - pure sum
					  clt_parameters.corr_sym,
					  clt_parameters.corr_offset,
					  clt_parameters.corr_red,
					  clt_parameters.corr_blue,
					  clt_parameters.getCorrSigma(image_dtt.isMonochrome()),

					  clt_parameters.min_shot,       // 10.0;  // Do not adjust for shot noise if lower than
					  clt_parameters.scale_shot,     // 3.0;   // scale when dividing by sqrt ( <0 - disable correction)
					  clt_parameters.diff_sigma,     // 5.0;//RMS difference from average to reduce weights (~ 1.0 - 1/255 full scale image)
					  clt_parameters.diff_threshold, // 5.0;   // RMS difference from average to discard channel (~ 1.0 - 1/255 full scale image)
					  clt_parameters.diff_gauss,     // true;  // when averaging images, use gaussian around average as weight (false - sharp all/nothing)
					  clt_parameters.min_agree,      // 3.0;   // minimal number of channels to agree on a point (real number to work with fuzzy averages)
					  clt_parameters.dust_remove,    // Do not reduce average weight when only one image differes much from the average
					  clt_parameters.keep_weights,   // Add port weights to RGBA stack (debug feature)
					  geometryCorrection,            // final GeometryCorrection  geometryCorrection,
					  null,                          // final GeometryCorrection  geometryCorrection_main, // if not null correct this camera (aux) to the coordinates of the main
					  clt_kernels,                   // final double [][][][][][] clt_kernels, // [channel_in_quad][color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
//					  clt_parameters.kernel_step,
					  clt_parameters.clt_window,
					  shiftXY, //
					  disparity_corr, // final double              disparity_corr, // disparity at infinity
					  fine_corr,
					  clt_parameters.corr_magic_scale, // still not understood coefficient that reduces reported disparity value.  Seems to be around 0.85
					  clt_parameters.shift_x,       // final int               shiftX, // shift image horizontally (positive - right) - just for testing
					  clt_parameters.shift_y,       // final int               shiftY, // shift image vertically (positive - down)
					  // 2021 new next 5
					  mcorr_sel, // 	final int                 mcorr_sel,    // Which pairs to correlate // +1 - all, +2 - dia, +4 - sq, +8 - neibs, +16 - hor + 32 - vert
					  clt_parameters.img_dtt.mcorr_comb_width,					// final int                 mcorr_comb_width,  // combined correlation tile width
					  clt_parameters.img_dtt.mcorr_comb_height,					// final int                 mcorr_comb_height, // combined correlation tile full height
					  clt_parameters.img_dtt.mcorr_comb_offset,					// final int                 mcorr_comb_offset, // combined correlation tile height offset: 0 - centered (-height/2 to height/2), height/2 - only positive (0 to height)
					  clt_parameters.img_dtt.mcorr_comb_disp,					// final double              mcorr_comb_disp,   // Combined tile per-pixel disparity for baseline == side of a square
					  clt_parameters.tileX,         // final int               debug_tileX,
					  clt_parameters.tileY,         // final int               debug_tileY,
					  (clt_parameters.dbg_mode & 64) != 0, // no fract shift
					  (clt_parameters.dbg_mode & 128) != 0, // no convolve
					  //				  (clt_parameters.dbg_mode & 256) != 0, // transpose convolve
					  threadsMax,
					  debugLevel);			  
		  }
		  scan.disparity_map = disparity_map;
		  scan.texture_tiles = texture_tiles;
		  scan.is_measured =   true; // but no disparity map/textures
		  scan.is_combo =      false;
		  scan.has_lma = null;
		  scan.getLMA(); // recalculate		  
		  scan.resetProcessed();
//		  return scan;
	  }


	  public CLTPass3d  CLTMeasure( // perform single pass according to prepared tiles operations and disparity // USED in lwir
			  final CLTParameters           clt_parameters,
			  final CLTPass3d     scan,
			  final boolean       save_textures,
			  final boolean       save_corr,
//			  final double [][]   mismatch,    // null or double [12][]
			  final GeometryCorrection geometryCorrection_main, // If not null - covert to main camera coordinates
			  final int           threadsMax,  // maximal number of threads to launch
			  final boolean       updateStatus,
			  final int           debugLevel)
	  {
		  // TODO: Move to clt_parameters
		  final int dbg_x = -295-debugLevel;
		  final int dbg_y = -160-debugLevel;
		  final int tilesX = tp.getTilesX();
		  final int tilesY = tp.getTilesY();
		  double [] disparity = scan.getDisparity();
		  double [] strength =  scan.getStrength();
		  boolean [] selection = scan.getSelected();

		  if (selection == null) {
			  selection = new boolean[tilesX*tilesY];
			  for (int nTile = 0; nTile < selection.length; nTile++) {
				  selection[nTile] = !Double.isNaN(disparity[nTile]) && (strength[nTile] > 0.0);
			  }
			  scan.setSelected(selection);
		  }
		  if ((scan.disparity == null) || (scan.tile_op == null)) {
			  scan.setTileOpDisparity(
					  scan.getSelected(), // boolean [] selection,
					  scan.getDisparity()); // double []  disparity)
		  }

		  int [][]     tile_op =         scan.tile_op;
		  double [][]  disparity_array = scan.disparity;
		  // undecided, so 2 modes of combining alpha - same as rgb, or use center tile only
//		  double [][][][]     clt_corr_combo =    null; // new double [ImageDtt.TCORR_TITLES.length][tilesY][tilesX][]; // will only be used inside?
		  if (debugLevel > -1){
			  int numTiles = 0;
			  for (int ty = 0; ty < tile_op.length; ty ++) for (int tx = 0; tx < tile_op[ty].length; tx ++){
				  if (tile_op[ty][tx] != 0) numTiles ++;
			  }
			  System.out.println("CLTMeasure(): numTiles = "+numTiles);
			  if ((dbg_y >= 0) && (dbg_x >= 0) && (tile_op[dbg_y][dbg_x] != 0)){
				  System.out.println("CLTMeasure(): tile_op["+dbg_y+"]["+dbg_x+"] = "+tile_op[dbg_y][dbg_x]);
			  }
		  }
		  double min_corr_selected = clt_parameters.min_corr;
//		  double [][] disparity_map = save_corr ? new double [ImageDtt.DISPARITY_TITLES.length][] : null; //[0] -residual disparity, [1] - orthogonal (just for debugging)
		  double [][] disparity_map = save_corr ? new double [ImageDtt.getDisparityTitles(getNumSensors(), isMonochrome()).length][] : null; //[0] -residual disparity, [1] - orthogonal (just for debugging)
		  double [][] shiftXY = new double [getNumSensors()][2];
		  if (!clt_parameters.fine_corr_ignore) {// invalid for AUX!
			  double [][] shiftXY0 = {
					  {clt_parameters.fine_corr_x_0,clt_parameters.fine_corr_y_0},
					  {clt_parameters.fine_corr_x_1,clt_parameters.fine_corr_y_1},
					  {clt_parameters.fine_corr_x_2,clt_parameters.fine_corr_y_2},
					  {clt_parameters.fine_corr_x_3,clt_parameters.fine_corr_y_3}};
			  // FIXME - only first 4 sensors have correction. And is it the same for aux and main? 
			  for (int i = 0; i < shiftXY0.length;i++) {
				  shiftXY[i] = shiftXY0[i];
			  }
		  }

		  double [][][][] texture_tiles =   save_textures ? new double [tilesY][tilesX][][] : null; // ["RGBA".length()][];
		  ImageDtt image_dtt = new ImageDtt(
				  getNumSensors(),
				  clt_parameters.transform_size,
				  clt_parameters.img_dtt,
				  isAux(),
				  isMonochrome(),
				  isLwir(),
				  clt_parameters.getScaleStrength(isAux()));
		  image_dtt.getCorrelation2d(); // initiate image_dtt.correlation2d, needed if disparity_map != null  
		  double z_correction =  clt_parameters.z_correction;
		  if (clt_parameters.z_corr_map.containsKey(image_name)){ // not used in lwir
			  z_correction +=clt_parameters.z_corr_map.get(image_name);
		  }
		  final double disparity_corr = (z_correction == 0) ? 0.0 : geometryCorrection.getDisparityFromZ(1.0/z_correction);

		  // fix this.fine_corr
		  if (this.fine_corr.length != getNumSensors()) {
			  System.out.println ("**** this.fine_corr.length != getNumSensors(), fixing");
			  double [][][] fine_corr0 = this.fine_corr;
			  this.fine_corr = new double [getNumSensors()][2][6];
			  for (int i = 0; i < fine_corr0.length; i++) {
				  this.fine_corr[i] = fine_corr0[i];
			  }
		  }

		  int mcorr_sel = ImageDttParameters.corrSelEncode(clt_parameters.img_dtt, getNumSensors());
		  image_dtt.clt_aberrations_quad_corr(
				  clt_parameters.img_dtt,       // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
				  1,                            // final int  macro_scale, // to correlate tile data instead of the pixel data: 1 - pixels, 8 - tiles
				  tile_op,                      // per-tile operation bit codes
				  disparity_array,              // clt_parameters.disparity,     // final double            disparity,
				  getImageData(),               // final double [][][]      imade_data, // first index - number of image in a quad
				  saturation_imp,               // boolean [][] saturation_imp, // (near) saturated pixels or null
				  // correlation results - final and partial
				  // 2021 replaced next 3
				  null,                         // final double [][][][]     clt_corr_out,   // sparse (by the first index) [type][tilesY][tilesX][(2*transform_size-1)*(2*transform_size-1)] or null
				  null,                         // final double [][][][]     clt_combo_out,  // sparse (by the first index) [type][tilesY][tilesX][(combo_tile_size] or null
				  null,                         // final double [][][][]     clt_combo_dbg,  // generate sparse  partial rotated/scaled pairs
				  //	Use it with disparity_maps[scan_step]?		  clt_mismatch,    // [tp.tilesY][tp.tilesX][pair]{dx,dy,weight}[(2*transform_size-1)*(2*transform_size-1)] // transpose unapplied. null - do not calculate
				  disparity_map,    // [12][tp.tilesY * tp.tilesX]
				  texture_tiles,        // [tp.tilesY][tp.tilesX]["RGBA".length()][];
//				  tilesX * image_dtt.transform_size, // imp_quad[0].getWidth(),       // final int width,
				  geometryCorrection.getSensorWH()[0], // imp_quad[0].getWidth(),       // final int width,
				  
				  clt_parameters.getFatZero(isMonochrome()),      // add to denominator to modify phase correlation (same units as data1, data2). <0 - pure sum
				  clt_parameters.corr_sym,
				  clt_parameters.corr_offset,
				  clt_parameters.corr_red,
				  clt_parameters.corr_blue,
				  clt_parameters.getCorrSigma(image_dtt.isMonochrome()),
				  
				  clt_parameters.min_shot,       // 10.0;  // Do not adjust for shot noise if lower than
				  clt_parameters.scale_shot,     // 3.0;   // scale when dividing by sqrt ( <0 - disable correction)
				  clt_parameters.diff_sigma,     // 5.0;//RMS difference from average to reduce weights (~ 1.0 - 1/255 full scale image)
				  clt_parameters.diff_threshold, // 5.0;   // RMS difference from average to discard channel (~ 1.0 - 1/255 full scale image)
				  clt_parameters.diff_gauss,     // true;  // when averaging images, use gaussian around average as weight (false - sharp all/nothing)
				  clt_parameters.min_agree,      // 3.0;   // minimal number of channels to agree on a point (real number to work with fuzzy averages)
				  clt_parameters.dust_remove,    // Do not reduce average weight when only one image differes much from the average
				  clt_parameters.keep_weights,   // Add port weights to RGBA stack (debug feature)
				  geometryCorrection,           // final GeometryCorrection  geometryCorrection,
				  geometryCorrection_main, // final GeometryCorrection  geometryCorrection_main, // if not null correct this camera (aux) to the coordinates of the main
				  clt_kernels,                  // final double [][][][][][] clt_kernels, // [channel_in_quad][color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
//				  clt_parameters.kernel_step,
				  clt_parameters.clt_window,
				  shiftXY, //
				  disparity_corr, // final double              disparity_corr, // disparity at infinity
				  (clt_parameters.fcorr_ignore? null: this.fine_corr),
				  clt_parameters.corr_magic_scale, // still not understood coefficient that reduces reported disparity value.  Seems to be around 0.85
				  clt_parameters.shift_x,       // final int               shiftX, // shift image horizontally (positive - right) - just for testing
				  clt_parameters.shift_y,       // final int               shiftY, // shift image vertically (positive - down)
				  // 2021 new next 5
				  mcorr_sel, // 	final int                 mcorr_sel,    // Which pairs to correlate // +1 - all, +2 - dia, +4 - sq, +8 - neibs, +16 - hor + 32 - vert
				  clt_parameters.img_dtt.mcorr_comb_width,					// final int                 mcorr_comb_width,  // combined correlation tile width
				  clt_parameters.img_dtt.mcorr_comb_height,					// final int                 mcorr_comb_height, // combined correlation tile full height
				  clt_parameters.img_dtt.mcorr_comb_offset,					// final int                 mcorr_comb_offset, // combined correlation tile height offset: 0 - centered (-height/2 to height/2), height/2 - only positive (0 to height)
				  clt_parameters.img_dtt.mcorr_comb_disp,					// final double              mcorr_comb_disp,   // Combined tile per-pixel disparity for baseline == side of a square
				  clt_parameters.tileX,         // final int               debug_tileX,
				  clt_parameters.tileY,         // final int               debug_tileY,
				  (clt_parameters.dbg_mode & 64) != 0, // no fract shift
				  (clt_parameters.dbg_mode & 128) != 0, // no convolve
				  //				  (clt_parameters.dbg_mode & 256) != 0, // transpose convolve
				  threadsMax,
				  debugLevel);
		  scan.disparity_map = disparity_map;
		  scan.texture_tiles = texture_tiles;
		  scan.is_measured =   true;
		  scan.is_combo =      false;
		  scan.resetProcessed();
		  return scan;
	  }

	  
	  public CLTPass3d  CLTMeasCorr( // perform single pass according to prepared tiles operations and disparity // not used in lwir
			  CLTParameters    clt_parameters,
			  final int         scanIndex,
			  final boolean     save_textures,
			  final int         clust_radius,
			  final boolean     run_lma, //			  
			  final double      max_chn_diff, // filter correlation results by maximum difference between channels
			  final double      mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
			  final int         threadsMax,  // maximal number of threads to launch
			  final boolean     updateStatus,
			  final int         debugLevel)
	  {
		  CLTPass3d scan = tp.clt_3d_passes.get(scanIndex);
		  boolean       need_diffs = true;
		  CLTMeas( // perform single pass according to prepared tiles operations and disparity
				  clt_parameters, // EyesisCorrectionParameters.CLTParameters           clt_parameters,
				  scan,           // final CLTPass3d   scan,
				  save_textures,  // final boolean     save_textures,
				  need_diffs,     // final boolean       need_diffs,     // calculate diffs even if textures are not needed 
				  clust_radius,   // final int         clust_radius,
				  true,           // final boolean     save_corr,
				  run_lma,        // final boolean     run_lma, // =    true;
				  max_chn_diff,   // final double        max_chn_diff, // filter correlation results by maximum difference between channels
				  mismatch_override, // final double        mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
				  threadsMax,     // final int         threadsMax,  // maximal number of threads to launch
				  updateStatus,   // final boolean     updateStatus,
				  debugLevel);    // final int         debugLevel);
		  return scan;
	  }
//	  public CLTPass3d remeasure ( //??
//			  CLTParameters    clt_parameters,
//			  double []        disparity) { // all that are not null
//		  return null;
//	  }
	  // Trying 10/2021 ImageDttCPU methods. 7/18/22 - added max_chn_diff to get rid of ghosts in the sky
	  public CLTPass3d CLTMeas( // perform single pass according to prepared tiles operations and disparity // USED in lwir
			  final CLTParameters clt_parameters,
			  final CLTPass3d     scan,
			  final boolean       save_textures, // ignored for radius > 0
			  final boolean       need_diffs,     // calculate diffs even if textures are not needed. Also calculates low-res 
			  final int           clust_radius,
			  final boolean       save_corr,
			  final boolean       run_lma, // =    true;
			  final double        max_chn_diff, // filter correlation results by maximum difference between channels
			  final double        mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
			  final int           threadsMax,  // maximal number of threads to launch
			  final boolean       updateStatus,
			  final int           debugLevel)
	  {
		  saveQuadClt(); // to re-load new set of Bayer images to the GPU (do nothing for CPU). Spend several days to find this bug!
		  boolean float_center = true;   // false - center around provided value TODO: Move to clt_parameters?		  
		  boolean show_2d_correlations = (debugLevel>100); // true; // debug feature
		  final int   tilesX = tp.getTilesX();
		  final int   tilesY = tp.getTilesY();
		  int [][]    tile_op =         scan.tile_op;
		  int num_sensors = getNumSensors();
		  double [][] disparity_array = scan.disparity; // Fixme!needs to be updated for multiple !!!
		  double [][] disparity_map = (save_corr || need_diffs) ? new double [ImageDtt.getDisparityTitles(getNumSensors(), isMonochrome()).length][] : null; //[0] -residual disparity, [1] - orthogonal (just for debugging)
		  double [][][][] texture_tiles =   null; // save_textures ? new double [tilesY][tilesX][][] : null; // ["RGBA".length()][];
		  final GeometryCorrection cond_gc = hasGPU() ? null: geometryCorrection; // to skip calculating left for GPU
		  // if (clust_radius > 0) disp_dist is still has to be calculated, even for GPU 
		  final GeometryCorrection cond_gc_center = (hasGPU() && (clust_radius == 0) ) ? null: geometryCorrection; // to skip calculating left for GPU
		  ImageDtt image_dtt = new ImageDtt(
				  getNumSensors(),
				  clt_parameters.transform_size,
				  clt_parameters.img_dtt,
				  isAux(),
				  isMonochrome(),
				  isLwir(),
				  clt_parameters.getScaleStrength(isAux()),
				  getGPUQuad());
		  if (save_corr) {
			  image_dtt.getCorrelation2d(); // initiate image_dtt.correlation2d, needed if disparity_map != null  
		  }
		  double z_correction =  clt_parameters.z_correction;
		  if (clt_parameters.z_corr_map.containsKey(image_name)){ // not used in lwir
			  z_correction +=clt_parameters.z_corr_map.get(image_name);
		  }
		  // currently it was 0.
		  if (z_correction != 0) {
			  System.out.println("CLTMeas(): z_correction="+z_correction+" != 0! ***********");
		  }
		  final double disparity_corr = (z_correction == 0) ? clt_parameters.imp.disparity_corr : geometryCorrection.getDisparityFromZ(1.0/z_correction);
		  // Not adding here ref_scene.getDispInfinityRef() ; // 12/11/2025 - added ref_scene.getDispInfinityRef()
		  int mcorr_sel = save_corr ? ImageDttParameters.corrSelEncode(clt_parameters.img_dtt, getNumSensors()) : 0;
		  TpTask[] tp_tasks = GpuQuad.setTasks( // null on geometryCorrection 
				  num_sensors,                  // final int                      num_cams,
				  disparity_array,              // final double [][]	           disparity_array,  // [tilesY][tilesX] - individual per-tile expected disparity
				  disparity_corr,               // final double                   disparity_corr,
				  tile_op,                      // final int [][]                 tile_op,          // [tilesY][tilesX] - what to do - 0 - nothing for this tile
				  // need to calculate disp_dist before use of quadCorrTD_tilted
				  cond_gc_center,                // final GeometryCorrection       geometryCorrection,
				  threadsMax);                  // final int                      threadsMax)       // maximal number of threads to launch
//getTransformSize()
		  final boolean             save_corr_GPU = save_corr && hasGPU();
		  final boolean             save_corr_CPU = save_corr && !hasGPU();
		  final double [][][][]     dcorr_td = save_corr_CPU ? new double[tp_tasks.length][][][] : null;
		  final float  [][][][]     fcorr_td = save_corr_GPU ?new float  [tilesY][tilesX][][] : null; // [pair][4*64] transform domain representation of 6 corr pairs		  
		  
		  double [][][][][] clt_data =  null;
		  double [] tile_corr_weights = null;
			double[][] dbg_tilts =      null;
		  
		  final double gpu_sigma_corr =     clt_parameters.getGpuCorrSigma(isMonochrome());
		  final double gpu_sigma_rb_corr =  isMonochrome()? 1.0 : clt_parameters.gpu_sigma_rb_corr;
		  final double gpu_sigma_log_corr = clt_parameters.getGpuCorrLoGSigma(isMonochrome());
		  
		  //TODO: split !
		  final boolean             save_diff =   save_textures || need_diffs; // true; // separately save differences and 
		  final boolean             save_lowres = save_textures || need_diffs; // true; // low-res images
		  
		  if (clust_radius > 0) { // will not generate textures
			  // set tasks for all non-NaN target disparities
			  TpTask [] tp_tasks_target = GpuQuad.setTasks(
					  num_sensors,                  // final int                      num_cams,
					  disparity_array,              // final double [][]	           disparity_array,  // [tilesY][tilesX] - individual per-tile expected disparity
					  disparity_corr,               // final double                   disparity_corr,
					  null, // tile_op,             // final int [][]                 tile_op,          // [tilesY][tilesX] - what to do - 0 - nothing for this tile
					  cond_gc,                      // final GeometryCorrection       geometryCorrection,
					  threadsMax);                  // final int                      threadsMax)       // maximal number of threads to launch
			  dbg_tilts = show_2d_correlations? (new double [4][]): null;
			  int clust_radius_tilt = (clust_radius > 1)? (clust_radius + clt_parameters.img_dtt.tilt_clust_extra): clust_radius; 
			  tile_corr_weights =   image_dtt.quadCorrTD_tilted( // returns tile weights to be used for scaling fat zeros during transform domain -> pixel domain transform
					  getImageData(),                      // final double [][][]       image_data,      // first index - number of image in a quad
					  geometryCorrection.getSensorWH()[0], // final int                 width,
					  tp_tasks,                            // final TpTask []           tp_tasks,
					  tp_tasks_target,                     // final TpTask []           tp_tasks_target,  // null or wider array to provide target disparity for neighbors
					  clt_parameters.img_dtt,              // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
					  // dcorr_td should be either null, or double [tp_tasks.length][][];
					  dcorr_td,                            // final double [][][][]     dcorr_td,        // [tile][pair][4][64] sparse by pair transform domain representation of corr pairs
					  fcorr_td,                            // final float  [][][][]     fcorr_td,        // [tilesY][tilesX][pair][4*64] transform domain representation of 6 corr pairs
					  // no combo here - rotate, combine in pixel domain after interframe
					  clt_kernels,                         // final double [][][][][][] clt_kernels,     // [sensor][color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
					  geometryCorrection,                  // final GeometryCorrection  geometryCorrection,
//					  clt_parameters.kernel_step,          // final int                 kernel_step,
					  clt_parameters.clt_window,           // final int                 window_type,
					  clt_parameters.corr_red,             // final double              corr_red,
					  clt_parameters.corr_blue,            // final double              corr_blue,

					  clt_parameters.gpu_sigma_r,        // 0.9, 1.1
					  clt_parameters.gpu_sigma_b,        // 0.9, 1.1
					  clt_parameters.gpu_sigma_g,        // 0.6, 0.7
					  clt_parameters.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_rb_corr;
					  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
					  
						// related to tilt
					  float_center,                        // boolean                   float_center, // false - center around provided value
					  clust_radius_tilt,                   // final int                 clustRadiusTilt,  // 1 - single tile, 2 - 3x3, 3 - 5x5, ...
					  clust_radius,                        // final int                 clustRadius,  // 1 - single tile, 2 - 3x3, 3 - 5x5, ...
					  clt_parameters.img_dtt.tilt_arange,                          // 1.0,  // final double arange, // absolute disparity range to consolidate
					  clt_parameters.img_dtt.tilt_rrange,                              // 0.1,  // final double rrange, // relative disparity range to consolidate
					  clt_parameters.img_dtt.tilt_no_tilt,                             // 0.5,  // final double no_tilt, // no tilt if center disparity is lower
					  clt_parameters.img_dtt.tilt_damp_tilt,                           // 0.01, // final double damp_tilt,    // 0.1?
					  
					  disparity_array,                     // final double [][]	        disparity_array,  // [tilesY][tilesX] - individual per-tile expected disparity - to be updated from tp_tasks
					  mcorr_sel,                           // final int                 mcorr_sel,    // Which pairs to correlate // +1 - all, +2 - dia, +4 - sq, +8 - neibs, +16 - hor + 32 - vert
					  dbg_tilts,                           // final double [][]         dbg_tilts, // [2][tilesY * tiles X];
					  clt_parameters.tileX,                // final int                 debug_tileX,
					  clt_parameters.tileY,                // final int                 debug_tileY,
					  threadsMax,                          // final int                 threadsMax,       // maximal number of threads to launch
					  debugLevel);                         // final int                 globalDebugLevel);
//			  tile_corr_weights = null; // FIXME: Remove update target disparity (disparity_array) from td_tasks;
			  // tile_corr_weights - now valid for ImageDtt:clt_process_tl_correlations (GPU version)
		  } else { // single tile, no tilt/averaging
			  if (hasGPU()) {
				  clt_data = null; // FIXME: provide texture data if needed separately  
				  image_dtt.quadCorrTD( // maybe remove "imageDtt."
						  clt_parameters.img_dtt,            // final ImageDttParameters imgdtt_params,    // Now just extra correlation parameters, later will include, most others
						  tp_tasks, // *** will be updated inside from GPU-calculated geometry
						  fcorr_td, // fcorrs_td[nscene],                 // [tilesY][tilesX][pair][4*64] transform domain representation of 6 corr pairs
//						  geometryCorrection, //
						  clt_parameters.gpu_sigma_r,        // 0.9, 1.1
						  clt_parameters.gpu_sigma_b,        // 0.9, 1.1
						  clt_parameters.gpu_sigma_g,        // 0.6, 0.7
						  clt_parameters.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_rb_corr;
						  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,           // +used
						  clt_parameters.corr_blue,          // +used
						  mcorr_sel,                         // final int                 mcorr_sel,    // Which pairs to correlate // +1 - all, +2 - dia, +4 - sq, +8 - neibs, +16 - hor + 32 - vert
						  threadsMax,       // maximal number of threads to launch
						  debugLevel);
				  boolean show_dbg_img = debugLevel > 1000;
				  if (show_dbg_img) {
						int tile_size = getTileSize();
						int [] wh_dbg = {tilesX*tile_size,tilesY*tile_size};
						double [][] dbg = ((QuadCLT) this).renderDoubleFromTDMono (
								-1, // final int         sensor_mask,
								wh_dbg,              // null, // int []  wh,
				                false);          // boolean use_reference
						ShowDoubleFloatArrays.showArrays(
								dbg,
								wh_dbg[0],
								wh_dbg[1],
								true,
								getImageName()+"-test_CLTMeas");
				  }
				  
			  } else {
				  clt_data = image_dtt.quadCorrTD( // clt_data [task][sensor][color][][];
						  getImageData(),                      // final double [][][]       image_data,      // first index - number of image in a quad
						  geometryCorrection.getSensorWH()[0], // final int                 width,
						  tp_tasks,                            // tp_tasks,                            // final TpTask []           tp_tasks,
						  clt_parameters.img_dtt,              // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
						  // dcorr_td should be either null, or double [tp_tasks.length][][];
						  dcorr_td,                            // final double [][][][]     dcorr_td,        // [tile][pair][4][64] sparse by pair transform domain representation of corr pairs
						  // no combo here - rotate, combine in pixel domain after interframe
						  clt_kernels,                         // final double [][][][][][] clt_kernels,     // [sensor][color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
//						  clt_parameters.kernel_step,          // final int                 kernel_step,
						  clt_parameters.clt_window,           // final int                 window_type,
						  clt_parameters.corr_red,             // final double              corr_red,
						  clt_parameters.corr_blue,            // final double              corr_blue,
						  mcorr_sel,                           // final int                 mcorr_sel,    // Which pairs to correlate // +1 - all, +2 - dia, +4 - sq, +8 - neibs, +16 - hor + 32 - vert
						  clt_parameters.tileX,                // final int                 debug_tileX,
						  clt_parameters.tileY,                // final int                 debug_tileY,
						  threadsMax,                          // final int                 threadsMax,       // maximal number of threads to launch
						  debugLevel);                         // final int                 globalDebugLevel);
			  }
			  
		  }
		  int num_pairs_with_combo = image_dtt.getCorrelation2d().getNumPairs() + 1;
		  double [][][][]     clt_corr_out = (save_corr && show_2d_correlations)?(new double [num_pairs_with_combo][][][]) : null;
		  double  [][][]      dcorr_tiles =  (save_corr && show_2d_correlations)? (new double [tp_tasks.length][][]) : null;
		  // 			double [][][]     dcorr_tiles = (fclt_corr != null)? (new double [tp_tasks_ref.length][][]):null;

		  if (save_corr) {
			  if (hasGPU()) {
					image_dtt.clt_process_tl_correlations( // convert to pixel domain and process correlations already prepared in fcorr_td and/or fcorr_combo_td
							clt_parameters.img_dtt,		   // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
							fcorr_td,		      	       // final float  [][][][]     fcorr_td,        // [tilesY][tilesX][pair][4*64] transform domain representation of all selected corr pairs
							null, // num_acc,              // float [][][]                num_acc,         // number of accumulated tiles [tilesY][tilesX][pair] (or null)       
							tile_corr_weights,             // dcorr_weight,                  // double []                 dcorr_weight,    // alternative to num_acc, compatible with CPU processing (only one non-zero enough)
							clt_parameters.gpu_corr_scale, //  final double              gpu_corr_scale,  //  0.75; // reduce GPU-generated correlation values
							clt_parameters.getGpuFatZero(isMonochrome()),   // final double     gpu_fat_zero,    // clt_parameters.getGpuFatZero(is_mono);absolute == 30.0
							image_dtt.transform_size - 1,  // final int                 gpu_corr_rad,    // = transform_size - 1 ?
					        // The tp_tasks data should be decoded from GPU to get coordinates
							tp_tasks,                  // final TpTask []           tp_tasks,        // data from the reference frame - will be applied to LMW for the integrated correlations
							null, // final double [][][]       far_fgbg,        // null, or [tilesY][tilesX]{disp(fg)-disp(bg), str(fg)-str(bg)} hints for LMA FG/BG split 
							geometryCorrection.getRXY(false), // final double [][]         rXY,             // from geometryCorrection
							// next both can be nulls
							clt_corr_out, // null,                          // final double [][][][]     clt_corr_out,   // sparse (by the first index) [type][tilesY][tilesX][(2*transform_size-1)*(2*transform_size-1)] or null
						    // combo will be added as extra pair if mcorr_comb_width > 0 and clt_corr_out has a slot for it
							// to be converted to float
							dcorr_tiles,                   // final double  [][][]      dcorr_tiles,     // [tile][pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
							// When clt_mismatch is non-zero, no far objects extraction will be attempted
							false,                         // final boolean             use_rms, // DISPARITY_STRENGTH_INDEX means LMA RMS (18/04/2023)
							//optional, may be null
							disparity_map,                 // final double [][]         disparity_map,   // [8][tilesY][tilesX], only [6][] is needed on input or null - do not calculate
							null,                          // final double [][]         ddnd,            // data for LY. SHould be either null or [num_sensors][]
							// REMOVE 'true'
							run_lma, // clt_parameters.correlate_lma,  // final boolean             run_lma,         // calculate LMA, false - CM only
  // define combining of all 2D correlation pairs for CM (LMA does not use them)
							clt_parameters.img_dtt.mcorr_comb_width, //final int                 mcorr_comb_width,  // combined correlation tile width (set <=0 to skip combined correlations)
							clt_parameters.img_dtt.mcorr_comb_height,//final int                 mcorr_comb_height, // combined correlation tile full height
							clt_parameters.img_dtt.mcorr_comb_offset,//final int                 mcorr_comb_offset, // combined correlation tile height offset: 0 - centered (-height/2 to height/2), height/2 - only positive (0 to height)
							clt_parameters.img_dtt.mcorr_comb_disp,	 //final double              mcorr_comb_disp,   // Combined tile per-pixel disparity for baseline == side of a square
							clt_parameters.clt_window,     // final int                 window_type,     // GPU: will not be used
							clt_parameters.tileX,          // final int                 debug_tileX,
							clt_parameters.tileY,          // final int                 debug_tileY,
							threadsMax,                    // final int                 threadsMax,      // maximal number of threads to launch
							null,                          // final String              debug_suffix,
							debugLevel + 2+1); // -1 );              // final int                 globalDebugLevel)
				  
			  } else {			  
				  image_dtt.clt_process_tl_correlations( // convert to pixel domain and process correlations already prepared in fcorr_td and/or fcorr_combo_td
						  clt_parameters.img_dtt,              // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
						  tp_tasks,                            // final TpTask []           tp_tasks,        // data from the reference frame - will be applied to LMW for the integrated correlations
						  // only listed tiles will be processed
						  geometryCorrection.getRXY(false),    // final double [][]         rXY,             // from geometryCorrection
						  tilesX,                              // final int                 tilesX,          // tp_tasks may lack maximal tileX, tileY  
						  tilesY,                              // final int                 tilesY,
						  dcorr_td,                            // final double [][][][]     dcorr_td,        // [tile][pair][4][64] sparse by pair transform domain representation of corr pairs
						  tile_corr_weights,                   // final double []           dcorr_weight,    // [tile] weighted number of tiles averaged (divide squared fat zero by this)
						  clt_corr_out,                        // final double [][][][]     clt_corr_out,   // sparse (by the first index) [type][tilesY][tilesX][(2*transform_size-1)*(2*transform_size-1)] or null
						  // combo will be added as extra pair if mcorr_comb_width > 0 and clt_corr_out has a slot for it
						  // to be converted to float
						  dcorr_tiles,                         // final double  [][][]      dcorr_tiles,     // [tile][pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
						  // When clt_mismatch is non-zero, no far objects extraction will be attempted
						  //optional, may be null
						  disparity_map,                       // final double [][]         disparity_map,   // [8][tilesY][tilesX], only [6][] is needed on input or null - do not calculate
						  null,                          // final double [][]         ddnd,            // data for LY. SHould be either null or [num_sensors][]
						  run_lma,                             // final boolean             run_lma,         // calculate LMA, false - CM only (will not initialize LMA slices in disparity_map
						  clt_parameters.getGpuFatZero(isMonochrome()), //final double              afat_zero2,      // gpu_fat_zero ==30? clt_parameters.getGpuFatZero(is_mono); absolute fat zero, same units as components squared values
						  //					  clt_parameters.getCorrSigma(image_dtt.isMonochrome()), // final double              corr_sigma,
						  clt_parameters.gpu_sigma_m,          // final double              corr_sigma,      //
						  // define combining of all 2D correlation pairs for CM (LMA does not use them)
						  clt_parameters.img_dtt.mcorr_comb_width, // final int                 mcorr_comb_width,  // combined correlation tile width
						  clt_parameters.img_dtt.mcorr_comb_height,// final int                 mcorr_comb_height, // combined correlation tile full height
						  clt_parameters.img_dtt.mcorr_comb_offset,// final int                 mcorr_comb_offset, // combined correlation tile height offset: 0 - centered (-height/2 to
						  clt_parameters.img_dtt.mcorr_comb_disp,  // final double              mcorr_comb_disp,   // Combined tile per-pixel disparity for baseline == side of a square
						  clt_parameters.clt_window,     // final int                 window_type,     // GPU: will not be used
						  clt_parameters.tileX,          // final int                 debug_tileX,
						  clt_parameters.tileY,          // final int                 debug_tileY,
						  threadsMax,                    // final int                 threadsMax,      // maximal number of threads to launch
						  null,                          // final String              debug_suffix,
						  debugLevel -1 );              // final int                 globalDebugLevel)
			  }
		  }
		  if (hasGPU() && (save_textures || save_diff || save_lowres) && (clust_radius == 0)) { // after
			  if (save_textures) {
				  texture_tiles =  image_dtt.process_texture_tiles(
						  clt_parameters.corr_red,       // double    corr_red,
						  clt_parameters.corr_blue,      // double    corr_blue,
						  clt_parameters.min_shot,       // double    min_shot,           // 10.0
						  clt_parameters.scale_shot,     // double    scale_shot,         // 3.0
						  clt_parameters.diff_sigma,     // double    diff_sigma,         // pixel value/pixel change Used much larger sigma = 10.0 instead of 1.5
						  clt_parameters.diff_threshold, // double    diff_threshold,     // pixel value/pixel change
						  clt_parameters.min_agree,      // double    min_agree,          // minimal number of channels to agree on a point (real number to work with fuzzy averages)
						  clt_parameters.dust_remove,    // boolean   dust_remove
						  0);                            // int       keep_weights       // 2 bits now, move to parameters
			  }
			  if (save_diff || save_lowres) {
				  image_dtt.get_diffs_lowres( // CUDA_ERROR_INVALID_VALUE (because no tiles)
						  clt_parameters.corr_red,       // double    corr_red,
						  clt_parameters.corr_blue,      // double    corr_blue,
						  clt_parameters.min_shot,       // double    min_shot,           // 10.0
						  clt_parameters.scale_shot,     // double    scale_shot,         // 3.0
						  clt_parameters.diff_sigma,     // double    diff_sigma,         // pixel value/pixel change Used much larger sigma = 10.0 instead of 1.5
						  clt_parameters.diff_threshold, // double    diff_threshold,     // pixel value/pixel change
						  clt_parameters.min_agree,      // double    min_agree,          // minimal number of channels to agree on a point (real number to work with fuzzy averages)
						  clt_parameters.dust_remove,    // boolean   dust_remove
						  save_diff,                     // boolean     save_diff,
						  save_lowres,                   // boolean     save_lowres,
						  disparity_map);                // double [][] disparity_map   // [8][tilesY][tilesX], only [6][] is needed on input or null - do not calculate			
			  }
		  }
		  if (!hasGPU() && ((save_textures || need_diffs) && (clt_data != null))) { // (clt_data == null) if clust_radius > 0
			  texture_tiles =  image_dtt.clt_process_texture_tiles( // final double [][][][]     texture_tiles
					  clt_parameters.img_dtt,              // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
					  tp_tasks,                            // final TpTask []           tp_tasks,        // data from the reference frame - will be applied to LMW for the integrated correlations
					  // only listed tiles will be processed
					  geometryCorrection.getRXY(false),    // final double [][]         rXY,             // from geometryCorrection
					  tilesX,                              // final int                 tilesX,          // tp_tasks may lack maximal tileX, tileY  
					  tilesY,                              // final int                 tilesY,
					  clt_data,                            // final double [][][][][]   clt_data,
					  //optional, may be null
					  save_diff,                           // final boolean             save_diff,
					  save_lowres,                         //	final boolean             save_lowres,
					  //optional, may be null
					  (need_diffs? disparity_map: null),   // final double [][]         disparity_map,   // [8][tilesY][tilesX], only [6][] is needed on input or null - do not calculate
					  // TODO: Make a separate texture_sigma? 
					  clt_parameters.getTextureSigma(image_dtt.isMonochrome()),  // final double              texture_sigma, // corr_sigma,      //
					  clt_parameters.corr_red,
					  clt_parameters.corr_blue,
					  clt_parameters.min_shot,       // final double              min_shot,        // 10.0;  // Do not adjust for shot noise if lower than
					  clt_parameters.scale_shot,     // 3.0;   // scale when dividing by sqrt ( <0 - disable correction)
					  clt_parameters.diff_sigma,     // 5.0;//RMS difference from average to reduce weights (~ 1.0 - 1/255 full scale image)
					  clt_parameters.diff_threshold, // 5.0;   // RMS difference from average to discard channel (~ 1.0 - 1/255 full scale image)
					  clt_parameters.diff_gauss,     // true;  // when averaging images, use gaussian around average as weight (false - sharp all/nothing)
					  clt_parameters.min_agree,      // 3.0;   // minimal number of channels to agree on a point (real number to work with fuzzy averages)
					  clt_parameters.dust_remove,    // Do not reduce average weight when only one image differes much from the average
					  clt_parameters.keep_weights,   // Add port weights to RGBA stack (debug feature)
					  clt_parameters.clt_window,     // final int                 window_type,     // GPU: will not be used
					  clt_parameters.tileX,          // final int                 debug_tileX,
					  clt_parameters.tileY,          // final int                 debug_tileY,
					  threadsMax,                    // final int                 threadsMax,      // maximal number of threads to launch
					  debugLevel -1 );              // final int                 globalDebugLevel)
		  }
		  
		  // display correlation images (add "combo_all" to titles if needed)
		  if (show_2d_correlations) {
			  if (dbg_tilts != null) {
					ShowDoubleFloatArrays.showArrays( // out of boundary 15
							dbg_tilts,
							tilesX,
							tilesY,
							true,
							getImageName()+"-TILTS-FZ"+(clt_parameters.getGpuFatZero(isMonochrome()))+"-CLUST"+clust_radius,
							new String[] {"tiltX","tiltY","center","weight"});
			  }
			  float  [][][] fclt_corr = new float [dcorr_tiles.length][][]; // dcorr_tiles== null
			  ImageDtt.convertFcltCorr(
						dcorr_tiles, // double [][][] dcorr_tiles,// [tile][sparse, correlation pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
						fclt_corr);  // float  [][][] fclt_corr) //  new float [tilesX * tilesY][][] or null
				float [][] dbg_corr_rslt_partial = ImageDtt.corr_partial_dbg( // not used in lwir
						fclt_corr, // final float  [][][]     fcorr_data,       // [tile][pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
						tp_tasks, // final TpTask []         tp_tasks,        //
						tilesX,    //final int               tilesX,
						tilesY,    //final int               tilesX,
						2*image_dtt.transform_size - 1,	// final int               corr_size,
						1000, // will be limited by available layersfinal int               layers0,
						clt_parameters.corr_border_contrast, // final double            border_contrast,
						threadsMax, // final int               threadsMax,     // maximal number of threads to launch
						debugLevel); // final int               globalDebugLevel)
				
				String [] titles = new String [dbg_corr_rslt_partial.length]; // dcorr_tiles[0].length];
				int ind_length = image_dtt.getCorrelation2d().getCorrTitles().length;
				
				System.arraycopy(image_dtt.getCorrelation2d().getCorrTitles(), 0, titles, 0, ind_length);
				for (int i = ind_length; i < titles.length; i++) {
					titles[i] = "combo-"+(i - ind_length);
				}
				ShowDoubleFloatArrays.showArrays( // out of boundary 15
						dbg_corr_rslt_partial,
						tilesX*(2*image_dtt.transform_size),
						tilesY*(2*image_dtt.transform_size),
						true,
						getImageName()+"-CORR2D-FZ"+(clt_parameters.getGpuFatZero(isMonochrome()))+"-CLUST"+clust_radius,
						titles); //CORR_TITLES);
		  }
		  scan.disparity_map = disparity_map;
		  scan.texture_tiles = texture_tiles;
		  scan.is_measured =   true; // but no disparity map/textures
		  scan.is_combo =      false;
		  scan.has_lma = null;
		  scan.setAvgVal();
		  scan.setSecondMax(); // always needed even with max_chn_diff==0 as it is exported in a file
		  if (max_chn_diff > 0 ) {
			  scan.resetByDiff(
					  max_chn_diff,
					  mismatch_override);
		  }
		  scan.getLMA(); // recalculate
		  scan.getNumTileMax(); // calculate
		  scan.resetProcessed();
		  return scan;
	  }
	  
	  
// Has GPU version!
	  public CLTPass3d  CLTMeasureLY( // perform single pass according to prepared tiles operations and disparity // USED in lwir
			  final CLTParameters clt_parameters,
			  final int           scanIndex,
			  final int           bgIndex, // combine, if >=0
			  final int           threadsMax,  // maximal number of threads to launch
			  final boolean       updateStatus,
			  int           debugLevel)
	  {
		  CLTPass3d scan = tp.clt_3d_passes.get(scanIndex);
		  CLTMeasureLY( // perform single pass according to prepared tiles operations and disparity // USED in lwir
				  clt_parameters, // final CLTParameters clt_parameters,
				  scan,           // final CLTPass3d     scan,
				  bgIndex,        // final int           bgIndex, // combine, if >=0
				  threadsMax,     // final int           threadsMax,  // maximal number of threads to launch
				  updateStatus,   // final boolean       updateStatus,
				  debugLevel);    // int           debugLevel);
		  return scan;

	  }
	  
	  public void  CLTMeasureLY( // perform single pass according to prepared tiles operations and disparity // USED in lwir
			  final CLTParameters clt_parameters,
			  final CLTPass3d     scan,
			  final int           bgIndex, // combine, if >=0
			  final int           threadsMax,  // maximal number of threads to launch
			  final boolean       updateStatus,
			  int           debugLevel)
	  {
		  final int dbg_x = -295-debugLevel;
		  final int dbg_y = -160-debugLevel;
		  final int tilesX = tp.getTilesX();
		  final int tilesY = tp.getTilesY();
		  final int cluster_size =clt_parameters.tileStep;
		  final int clustersX= (tilesX + cluster_size - 1) / cluster_size;
		  final int clustersY= (tilesY + cluster_size - 1) / cluster_size;

//		  CLTPass3d scan = tp.clt_3d_passes.get(scanIndex);
		  scan.setLazyEyeClusterSize(cluster_size);
		  boolean [] force_disparity= new boolean[clustersX * clustersY];
		  //		  scan.setLazyEyeForceDisparity(force_disparity);
		  if (bgIndex >= 0) {
			  CLTPass3d    bg_scan = tp.clt_3d_passes.get(bgIndex);
			  // if at least one tile in a cluster is BG, use BG for the whole cluster and set lazy_eye_force_disparity
			  for (int cY = 0; cY < clustersY; cY ++) {
				  for (int cX = 0; cX < clustersX; cX ++) {
					  boolean has_bg = false;
					  for (int cty = 0; (cty < cluster_size) && !has_bg; cty++) {
						  int ty = cY * cluster_size + cty;
						  if (ty < tilesY) for (int ctx = 0; ctx < cluster_size; ctx++) {
							  int tx = cX * cluster_size + ctx;
							  if ((tx < tilesX ) && (bg_scan.tile_op[ty][tx] > 0)) {
								  has_bg = true;
								  break;
							  }
						  }
					  }
					  if (has_bg) {
						  for (int cty = 0; cty < cluster_size; cty++) {
							  int ty = cY * cluster_size + cty;
							  if (ty < tilesY) for (int ctx = 0; ctx < cluster_size; ctx++) {
								  int tx = cX * cluster_size + ctx;
								  if (tx < tilesX ) {
									  scan.tile_op[ty][tx] =   bg_scan.tile_op[ty][tx];
									  scan.disparity[ty][tx] = bg_scan.disparity[ty][tx];
								  }
							  }
						  }
						  force_disparity[cY * clustersX + cX] = true;
					  }
				  }
			  }
			  scan.setLazyEyeForceDisparity(force_disparity);
		  }
		  int [][]     tile_op =         scan.tile_op;
		  double [][]  disparity_array = scan.disparity;

		  // Should not happen !
		  if (scan.disparity == null) { // not used in lwir
			  System.out.println ("** BUG: should not happen - scan.disparity == null ! **");
			  System.out.println ("Trying to recover");
			  double [] backup_disparity = scan.getDisparity(0);
			  if (backup_disparity == null) {
				  System.out.println ("** BUG: no disparity at all !");
				  backup_disparity = new double[tilesX*tilesY];
			  }
			  scan.disparity = new double[tilesY][tilesX];
			  for (int ty = 0; ty < tilesY; ty++) {
				  for (int tx = 0; tx < tilesX; tx++) {
					  scan.disparity[ty][tx] = backup_disparity[ty*tilesX + tx];
					  if (Double.isNaN(scan.disparity[ty][tx])) {
						  scan.disparity[ty][tx] = 0;
						  tile_op[ty][tx] = 0;
					  }
				  }
			  }
			  disparity_array = scan.disparity;
		  }

		  if (debugLevel > -1){
			  int numTiles = 0;
			  for (int ty = 0; ty < tile_op.length; ty ++) for (int tx = 0; tx < tile_op[ty].length; tx ++){
				  if (tile_op[ty][tx] != 0) numTiles ++;
			  }
//			  System.out.println("CLTMeasure("+scanIndex+"): numTiles = "+numTiles);
			  System.out.println("CLTMeasure(): numTiles = "+numTiles);
			  if ((dbg_y >= 0) && (dbg_x >= 0) && (tile_op[dbg_y][dbg_x] != 0)){
//				  System.out.println("CLTMeasure("+scanIndex+"): tile_op["+dbg_y+"]["+dbg_x+"] = "+tile_op[dbg_y][dbg_x]);
				  System.out.println("CLTMeasure(): tile_op["+dbg_y+"]["+dbg_x+"] = "+tile_op[dbg_y][dbg_x]);
			  }
		  }
		  double min_corr_selected = clt_parameters.min_corr;
		  double [][] shiftXY = new double [getNumSensors()][2];
		  if (!clt_parameters.fine_corr_ignore) {// invalid for AUX!
			  double [][] shiftXY0 = {
					  {clt_parameters.fine_corr_x_0,clt_parameters.fine_corr_y_0},
					  {clt_parameters.fine_corr_x_1,clt_parameters.fine_corr_y_1},
					  {clt_parameters.fine_corr_x_2,clt_parameters.fine_corr_y_2},
					  {clt_parameters.fine_corr_x_3,clt_parameters.fine_corr_y_3}};
			  // FIXME - only first 4 sensors have correction. And is it the same for aux and main? 
			  for (int i = 0; i < shiftXY0.length;i++) {
				  shiftXY[i] = shiftXY0[i];
			  }
		  }

		  ImageDtt image_dtt = new ImageDtt(
				  getNumSensors(),
				  clt_parameters.transform_size,
				  clt_parameters.img_dtt,
				  isAux(),
				  isMonochrome(),
				  isLwir(),
				  clt_parameters.getScaleStrength(isAux()));
		  image_dtt.getCorrelation2d(); // initiate image_dtt.correlation2d, needed if disparity_map != null  
		  double z_correction =  clt_parameters.z_correction;
		  if (clt_parameters.z_corr_map.containsKey(image_name)){ // not used in lwir
			  z_correction +=clt_parameters.z_corr_map.get(image_name);
		  }
		  final double disparity_corr = (z_correction == 0) ? 0.0 : geometryCorrection.getDisparityFromZ(1.0/z_correction);

		  if (debugLevel > -3) { // -5){
			  tp.showScan(
					  scan,   // CLTPass3d   scan,
					  "LY-combo_scan-"+scan+"_post"); //String title)
		  }
		  // use new, LMA-based mismatch calculation
		  double [][] lazy_eye_data;
		  lazy_eye_data = image_dtt.cltMeasureLazyEye ( // returns d,s lazy eye parameters 
				  clt_parameters.img_dtt,       // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
				  tile_op,                      // final int [][]            tile_op,         // [tilesY][tilesX] - what to do - 0 - nothing for this tile
				  disparity_array,              // final double [][]         disparity_array, // [tilesY][tilesX] - individual per-tile expected disparity
				  getImageData(),               // final double [][][]      imade_data, // first index - number of image in a quad
				  saturation_imp,               // final boolean [][]        saturation_imp, // (near) saturated pixels or null
				  tilesX * image_dtt.transform_size, // 	final int                 width,
				  clt_parameters.getFatZero(isMonochrome()),      // final double              corr_fat_zero,    // add to denominator to modify phase correlation (same units as data1, data2). <0 - pure sum
				  clt_parameters.corr_red,      // final double              corr_red,
				  clt_parameters.corr_blue,     // final double              corr_blue,
				  clt_parameters.getCorrSigma(image_dtt.isMonochrome()), // final double              corr_sigma,
				  min_corr_selected, // 0.0001; //final double              min_corr,        // 0.02; // minimal correlation value to consider valid
				  geometryCorrection,            // final GeometryCorrection  geometryCorrection,
				  null,                          // final GeometryCorrection  geometryCorrection_main, // if not null correct this camera (aux) to the coordinates of the main
				  clt_kernels,                   // final double [][][][][][] clt_kernels, // [channel_in_quad][color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
//				  clt_parameters.kernel_step,    // final int                 kernel_step,
				  clt_parameters.clt_window,     // final int                 window_type,
				  shiftXY,                       // final double [][]         shiftXY, // [port]{shiftX,shiftY}
				  disparity_corr,                // final double              disparity_corr, // disparity at infinity
				  clt_parameters.shift_x,        // final double              shiftX, // shift image horizontally (positive - right) - just for testing
				  clt_parameters.shift_y,        // final double              shiftY, // shift image vertically (positive - down)
				  clt_parameters.tileStep,       // final int                 tileStep, // process tileStep x tileStep cluster of tiles when adjusting lazy eye parameters
				  clt_parameters.img_dtt.getMcorrSelLY(getNumSensors()), //    final int                 mcorr_sel, // +1 - all, +2 - dia, +4 - sq, +8 - neibs, +16 - hor + 32 - vert

				  clt_parameters.img_dtt.mcorr_comb_width,					// final int                 mcorr_comb_width,  // combined correlation tile width
				  clt_parameters.img_dtt.mcorr_comb_height,					// final int                 mcorr_comb_height, // combined correlation tile full height
				  clt_parameters.img_dtt.mcorr_comb_offset,					// final int                 mcorr_comb_offset, // combined correlation tile height offset: 0 - centered (-height/2 to height/2), height/2 - only positive (0 to height)
				  clt_parameters.img_dtt.mcorr_comb_disp,					// final double              mcorr_comb_disp,   // Combined tile per-pixel disparity for baseline == side of a square

				  clt_parameters.tileX,        // final int                 debug_tileX,
				  clt_parameters.tileY,         // final int                 debug_tileY,
				  threadsMax, // final int                 threadsMax,  // maximal number of threads to launch
				  debugLevel - 2); // final int                 globalDebugLevel)
		  scan.setLazyEyeData(lazy_eye_data);
		  scan.is_measured =   true; // but no disparity map/textures
		  scan.is_combo =      false;
		  scan.resetProcessed();
//		  return scan;
	  }






	  public ImagePlus [] conditionImageSetBatch( // used in batchCLT3d // not used in lwir
			  final int                           nSet, // index of the 4-image set
			  final CLTParameters           clt_parameters,
			  final int [][]                      fileIndices, // =new int [numImagesToProcess][2]; // file index, channel number
			  final ArrayList<String>             setNames, //  = new ArrayList<String>();
			  final ArrayList<ArrayList<Integer>> setFiles, //  = new ArrayList<ArrayList<Integer>>();
			  final double []                     referenceExposures, // =eyesisCorrections.calcReferenceExposures(debugLevel); // multiply each image by this and divide by individual (if not NaN)
			  final double []                     scaleExposures, //  = new double[channelFiles.length]; //
			  final boolean [][]                  saturation_imp, //  = (clt_parameters.sat_level > 0.0)? new boolean[channelFiles.length][] : null;
			  final int                           debugLevel)
	  {
		  final boolean                       batch_mode = clt_parameters.batch_run; //disable any debug images
		  String [] sourceFiles=correctionsParameters.getSourcePaths();
		  int maxChn = 0;
		  for (int i = 0; i < setFiles.get(nSet).size(); i++){
			  int chn = fileIndices[setFiles.get(nSet).get(i)][1];
			  if (chn > maxChn) maxChn = chn;
		  }
		  int [] channelFiles = new int[maxChn+1];
		  for (int i =0; i < channelFiles.length; i++) channelFiles[i] = -1;
		  for (int i = 0; i < setFiles.get(nSet).size(); i++){
			  channelFiles[fileIndices[setFiles.get(nSet).get(i)][1]] = setFiles.get(nSet).get(i);
		  }

		  ImagePlus [] imp_srcs = new ImagePlus[channelFiles.length];
		  this.geometryCorrection.woi_tops = new int [channelFiles.length];
		  this.geometryCorrection.camera_heights = new int [channelFiles.length];
		  
		  double [][] dbg_dpixels =  batch_mode? null : (new double [channelFiles.length][]);

		  for (int srcChannel=0; srcChannel<channelFiles.length; srcChannel++){
			  int nFile=channelFiles[srcChannel];
			  imp_srcs[srcChannel]=null;
			  if (nFile >=0){
				  imp_srcs[srcChannel] = eyesisCorrections.getJp4Tiff(sourceFiles[nFile], this.geometryCorrection.woi_tops, this.geometryCorrection.camera_heights);

				  scaleExposures[srcChannel] = 1.0;
				  if (!Double.isNaN(referenceExposures[nFile]) && (imp_srcs[srcChannel].getProperty("EXPOSURE")!=null)){
					  scaleExposures[srcChannel] = referenceExposures[nFile]/Double.parseDouble((String) imp_srcs[srcChannel].getProperty("EXPOSURE"));
					  if (debugLevel > -1) {
						  System.out.println("Will scale intensity (to compensate for exposure) by  "+scaleExposures[srcChannel]+
								  ", EXPOSURE = "+imp_srcs[srcChannel].getProperty("EXPOSURE"));
					  }
				  }
				  imp_srcs[srcChannel].setProperty("name",    correctionsParameters.getNameFromSourceTiff(sourceFiles[nFile]));
				  imp_srcs[srcChannel].setProperty("channel", srcChannel); // it may already have channel
				  imp_srcs[srcChannel].setProperty("path",    sourceFiles[nFile]); // it may already have channel

				  if (this.correctionsParameters.pixelDefects && (eyesisCorrections.defectsXY!=null)&& (eyesisCorrections.defectsXY[srcChannel]!=null)){
					  // apply pixel correction
					  int numApplied=	eyesisCorrections.correctDefects(
							  imp_srcs[srcChannel],
							  srcChannel,
							  debugLevel);
					  if ((debugLevel>0) && (numApplied>0)) { // reduce verbosity after verified defect correction works
						  System.out.println("Corrected "+numApplied+" pixels in "+sourceFiles[nFile]);
					  }
				  }
				  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
				  int width =  imp_srcs[srcChannel].getWidth();
				  int height = imp_srcs[srcChannel].getHeight();
				  if (!batch_mode && (debugLevel > -1)) {
					  double [] max_pix= {0.0, 0.0, 0.0, 0.0};
//					  for (int y = 0; y < height-1; y+=2){
					  for (int y = 0; y < 499; y+=2){
//						  for (int x = 0; x < width-1; x+=2){
						  for (int x = width/2; x < width-1; x+=2){
							  if (pixels[y*width+x        ] > max_pix[0])  max_pix[0] = pixels[y*width+x        ];
							  if (pixels[y*width+x+      1] > max_pix[1])  max_pix[1] = pixels[y*width+x+      1];
							  if (pixels[y*width+x+width  ] > max_pix[2])  max_pix[2] = pixels[y*width+x+width  ];
							  if (pixels[y*width+x+width+1] > max_pix[3])  max_pix[3] = pixels[y*width+x+width+1];
						  }
					  }
					  System.out.println(String.format("channel %d max_pix[] = %6.2f %6.2f %6.2f %6.2f", srcChannel, max_pix[0], max_pix[1], max_pix[2], max_pix[3]));
					  dbg_dpixels[srcChannel] = new double [pixels.length];
					  for (int i = 0; i < pixels.length; i++) dbg_dpixels[srcChannel][i] = pixels[i];
					  //						  imp_srcs[srcChannel].show();
				  }
				  if (clt_parameters.sat_level > 0.0){
					  double [] saturations = {
							  Double.parseDouble((String) imp_srcs[srcChannel].getProperty("saturation_1")),
							  Double.parseDouble((String) imp_srcs[srcChannel].getProperty("saturation_0")),
							  Double.parseDouble((String) imp_srcs[srcChannel].getProperty("saturation_3")),
							  Double.parseDouble((String) imp_srcs[srcChannel].getProperty("saturation_2"))};
					  saturation_imp[srcChannel] = new boolean[width*height];
					  System.out.println(String.format("channel %d saturations = %6.2f %6.2f %6.2f %6.2f", srcChannel,
							  saturations[0],saturations[1],saturations[2],saturations[3]));
					  double [] scaled_saturations = new double [saturations.length];
					  for (int i = 0; i < scaled_saturations.length; i++){
						  scaled_saturations[i] = saturations[i] * clt_parameters.sat_level;
					  }
					  for (int y = 0; y < height-1; y+=2){
						  for (int x = 0; x < width-1; x+=2){
							  if (pixels[y*width+x        ] > scaled_saturations[0])  saturation_imp[srcChannel][y*width+x        ] = true;
							  if (pixels[y*width+x+      1] > scaled_saturations[1])  saturation_imp[srcChannel][y*width+x      +1] = true;
							  if (pixels[y*width+x+width  ] > scaled_saturations[2])  saturation_imp[srcChannel][y*width+x+width  ] = true;
							  if (pixels[y*width+x+width+1] > scaled_saturations[3])  saturation_imp[srcChannel][y*width+x+width+1] = true;
						  }
					  }
				  }


				  if (this.correctionsParameters.vignetting){
					  if ((eyesisCorrections.channelVignettingCorrection==null) || (srcChannel<0) || (srcChannel>=eyesisCorrections.channelVignettingCorrection.length) || (eyesisCorrections.channelVignettingCorrection[srcChannel]==null)){
						  System.out.println("No vignetting data for channel "+srcChannel);
						  return null;
					  }
					  ///						  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();


					  if (pixels.length!=eyesisCorrections.channelVignettingCorrection[srcChannel].length){
						  System.out.println("Vignetting data for channel "+srcChannel+" has "+eyesisCorrections.channelVignettingCorrection[srcChannel].length+" pixels, image "+sourceFiles[nFile]+" has "+pixels.length);
						  return null;
					  }
					  // TODO: Move to do it once:
					  double min_non_zero = 0.0;
					  for (int i=0;i<pixels.length;i++){
						  double d = eyesisCorrections.channelVignettingCorrection[srcChannel][i];
						  if ((d > 0.0) && ((min_non_zero == 0) || (min_non_zero > d))){
							  min_non_zero = d;
						  }
					  }
					  double max_vign_corr = clt_parameters.vignetting_range*min_non_zero;

					  System.out.println("Vignetting data: channel="+srcChannel+", min = "+min_non_zero);
					  for (int i=0;i<pixels.length;i++){
						  double d = eyesisCorrections.channelVignettingCorrection[srcChannel][i];
						  if (d > max_vign_corr) d = max_vign_corr;
						  pixels[i]*=d;
					  }
					  // Scale here, combine with vignetting later?
					  ///						  int width =  imp_srcs[srcChannel].getWidth();
					  ///						  int height = imp_srcs[srcChannel].getHeight();
					  for (int y = 0; y < height-1; y+=2){
						  for (int x = 0; x < width-1; x+=2){
							  pixels[y*width+x        ] *= clt_parameters.scale_g;
							  pixels[y*width+x+width+1] *= clt_parameters.scale_g;
							  pixels[y*width+x      +1] *= clt_parameters.scale_r;
							  pixels[y*width+x+width  ] *= clt_parameters.scale_b;
						  }
					  }

				  } else { // assuming GR/BG pattern
					  System.out.println("Applying fixed color gain correction parameters: Gr="+
							  clt_parameters.novignetting_r+", Gg="+clt_parameters.novignetting_g+", Gb="+clt_parameters.novignetting_b);
					  ///						  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
					  ///						  int width =  imp_srcs[srcChannel].getWidth();
					  ///						  int height = imp_srcs[srcChannel].getHeight();
					  double kr = clt_parameters.scale_r/clt_parameters.novignetting_r;
					  double kg = clt_parameters.scale_g/clt_parameters.novignetting_g;
					  double kb = clt_parameters.scale_b/clt_parameters.novignetting_b;
					  for (int y = 0; y < height-1; y+=2){
						  for (int x = 0; x < width-1; x+=2){
							  pixels[y*width+x        ] *= kg;
							  pixels[y*width+x+width+1] *= kg;
							  pixels[y*width+x      +1] *= kr;
							  pixels[y*width+x+width  ] *= kb;
						  }
					  }
				  }
			  }
		  }
		  // temporary applying scaleExposures[srcChannel] here, setting it to all 1.0
		  System.out.println("Temporarily applying scaleExposures[] here - 3" );
		  for (int srcChannel=0; srcChannel<channelFiles.length; srcChannel++){
			  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
			  for (int i = 0; i < pixels.length; i++){
				  pixels[i] *= scaleExposures[srcChannel];
			  }
			  scaleExposures[srcChannel] = 1.0;
		  }

		  // may need to equalize gains between channels
		  if (clt_parameters.gain_equalize || clt_parameters.colors_equalize){
			  channelGainsEqualize(
					  clt_parameters.gain_equalize,
					  clt_parameters.colors_equalize,
					  clt_parameters.nosat_equalize, // boolean nosat_equalize,
					  channelFiles,
					  imp_srcs,
					  saturation_imp, // boolean[][] saturated,
					  setNames.get(nSet), // just for debug messages == setNames.get(nSet)
					  debugLevel);
		  }


		  if (!batch_mode && (debugLevel > -1) && (saturation_imp != null)){
			  String [] titles = {"chn0","chn1","chn2","chn3"};
			  double [][] dbg_satur = new double [saturation_imp.length] [saturation_imp[0].length];
			  for (int srcChannel=0; srcChannel<channelFiles.length; srcChannel++){
				  for (int i = 0; i < saturation_imp[srcChannel].length; i++){
					  dbg_satur[srcChannel][i] = saturation_imp[srcChannel][i]? 1.0 : 0.0;
				  }
			  }
			  int width =  imp_srcs[0].getWidth();
			  int height = imp_srcs[0].getHeight();
			  ShowDoubleFloatArrays.showArrays(dbg_satur, width, height, true, "Saturated" , titles);

			  if (debugLevel > -1) { // 0){
				  double [][] dbg_dpixels_norm = new double [channelFiles.length][];
				  for (int srcChannel=0; srcChannel<channelFiles.length; srcChannel++){
					  float [] pixels=(float []) imp_srcs[srcChannel].getProcessor().getPixels();
					  dbg_dpixels_norm[srcChannel] = new double[pixels.length];
					  for (int i = 0; i < pixels.length; i++){
						  dbg_dpixels_norm[srcChannel][i] = pixels[i];
					  }
				  }
				  ShowDoubleFloatArrays.showArrays(dbg_dpixels, width, height, true, "dpixels" , titles);
				  ShowDoubleFloatArrays.showArrays(dbg_dpixels_norm, width, height, true, "dpixels_norm" , titles);
				  double [][] dbg_dpixels_split = new double [4 * dbg_dpixels.length][dbg_dpixels[0].length / 4];
				  String [] dbg_titles = {"g1_0","r_0","b_0","g2_0","g1_2","r_1","b_1","g2_1","g1_2","r_2","b_2","g2_2","g1_3","r_3","b_3","g2_3"};
				  for (int srcChn = 0; srcChn < 4; srcChn++) {
					  for (int y = 0; y < height-1; y+=2){
						  for (int x = 0; x < width-1; x+=2){
							  dbg_dpixels_split[ 0 + 4 * srcChn][ y*width/4 +x/2 ] = dbg_dpixels_norm[srcChn][y * width + x             ];
							  dbg_dpixels_split[ 3 + 4 * srcChn][ y*width/4 +x/2 ] = dbg_dpixels_norm[srcChn][y * width + x  + width + 1];
							  dbg_dpixels_split[ 1 + 4 * srcChn][ y*width/4 +x/2 ] = dbg_dpixels_norm[srcChn][y * width + x  +         1];
							  dbg_dpixels_split[ 2 + 4 * srcChn][ y*width/4 +x/2 ] = dbg_dpixels_norm[srcChn][y * width + x  + width    ];
						  }
					  }
				  }
				  ShowDoubleFloatArrays.showArrays(dbg_dpixels_split, width/2, height/2, true, "dpixels_split" , dbg_titles);
			  }
		  }

		  return imp_srcs;
	  }

	  public void batchCLT3d( // Same can be ran for aux? // not used in lwir
			  TwoQuadCLT       twoQuadCLT, //maybe null in no-rig mode, otherwise may contain rig measurements to be used as infinity ground truth
			  CLTParameters          clt_parameters,
			  ColorProcParameters                               colorProcParameters,
			  CorrectionColorProc.ColorGainsParameters          channelGainParameters, // also need aux!
			  EyesisCorrectionParameters.RGBParameters          rgbParameters,
			  EyesisCorrectionParameters.EquirectangularParameters equirectangularParameters,
			  final int        threadsMax,  // maximal number of threads to launch
			  final boolean    updateStatus,
			  final int        debugLevel)
	  {
		  final int        debugLevelInner=clt_parameters.batch_run? -2: debugLevel;
		  this.startTime=System.nanoTime();
		  String [] sourceFiles=correctionsParameters.getSourcePaths();
		  boolean [] enabledFiles=new boolean[sourceFiles.length];
		  for (int i=0;i<enabledFiles.length;i++) enabledFiles[i]=false;
		  int numFilesToProcess=0;
		  int numImagesToProcess=0;
		  for (int nFile=0;nFile<enabledFiles.length;nFile++){
			  if ((sourceFiles[nFile]!=null) && (sourceFiles[nFile].length()>1)) {
				  int [] channels= fileChannelToSensorChannels(correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile]));
				  if (channels!=null){
					  for (int i=0;i<channels.length;i++) if (eyesisCorrections.isChannelEnabled(channels[i])){
						  if (!enabledFiles[nFile]) numFilesToProcess++;
						  enabledFiles[nFile]=true;
						  numImagesToProcess++;
					  }
				  }
			  }
		  }
		  if (numFilesToProcess==0){
			  System.out.println("No files to process (of "+sourceFiles.length+")");
			  return;
		  } else {
			  if (debugLevel>0) System.out.println(numFilesToProcess+ " files to process (of "+sourceFiles.length+"), "+numImagesToProcess+" images to process");
		  }
		  double [] referenceExposures=eyesisCorrections.calcReferenceExposures(debugLevelInner); // multiply each image by this and divide by individual (if not NaN)
		  int [][] fileIndices=new int [numImagesToProcess][2]; // file index, channel number
		  int index=0;
		  for (int nFile=0;nFile<enabledFiles.length;nFile++){
			  if ((sourceFiles[nFile]!=null) && (sourceFiles[nFile].length()>1)) {
				  int [] channels= fileChannelToSensorChannels(correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile]));
				  if (channels!=null){
					  for (int i=0;i<channels.length;i++) if (eyesisCorrections.isChannelEnabled(channels[i])){
						  fileIndices[index  ][0]=nFile;
						  fileIndices[index++][1]=channels[i];
					  }
				  }
			  }
		  }
		  ArrayList<String> setNames = new ArrayList<String>();
		  ArrayList<ArrayList<Integer>> setFiles = new ArrayList<ArrayList<Integer>>();

		  for (int iImage=0;iImage<fileIndices.length;iImage++){
			  int nFile=fileIndices[iImage][0];
			  String setName = correctionsParameters.getNameFromSourceTiff(sourceFiles[nFile]);
			  if (!setNames.contains(setName)) {
				  setNames.add(setName);
				  setFiles.add(new ArrayList<Integer>());
			  }
			  setFiles.get(setNames.indexOf(setName)).add(nFile); // .add(new Integer(nFile));
		  }

		  // enable debug for single-image when clt_batch_dbg1 is on
		  if (correctionsParameters.clt_batch_dbg1 && (setNames.size() < 2)) {
			  clt_parameters.batch_run = false; // disable batch_run for single image if  clt_batch_dbg1 is on
		  }

		  // Do per 4-image set processing
		  int nSet = 0;
		  for (nSet = 0; nSet < setNames.size(); nSet++){
			  if ((nSet > 0) &&(debugLevel > -2)) {
				  System.out.println("Processing set "+(nSet+0)+" (of "+setNames.size()+") finished at "+
				  IJ.d2s(0.000000001*(System.nanoTime()-this.startSetTime),3)+" sec, --- Free memory26="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
			  }
			  this.startSetTime = System.nanoTime();
//			  boolean [][] saturation_imp = (clt_parameters.sat_level > 0.0)? new boolean[QUAD][] : null;
//			  double [] scaleExposures = new double[QUAD]; //
			  boolean [][] saturation_imp = (clt_parameters.sat_level > 0.0)? new boolean[getNumSensors()][] : null;
			  double [] scaleExposures = new double[getNumSensors()]; //
			  ImagePlus [] imp_srcs = conditionImageSetBatch(
					  nSet,               // final int                           nSet, // index of the 4-image set
					  clt_parameters,     // final EyesisCorrectionParameters.CLTParameters           clt_parameters,
					  fileIndices,        // final int [][]                      fileIndices, // =new int [numImagesToProcess][2]; // file index, channel number
					  setNames,           // final ArrayList<String>             setNames, //  = new ArrayList<String>();
					  setFiles,           // final ArrayList<ArrayList<Integer>> setFiles, //  = new ArrayList<ArrayList<Integer>>();
					  referenceExposures, //final double []                     referenceExposures, // =eyesisCorrections.calcReferenceExposures(debugLevel); // multiply each image by this and divide by individual (if not NaN)
					  scaleExposures,     // final double []                     scaleExposures, //  = new double[channelFiles.length]; //
					  saturation_imp,     // final boolean [][]                  saturation_imp, //  = (clt_parameters.sat_level > 0.0)? new boolean[channelFiles.length][] : null;
					  debugLevelInner);   // final int                           debugLevel)

			  // once per quad here
			  if (imp_srcs == null) continue;
			  // creating GeometryCorrection instance for  applyPixelShift()

			  if (correctionsParameters.clt_batch_apply_man) {
				  boolean fine_corr_set = !clt_parameters.fine_corr_ignore;
				  if (fine_corr_set) {
					  boolean nz = false;
					  double [][] shiftXY = new double [getNumSensors()][2];
					  double [][] shiftXY0 = {
							  {clt_parameters.fine_corr_x_0,clt_parameters.fine_corr_y_0},
							  {clt_parameters.fine_corr_x_1,clt_parameters.fine_corr_y_1},
							  {clt_parameters.fine_corr_x_2,clt_parameters.fine_corr_y_2},
							  {clt_parameters.fine_corr_x_3,clt_parameters.fine_corr_y_3}};
					  // FIXME - only first 4 sensors have correction. And is it the same for aux and main? 
					  for (int i = 0; i < shiftXY0.length;i++) {
						  shiftXY[i] = shiftXY0[i];
					  }
					  for (int i = 0; i < shiftXY.length; i++){
						  for (int j = 0; j < shiftXY[i].length; j++){
							  if (shiftXY[i][j] != 0.0) {
								  nz = true;
								  break;
							  }
						  }
					  }
					  if (nz) {
						  geometryCorrection.getCorrVector().applyPixelShift(
								  shiftXY); // double [][] pXY_shift)
						  clt_parameters.fine_corr_ignore = true;
						  System.out.println("Detected non-zero manual pixel correction, applying it to extrinsics (azimuth, tilt) and disabling");
					  }
				  }

			  } else if (!clt_parameters.fine_corr_ignore){ // temporary? Remove DC from the manual correction
				  double [][] shiftXY = new double [getNumSensors()][2];
				  double [][] shiftXY0 = {
						  {clt_parameters.fine_corr_x_0,clt_parameters.fine_corr_y_0},
						  {clt_parameters.fine_corr_x_1,clt_parameters.fine_corr_y_1},
						  {clt_parameters.fine_corr_x_2,clt_parameters.fine_corr_y_2},
						  {clt_parameters.fine_corr_x_3,clt_parameters.fine_corr_y_3}};
				  // FIXME - only first 4 sensors have correction. And is it the same for aux and main? 
				  for (int i = 0; i < shiftXY0.length;i++) {
					  shiftXY[i] = shiftXY0[i];
				  }
				  double [] pXY_avg = {0.0,0.0};
				  for (int i = 0; i < shiftXY.length; i++){
					  for (int j = 0; j < 2; j++) {
						  pXY_avg[j] += shiftXY[i][j]/shiftXY.length;
					  }
				  }
				  for (int i = 0; i < shiftXY.length; i++){
					  for (int j = 0; j < 2; j++) {
						  shiftXY[i][j] -= pXY_avg[j];
					  }
				  }
				// FIXME - only first 4 sensors have correction. And is it the same for aux and main?
				  clt_parameters.fine_corr_x_0 = shiftXY[0][0];
				  clt_parameters.fine_corr_y_0 = shiftXY[0][1];
				  clt_parameters.fine_corr_x_1 = shiftXY[1][0];
				  clt_parameters.fine_corr_y_1 = shiftXY[1][1];
				  clt_parameters.fine_corr_x_2 = shiftXY[2][0];
				  clt_parameters.fine_corr_y_2 = shiftXY[2][1];
				  clt_parameters.fine_corr_x_3 = shiftXY[3][0];
				  clt_parameters.fine_corr_y_3 = shiftXY[3][1];
			  }
			  if (correctionsParameters.clt_batch_extrinsic) {
				  if (tp != null) tp.resetCLTPasses();
				  boolean ok = preExpandCLTQuad3d( // returns ImagePlus, but it already should be saved/shown
						  clt_parameters,
						  // adding these parameters for more flexibility in accuracy/speed
						  clt_parameters.gr_max_clust_radius, //int      gr_max_clust_radius,			  
						  clt_parameters.disp_scan_start,     // double  disp_scan_start,
						  clt_parameters.disp_scan_step,      // double  disp_scan_step,
						  clt_parameters.disp_scan_count,     // double  disp_scan_count,
						  false,                              // boolean no_bg_generate,			  
						  false,                              // boolean no_lma,			  
						  colorProcParameters,
						  rgbParameters,
						  threadsMax,  // maximal number of threads to launch
						  updateStatus,
						  debugLevelInner);
				  if (ok) {
					  System.out.println("Adjusting extrinsics");
					  extrinsicsCLT(
							  clt_parameters, // EyesisCorrectionParameters.CLTParameters           clt_parameters,
							  false,          // adjust_poly,
							  -1.0, // double inf_min,
							  1.0,  // double inf_max,
							  threadsMax,     //final int        threadsMax,  // maximal number of threads to launch
							  updateStatus,   // final boolean    updateStatus,
							  debugLevelInner);    // final int        debugLevel)
				  }
			  }
			  if (correctionsParameters.clt_batch_poly) {
				  if (tp != null) tp.resetCLTPasses();
				  boolean ok = preExpandCLTQuad3d( // returns ImagePlus, but it already should be saved/shown
						  clt_parameters,
						  // adding these parameters for more flexibility in accuracy/speed
						  clt_parameters.gr_max_clust_radius, //int      gr_max_clust_radius,			  
						  clt_parameters.disp_scan_start,     // double  disp_scan_start,
						  clt_parameters.disp_scan_step,      // double  disp_scan_step,
						  clt_parameters.disp_scan_count,     // double  disp_scan_count,
						  false,                              // boolean no_bg_generate,			  
						  false,                              // boolean no_lma,			  
						  colorProcParameters,
						  rgbParameters,
						  threadsMax,  // maximal number of threads to launch
						  updateStatus,
						  debugLevelInner);
				  if (ok) {
					  System.out.println("Adjusting polynomial fine crorection");
					  extrinsicsCLT(
							  clt_parameters, // EyesisCorrectionParameters.CLTParameters           clt_parameters,
							  true,           // adjust_poly,
							  -1.0, // double inf_min,
							  1.0,  // double inf_max,
							  threadsMax,     //final int        threadsMax,  // maximal number of threads to launch
							  updateStatus,   // final boolean    updateStatus,
							  debugLevelInner);    // final int        debugLevel)
				  }

			  }
			  if (correctionsParameters.clt_batch_4img){ // not used in lwir
				  processCLTQuadCorrCPU( // returns ImagePlus, but it already should be saved/shown
						  saturation_imp, // boolean [][] saturation_imp, // (near) saturated pixels or null
						  clt_parameters,
						  colorProcParameters,
						  channelGainParameters,
						  rgbParameters,
						  scaleExposures,
						  false, // apply_corr, // calculate and apply additional fine geometry correction
						  false, // infinity_corr, // calculate and apply geometry correction at infinity
						  threadsMax,  // maximal number of threads to launch
						  updateStatus,
						  debugLevelInner);
			  }
			  if (correctionsParameters.clt_batch_explore) {
				  if (tp != null) tp.resetCLTPasses();
				  boolean ok = preExpandCLTQuad3d( // returns ImagePlus, but it already should be saved/shown
						  clt_parameters,
						  // adding these parameters for more flexibility in accuracy/speed
						  clt_parameters.gr_max_clust_radius, //int      gr_max_clust_radius,			  
						  clt_parameters.disp_scan_start,     // double  disp_scan_start,
						  clt_parameters.disp_scan_step,      // double  disp_scan_step,
						  clt_parameters.disp_scan_count,     // double  disp_scan_count,
						  false,                              // boolean no_bg_generate,			  
						  false,                              // boolean no_lma,			  
						  colorProcParameters,
						  rgbParameters,
						  threadsMax,  // maximal number of threads to launch
						  updateStatus,
						  debugLevelInner);
				  if (ok) {
					  System.out.println("Explore 3d space");
					  expandCLTQuad3d( // returns ImagePlus, but it already should be saved/shown
							  clt_parameters,
							  colorProcParameters,
							  channelGainParameters,
							  rgbParameters,
							  threadsMax,  // maximal number of threads to launch
							  updateStatus,
							  debugLevelInner);
				  } else continue;

			  } else continue; // if (correctionsParameters.clt_batch_explore)

			  if (correctionsParameters.clt_batch_surf) {
				  tp.showPlanes(
						  clt_parameters,
						  geometryCorrection,
						  threadsMax,
						  updateStatus,
						  debugLevelInner);

			  } else continue; // if (correctionsParameters.clt_batch_surf)

			  if (correctionsParameters.clt_batch_assign) {
					// prepare average RGBA for the last scan
					setPassAvgRBGA(                      // get image from a single pass, return relative path for x3d // USED in lwir
							clt_parameters,                           // CLTParameters           clt_parameters,
							tp.clt_3d_passes.size() - 1, // int        scanIndex,
							threadsMax,                               // int        threadsMax,  // maximal number of threads to launch
							updateStatus,                             // boolean    updateStatus,
							debugLevelInner);                         // int        debugLevel)
				  
				  double [][] assign_dbg = tp.assignTilesToSurfaces(
						  clt_parameters,
						  geometryCorrection,
						  threadsMax,
						  updateStatus,
						  debugLevelInner);
				  if (assign_dbg == null) continue;
			  } else continue; // if (correctionsParameters.clt_batch_assign)

			  if (correctionsParameters.clt_batch_gen3d) {
				  boolean ok = output3d(
						  clt_parameters,      // EyesisCorrectionParameters.CLTParameters           clt_parameters,
						  colorProcParameters, // EyesisCorrectionParameters.ColorProcParameters colorProcParameters,
						  rgbParameters,       // EyesisCorrectionParameters.RGBParameters             rgbParameters,
						  threadsMax,          // final int        threadsMax,  // maximal number of threads to launch
						  updateStatus,        // final boolean    updateStatus,
						  debugLevelInner);         // final int        debugLevel)
				  if (!ok) continue;
			  } else continue; // if (correctionsParameters.clt_batch_gen3d)

			  Runtime.getRuntime().gc();
			  if (eyesisCorrections.stopRequested.get()>0) {
				  System.out.println("User requested stop");
				  System.out.println("Processing "+(nSet + 1)+" file sets (of "+setNames.size()+") finished at "+
						  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory27="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
				  return;
			  }
		  }
		  if (debugLevel > -2) {
			  System.out.println("Processing set "+nSet+" (of "+setNames.size()+") finished at "+
			  IJ.d2s(0.000000001*(System.nanoTime()-this.startSetTime),3)+" sec, --- Free memory28="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
		  }

		  System.out.println("Processing "+fileIndices.length+" files ("+setNames.size()+" file sets) finished in "+
				  IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory29="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
	  }

	  public boolean setGpsLla( // USED in lwir
			  String source_file)
	  {
		  /*
		  ImagePlus imp=(new JP46_Reader_camera(false)).open(
				  "", // path,
				  source_file,
				  "",  //arg - not used in JP46 reader
				  true, // un-apply camera color gains
				  null, // new window
				  false); // do not show
		   */
		  ImagePlus imp=null;
		  try {
			  imp = (new ImagejJp4Tiff()).readTiffJp4(
					  source_file,
					  true);
		  } catch (IOException | FormatException e) {
			  // TODO Auto-generated catch block
			  e.printStackTrace();
		  } // scale);

		  if (imp.getProperty("LATITUDE") != null){
			  gps_lla = new double[3];
			  for (int i = 0; i < 3; i++) {
				  gps_lla[i] = Double.NaN;
			  }
			  if (imp.getProperty("LATITUDE")  != null) gps_lla[0] =Double.parseDouble((String) imp.getProperty("LATITUDE"));
			  if (imp.getProperty("LONGITUDE") != null) gps_lla[1] =Double.parseDouble((String) imp.getProperty("LONGITUDE"));
			  if (imp.getProperty("ALTITUDE")  != null) gps_lla[2] =Double.parseDouble((String) imp.getProperty("ALTITUDE"));
			  return true;
		  }
		  if (imp.getProperty("STD_GPS_Latitude")  != null) {
			  gps_lla = new double[3];
			  for (int i = 0; i < 3; i++) {
				  gps_lla[i] = Double.NaN;
			  }
			  if (imp.getProperty("STD_GPS_Latitude")  != null) gps_lla[0] =parseDegreeMinSec((String) imp.getProperty("STD_GPS_Latitude"));
			  if (imp.getProperty("STD_GPS_Longitude")  != null) gps_lla[1] =parseDegreeMinSec((String) imp.getProperty("STD_GPS_Longitude"));
			  if (imp.getProperty("STD_GPS_Altitude")  != null) gps_lla[2] =parseStringToEndString((String) imp.getProperty("STD_GPS_Altitude"),"m");
			  return true;
		  }
		  return false; // not used in lwir
	  }

	  static double parseDegreeMinSec(String s) {
		  int indx_degree = s.indexOf("\u00B0");
		  int indx_minute  = s.indexOf("\u2032");
		  if (indx_minute < 0) {
			  indx_minute  = s.indexOf("'");
		  }
		  int indx_second  = s.indexOf("\u2033");
		  if (indx_second < 0) {
			  indx_second  = s.indexOf("\"");
		  }
		  double degrees =  Double.parseDouble(s.substring(0,indx_degree));
		  double min_sec = Double.parseDouble(s.substring(indx_degree+1, indx_minute))/60.0 +
				  Double.parseDouble(s.substring(indx_minute+1, indx_second))/3600.0;
		  if (degrees < 0) {
			  return degrees - min_sec;
		  } else {
			  return degrees + min_sec;
		  }
	  }
	  static double parseStringToEndString(String s, String end_string) {
		  int indx_end = s.indexOf(end_string);
		  return Double.parseDouble(s.substring(0, indx_end));
	  }
	  
	  public boolean writeKml( // USED in lwir OLD
			  String image_name,
			  int debugLevel )
	  {
		  if (image_name == null) {
			  image_name = this.image_name;
		  }
		  String [] sourceFiles_main=correctionsParameters.getSourcePaths();
		  SetChannels [] set_channels = setChannels(image_name,debugLevel); // only for specified image timestamp
		  int kml_sensors = correctionsParameters.kml_sensors;	

		  ArrayList<String> path_list = new ArrayList<String>();
		  for (int i = 0; i < set_channels.length; i++) {
			  for (int nchn = 0; nchn < set_channels[i].file_number.length; nchn++) {
				  if ((kml_sensors & (1 << nchn)) != 0) {
					  path_list.add(sourceFiles_main[set_channels[i].file_number[nchn]]);
				  }
			  }
		  }
		  for (String fname:path_list) {
			  if (debugLevel > -1) System.out.println("writeKml(): "+fname);
			  if (setGpsLla(fname)) {
				  break;
			  }
		  }
		  if (gps_lla != null) {
			  String kml_copy_dir= correctionsParameters.selectX3dDirectory(
					  image_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
					  null,
					  true,  // smart,
					  true);  //newAllowed, // save
			  double ts = Double.parseDouble(image_name.replace('_', '.'));
			  X3dOutput.generateKML(
					  kml_copy_dir+ Prefs.getFileSeparator()+image_name+".kml", // String path,
					  false, // boolean overwrite,
					  "", // String icon_path, //<href>x3d/1487451413_967079.x3d</href> ?
					  ts, // double timestamp,
					  gps_lla); // double [] lla)
		  } else {
			  if (debugLevel > -1) {
				  System.out.println("GPS data not available, skipping KML file generation (TODO: maybe make some default LLA?)");
			  }
		  }
		  return true;
	  }

	  public boolean writeKml( // USED in lwir
			  int debugLevel )
	  {
		  String image_name = getImageName();
		  String [] sourceFiles_main=correctionsParameters.getSourcePaths();
		  SetChannels [] set_channels = setChannels(image_name,debugLevel); // only for specified image timestamp
		  int kml_sensors = correctionsParameters.kml_sensors;	

		  ArrayList<String> path_list = new ArrayList<String>();
		  for (int i = 0; i < set_channels.length; i++) {
			  for (int nchn = 0; nchn < set_channels[i].file_number.length; nchn++) {
				  if ((kml_sensors & (1 << nchn)) != 0) {
					  path_list.add(sourceFiles_main[set_channels[i].file_number[nchn]]);
				  }
			  }
		  }
		  for (String fname:path_list) {
			  if (debugLevel > -1) System.out.println("writeKml(): "+fname);
			  if (setGpsLla(fname)) {
				  break;
			  }
		  }
		  if (gps_lla != null) {
			  String kml_copy_dir= correctionsParameters.selectX3dDirectory(
					  image_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
					  null,
					  true,  // smart,
					  true);  //newAllowed, // save
			  double ts = Double.parseDouble(image_name.replace('_', '.'));
			  X3dOutput.generateKML(
					  kml_copy_dir+ Prefs.getFileSeparator()+image_name+".kml", // String path,
					  false, // boolean overwrite,
					  "", // String icon_path, //<href>x3d/1487451413_967079.x3d</href> ?
					  ts, // double timestamp,
					  gps_lla); // double [] lla)
		  } else {
			  if (debugLevel > -1) {
				  System.out.println("GPS data not available, skipping KML file generation (TODO: maybe make some default LLA?)");
			  }
		  }
		  return true;
	  }
	  
	  
	  
	  public boolean createThumbNailImage( // USED in lwir
			  ImagePlus imp,
			  String dir,
			  String name,
			  int debugLevel)
	  {
		  String thumb_path = dir +  Prefs.getFileSeparator() + name+".jpeg";
		  if (new File(thumb_path).exists() && !correctionsParameters.thumb_overwrite) {
			  System.out.println("file "+thumb_path+" exists, skipping thumbnail generation");
			  return false;
		  }

		  int image_width = imp.getWidth();
		  int image_height = imp.getHeight();
		  ImageProcessor ip = imp.getProcessor().duplicate();
		  if ((image_width >=  correctionsParameters.thumb_width) &&
				  (image_height >=  correctionsParameters.thumb_height)) {
			  double scale_h = 1.0 * (correctionsParameters.thumb_width + 1)/image_width;
			  double scale_v = 1.0 * (correctionsParameters.thumb_height + 1)/image_height;
			  double scale = ((scale_h > scale_v) ? scale_h : scale_v) / correctionsParameters.thumb_size;


			  ip.setInterpolationMethod(ImageProcessor.BICUBIC);
			  if (!isMonochrome()) {
				  ip.blurGaussian(2.0);
			  }
			  ip.scale(scale, scale);
			  int lm = (int) Math.round (((image_width*scale)-correctionsParameters.thumb_width)* correctionsParameters.thumb_h_center + (0.5*image_width*(1.0-scale)));
			  int tm = (int) Math.round (((image_height*scale)-correctionsParameters.thumb_height)* correctionsParameters.thumb_v_center + (0.5*image_height*(1.0-scale)));
			  Rectangle r = new Rectangle(lm,tm,correctionsParameters.thumb_width,correctionsParameters.thumb_height);
			  ip.setRoi(r);
			  ip = ip.crop();
		  } else {

		  }
		  ImagePlus ip_thumb = new ImagePlus(name,ip);
		  EyesisCorrections.saveAndShow(
				  ip_thumb,
				  dir,
				  false,
				  false,
				  correctionsParameters.JPEG_quality, // jpegQuality); // jpegQuality){//  <0 - keep current, 0 - force Tiff, >0 use for JPEG
				  (debugLevel > -2) ? debugLevel : 1); // int debugLevel (print what it saves)

		  return true;
	  }

	  public boolean writePreview(
			  double [] data,
			  int debugLevel
			  )
	  {
		  int width = getTileProcessor().getTilesX() *getTileProcessor().getTileSize(); 
		  int height = data.length/width;
		  String set_name = getImageName();
		  if (set_name == null ) {
			  SetChannels [] set_channels = setChannels(debugLevel);
			  set_name = set_channels[0].set_name;
		  }
		  String model_dir= correctionsParameters.selectX3dDirectory(
				  set_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
				  null,
				  true,  // smart,
				  true);  //newAllowed, // save\
		  String title = getImageName()+"-preview";
		  ImagePlus imp= ShowDoubleFloatArrays.makeArrays(data, width, height, title);
		  String preview_path = model_dir +  Prefs.getFileSeparator() + title+".jpeg";
		  if (new File(preview_path).exists() && !correctionsParameters.thumb_overwrite) {
			  System.out.println("file "+preview_path+" exists, skipping preview generation");
			  return false;
		  }
		  if (debugLevel > -2) {
			  System.out.println("Saving preview image to "+preview_path);
		  }
		  EyesisCorrections.saveAndShow(
				  imp,
				  model_dir,
				  false,
				  false,
				  correctionsParameters.JPEG_quality, // jpegQuality); // jpegQuality){//  <0 - keep current, 0 - force Tiff, >0 use for JPEG
				  (debugLevel > -2) ? debugLevel : 1); // int debugLevel (print what it saves)
		  return true;
	  }
	  public boolean writeLwirPreview(
			  final CLTParameters  clt_parameters,
			  double []            data,
			  double []            minmax, // null for auto
			  QuadCLT              scene,
			  int                  tex_palette,
			  String               suffix,
			  int                  debugLevel) {
		  return writeLwirPreview(
				  clt_parameters,
				  data,
				  getTileProcessor().getTilesX() *getTileProcessor().getTileSize(),
				  minmax, // 			  double []            minmax, // null for auto
				  scene,
				  tex_palette,
				  suffix,
				  debugLevel);
	  }
	  
	  public boolean writeLwirPreview(
			  final CLTParameters  clt_parameters,
			  double []            data,
			  int                  width,
			  double []            minmax, // null for auto
			  QuadCLT              scene,
			  int                  tex_palette,
			  String               suffix,
			  int                  debugLevel) {
		  if (scene == null) {
			  scene = (QuadCLT) this;
		  }
		  double [][] rendered_texture = new double[][] {data, new double[data.length]};
		  for (int i = 0; i < rendered_texture[0].length; i++) {
			  rendered_texture[1][i] = Double.isNaN(rendered_texture[0][i])? 0.0: 1.0;
		  }
		  if (!suffix.equals("")) {
			  if (minmax == null) { // to replace standard preview file name
				  minmax = scene.getColdHot(); // used in linearStackToColor (from this current scene)
				  suffix +="-AUTORANGE";
			  } else {
				  suffix +="-RANGE"+(minmax[1]-minmax[0]);
			  }
		  }
		  int height = data.length/width;
		  String set_name = getImageName();
		  if (set_name == null ) {
			  SetChannels [] set_channels = setChannels(debugLevel);
			  set_name = set_channels[0].set_name;
		  }
		  String model_dir= correctionsParameters.selectX3dDirectory(
				  set_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
				  null,
				  true,  // smart,
				  true);  //newAllowed, // save\
		  String title = getImageName()+suffix+"-preview";
		  ImagePlus imp =  	  QuadCLTCPU.linearStackToColorLWIR(
					clt_parameters, // CLTParameters  clt_parameters,
					tex_palette, // int            lwir_palette, // <0 - do not convert
					minmax, // double []      minmax,
					title, // String         name,
					"", // String         suffix, // such as disparity=...
					true, // boolean        toRGB,
					rendered_texture, // faded_textures[nslice], // double [][]    texture_data,
					width, // int            width, // int tilesX,
					height, // int            height, // int tilesY,
					debugLevel); // int            debugLevel )
		  int jpeg_q = correctionsParameters.JPEG_quality;
		  String preview_path = model_dir +  Prefs.getFileSeparator() + title+((jpeg_q==0)? ".tiff":".jpeg");
		  if (new File(preview_path).exists() && !correctionsParameters.thumb_overwrite) {
			  System.out.println("file "+preview_path+" exists, skipping preview generation");
			  return false;
		  }
		  if (debugLevel > -2) {
			  System.out.println("Saving preview image to "+preview_path);
		  }
		  EyesisCorrections.saveAndShow(
				  imp,
				  model_dir,
				  false,
				  false,
				  jpeg_q, // jpegQuality); // jpegQuality){//  <0 - keep current, 0 - force Tiff, >0 use for JPEG
				  (debugLevel > -2) ? debugLevel : 1); // int debugLevel (print what it saves)
		  return true;
	  }
	 
	  public boolean writeLwirGeoTiffARGB(
			  final CLTParameters  clt_parameters,
			  double []            data,
			  double []            lla,  // latitude, longitude, altitude (or null)
			  double []            xy_center, // X,Y of top left to vertical (negative meters)
			  LocalDateTime        dt,   // local date/time or null
			  double               pix_in_meters,
			  int                  width,
			  double []            minmax, // null for auto
			  QuadCLT              scene,
			  int                  tex_palette,
			  String               suffix, // include "-geo"
			  int                  debugLevel) {
		  if (scene == null) {
			  scene = (QuadCLT) this;
		  }
		  double [][] rendered_texture = new double[][] {data, new double[data.length]};
		  for (int i = 0; i < rendered_texture[0].length; i++) {
			  rendered_texture[1][i] = Double.isNaN(rendered_texture[0][i])? 0.0: 1.0;
		  }
		  if (!suffix.equals("")) {
			  if (minmax == null) { // to replace standard preview file name
				  minmax = scene.getColdHot(); // used in linearStackToColor (from this current scene)
				  suffix +="-AUTORANGE";
			  } else {
				  suffix +="-RANGE"+(minmax[1]-minmax[0]);
			  }
		  }
		  int height = data.length/width;
		  String set_name = getImageName();
		  if (set_name == null ) {
			  SetChannels [] set_channels = setChannels(debugLevel);
			  set_name = set_channels[0].set_name;
		  }
		  String model_dir= correctionsParameters.selectX3dDirectory(
				  set_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
				  null,
				  true,  // smart,
				  true);  //newAllowed, // save\
		  String title = getImageName()+suffix;
		  ImagePlus imp =  	  QuadCLTCPU.linearStackToColorLWIR(
				  clt_parameters, // CLTParameters  clt_parameters,
				  tex_palette, // int            lwir_palette, // <0 - do not convert
				  minmax, // double []      minmax,
				  title, // String         name,
				  "", // String         suffix, // such as disparity=...
				  true, // boolean        toRGB,
				  rendered_texture, // faded_textures[nslice], // double [][]    texture_data,
				  width, // int            width, // int tilesX,
				  height, // int            height, // int tilesY,
				  debugLevel); // int            debugLevel )
		  String image_path = model_dir +  Prefs.getFileSeparator() + title+".tiff";
		  ElphelTiffWriter.saveTiffARGBOrGray32(
				  imp,
				  image_path,
				  lla,           // double []     lla,  // latitude, longitude, altitude (or null)
				  xy_center,     //double []            xy_center, // X,Y of top left to vertical (negative meters)
				  dt,            // LocalDateTime dt,   // local date/time or null
				  pix_in_meters, //double        pix_in_meters, // resolution or Double.NaN
				  false,         // imageJTags,
				  debugLevel);
		  return true;
	  }
	  
	  // Currently only a single-slice 32-bit file (with NaN for tranparency)
	  public boolean writeLwirGeoTiff32(
			  final CLTParameters  clt_parameters,
			  double []            data,
			  double []            lla,  // latitude, longitude, altitude (or null)
			  double []            xy_center, // X,Y of top left to vertical (negative meters)
			  LocalDateTime        dt,   // local date/time or null
			  double               pix_in_meters,
			  int                  width,
			  QuadCLT              scene,
			  String               suffix, // include "-geo"
			  int                  debugLevel) {
		  if (scene == null) {
			  scene = (QuadCLT) this;
		  }
		  int height = data.length/width;
		  String set_name = getImageName();
		  if (set_name == null ) {
			  SetChannels [] set_channels = setChannels(debugLevel);
			  set_name = set_channels[0].set_name;
		  }
		  String model_dir= correctionsParameters.selectX3dDirectory(
				  set_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
				  null,
				  true,  // smart,
				  true);  //newAllowed, // save\
		  String title = getImageName()+suffix;

		  ImagePlus imp = ShowDoubleFloatArrays.makeArrays(
				  data, // double[] pixels,
				  width, // int width,
				  height, // int height,
				  title); // String title);
		  String image_path = model_dir +  Prefs.getFileSeparator() + title+".tiff";
		  ElphelTiffWriter.saveTiffARGBOrGray32(
				  imp,
				  image_path,
				  lla,           // double []     lla,  // latitude, longitude, altitude (or null)
				  xy_center,     //double []            xy_center, // X,Y of top left to vertical (negative meters)
				  dt,            // LocalDateTime dt,   // local date/time or null
				  pix_in_meters, //double        pix_in_meters, // resolution or Double.NaN
				  false,         // imageJTags,
				  debugLevel);
		  return true;
	  }
	  
	  
	  
	  
	/** broke  
	  public boolean writeRatingFile( // USED in lwir
			  int debugLevel
			  )
	  {
		  String set_name = image_name;
		  if (set_name == null ) {
			  SetChannels [] set_channels = setChannels(debugLevel);
			  set_name = set_channels[0].set_name;
		  }

		  String model_dir= correctionsParameters.selectX3dDirectory(
				  set_name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
				  null,
				  true,  // smart,
				  true);  //newAllowed, // save
		  String fname = model_dir+ Prefs.getFileSeparator()+"rating.txt";
		  File rating_file = new File(fname);
		  if (rating_file.exists()) {
			  if (debugLevel > -2){
				  System.out.println("file "+rating_file.getPath()+" exists, skipping overwrite");
			  }
			  return false;
		  }
		  List<String> lines = Arrays.asList(correctionsParameters.default_rating+"");
		  Path path = Paths.get(fname);
		  try {
			  Files.write(path,lines); // , Charset.forName("UTF-8"));
		  } catch (IOException e1) {
			  // TODO Auto-generated catch block
			  e1.printStackTrace();
			  return false;
		  }
		  try {
			  Path fpath = Paths.get(rating_file.getCanonicalPath());
			  Set<PosixFilePermission> perms =  Files.getPosixFilePermissions(fpath);
			  perms.add(PosixFilePermission.OTHERS_WRITE);
			  Files.setPosixFilePermissions(fpath, perms);
		  } catch (IOException e) {
			  // TODO Auto-generated catch block
			  e.printStackTrace();
			  return false;
		  }
		  return true;
	  }

	  */
	  public void setPassAvgRBGA( // get image from a single pass, return relative path for x3d // USED in lwir
			  CLTParameters           clt_parameters,
			  int        scanIndex,
			  int        threadsMax,  // maximal number of threads to launch
			  boolean    updateStatus,
			  int        debugLevel)
	  {
		  CLTPass3d scan = tp.clt_3d_passes.get(scanIndex);
		  setPassAvgRBGA( // get image from a single pass, return relative path for x3d // USED in lwir
				  clt_parameters,
				  scan,
				  threadsMax,  // maximal number of threads to launch
				  updateStatus,
				  debugLevel);
	  }
	  
	  
	  public void setPassAvgRBGA( // get image from a single pass, return relative path for x3d // USED in lwir
			  CLTParameters           clt_parameters,
			  CLTPass3d  scan,			  
			  int        threadsMax,  // maximal number of threads to launch
			  boolean    updateStatus,
			  int        debugLevel)
	  {
		  final int tilesX = scan.getTileProcessor().getTilesX();
		  final int tilesY = scan.getTileProcessor().getTilesY();
		  
		  double [][][][] texture_tiles = scan.texture_tiles; 
		  if (texture_tiles == null) { // measure textures in CPU mode?
			  CLTMeasureTextures( // perform single pass according to prepared tiles operations and disparity // not used in lwir
					  clt_parameters,   // final CLTParameters           clt_parameters,
					  scan,             // final CLTPass3d   scan,
					  threadsMax,
					  updateStatus,
					  debugLevel);
			  
//			  return;
		  }
		  int num_layers = 0;
		  for (int ty = 0; ty < tilesY; ty++){
			  if (texture_tiles[ty] != null){
				  for (int tx = 0; tx < tilesX; tx++){
					  if (texture_tiles[ty][tx] != null){
						  num_layers = texture_tiles[ty][tx].length;
						  break;
					  }
				  }
				  if (num_layers > 0) break;
			  }
			  if (num_layers > 0) break;
		  }
		  int numTiles = tilesX * tilesY;
		  int num_sensors = getNumSensors();
		  double [] scales = new double [num_layers];
		  for (int n = 0; n < num_layers; n++){
			  if       (n < 3)                  scales[n] = 1.0/255.0; // R,B,G
			  else if  (n == 3)                 scales[n] = 1.0; //alpha
			  else if  (n < (num_sensors + 4))  scales[n] = 1.0; // ports 0..3
			  else                              scales[n] = 1.0/255.0; // RBG rms, in 1/255 units, but small
		  }
		  double [][] tileTones = new double [num_layers][numTiles];
		  for (int ty = 0; ty < tilesY; ty++ ) if (texture_tiles[ty] != null){
			  for (int tx = 0; tx < tilesX; tx++ ) if (texture_tiles[ty][tx] != null) {
				  int indx = ty * tilesX + tx;
				  for (int n = 0; n < num_layers; n++) if (texture_tiles[ty][tx][n] != null){
					  double s = 0.0;
					  for (int i = 0; i < texture_tiles[ty][tx][n].length; i++){
						  s += texture_tiles[ty][tx][n][i];
					  }
					  s /= (texture_tiles[ty][tx][n].length/4); // overlapping tiles
					  s *= scales[n];
					  tileTones[n][indx] = s;
				  }
			  }
		  }
		  scan.setTilesRBGA(tileTones); // Alpha is very low, ~1/400
		  if (debugLevel>10) {
			  ShowDoubleFloatArrays.showArrays(
					  tileTones,
					  tilesX,
					  tilesY,
					  true,
					  "tileTones");
		  }
		  
		  
	  }

	  public void gpuResetCorrVector() {
//		  if (getGPU() != null) getGPU().resetGeometryCorrectionVector();
		  // do nothing here, GPU version overwrites this
	  }
	  public void gpuResetGeometryCorrection() {
//		  if (getGPU() != null) getGPU().resetGeometryCorrection();
		  // do nothing here, GPU version overwrites this
	  }

	  
	  // apply delta to each parameter, perform LY measurement and calculate difference
	  public void debugLYDerivatives(
			  ExtrinsicAdjustment     ea,
			  int                     scanIndex,
			  CLTParameters           clt_parameters,
			  boolean                 update_disparity, // re-measure disparity before measuring LY
			  final int               threadsMax,  // maximal number of threads to launch
			  final boolean           updateStatus,
			  double                  delta,
			  boolean                 use_tarz,  // derivatives by tarz, notg symmetrical vectors
			  final int               debugLevel)
	  {   
//		  delta = 0.001;
		  /*double [] parameter_scales4 = { // multiply delay for each parameter
				  0.3,  // 0.014793657667505566, // 00 10 tilt0
				  0.3,  // 0.015484017460841183, // 01 10 tilt1
				  0.3,  // 0.02546712771769517,  // 02 10 tilt2
				  
				  0.3,  // 0.02071573747995167,  // 03 10 az0
				  0.3,  // 0.026584237444512468, // 04 10 az1
				  0.3,  // 0.014168012698804967, // 05 10 az2
				  
				  2.0,  // 1.8554483718240792E-4,// 06 roll0
				  0.3, //2.3170738149889717E-4,  // 07 roll1
				  0.3, //3.713239026512266E-4,   // 08 roll2
				  0.3, //2.544834643007531E-4,   // 09 roll3
				  0.3, // 2.5535557646736286E-4, // 10 zoom0 
				  0.3, // 1.98531249109261E-4,   // 11 zoom1
				  0.3, // 2.1802727086879284E-4, // 12 zoom2
				  
				  150, // 8.814346720176489E-1,  // 5,  // 13 10000x omega-tilt
				  150, // 7.071297501674136E-1,  // 5,  // 14 10000x omega az
				  150, // 1.306306793587865E-0,  // 4,  // 15 10000x omega roll
				  300, // 2.8929916645453735E-0, // 4,  // 16 10000x vx
				  300, // 2.943408022525927E-0,  // 4,  // 17 10000x vy
				  500.0}; // 390.6185365641268};    //4};  // 18 100000x vz
				  */
		  double scale_tl = 0.3;
		  double scale_az = 0.3;
		  double scale_rl0 = 2.0;
		  double scale_rl = 0.3;
		  double scale_zoom = 0.3;
		  double [] scales_imu = {
				  150, // 8.814346720176489E-1,  // 5,  // 13 10000x omega-tilt
				  150, // 7.071297501674136E-1,  // 5,  // 14 10000x omega az
				  150, // 1.306306793587865E-0,  // 4,  // 15 10000x omega roll
				  300, // 2.8929916645453735E-0, // 4,  // 16 10000x vx
				  300, // 2.943408022525927E-0,  // 4,  // 17 10000x vy
				  500.0}; // 390.6185365641268};    //4};  // 18 100000x vz
//		  delta = 0.001; // should be 0.001
		  boolean debug_img = false;
		  int debugLevelInner = -5;
		  CLTPass3d scan = tp.clt_3d_passes.get(scanIndex);
		  CorrVector corr_vector = geometryCorrection.getCorrVector().clone();
//		  String [] corr_names = corr_vector.getCorrNames();
		  int num_sensors=getNumSensors();
		  double [] parameter_scales = new double [corr_vector.getLength()];	
		  for (int i = 0; i < num_sensors; i++) {
			  parameter_scales    [corr_vector.getRollIndex()+   i] = ((i > 0) || use_tarz)? scale_rl : scale_rl0;
			  if (i < num_sensors - 1) {
				  parameter_scales[corr_vector.getTiltIndex()+   i]=scale_tl;
				  parameter_scales[corr_vector.getAzimuthIndex()+i]=scale_az;
				  parameter_scales[corr_vector.getZoomIndex()+   i]=scale_zoom;
			  }
		  }
		  for (int i = 0; i < scales_imu.length; i++) {
			  parameter_scales[corr_vector.getIMUIndex()+   i] = scales_imu[i];
		  }		  
		  double [] curr_corr_arr = corr_vector.toArray();
		  int clusters = ea.clustersX * ea.clustersY;
		  int num_ly = ExtrinsicAdjustment.get_INDX_LENGTH(getNumSensors()); // scan.getLazyEyeData().length;
		  int num_pars = curr_corr_arr.length;
		  double [][][] ly_diff = new double [num_pars][num_ly][clusters];
		  for (int np = 0; np < num_pars; np++) {
			  for (int nl = 0; nl < num_ly; nl++) {
				  for (int cluster = 0; cluster < clusters; cluster++) {
					  ly_diff[np][nl][cluster] = Double.NaN;
				  }
			  }
		  }
		  // save initial ly data
		  double [][] ly_initial = new double [clusters][];
		  double [][] ly = scan.getLazyEyeData();
		  for (int cluster = 0; cluster < clusters; cluster++) if (ly[cluster]!=null){
			  ly_initial[cluster] = ly[cluster].clone();
		  }
		  System.out.println(geometryCorrection.getCorrVector().toString());
		  if (debug_img) {
			  ea.showInput(
					  ly_initial, // double[][] data,
					  "drv_reference");// String title);
		  }
		  
//		  String [] titles = corr_vector.getCorrNames(); // new String [num_pars]; //ea.getSymNames(); // why "S" here, while it is tarz???
		  // geometryCorrection.getCorrVector(par_inc,null) converts sym -> tarz
		  String [] titles;
		  if (use_tarz) {
			  titles = corr_vector.getCorrNames();
		  } else {
			  titles = new String [num_pars]; //ea.getSymNames(); // why "S" here, while it is tarz???
			  for (int i = 0; i < num_pars; i++) {
				  titles[i] = "S"+i;
			  }
		  }
		  System.out.println("Initial:\n"+corr_vector.toString(true));  // true - short out
		  double min_strength = 0.1; // 0.23		  
		  int [] pfmt = {8,3};
		  if (debugLevel > -3) {
			  System.out.println(ea.stringWeightedLY(
					  scan.getLazyEyeData(), // double [][] data,
					  null,                  // double [][] ref_data,
					  min_strength,          // double min_strength,
					  pfmt,                  // int [] format,
	                  "_00"));               // String suffix))
		  }

		  for (int npar = 0; npar < num_pars; npar++) {
			  // perform asymmetric delta
			  double [] par_inc = new double [num_pars];
			  par_inc[npar] = delta * parameter_scales[npar];
			  CorrVector corr_delta;			  
			  if (use_tarz) {
				  corr_delta = new CorrVector (geometryCorrection,par_inc);
			  } else {
				  corr_delta = geometryCorrection.getCorrVector(par_inc,null); // , par_mask); all parameters			  
			  }
			  CorrVector corr_vectorp = corr_vector.clone();
			  corr_vectorp.incrementVector(corr_delta,  1.0); // 0.5 for p/m
			  geometryCorrection.setCorrVector(corr_vectorp) ;
			  double rdelta = 1.0/ par_inc[npar];
			  System.out.println(npar+": "+ titles[npar]+", scale="+rdelta); // +"\n"+(geometryCorrection.getCorrVector().toString()));
			  System.out.println("delta:\n"+corr_delta.toString(true));  // true - short out
			  System.out.println("vector:\n"+corr_vectorp.toString(true));  // true - short out
			  gpuResetCorrVector();
			  if (update_disparity) {
				  CLTMeasureCorr( // perform single pass according to prepared tiles operations and disparity
						  clt_parameters,
						  scanIndex,
						  false,             // final boolean     save_textures,
						  0,                 // final int         clust_radius,
						  tp.threadsMax,     // maximal number of threads to launch
						  false,             // updateStatus,
						  debugLevelInner -1); // - 1); // -5-1
			  }
			  CLTMeasureLY( // perform single pass according to prepared tiles operations and disparity // USED in lwir
					  clt_parameters,
					  scanIndex,     // final int           scanIndex,
					  // only combine and calculate once, next passes keep
					  // remeasure each pass - target disparity is the same, but vector changes
					  0, // bg_scan, // (num_iter >0)? -1: bg_scan,        // final int           bgIndex, // combine, if >=0
					  tp.threadsMax,  // maximal number of threads to launch
					  false, // updateStatus,
					  debugLevelInner -1); // - 1); // -5-1
			  ly = scan.getLazyEyeData();
			  if (debug_img) {
				  ea.showInput(
						  ly, // double[][] data,
						  "drv_par"+npar);// String title);
			  }
			  /* Tested - no difference
			  CLTMeasureLY( // perform single pass according to prepared tiles operations and disparity // USED in lwir
					  clt_parameters,
					  scanIndex,     // final int           scanIndex,
					  // only combine and calculate once, next passes keep
					  // remeasure each pass - target disparity is the same, but vector changes
					  0, // bg_scan, // (num_iter >0)? -1: bg_scan,        // final int           bgIndex, // combine, if >=0
					  tp.threadsMax,  // maximal number of threads to launch
					  false, // updateStatus,
					  debugLevelInner -1); // - 1); // -5-1
			  ly = scan.getLazyEyeData();
			  ea.showInput(
					  ly, // double[][] data,
					  "drv_par"+npar+"-B");// String title);
			*/
//			  double min_strength = 0.23;
//			  int [] pfmt = {8,3};
			  if (debugLevel > -3) {
				  System.out.println(ea.stringWeightedLY(
						  ly,                    // double [][] data,
						  null,                  // double [][] ref_data,
						  min_strength,          // double min_strength,
						  pfmt,                  // int [] format,
		                  "_"+titles[npar]));    // String suffix))
			  }

			  for (int cluster = 0; cluster < clusters; cluster++) if ((ly_initial[cluster] != null) && (ly[cluster]!=null)){
				  for (int nl = 0; nl < ly_initial[cluster].length; nl++) {
					  ly_diff[npar][nl][cluster] =  rdelta * (ly[cluster][nl] - ly_initial[cluster][nl]); 
				  }
			  }
			  if (debugLevel > -3) {
				  double [][] ly_diff1 = new double [ly_initial.length][];
				  for (int cluster = 0; cluster < clusters; cluster++) if ((ly_initial[cluster] != null) && (ly[cluster]!=null)){
					  ly_diff1[cluster] = new double [ly_initial[cluster].length];
					  for (int nl = 0; nl < ly_initial[cluster].length; nl++) {
						  ly_diff1[cluster][nl] =  rdelta * (ly[cluster][nl] - ly_initial[cluster][nl]); 
					  }
				  }
				  System.out.println(ea.stringWeightedLY(
						  ly_diff1,         // double [][] data,
						  ly_initial,            // double [][] ref_data,
						  min_strength,          // double min_strength,
						  pfmt,                  // int [] format,
		                  "_d"+titles[npar]));    // String suffix))
			  }
		  }
		  geometryCorrection.setCorrVector(corr_vector) ; // restore
		  gpuResetCorrVector();
		  /*
		  for (int npar = 0; npar < num_pars; npar++) {
			  ShowDoubleFloatArrays.showArrays(
					  ly_diff[npar],
					  ea.clustersX,
					  ea.clustersY,
					  true,
					  "dLY_dpar_"+npar ,
					  ExtrinsicAdjustment.DATA_TITLES);
		  }
		  */
		  int gap = 10;
//		  int width  = 3 * ea.clustersX + 2 * gap;
//		  int height = 3 * ea.clustersY + 2 * gap;
		  int rows = ea.getRowsCols()[0];
		  int cols = ea.getRowsCols()[1];
		  int width  = cols * (ea.clustersX + gap) - gap;
		  int height = rows * (ea.clustersY + gap) - gap;
		  double [][] dbg_img = new double [num_pars][width*height];
		  /*
		  for (int par = 0; par < num_pars; par++) {
			  for (int mode = 0; mode < ExtrinsicAdjustment.POINTS_SAMPLE; mode++) {
				  int x0 = (mode % 3) * (ea.clustersX + gap);
				  int y0 = (mode / 3) * (ea.clustersY + gap);
				  for (int cluster = 0; cluster < clusters;  cluster++) {
					  int x = x0 + (cluster % ea.clustersX);
					  int y = y0 + (cluster / ea.clustersX);
					  int pix = x + y * width;
					  int indx = (mode == 0) ? ExtrinsicAdjustment.INDX_DIFF : (ExtrinsicAdjustment.INDX_DD0 + mode - 1);
					  dbg_img[par][pix] = ly_diff[par][indx][cluster];
				  }
			  }
		  }
		  ShowDoubleFloatArrays.showArrays(
				  dbg_img,
				  width,
				  height,
				  true,
				  "dLY_dpar_"+delta+(update_disparity?"U":""),
				  titles);
		  */
		  dbg_img = new double [num_pars][width*height];
		  for (int par = 0; par < num_pars; par++) {
			  for (int mode = 0; mode < ExtrinsicAdjustment.get_POINTS_SAMPLE(getNumSensors()); mode++) {
				  int x0 = (mode % cols) * (ea.clustersX + gap);
				  int y0 = (mode / cols) * (ea.clustersY + gap);
				  for (int cluster = 0; cluster < clusters;  cluster++) {
					  int x = x0 + (cluster % ea.clustersX);
					  int y = y0 + (cluster / ea.clustersX);
					  int pix = x + y * width;
					  int indx = (mode == 0) ? ExtrinsicAdjustment.INDX_DIFF : (ExtrinsicAdjustment.get_INDX_DD0(getNumSensors()) + mode - 1);
					  if (mode == 0) {
						  dbg_img[par][pix] = -ly_diff[par][indx][cluster];
					  } else {
						  dbg_img[par][pix] = ly_diff[par][indx][cluster];
					  }
				  }
			  }
		  }
		  ShowDoubleFloatArrays.showArrays(
				  dbg_img,
				  width,
				  height,
				  true,
				  "dLY_dpar_"+delta+"DINV"+(update_disparity?"U":""),
				  titles);

		  dbg_img = new double [num_pars][width*height];
		  for (int par = 0; par < num_pars; par++) {
			  for (int mode = 0; mode < ExtrinsicAdjustment.get_POINTS_SAMPLE(getNumSensors()); mode++) {
				  int x0 = (mode % cols) * (ea.clustersX + gap);
				  int y0 = (mode / cols) * (ea.clustersY + gap);
				  for (int cluster = 0; cluster < clusters;  cluster++) {
					  int x = x0 + (cluster % ea.clustersX);
					  int y = y0 + (cluster / ea.clustersX);
					  int pix = x + y * width;
					  int indx = (mode == 0) ? ExtrinsicAdjustment.INDX_DIFF : (ExtrinsicAdjustment.INDX_X0 + mode - 1);
					  if (mode == 0) {
						  dbg_img[par][pix] = -ly_diff[par][indx][cluster];
					  } else {
						  dbg_img[par][pix] = ly_diff[par][indx][cluster];
					  }
				  }
			  }
		  }
		  ShowDoubleFloatArrays.showArrays(
				  dbg_img,
				  width,
				  height,
				  true,
				  "dLY_dpar_"+delta+"DINV"+(update_disparity?"U":"")+"-XY",
				  titles);
		  
		  return;
	  }

	  public void testAltCorr (
			  CLTParameters             clt_parameters,
			  double [][] dsi
			  ) {
		  boolean       need_diffs = false;
		  double [][] src_data = {
				  this.dsi[is_aux?TwoQuadCLT.DSI_DISPARITY_AUX:TwoQuadCLT.DSI_DISPARITY_MAIN],
				  this.dsi[is_aux?TwoQuadCLT.DSI_STRENGTH_AUX:TwoQuadCLT.DSI_STRENGTH_MAIN],
				  this.dsi[is_aux?TwoQuadCLT.DSI_DISPARITY_AUX_LMA:TwoQuadCLT.DSI_DISPARITY_MAIN_LMA],
		  };

		  CLTPass3d pass = new CLTPass3d (this.tp, 0);
		  boolean [] selection = new boolean [src_data[0].length];
		  boolean [] selection_all = new boolean [src_data[0].length];
		  for (int i = 0; i < selection.length; i++) {
			  selection[i] =     (src_data[1][i] > 0) && (Double.isNaN(src_data[2][i])); // that do not have LMA
			  selection_all[i] = (src_data[1][i] > 0);

		  }
		  for (int clust_radius = 00; clust_radius < 5; clust_radius++) {
			  pass.setTileOpDisparity(
					  ((clust_radius == 0)? selection_all: selection), // boolean [] selection,
					  src_data[0] ); // double []  disparity)
			  CLTMeas( // perform single pass according to prepared tiles operations and disparity // USED in lwir
					  clt_parameters, // final CLTParameters clt_parameters,
					  pass,           // final CLTPass3d     scan,
					  false,          // final boolean       save_textures0,
					  need_diffs,     // final boolean       need_diffs,     // calculate diffs even if textures are not needed 
					  clust_radius,   // final int           clust_radius,
					  true,           // final boolean       save_corr,
					  true,           // final boolean       run_lma, // =    true;
					  0.0,            // final double        max_chn_diff, // filter correlation results by maximum difference between channels
					  -1.0,           // final double        mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
					  100,            // final int           threadsMax,  // maximal number of threads to launch
					  true,           // final boolean       updateStatus,
					  0);             // final int           debugLevel) 
			  tp.showScan(
					  pass, // CLTPass3d   scan,
					  getImageName()+"-MAP-FZ"+(clt_parameters.getGpuFatZero(isMonochrome()))+"-CLUST"+clust_radius);
			  tp.showLmaCmStrength(
					  pass, // CLTPass3d   scan,
					  64, // 	int         bins,
					  "Strength_lma_vs_cm_cr"+clust_radius+"-FZ"+(clt_parameters.getGpuFatZero(isMonochrome()))); // String      title)
			  if (clust_radius == 0) {
				  tp.adjustLmaStrength (
						  clt_parameters.img_dtt, // ImageDttParameters  imgdtt_params,
						  pass, // CLTPass3d           scan,
						  1); // int                 debugLevel)
			  }
		  }
		  return;
	  }
	
}
