/**
 **
 ** Interscene - Process scene sequences
 **
 ** Copyright (C) 2023 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  Interscene.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/>.
 ** -----------------------------------------------------------------------------**
 **
 */
package com.elphel.imagej.tileprocessor;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
import org.apache.commons.math3.geometry.euclidean.threed.RotationOrder;

import com.elphel.imagej.calibration.CalibrationFileManagement;
import com.elphel.imagej.cameras.CLTParameters;
import com.elphel.imagej.cameras.ColorProcParameters;
import com.elphel.imagej.cameras.EyesisCorrectionParameters;
import com.elphel.imagej.common.DoubleGaussianBlur;
import com.elphel.imagej.common.ShowDoubleFloatArrays;
import com.elphel.imagej.gpu.GpuQuad;
import com.elphel.imagej.gpu.TpTask;
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.Imx5;

import ij.ImagePlus;
import ij.Prefs;
import ij.text.TextWindow;

public class Interscene {
	public final OpticalFlow opticalFlow;
	// interscene adjustments failure reasons.
	public static final int FAIL_REASON_LMA =            1; // LMA failed
	public static final int FAIL_REASON_INTERSCENE =     2; // clt_process_tl_interscene() returned null
	public static final int FAIL_REASON_MIN =            3; // average pixel offset is below specified threshold (FPN)
	public static final int FAIL_REASON_MAX =            4; // average pixel offset is above specified threshold (overlap)
	public static final int FAIL_REASON_NULL =           5; // null offsets array
	public static final int FAIL_REASON_EMPTY =          6; // No offset pairs in offsets array
	public static final int FAIL_REASON_ROLL =           7; // Too high roll between the images
	public static final int FAIL_REASON_ZOOM =           8; // Too high zoom ratio between the images
	
	public static String getFailReason(int fr) {
		switch (fr) {
		case FAIL_REASON_LMA:        return "FAIL_REASON_LMA";
		case FAIL_REASON_INTERSCENE: return "FAIL_REASON_INTERSCENE";
		case FAIL_REASON_MIN:        return "FAIL_REASON_MIN";
		case FAIL_REASON_MAX:        return "FAIL_REASON_MAX";
		case FAIL_REASON_NULL:       return "FAIL_REASON_NULL";
		case FAIL_REASON_EMPTY:      return "FAIL_REASON_EMPTY";
		case FAIL_REASON_ROLL:       return "FAIL_REASON_ROLL";
		case FAIL_REASON_ZOOM:       return "FAIL_REASON_ZOOM";
		default:
			return "unknown scene-matching failure reason="+fr;
		}
	}
	
	public static final double [] ZERO3 = {0.0,0.0,0.0};
//	public static double  LINE_ERR = 0.1;
	public static int     THREADS_MAX =          100;  // maximal number of threads to launch
	
	public Interscene (OpticalFlow opticalFlow) {
		this.opticalFlow = opticalFlow;
	}
/*
 * Should be done before calling getSceneRange():
		for (int scene_index =  ref_index - 1; scene_index >= 0 ; scene_index--) {
			// to include ref scene photometric calibration
			quadCLTs[scene_index] = quadCLTs[ref_index].spawnNoModelQuadCLT(
					set_channels[scene_index].set_name,
					clt_parameters,
					colorProcParameters, //
					threadsMax,
					debugLevel-2);
		} // split cycles to remove output clutter

 */
	/**
	 * Calculate subrange and a center index, starting from the end of the input range
	 * (former reference). In addition to the IMS data this estimation requires
	 * knowledge of the ground level. Initial estimate is provided as input,
	 * then, after knowing the center (reference) index, the DSI will be calculated
	 * and compared with the estimate. If it differs too much the range will be
	 * recalculated for the updated ground level. 
	 * @param clt_parameters  processing parameters
	 * @param scenes          scenes with IMS data
	 * @param ground_level    estimated ground level ASL
	 * @param src_range       a pair of {start, end} scene indices (0<=start,end<
	 * @param debugLevel      debug level
	 * @return  {start,end, reference} indices, end index is now the same as src_range[1]          
	 */
	public static int []  getSceneRange(
			final CLTParameters          clt_parameters,
			final QuadCLT[]              scenes, //
			final double                 ground_level,
			final int []                 src_range, // range of scenes to consider {start,end}
			final int                    debugLevel) {
		int tilesX =  scenes[src_range[1]].getTileProcessor().getTilesX();
        int tile_size = scenes[src_range[1]].getTileProcessor().getTileSize();
		double min_offset =         0.0; // clt_parameters.imp.min_offset;
		double max_offset =         clt_parameters.imp.max_rel_offset * tilesX * tile_size;
		double max_roll =           clt_parameters.imp.max_roll_deg*Math.PI/180.0;
		double max_zoom_diff =      clt_parameters.imp.max_zoom_diff;
		
		return null;
	}
	
	/**
	 * Calculate centered initial orientations, return center index
	 * and also set to config files
	 * @param clt_parameters
	 * @param min_num_scenes
	 * @param colorProcParameters
	 * @param debayerParameters
	 * @param rgbParameters
	 * @param quadCLT_main
	 * @param quadCLTs
	 * @param ref_index
	 * @param set_channels
	 * @param batch_mode
	 * @param earliest_scene
	 * @param start_ref_pointers
	 * @param threadsMax
	 * @param updateStatus
	 * @param debugLevel
	 * @return
	 */
	public static int setInitialOrientationsCenterIms(
			final CLTParameters                          clt_parameters,
			final boolean                                compensate_ims_rotation, // probably deprecated			
			final boolean                                inertial_only,			
			int                                          min_num_scenes,
			final ColorProcParameters                    colorProcParameters,
			EyesisCorrectionParameters.DebayerParameters debayerParameters,			
			EyesisCorrectionParameters.RGBParameters     rgbParameters,
			QuadCLT                                      quadCLT_main, // tiles should be set			
			final QuadCLT[]                              quadCLTs, //
			final int                                    ref_index,
			final QuadCLT.SetChannels []                 set_channels,
			final boolean                                batch_mode,
			int                                          earliest_scene,
			int []                                       start_ref_pointers, // [0] - earliest valid scene, [1] ref_index
			final int                                    threadsMax,  // int               threadsMax,
			final boolean                                updateStatus,
			final int                                    debugLevel) {
		boolean readjust = clt_parameters.imp.refine_invert; // false; //true; // re-run LMA for the later part		
		// go twice, then inverse 1-st part.
		// when going first - do not go beyond center (center by IMS coordinates) or just index?
		// Index is easier, no need to change
//		QuadCLT[]              quadCLTs_half = new QuadCLT[(quadCLTs.length+1)/2];
//		QuadCLT.SetChannels [] set_channels_half;
		// consider if it is using initial DSI from previous in overlap mode
    	boolean overlap_sequences =  clt_parameters.imp.overlap_sequences;
		int [] start_ref_pointers1 = new int[2];
		// try to read previously adjusted half-range
		ErsCorrection ers_reference = quadCLTs[ref_index].getErsCorrection();
		int cent_index =        earliest_scene + (ref_index - earliest_scene) / 2;
		int min_num_scenes_half =        min_num_scenes / 2;
		boolean reused_overlap = false;
		if (overlap_sequences) {
			int adjusted_scene_index =  ref_index - 1;
			for (; adjusted_scene_index >= 0 ; adjusted_scene_index--) {
				// to include ref scene photometric calibration
				quadCLTs[adjusted_scene_index] = quadCLTs[ref_index].spawnNoModelQuadCLT(
						set_channels[adjusted_scene_index].set_name,
						clt_parameters,
						colorProcParameters, //
						threadsMax,
						debugLevel-2);
				String ts = quadCLTs[adjusted_scene_index].getImageName();
				if ((ts == null) || (ers_reference.getSceneXYZATR(ts) == null)){
					break;
				}
				if (adjusted_scene_index < (min_num_scenes_half-1)) {
					if (debugLevel > -3) {
						System.out.println("Overlap mode, too few scenes left for the second half, shortening first to "+
								(ref_index - adjusted_scene_index - 1));
					}
					break;
				}
				if (adjusted_scene_index < cent_index) {
					if (debugLevel > -3) {
						System.out.println("Overlap mode, enough scenes for half of the remaining number"+
								(ref_index - adjusted_scene_index - 1));
					}
					break;
				}
				
			} // split cycles to remove output clutter
			adjusted_scene_index++;
			if ((ref_index - adjusted_scene_index) >= min_num_scenes_half) {
				if (adjusted_scene_index > cent_index) {
					if (debugLevel > -3) {
						System.out.println("Overlap mode, reusing previous scenes adjustment scenes, number of scenes = "+
								(ref_index - adjusted_scene_index));
					}
					cent_index = adjusted_scene_index;
				} else {
					if (debugLevel > -3) {
						System.out.println("Overlap mode, partially reusing previous scenes adjustment scenes, number of scenes = "+
								(ref_index - cent_index)+ " of "+(ref_index - adjusted_scene_index));
					}
				}
				reused_overlap = true;
			} else {
				if (debugLevel > -3) {
					System.out.println("Overlap mode, too few scenes to reuse: "+
							(ref_index - adjusted_scene_index)+" < "+ min_num_scenes_half);
				}
			}
		}
		if (!reused_overlap) {
			// TODO: add ref scene itself to the list?		
			cent_index = setInitialOrientationsIms(
					clt_parameters,      // final CLTParameters          clt_parameters,
					compensate_ims_rotation,    // final boolean         compensate_ims_rotation,
					inertial_only,       // final boolean                inertial_only,
					min_num_scenes_half,     // int                          min_num_scenes,
					colorProcParameters, // final ColorProcParameters    colorProcParameters,
					quadCLTs,            // final QuadCLT[]              quadCLTs, //
					ref_index,           // final int                    ref_index,
					set_channels,        // final QuadCLT.SetChannels [] set_channels,
					batch_mode,          // final boolean                batch_mode,
					cent_index,     // int                          earliest_scene,
					start_ref_pointers1, // int []                       start_ref_pointers, // [0] - earliest valid scene, [1] ref_index
					threadsMax,          // final int                    threadsMax,  // int               threadsMax,
					updateStatus,        // final boolean                updateStatus,
					debugLevel);         // final int                    debugLevel)
			if (cent_index < 0) {
				System.out.println("setInitialOrientationsIms() first half failed. Consider more graceful bail out.");
				start_ref_pointers[0] = start_ref_pointers1[0];
				return cent_index;
			}
		}
		boolean generate_egomotion =         clt_parameters.imp.generate_egomotion; // generate egomotion table (image-based and ims)
		if (generate_egomotion) {
			String  ego_path = quadCLTs[ref_index].getX3dDirectory()+Prefs.getFileSeparator()+
					quadCLTs[ref_index].getImageName()+
					"-ego-first_half.csv"; 
			String  ego_comment = null;
		    Interscene.generateEgomotionTable(
		    		clt_parameters, // CLTParameters  clt_parameters,
					quadCLTs, // QuadCLT []     quadCLTs,
					ref_index,//            ref_indx,
					cent_index, // earliest_scene, // int            earliest_scene,
					ego_path, // String         path,
					ego_comment, // String         comment);
					debugLevel); // int            debugLevel);

			if (debugLevel> -3) {
				System.out.println("Egomotion table saved to "+ego_path);
			}
		}				
		
		// create new dsi for quadCLTs[earliest_scene1]
		OpticalFlow.buildRefDSI( // returned is a different instance than input -FIXED
				clt_parameters,                   // CLTParameters                                 clt_parameters,
				false,                            // boolean  fast,
				true,                             // boolean                                       skip_photo,
				debayerParameters,                // EyesisCorrectionParameters.DebayerParameters  debayerParameters,
				colorProcParameters,              // ColorProcParameters                           colorProcParameters,
			    rgbParameters, // EyesisCorrectionParameters.RGBParameters      rgbParameters,
				batch_mode,                       // boolean                             batch_mode,
				set_channels[cent_index].set_name,// String                              set_name,
				quadCLT_main,                     // QuadCLT                             quadCLT_main, // tiles should be set
				quadCLTs[cent_index],             // QuadCLT                             quadCLT_ref, // tiles should be set
			    threadsMax,                       // final int                                     threadsMax,  // maximal number of threads to launch
			    updateStatus,                     // final boolean    updateStatus,
			    debugLevel);                      // final int        debugLevel);
// TODO: add {start,end} pointers to quadCLTs[cent_index]
// TODO: add pointer to center to ref_index
// check quadCLTs[cent_index].dsi here		
		quadCLTs[cent_index] = (QuadCLT) quadCLT_main.spawnQuadCLT( // restores dsi from "DSI-MAIN"
				set_channels[cent_index].set_name,
				clt_parameters,
				colorProcParameters, //
				threadsMax,
				debugLevel);
		quadCLTs[cent_index].setQuadClt(); // just in case ?
		// NOt needed?
//		quadCLTs[cent_index].setDSRBG(
//				clt_parameters, // CLTParameters  clt_parameters,
//				threadsMax,     // int            threadsMax,  // maximal number of threads to launch
//				updateStatus,   // boolean        updateStatus,
//				debugLevel);    // int            debugLevel)
		
		
		int [] start_ref_pointers2 = new int[2];
		int earliest_scene2 = setInitialOrientationsIms(
				clt_parameters,      // final CLTParameters          clt_parameters,
				inertial_only,       // final boolean                                inertial_only,
				compensate_ims_rotation, // final boolean                compensate_ims_rotation,
				min_num_scenes_half, // int                          min_num_scenes,
				colorProcParameters, // final ColorProcParameters    colorProcParameters,
				quadCLTs,            // final QuadCLT[]              quadCLTs, //
				cent_index,          // final int                    ref_index,
				set_channels,        // final QuadCLT.SetChannels [] set_channels,
				batch_mode,          // final boolean                batch_mode,
				earliest_scene,      // int                          earliest_scene,
				start_ref_pointers2, // int []                       start_ref_pointers, // [0] - earliest valid scene, [1] ref_index
				threadsMax,          // final int                    threadsMax,  // int               threadsMax,
				updateStatus,        // final boolean                updateStatus,
				debugLevel);         // final int                    debugLevel)
		if (earliest_scene2 < 0) {
			System.out.println("setInitialOrientationsIms() second half failed. Consider more graceful bail out.");
			start_ref_pointers[0] = start_ref_pointers1[0];
			return earliest_scene2; // cent_index;
		}

		if (generate_egomotion) {
			String  ego_path = quadCLTs[cent_index].getX3dDirectory()+Prefs.getFileSeparator()+
					quadCLTs[ref_index].getImageName()+
					"-ego-preinvert-"+quadCLTs[cent_index].getNumOrient()+".csv"; 
			String  ego_comment = null;
		    Interscene.generateEgomotionTable(
		    		clt_parameters, // CLTParameters  clt_parameters,
					quadCLTs, // QuadCLT []     quadCLTs,
					cent_index,//            ref_indx,
					earliest_scene, // int            earliest_scene,
					ego_path, // String         path,
					ego_comment, // String         comment);
					debugLevel); // int debugLevel)
			if (debugLevel> -3) {
				System.out.println("Egomotion table saved to "+ego_path);
			}
        }
		
		// invert first half, reference to the cent_index, add to cent_index map, generate ref_index ponter and cent_index,
		if (debugLevel > -3) {
			System.out.println("pre invertInitialOrientation(): ref_index "+ref_index+", cent_index="+
					cent_index+", last_index="+". readjust="+readjust+"\n");
		}
		
		
		int n_unresolved = invertInitialOrientation(
				clt_parameters, // final CLTParameters          clt_parameters,
				batch_mode,     // final boolean                batch_mode,
				readjust,       // final boolean                readjust,
				false,          // final int set_self,   // set record for the new reference to new reference
				quadCLTs,       // final QuadCLT[]              quadCLTs, //
				ref_index,      // final int                    last_index,  // last to process (normally old reference)
				cent_index,     // final int                    ref_index,  // new reference index (center)
				ref_index,      // final int                    ref_old,    // original ref_index (normally == last_index)				
				earliest_scene2, //final int                    earliest_index// first to process
				debugLevel);    // final int                    debugLevel
		if (generate_egomotion) {
			String  ego_path = quadCLTs[cent_index].getX3dDirectory()+Prefs.getFileSeparator()+
					quadCLTs[ref_index].getImageName()+
					"-ego-afterinvert-"+quadCLTs[cent_index].getNumOrient()+".csv"; 
			String  ego_comment = null;
		    Interscene.generateEgomotionTable(
		    		clt_parameters, // CLTParameters  clt_parameters,
					quadCLTs, // QuadCLT []     quadCLTs,
					cent_index,//            ref_indx,
					earliest_scene, // int            earliest_scene,
					ego_path, // String         path,
					ego_comment, // String         comment);
					debugLevel); // int debugLevel)
			if (debugLevel> -3) {
				System.out.println("Egomotion table saved to "+ego_path);
			}
        }
		if (n_unresolved != 0) {
			earliest_scene2 = -1;
			System.out.println("invertInitialOrientation(). Consider more graceful bail out.");
			start_ref_pointers[0] = start_ref_pointers1[0];
			return earliest_scene2; // cent_index;
		}
		
		
		String cent_ts = quadCLTs[cent_index].getImageName();
		// write config for both ref_index and cent_index scenes
//		ErsCorrection ers_reference = quadCLTs[ref_index].getErsCorrection();
		/*
		ErsCorrection ers_center =    quadCLTs[cent_index].getErsCorrection();
		String cent_ts = quadCLTs[cent_index].getImageName();
		double [][] center_xyzatr = new double [][] {ers_reference.getSceneXYZ(cent_ts), ers_reference.getSceneATR(cent_ts)};
		double [][] inv_cent_xyzatr = ErsCorrection.invertXYZATR(center_xyzatr);
		// Invert half-sequence to reference cent_index
		for (int scene_index = cent_index; scene_index <= ref_index; scene_index++) { // include cent_index itself to the map
			double [][] scene_xyzatr,dxyzatr_dt;
			if (scene_index == ref_index) {
				scene_xyzatr = new double [2][3];
				dxyzatr_dt = ers_reference.getErsXYZATR_dt();
			} else {
				String ts = quadCLTs[scene_index].getImageName();
				scene_xyzatr = ers_reference.getSceneXYZATR(ts);
				dxyzatr_dt =   ers_reference.getSceneErsXYZATR_dt(ts);
			}
			double [][] scene_cent_xyzatr = ErsCorrection.combineXYZATR(scene_xyzatr, inv_cent_xyzatr);
			ers_center.addScene(quadCLTs[scene_index].getImageName(),
					scene_cent_xyzatr,
					dxyzatr_dt  // ers_scene.getErsXYZATR_dt(),		
					);
		}
		*/
		// set pointers
		quadCLTs[ref_index].setRefPointer(cent_ts); // write pointer to center scene to reference scene
		quadCLTs[cent_index].setFirstLastPointers(quadCLTs[earliest_scene2],quadCLTs[ref_index]);// set first/last to center scene
		// set quadCLTs[ref_index].set_orient(0); // ? as it has only half?
		
		quadCLTs[ref_index].saveInterProperties( // save properties for interscene processing (extrinsics, ers, ...)  // null pointer
				null, // String path,             // full name with extension or w/o path to use x3d directory
				debugLevel+1);
		
		
		quadCLTs[cent_index].set_orient(1); // first orientation
		quadCLTs[cent_index].set_accum(0);  // reset accumulations ("build_interscene") number
		quadCLTs[cent_index].saveInterProperties( // save properties for interscene processing (extrinsics, ers, ...)  // null pointer
				null, // String path,             // full name with extension or w/o path to use x3d directory
				debugLevel+1);
		if (debugLevel > -3) {
			System.out.println("setInitialOrientationsCenterIms(): return earliest_scene2="+earliest_scene2);
		}
		return earliest_scene2;
	}
	
	
	
	public static int invertInitialOrientation(
			final CLTParameters          clt_parameters,
			final boolean                batch_mode,
			final boolean                readjust,
			final boolean                set_self,   // set record for the new reference to new reference
			final QuadCLT[]              quadCLTs,   //
			final int                    last_index,  // original ref_index (last)
			final int                    ref_index,  // new reference index (center)
			final int                    ref_old,    // original ref_index (normally == last_index)
			final int                    earliest_index, // only used with readjust
			final int                    debugLevel
			) {
//Readjust may be needed similar to compensateDSI() - top DSI was calculated for wider scene 
// range and be offset from the single-scene DSI		
		if (debugLevel > -3) {
			System.out.println("\ninvertInitialOrientation(): earliest_index "+earliest_index+", ref_index="+
					ref_index+", last_index="+last_index+". readjust="+readjust+"\n");
		}
		ErsCorrection ers_old_reference = quadCLTs[ref_old].getErsCorrection();
		int offs = set_self ? 0 : 1;
		ErsCorrection ers_reference =    quadCLTs[ref_index].getErsCorrection();
		String cent_ts = quadCLTs[ref_index].getImageName();
		double [][] center_xyzatr = new double [][] {ers_old_reference.getSceneXYZ(cent_ts), ers_old_reference.getSceneATR(cent_ts)};
		double [][] inv_cent_xyzatr = ErsCorrection.invertXYZATR(center_xyzatr);
		if (!readjust) {
			// Invert half-sequence to reference cent_index
			for (int scene_index = ref_index + offs; scene_index <= last_index; scene_index++) { // include cent_index itself to the map
				double [][] scene_xyzatr,dxyzatr_dt;
				if (scene_index == ref_old) {
					scene_xyzatr = new double [2][3];
					dxyzatr_dt = ers_old_reference.getErsXYZATR_dt();
				} else {
					String ts = quadCLTs[scene_index].getImageName();
					scene_xyzatr = ers_old_reference.getSceneXYZATR(ts);
					dxyzatr_dt =   ers_old_reference.getSceneErsXYZATR_dt(ts);
				}
				double [][] scene_cent_xyzatr = ErsCorrection.combineXYZATR(scene_xyzatr, inv_cent_xyzatr);
				ers_reference.addScene(quadCLTs[scene_index].getImageName(),
						scene_cent_xyzatr,
						dxyzatr_dt  // ers_scene.getErsXYZATR_dt(),		
						);
			}
			return 0;
		} else { //		if (readjust) {
			boolean        lma_use_Z =     clt_parameters.imp.lma_use_Z;          // true;       // lpf x and y, re-adjust X,Y,Z,A,T,R with pull for X,Y. Disables  
			boolean        lma_use_R =     clt_parameters.imp.lma_use_R;          // true;       // lpf x and y, re-adjust X,Y,Z,A,T,R with pull for X,Y. Disables  
			boolean[]    param_select = 
					ErsCorrection.getParamSelect( // ZR - always
							lma_use_Z,   // boolean use_Z,
							lma_use_R,   // boolean use_R,
							true,   // boolean use_XY
							false,  // boolean use_AT,
							false,  // boolean use_ERS);//clt_parameters.ilp.ilma_lma_select;
							false,  // boolean use_ERS_tilt);
							false );// boolean use_ERS_roll);

			RMSEStats rmse_stats = new RMSEStats();
			double [][][] scenes_xyzatr = new double [last_index+1][][];
			double [][][] dxyzatr_dt =    new double [last_index+1][][];
			for (int nscene = earliest_index; nscene <= ref_index; nscene++) {
	        	String ts = quadCLTs[nscene].getImageName();
	        	scenes_xyzatr[nscene] = ers_reference.getSceneXYZATR(ts);
	        	dxyzatr_dt[nscene] = ers_reference.getSceneErsXYZATR_dt(quadCLTs[nscene].getImageName());
				
			}
			int tilesX =  quadCLTs[ref_index].getTileProcessor().getTilesX();
			int tilesY =  quadCLTs[ref_index].getTileProcessor().getTilesY();
	        int tile_size = quadCLTs[ref_index].getTileProcessor().getTileSize();
			double min_offset =         clt_parameters.imp.min_offset; // 0.0; // 
			double max_offset =         clt_parameters.imp.max_rel_offset * tilesX * tile_size;
			double [] min_max = {min_offset, 2*max_offset, 0.0} ; // {min, max, actual rms)
			// set reference
			double  min_ref_str =        clt_parameters.imp.min_ref_str;
			boolean ref_need_lma =       clt_parameters.imp.ref_need_lma;
			double  min_ref_frac=        clt_parameters.imp.min_ref_frac;
			boolean sfm_filter =         clt_parameters.imp.sfm_filter;  //true;   // use SfM filtering if available 
			double  sfm_minmax =         clt_parameters.imp.sfm_minmax;  //10.0;   // minimal value of the SfM gain maximum to consider available
			double  sfm_fracmax =        clt_parameters.imp.sfm_fracmax; // 0.75;  // minimal fraction of the SfM maximal gain
			double  sfm_fracall =        clt_parameters.imp.sfm_fracall; // 0.3;   // minimal relative area of the SfM-e		
	     	boolean fmg_initial_en =     clt_parameters.imp.fmg_initial_en; // enable IMS-based FPN mitigation for initial orientation
	    	double  fmg_distance =       clt_parameters.imp.fmg_distance; // try to find other reference scene not closer than this pixels
	    	// todo: improve to use "preferred" distance
	    	if (fmg_distance < (min_offset + 2)) {
	    		fmg_distance = min_offset + 2;
	    	}
	    	
	    	double  fmg_max_quad =       clt_parameters.imp.fmg_max_quad; // estimate offset by 4 points (rooll-aware, 25% from center) if center
	    	                                           // offset is too small
	    	boolean fmg_rectilinear =    clt_parameters.imp.fmg_rectilinear;// use rectilinear model for scene offset estimation 

			boolean debug2 =             !batch_mode; // false; // true;
			boolean [] reliable_ref =    null;
			boolean use_lma_dsi =        clt_parameters.imp.use_lma_dsi;
	        double [] reduced_strength = new double[1];
			double avg_z = quadCLTs[ref_index].getAverageZ(true); // use lma
			double [] lma_rms = new double[2];
	        // use combo if second pass?
			if (min_ref_str > 0.0) {
				reliable_ref = quadCLTs[ref_index].getReliableTiles( // will be null if does not exist.
						false, // boolean use_combo,
						min_ref_str,          // double min_strength,
						min_ref_frac,         // double min_ref_frac,
						ref_need_lma,         // boolean needs_lma);
						true, // ref_need_lma_combo,   // boolean needs_lma_combo);
						sfm_filter,  // boolean sfm_filter, // use SfM filtering if available                          
						sfm_minmax,  // double  sfm_minmax, // minimal value of the SfM gain maximum to consider available
						sfm_fracmax, // double  sfm_fracmax,// minimal fraction of the SfM maximal gain
						sfm_fracall, // double  sfm_fracall,// minimal relative area of the SfM-enabled tiles (do not apply filter if less)						
						reduced_strength,  // if not null will return >0 if had to reduce strength (no change if did not reduce)
						debugLevel); // int       debugLevel)
				if (reduced_strength[0] > 0) {
					use_lma_dsi = false; // too few points
				}
				if (debug2) {
					double [] dbg_img = new double [reliable_ref.length];
					for (int i = 0; i < dbg_img.length; i++) {
						dbg_img[i] = reliable_ref[i]?1:0;
					}
					ShowDoubleFloatArrays.showArrays(
							dbg_img,
							quadCLTs[ref_index].getTileProcessor().getTilesX(),
							quadCLTs[ref_index].getTileProcessor().getTilesY(),
							"reliable_ref");
				}
			}
			// Trying new version with motion blur and single-setting of the reference frame			
			boolean mb_en =       clt_parameters.imp.mb_en;
			double  mb_tau =      clt_parameters.imp.mb_tau;      // 0.008; // time constant, sec
			double  mb_max_gain = clt_parameters.imp.mb_max_gain_inter; // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
			TpTask[][]     tp_tasks_ref = new TpTask[2][];
			double [][]    pXpYD_ref = new double [tilesX*tilesY][];
	    	ArrayList<Integer> fpn_list = new ArrayList<Integer>();
			double [] reg_weights = clt_parameters.ilp.ilma_regularization_weights;
			int []    fail_reason = new int[1];   // null or int[1]: 0 - OK, 2 - LMA, 3 - min, 4 - max
//			boolean fpn_skip = true;
			double [][] last_corr_xyzatr = {ZERO3,ZERO3};
			for (int scene_index = ref_index + offs; scene_index <= last_index; scene_index++) { // include cent_index itself to the map
				double [][] scene_xyzatr_old; // ,dxyzatr_dt;
				if (scene_index == last_index) {
					scene_xyzatr_old = new double [2][3];
					dxyzatr_dt[scene_index] = ers_old_reference.getErsXYZATR_dt();
				} else {
					String ts = quadCLTs[scene_index].getImageName();
					scene_xyzatr_old = ers_old_reference.getSceneXYZATR(ts);
					dxyzatr_dt[scene_index] =   ers_old_reference.getSceneErsXYZATR_dt(ts);
				}
				double [][] initial_pose = ErsCorrection.combineXYZATR(scene_xyzatr_old, inv_cent_xyzatr);
				initial_pose = ErsCorrection.combineXYZATR(
						last_corr_xyzatr, // current correction
						initial_pose);    // previously known pose
				// Will be used in prepareLMA()
				quadCLTs[scene_index].getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
						QuadCLTCPU.scaleDtToErs(
								clt_parameters,
								dxyzatr_dt[scene_index])); // cam_dxyzatr));		
	// Trying new version with motion blur and single-setting of the reference frame
	    		double est_shift = quadCLTs[ref_index].estimateAverageShift(
	    				new double[2][3], // scenes_xyzatr[ref_index], // double [][] xyzatr0,
	    				initial_pose,    // double [][] xyzatr1,
	    				avg_z,                    // double average_z,
	    				false,                    // boolean use_rot,
	    				fmg_rectilinear);                   // boolean fmg_rectilinear)
	    		if (est_shift < fmg_max_quad) {
	    			est_shift = quadCLTs[ref_index].estimateAverageShift(
	    					new double[2][3], // scenes_xyzatr[ref_index], // double [][] xyzatr0,
	    					initial_pose,    // double [][] xyzatr1,
	    					avg_z,                    // double average_z,
	    					true,                    // boolean use_rot,
	    					fmg_rectilinear);                   // boolean rectilinear)
	    		}
	    		if (debugLevel > -4) {
	    			System.out.println("nscene="+scene_index+": est offset "+
	    					est_shift+"pix");
	    		}
//	    		double [][]scene_xyzatr = new double[][] {initial_pose[0].clone(),initial_pose[1].clone()};
	    		boolean adjust_OK = false;
	    		if (est_shift < min_max[0]) {
	    			fail_reason[0]=FAIL_REASON_MIN;
	    		} else {	
	    			// Trying new version with motion blur and single-setting of the reference frame
	    			scenes_xyzatr[scene_index] = adjustDiffPairsLMAInterscene( // compare two scenes, first may be reference, use motion blur
	    					clt_parameters,      // CLTParameters  clt_parameters,
	    					use_lma_dsi,         // clt_parameters.imp.use_lma_dsi,
	    					false,               //	boolean        fpn_disable,   // disable fpn filter if images are known to be too close
	    					true,                // boolean        disable_ers,
	    					min_max,             // double []      min_max,       // null or pair of minimal and maximal offsets
	    					fail_reason,         // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
	    					quadCLTs,            // QuadCLT []     quadCLTs,
	    					ref_index,           // int            ref_index,
	    					tp_tasks_ref,        // TpTask[][]     tp_tasks_ref,  // Should be TpTask[2][*]. If tp_tasks_ref[0] == null will calculate
	    					// at set first scene to the GPU
	    					pXpYD_ref,           // double [][]    pXpYD_ref,     // should be set or at least double [num_tiles][] if tp_tasks_ref[0] == null 			
	    					ref_index,           // int            nscene0,       // may be == ref_index
	    					scene_index,         // int            nscene1,       // compares to nscene0
	    					null,                // double []      ref_disparity, // null or alternative reference disparity
	    					reliable_ref,        // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
	    					scenes_xyzatr[ref_index], // double [][]    scene0_xyzatr,,
	    					initial_pose,        // double [][]    scene1_xyzatr,
	    					Double.NaN,          // double         average_z,     
	    					initial_pose,        // double []      scene1_xyzatr_pull, // if both are not null, specify target values to pull to 
	    					param_select, // clt_parameters.ilp.ilma_lma_select, // boolean[]      param_select,
	    					reg_weights,         // double []      param_regweights,
	    					lma_rms,             // double []      rms_out, // null or double [2]
	    					clt_parameters.imp.max_rms, // double         max_rms,
	    					mb_en,               // boolean        mb_en,
	    					mb_tau,              // double         mb_tau,      // 0.008; // time constant, sec
	    					mb_max_gain,         // double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
	    					clt_parameters.imp.debug_level); // int            debugLevel);
	    			adjust_OK = scenes_xyzatr[scene_index] != null;
	    		}				
	    		if (!adjust_OK) {
	    			System.out.println("LMA failed at nscene = "+scene_index+". Reason = "+fail_reason[0]+
	    					" ("+getFailReason(fail_reason[0])+")");
	    			fpn_list.add(scene_index);
	    			scenes_xyzatr[scene_index] = initial_pose;
	    			// should not be modified for initial adjust, but just in case
	    			quadCLTs[scene_index].getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
	    					QuadCLTCPU.scaleDtToErs(
	    							clt_parameters,
	    							dxyzatr_dt[scene_index]));
	    			continue;
	    		}
	    		// update	last_corr_xyzatr, // current correction
	    		double [][] corr_diff = ErsCorrection.combineXYZATR(
	    				scenes_xyzatr[scene_index],                // new
	    				ErsCorrection.invertXYZATR(initial_pose)); //old
	    		last_corr_xyzatr = ErsCorrection.combineXYZATR (
	    				last_corr_xyzatr,
	    				corr_diff);
	    		// not used here with IMS: after_spiral == false

				double [][] adjusted_xyzatr_dt = QuadCLTCPU.scaleDtFromErs(
						clt_parameters,
						quadCLTs[scene_index].getErsCorrection().getErsXYZATR_dt(
								));
				ers_reference.addScene(quadCLTs[scene_index].getImageName(),
						scenes_xyzatr[scene_index][0],
						scenes_xyzatr[scene_index][1],
						adjusted_xyzatr_dt[0], // ZERO3, // ers_scene.getErsXYZ_dt(),		
						adjusted_xyzatr_dt[1] // ZERO3 // ers_scene.getErsATR_dt()		
						);
				rmse_stats.add(lma_rms[0]);
				if (debugLevel > -3) {
					System.out.println("invertInitialOrientation(): scene "+scene_index+" (of "+ quadCLTs.length+") "+
							quadCLTs[ref_index].getImageName() + "/" + quadCLTs[scene_index].getImageName()+
							" Done. RMS="+lma_rms[0]+
							", maximal so far was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
				}
			} // for (int scene_index = ref_index; scene_index <= last_index; scene_index++) {
			
			if (debugLevel > -4) {
				System.out.println("num_fpn_mitigate= "+fpn_list.size());
			}
			if (fmg_initial_en && !fpn_list.isEmpty()) {
				// here max_offset is not critical, min_offset can be 0 too
				//double [] min_max = {min_offset, max_offset, 0.0} ; // {min, max, actual rms)
				if (fmg_distance < (min_max[0] + 2)) {
					fmg_distance = min_max[0] + 2;
				}
				
				int [][] fpn_pairs = getFPNPairs(
						fpn_list,          // ArrayList<Integer> fpn_list,
						fmg_distance, // double             fpn_mitigate_dist,
						fmg_rectilinear,       // boolean            rectilinear,
						quadCLTs,          // QuadCLT []         quadCLTs,
						scenes_xyzatr,     // double [][][]      scenes_xyzatr,
						avg_z,             // double             avg_z,
						last_index, // ref_index,         // int                ref_index, // >= earliest_scene 
						earliest_index);   // int                earliest_scene)
				if (getFPNUnResolved(fpn_pairs) > 0) {
					System.out.println("Unresolved FPN pairs:"+getFPNUnResolved(fpn_pairs));
					return getFPNUnResolved(fpn_pairs);
				}
				
				double [] min_max_fpn = {0,min_max[1]}; 
				for (int ipair = 0; ipair < fpn_pairs.length; ipair++) if (fpn_pairs[ipair][1] >= 0) {
					if (debugLevel > -4) {
						System.out.println("Mitigating FPN for scene "+fpn_pairs[ipair][0]+
								" being too close to reference "+ref_index+
								", using scene "+fpn_pairs[ipair][1]+" as a reference");
					}
					TpTask[][]     tp_tasks_rel_ref = new TpTask[2][];
	    			double [][] rel_xyzatr = adjustDiffPairsLMAInterscene( // compare two scenes, first may be reference, use motion blur
	    					clt_parameters,      // CLTParameters  clt_parameters,
	    					use_lma_dsi,         // clt_parameters.imp.use_lma_dsi,
	    					false,               //	boolean        fpn_disable,   // disable fpn filter if images are known to be too close
	    					true,                // boolean        disable_ers,
	    					min_max_fpn,// min_max,// double []    min_max,       // null or pair of minimal and maximal offsets
	    					fail_reason,         // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
	    					quadCLTs,            // QuadCLT []     quadCLTs,
	    					ref_index,           // int            ref_index,
	    					tp_tasks_rel_ref,    // TpTask[][]     tp_tasks_ref,  // Should be TpTask[2][*]. If tp_tasks_ref[0] == null will calculate
	    					// at set first scene to the GPU
	    					pXpYD_ref,           // double [][]    pXpYD_ref,     // should be set or at least double [num_tiles][] if tp_tasks_ref[0] == null 			
	    					fpn_pairs[ipair][1], // ref_index,           // int            nscene0,       // may be == ref_index
	    					fpn_pairs[ipair][0], // int            nscene1,       // compares to nscene0
	    					null,                // double []      ref_disparity, // null or alternative reference disparity
	    					reliable_ref,        //boolean []      reliable_ref, // null or bitmask of reliable reference tiles
	    					scenes_xyzatr[fpn_pairs[ipair][1]], // double [][]    scene0_xyzatr,,
	    					scenes_xyzatr[fpn_pairs[ipair][0]], // initial_pose,        // double [][]    scene1_xyzatr,
							Double.NaN,           //  double         average_z,     
	    					scenes_xyzatr[fpn_pairs[ipair][0]], // initial_pose,        // double []      scene1_xyzatr_pull, // if both are not null, specify target values to pull to 
	    					param_select, // clt_parameters.ilp.ilma_lma_select, // boolean[]      param_select,
	    					reg_weights,         // double []      param_regweights,
	    					lma_rms,             // double []      rms_out, // null or double [2]
	    					2*clt_parameters.imp.max_rms, // double         max_rms,
	    					mb_en,               // boolean        mb_en,
	    					mb_tau,              // double         mb_tau,      // 0.008; // time constant, sec
	    					mb_max_gain,         // double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
	    					clt_parameters.imp.debug_level); // int            debugLevel);
	    			boolean adjust_OK = rel_xyzatr != null;
					if (!adjust_OK) {
						// should not be modified for initial adjust, but just in case
						quadCLTs[fpn_pairs[ipair][0]].getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
								QuadCLTCPU.scaleDtToErs(
										clt_parameters,
										dxyzatr_dt[fpn_pairs[ipair][0]]));		
						System.out.println("LMA failed at scene pair = "+fpn_pairs[ipair][0]+
								" referenced from scene "+fpn_pairs[ipair][1]+". Reason = "+fail_reason[0]+
								" ("+getFailReason(fail_reason[0])+")");
					} else {
						scenes_xyzatr[fpn_pairs[ipair][0]] = rel_xyzatr; // save directly, no need to combine
						double [][] adjusted_xyzatr_dt = QuadCLTCPU.scaleDtFromErs(
								clt_parameters,
								quadCLTs[fpn_pairs[ipair][0]].getErsCorrection().getErsXYZATR_dt());
						ers_reference.addScene(quadCLTs[fpn_pairs[ipair][0]].getImageName(),
								scenes_xyzatr[fpn_pairs[ipair][0]][0],
								scenes_xyzatr[fpn_pairs[ipair][0]][1],
								adjusted_xyzatr_dt[0], // ZERO3, // ers_scene.getErsXYZ_dt(),		
								adjusted_xyzatr_dt[1]); // ZERO3 // ers_scene.getErsATR_dt()		
						rmse_stats.add(lma_rms[0]);
						if (debugLevel > -3) {
							System.out.println("Pass multi scene "+fpn_pairs[ipair][0]+" (of "+ quadCLTs.length+") "+
									quadCLTs[fpn_pairs[ipair][1]].getImageName() + "/" + quadCLTs[fpn_pairs[ipair][0]].getImageName()+
					                " Done. RMS="+lma_rms[0]+
					                ", maximal so far was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
						}
					}
				}
			}
			// Add to log
	    	StringBuffer sb = new StringBuffer();
	    	sb.append(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime())+"\n");
			sb.append("Finished invertInitialOrientation():\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("earliest_scene= "+earliest_index+"\n");
	    	sb.append("ref_index=      "+ref_index+"\n");
	    	sb.append("last_index=     "+last_index+"\n");
	    	sb.append("old_ref_index=  "+ref_old+"\n");
	    	sb.append("Maximal RMSE=   "+rmse_stats.getMax()+"\n");
	    	sb.append("Average RMSE=   "+rmse_stats.getAverage()+"\n");
	    	sb.append("------------------------\n\n");
			quadCLTs[ref_index].saveStringInModelDirectory(sb.toString(), QuadCLT.ORIENTATION_LOGS_SUFFIX); // String  suffix)
		}
		return 0;
	}
	
	public static int setInitialOrientationsIms(
			final CLTParameters          clt_parameters,
			final boolean                compensate_ims_rotation,
			final boolean                inertial_only, 			
			int                          min_num_scenes,
			final ColorProcParameters    colorProcParameters,
			final QuadCLT[]              quadCLTs, //
			final int                    ref_index,
			final QuadCLT.SetChannels [] set_channels,
			final boolean                batch_mode,
			int                          earliest_scene,
			int []                       start_ref_pointers, // [0] - earliest valid scene, [1] ref_index
			final int                    threadsMax,  // int               threadsMax,
			final boolean                updateStatus,
			final int                    debugLevel) {
		RMSEStats rmse_stats = new RMSEStats();
		double  min_ref_str =        clt_parameters.imp.min_ref_str;
		boolean ref_need_lma =       clt_parameters.imp.ref_need_lma;
		double  min_ref_frac=        clt_parameters.imp.min_ref_frac;
		boolean sfm_filter =         clt_parameters.imp.sfm_filter;  //true;   // use SfM filtering if available 
		double  sfm_minmax =         clt_parameters.imp.sfm_minmax;  //10.0;   // minimal value of the SfM gain maximum to consider available
		double  sfm_fracmax =        clt_parameters.imp.sfm_fracmax; // 0.75;  // minimal fraction of the SfM maximal gain
		double  sfm_fracall =        clt_parameters.imp.sfm_fracall; // 0.3;   // minimal relative area of the SfM-e		
    	int max_num_scenes =         clt_parameters.imp.max_num_scenes; // cut longer series
    	boolean ims_use =            clt_parameters.imp.ims_use;
    	
     	boolean fmg_initial_en =     clt_parameters.imp.fmg_initial_en; // enable IMS-based FPN mitigation for initial orientation
    	double  fmg_distance =       clt_parameters.imp.fmg_distance; // try to find other reference scene not closer than this pixels
    	double  fmg_max_quad =       clt_parameters.imp.fmg_max_quad; // estimate offset by 4 points (rooll-aware, 25% from center) if center
    	                                           // offset is too small
    	boolean fmg_rectilinear =    clt_parameters.imp.fmg_rectilinear;// use rectilinear model for scene offset estimation 
    	
    	double boost_max_short = 2.0; // 
    	double boost_zoom_short = 1.5; // 
    	
    	if (!ims_use) {
    		System.out.println("setInitialOrientationsIms(): IMS use is disabled");
    		return -1;
    	}
		if (clt_parameters.ilp.ilma_3d) {
			System.out.println("*** setInitialOrientationsIms(): clt_parameters.ilp.ilma_3d is TRUE. You may want to disable it when using IMS");
		}

		double [] lma_rms = new double[2];

		int tilesX =  quadCLTs[ref_index].getTileProcessor().getTilesX();
		int tilesY =  quadCLTs[ref_index].getTileProcessor().getTilesY();
        int tile_size = quadCLTs[ref_index].getTileProcessor().getTileSize();
		double min_offset =         clt_parameters.imp.min_offset; // 0.0; // 
		double max_offset =         clt_parameters.imp.max_rel_offset * tilesX * tile_size;
		double max_roll =           clt_parameters.imp.max_roll_deg*Math.PI/180.0;
		double max_zoom_diff =      clt_parameters.imp.max_zoom_diff;
		boolean fpn_skip =          clt_parameters.imp.fpn_skip; // if false - fail as before
		boolean fpn_rematch =       clt_parameters.imp.fpn_rematch; // if false - keep previous
		boolean        lma_use_Z =     clt_parameters.imp.lma_use_Z;          // true;       // lpf x and y, re-adjust X,Y,Z,A,T,R with pull for X,Y. Disables  
		boolean        lma_use_R =     clt_parameters.imp.lma_use_R;          // true;       // lpf x and y, re-adjust X,Y,Z,A,T,R with pull for X,Y. Disables  
		if (debugLevel > -3) {
			System.out.println("setInitialOrientationsIms(): lma_use_Z="+lma_use_Z+", lma_use_R="+lma_use_R);
	 	}
		boolean[]    param_select = 
				ErsCorrection.getParamSelect( // ZR - always
						lma_use_Z, // boolean use_Z,
						lma_use_R, // boolean use_R,
						true,      // boolean use_XY, May change with oorient preference
						false,     // boolean use_AT, May change with oorient preference
						false,     // boolean use_ERS);//clt_parameters.ilp.ilma_lma_select;
						false,     // boolean use_ERS_tilt);
						false);    // boolean use_ERS_roll);
		
		int []    fail_reason = new int[1];   // null or int[1]: 0 - OK, 2 - LMA, 3 - min, 4 - max
		for (int scene_index =  ref_index - 1; scene_index >= 0 ; scene_index--) {
			// to include ref scene photometric calibration
			quadCLTs[scene_index] = quadCLTs[ref_index].spawnNoModelQuadCLT(
					set_channels[scene_index].set_name,
					clt_parameters,
					colorProcParameters, //
					threadsMax,
					debugLevel-2);
		} // split cycles to remove output clutter
		
		
		ErsCorrection ers_reference = quadCLTs[ref_index].getErsCorrection();		
		int debug_scene =           -15;
		boolean debug2 =             !batch_mode; // false; // true;
		boolean [] reliable_ref =    null;
		boolean use_lma_dsi =        clt_parameters.imp.use_lma_dsi;
        double [] reduced_strength = new double[1];
        // use combo if second pass?
		if (min_ref_str > 0.0) {
			reliable_ref = quadCLTs[ref_index].getReliableTiles( // will be null if does not exist.
					false, // boolean use_combo,
					min_ref_str,          // double min_strength,
					min_ref_frac,         // double min_ref_frac,
					ref_need_lma,         // boolean needs_lma);
					true, // ref_need_lma_combo,   // boolean needs_lma_combo);
					sfm_filter,  // boolean sfm_filter, // use SfM filtering if available                          
					sfm_minmax,  // double  sfm_minmax, // minimal value of the SfM gain maximum to consider available
					sfm_fracmax, // double  sfm_fracmax,// minimal fraction of the SfM maximal gain
					sfm_fracall, // double  sfm_fracall,// minimal relative area of the SfM-enabled tiles (do not apply filter if less)						
					reduced_strength,  // if not null will return >0 if had to reduce strength (no change if did not reduce)
					debugLevel); // int       debugLevel)
			if (reduced_strength[0] > 0) {
				use_lma_dsi = false; // too few points
			}
			if (debug2) {
				double [] dbg_img = new double [reliable_ref.length];
				for (int i = 0; i < dbg_img.length; i++) {
					dbg_img[i] = reliable_ref[i]?1:0;
				}
				ShowDoubleFloatArrays.showArrays(
						dbg_img,
						quadCLTs[ref_index].getTileProcessor().getTilesX(),
						quadCLTs[ref_index].getTileProcessor().getTilesY(),
						"reliable_ref");
			}
		}
		
		double max_z_change = Double.NaN; // only applicable for drone images
		double avg_z = quadCLTs[ref_index].getAverageZ(true); // use lma
		if (max_zoom_diff > 0) { // ignore if set to
			max_z_change = avg_z * max_zoom_diff;
			if (debugLevel > -3) {
				System.out.println("Setting maximal Z-direction movement to "+ max_z_change+" m.");
			}
		}

//		boolean scale_ims_velocities = false; // true;
		double [][][] scenes_dxyzatr = QuadCLTCPU.getDxyzatrPIMU( // does not depend on correction
				clt_parameters, // CLTParameters clt_parameters,
				quadCLTs); //,       // QuadCLT[]     quadCLTs,
//				quadCLTs[ref_index].getPimuOffsets()); // boolean       scale)
		
		double [] quat_corr = compensate_ims_rotation? quadCLTs[ref_index].getQuatCorr() : null; //
		double [] enu_corr_metric = quadCLTs[ref_index].getENUCorrMetric();
		// quat_corr may still be null if does not exist
		
		if (debugLevel > -3) {
			System.out.println("setInitialOrientationsIms(): compensate_ims_rotation="+compensate_ims_rotation);
			System.out.println("setInitialOrientationsIms(): will "+((quat_corr==null)? "NOT ":"")+" correct orientation offset");
			QuadCLTCPU.showQuatCorr(quat_corr, enu_corr_metric);
		}
		boolean test_quat_corr = debugLevel > 1000;
		if (test_quat_corr) {
			testPIMU (
					clt_parameters, // final CLTParameters clt_parameters,
					quadCLTs,       // final QuadCLT[]     quadCLTs,
					ref_index,      // final int           ref_index,
					debugLevel);    // final int           debugLevel);
		}
		double [][][] ims_xyzatr;
		if (inertial_only) {
			ims_xyzatr = QuadCLT.integratePIMU(
						clt_parameters, // final CLTParameters clt_parameters,
						quadCLTs,       // final QuadCLT[]     quadCLTs,
						ref_index,      // final int           ref_index,
						null,           // double [][][]       dxyzatr,
			    		0, // final int           early_index,
			    		(quadCLTs.length -1) // int           last_index,
//			    		(new double[2][3]) //final double [][] pimu_offsets // null - will not subtract from velocities
			    		);
		} else {
			ims_xyzatr = quadCLTs[ref_index].getXyzatrIms(
					clt_parameters, // CLTParameters clt_parameters,
					quadCLTs, // QuadCLT[]  quadCLTs,
					quat_corr, // double []  quat_corr, // only applies to rotations - verify!
					debugLevel) ; // int        debugLevel)
		}
				
		double [][][] scenes_xyzatr =      new double [quadCLTs.length][][]; // previous scene relative to the next one
		scenes_xyzatr[ref_index] =         new double[2][3]; // all zeros
//		boolean after_spiral = false;
		boolean got_spiral = false;
		double [][] last_corr_xyzatr = {ZERO3,ZERO3};
		ers_reference.addScene(quadCLTs[ref_index].getImageName(), // add reference scene (itself) too
				scenes_xyzatr[ref_index][0],
				scenes_xyzatr[ref_index][1],
				scenes_dxyzatr[ref_index][0], // cam_dxyzatr_ref[0],  // later may overwrite from overlap		
				scenes_dxyzatr[ref_index][1]); //cam_dxyzatr_ref[1]); // ZERO3);// ers_scene.getErsATR_dt()	
		// Will be used in prepareLMA()
		quadCLTs[ref_index].getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
				QuadCLTCPU.scaleDtToErs(
						clt_parameters,
						scenes_dxyzatr[ref_index])); // cam_dxyzatr_ref));
		
		// Trying new version with motion blur and single-setting of the reference frame			
		boolean mb_en =       clt_parameters.imp.mb_en;
		double  mb_tau =      clt_parameters.imp.mb_tau;      // 0.008; // time constant, sec
		double  mb_max_gain = clt_parameters.imp.mb_max_gain_inter; // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
		TpTask[][]     tp_tasks_ref = new TpTask[2][];
		double [][]    pXpYD_ref = new double [tilesX*tilesY][];
    	ArrayList<Integer> fpn_list = new ArrayList<Integer>();
		double [] reg_weights = clt_parameters.ilp.ilma_regularization_weights;
//		min_max[1] = max_offset;
		for (int scene_index =  ref_index - 1; scene_index >= earliest_scene ; scene_index--) {
			if ((ref_index - scene_index) >= max_num_scenes){
				earliest_scene = scene_index + 1;
				if (debugLevel > -3) {
					System.out.println("Cutting too long series at scene "+scene_index+" (of "+ quadCLTs.length+". excluding) ");
				}
				// set this and all previous to null
				for (; scene_index >= 0 ; scene_index--) {
					ers_reference.addScene(quadCLTs[scene_index].getImageName()); // remove
				}
				break;
			}
			if (scene_index == debug_scene) {
				System.out.println("scene_index = "+scene_index);
				System.out.println("scene_index = "+scene_index);
			}
			QuadCLT scene_QuadClt = quadCLTs[scene_index];
			double [][] pose_ims = ims_xyzatr[scene_index];
			
			// Apply correction from the earlier processed scenes (closer to reference)
			double [][] initial_pose = ErsCorrection.combineXYZATR(
					last_corr_xyzatr, // current correction
					pose_ims);        // raw IMS prediction
			scenes_xyzatr[scene_index] = new double [][]{
					initial_pose[0].clone(),
					initial_pose[1].clone()
			};
			// Refine with LMA
			//boost_zoom_short
			double max_z_change_scaled = max_z_change;
			double [] min_max = {min_offset, max_offset, 0.0} ; // {min, max, actual rms)
			if ((ref_index - scene_index) < min_num_scenes) {
				min_max[1] = boost_max_short * max_offset;
				max_z_change_scaled = max_z_change * boost_zoom_short;
				if (debugLevel > -3) {
					System.out.println("As the current series ("+(ref_index - scene_index)+
							" scenes) is shorter than minimal ("+min_num_scenes+"), the maximal shift is scaled by "+
							boost_max_short + " to " + (boost_max_short * max_offset)+
							" pixels");
				}
			}
			if ((scene_index - earliest_scene) < min_num_scenes) {
				min_max[1] = boost_max_short * max_offset;
				max_z_change_scaled = max_z_change * boost_zoom_short;
				if (debugLevel > -3) {
					System.out.println("As the remaining number of scenes to process ("+(scene_index - earliest_scene + 1)+
							") is less than minimal ("+min_num_scenes+"), the maximal shift is scaled by "+
							boost_max_short + " to " + (boost_max_short * max_offset)+
							" pixels");
				}
			}
			// Will be used in prepareLMA()
			quadCLTs[scene_index].getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
					QuadCLTCPU.scaleDtToErs(
							clt_parameters,
							scenes_dxyzatr[scene_index])); // cam_dxyzatr));		
// Trying new version with motion blur and single-setting of the reference frame
    		double est_shift = quadCLTs[ref_index].estimateAverageShift(
    				scenes_xyzatr[ref_index], // double [][] xyzatr0,
    				scenes_xyzatr[scene_index],    // double [][] xyzatr1,
    				avg_z,                    // double average_z,
    				false,                    // boolean use_rot,
    				fmg_rectilinear);                   // boolean fmg_rectilinear)
    		if (est_shift < fmg_max_quad) {
    			est_shift = quadCLTs[ref_index].estimateAverageShift(
    					scenes_xyzatr[ref_index], // double [][] xyzatr0,
    					scenes_xyzatr[scene_index],    // double [][] xyzatr1,
    					avg_z,                    // double average_z,
    					true,                    // boolean use_rot,
    					fmg_rectilinear);                   // boolean rectilinear)
    		}
    		if (debugLevel > -4) {
    			System.out.println("nscene="+scene_index+": est offset "+
    					est_shift+"pix");
    		}
    		boolean adjust_OK = false;
    		if (est_shift < min_max[0]) {
    			fail_reason[0]=FAIL_REASON_MIN;
    		} else {	
    			scenes_xyzatr[scene_index] = adjustDiffPairsLMAInterscene( // compare two scenes, first may be reference, use motion blur
    					clt_parameters,      // CLTParameters  clt_parameters,
    					use_lma_dsi,         // clt_parameters.imp.use_lma_dsi,
    					false,               //	boolean        fpn_disable,   // disable fpn filter if images are known to be too close
    					true,                // boolean        disable_ers,
    					min_max,             // double []      min_max,       // null or pair of minimal and maximal offsets
    					fail_reason,         // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
    					quadCLTs,            // QuadCLT []     quadCLTs,
    					ref_index,           // int            ref_index,
    					tp_tasks_ref,        // TpTask[][]     tp_tasks_ref,  // Should be TpTask[2][*]. If tp_tasks_ref[0] == null will calculate
    					// at set first scene to the GPU
    					pXpYD_ref,           // double [][]    pXpYD_ref,     // should be set or at least double [num_tiles][] if tp_tasks_ref[0] == null 			
    					ref_index,           // int            nscene0,       // may be == ref_index
    					scene_index,         // int            nscene1,       // compares to nscene0
    					null,                // double []      ref_disparity, // null or alternative reference disparity
    					reliable_ref,        //boolean []     reliable_ref, // null or bitmask of reliable reference tiles
    					scenes_xyzatr[ref_index], // double [][]    scene0_xyzatr,,
    					initial_pose,        // double [][]    scene1_xyzatr,
    					Double.NaN,          // double         average_z,     
    					initial_pose,        // double []      scene1_xyzatr_pull, // if both are not null, specify target values to pull to 
    					param_select, // clt_parameters.ilp.ilma_lma_select, // boolean[]      param_select,
    					reg_weights,         // double []      param_regweights,
    					lma_rms,             // double []      rms_out, // null or double [2]
    					clt_parameters.imp.max_rms, // double         max_rms,
    					mb_en,               // boolean        mb_en,
    					mb_tau,              // double         mb_tau,      // 0.008; // time constant, sec
    					mb_max_gain,         // double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
    					clt_parameters.imp.debug_level); // int            debugLevel);
    			adjust_OK = scenes_xyzatr[scene_index] != null;
    		}
    		if (adjust_OK) { // check only for initial orientation, do not check on readjustments
				if (Math.abs(scenes_xyzatr[scene_index][1][2]) > max_roll) {
					fail_reason[0] = FAIL_REASON_ROLL;
					adjust_OK = false;
				}
				if (max_zoom_diff > 0) { // ignore if set to 
					if (Math.abs(scenes_xyzatr[scene_index][0][2]) > max_z_change) {
						if (Math.abs(scenes_xyzatr[scene_index][0][2]) > max_z_change_scaled) {
							fail_reason[0] = FAIL_REASON_ZOOM;
							adjust_OK = false;
						} else {
							System.out.println("Z-change "+Math.abs(scenes_xyzatr[scene_index][0][2])+
							"m exceeds limit of "+max_z_change+"m, but as it is beginning/end of the series, limit is raised to "+
							max_z_change_scaled+"m");
						}
					}
				}
			}
			// FAIL_REASON_ROLL
			handle_failure: {
				if (!adjust_OK) {
					System.out.println("LMA failed at nscene = "+scene_index+". Reason = "+fail_reason[0]+
							" ("+getFailReason(fail_reason[0])+")");
					if ((fail_reason[0]==FAIL_REASON_MIN) || ((fail_reason[0]==FAIL_REASON_LMA) && !got_spiral)) {
						fpn_list.add(scene_index);
						scenes_xyzatr[scene_index] = initial_pose;
						// should not be modified for initial adjust, but just in case
						quadCLTs[scene_index].getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
								QuadCLTCPU.scaleDtToErs(
										clt_parameters,
										scenes_dxyzatr[scene_index]));		
						if (fpn_skip) {
							System.out.println("fpn_skip is set, just using initial pose, num_fpn_mitigate="+fpn_list.size());
							break handle_failure; // to next scene
						} else {
							System.out.println("fpn_skip is not set, aborting series and adjusting earliest_scene");
							// set this and all previous to null
						}
					}
					// all other reasons lead to failure
					earliest_scene = scene_index + 1;
					if (debugLevel > -4) {
						System.out.println("Pass multi scene "+scene_index+" (of "+ quadCLTs.length+") "+
								quadCLTs[ref_index].getImageName() + "/" + scene_QuadClt.getImageName()+
								" FAILED. Setting earliest_scene to "+earliest_scene + " Failure reason: " + fail_reason[0]+
								" ("+getFailReason(fail_reason[0])+")");
					}
					// set this and all previous to null
					for (; scene_index >= 0 ; scene_index--) {
						ers_reference.addScene(quadCLTs[scene_index].getImageName()); // remove
						//					quadCLTs[scene_index] = null; // completely remove early scenes?
					}
					break;
				}
			}
			// update	last_corr_xyzatr, // current correction
			double [][] corr_diff = ErsCorrection.combineXYZATR(
					scenes_xyzatr[scene_index],                // new
					ErsCorrection.invertXYZATR(initial_pose)); //old
			last_corr_xyzatr = ErsCorrection.combineXYZATR (
					last_corr_xyzatr,
					corr_diff);
			// not used here with IMS: after_spiral == false
			
			double [][] adjusted_xyzatr_dt = QuadCLTCPU.scaleDtFromErs(
					clt_parameters,
					quadCLTs[scene_index].getErsCorrection().getErsXYZATR_dt(
							));
			ers_reference.addScene(scene_QuadClt.getImageName(),
					scenes_xyzatr[scene_index][0],
					scenes_xyzatr[scene_index][1],
					adjusted_xyzatr_dt[0], // ZERO3, // ers_scene.getErsXYZ_dt(),		
					adjusted_xyzatr_dt[1] // ZERO3 // ers_scene.getErsATR_dt()		
					);
			rmse_stats.add(lma_rms[0]);
			if (debugLevel > -3) {
				System.out.println("Pass multi scene "+scene_index+" (of "+ quadCLTs.length+") "+
						quadCLTs[ref_index].getImageName() + "/" + scene_QuadClt.getImageName()+
		                " Done. RMS="+lma_rms[0]+
		                ", maximal so far was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
			}
			
			
		} // for (int scene_index =  ref_index - 1; scene_index >= 0 ; scene_index--)
		if ((ref_index - earliest_scene + 1) < min_num_scenes) {
			System.out.println("Total number of useful scenes = "+(ref_index - earliest_scene + 1)+
					" < "+min_num_scenes+". Scrapping this series.");
			if (start_ref_pointers != null) {
				start_ref_pointers[0] = earliest_scene;
			}
			return -1;
		}
//		fpn_list.add(ref_index); // just for testing
		if (debugLevel > -4) {
			System.out.println("num_fpn_mitigate= "+fpn_list.size());
		}
		if (fmg_initial_en && !fpn_list.isEmpty()) {
			// here max_offset is not critical, min_offset can be 0 too
			double [] min_max = {min_offset, max_offset, 0.0} ; // {min, max, actual rms)
			if (fmg_distance < (min_max[0] + 2)) {
				fmg_distance = min_max[0] + 2;
			}
			int [][] fpn_pairs = getFPNPairs(
					fpn_list,          // ArrayList<Integer> fpn_list,
					fmg_distance, // double             fpn_mitigate_dist,
					fmg_rectilinear,       // boolean            rectilinear,
					quadCLTs,          // QuadCLT []         quadCLTs,
					scenes_xyzatr,     // double [][][]      scenes_xyzatr,
					avg_z,             // double             avg_z,
					ref_index,         // int                ref_index, // >= earliest_scene 
					earliest_scene);   // int                earliest_scene)
			// mitigating problem, that in the process of adjusting offset can fall below
			// the minimum and coordinates will be NaN:
			/*
				Mitigating FPN for scene 361 being too close to reference 344, using scene 367 as a reference
				interCorrPair(): avg_offs = 10.755562139763402 <= 256.0, sw = 4217.0 iter=3, RMS=0.5364197453455559 (Pure RMS=0.5364197453455559)
				interCorrPair(): avg_offs = 7.795390182218127 < 8.0, sw = 4219.0
				adjustPairsLMAInterscene() returned null
			 */
			double [] min_max_fpn = {0,min_max[1]}; 
			for (int ipair = 0; ipair < fpn_pairs.length; ipair++) if (fpn_pairs[ipair][1] >= 0) {
				if (debugLevel > -4) {
					System.out.println("Mitigating FPN for scene "+fpn_pairs[ipair][0]+
							" being too close to reference "+ref_index+
							", using scene "+fpn_pairs[ipair][1]+" as a reference");
				}
				TpTask[][]     tp_tasks_rel_ref = new TpTask[2][];
    			double [][] rel_xyzatr = adjustDiffPairsLMAInterscene( // compare two scenes, first may be reference, use motion blur
    					clt_parameters,      // CLTParameters  clt_parameters,
    					use_lma_dsi,         // clt_parameters.imp.use_lma_dsi,
    					false,               //	boolean        fpn_disable,   // disable fpn filter if images are known to be too close
    					true,                // boolean        disable_ers,
    					min_max_fpn, // min_max,// double []   min_max,       // null or pair of minimal and maximal offsets
    					fail_reason,         // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
    					quadCLTs,            // QuadCLT []     quadCLTs,
    					ref_index,           // int            ref_index,
    					tp_tasks_rel_ref,    // TpTask[][]     tp_tasks_ref,  // Should be TpTask[2][*]. If tp_tasks_ref[0] == null will calculate
    					// at set first scene to the GPU
    					pXpYD_ref,           // double [][]    pXpYD_ref,     // should be set or at least double [num_tiles][] if tp_tasks_ref[0] == null 			
    					fpn_pairs[ipair][1], // ref_index,           // int            nscene0,       // may be == ref_index
    					fpn_pairs[ipair][0],         // int            nscene1,       // compares to nscene0
    					null,                // double []      ref_disparity, // null or alternative reference disparity
    					reliable_ref,        //boolean []      reliable_ref, // null or bitmask of reliable reference tiles
    					scenes_xyzatr[fpn_pairs[ipair][1]], // double [][]    scene0_xyzatr,,
    					scenes_xyzatr[fpn_pairs[ipair][0]], // initial_pose,        // double [][]    scene1_xyzatr,
						Double.NaN,           //  double         average_z,     
    					scenes_xyzatr[fpn_pairs[ipair][0]], // initial_pose,        // double []      scene1_xyzatr_pull, // if both are not null, specify target values to pull to 
    					param_select, // clt_parameters.ilp.ilma_lma_select, // boolean[]      param_select,
    					reg_weights,         // double []      param_regweights,
    					lma_rms,             // double []      rms_out, // null or double [2]
    					clt_parameters.imp.max_rms, // double         max_rms,
    					mb_en,               // boolean        mb_en,
    					mb_tau,              // double         mb_tau,      // 0.008; // time constant, sec
    					mb_max_gain,         // double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
    					clt_parameters.imp.debug_level); // int            debugLevel);
    			boolean adjust_OK = rel_xyzatr != null;
				if (!adjust_OK) {
					// should not be modified for initial adjust, but just in case
					quadCLTs[fpn_pairs[ipair][0]].getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
							QuadCLTCPU.scaleDtToErs(
									clt_parameters,
									scenes_dxyzatr[fpn_pairs[ipair][0]]));		
					System.out.println("LMA failed at scene pair = "+fpn_pairs[ipair][0]+
							" referenced from scene "+fpn_pairs[ipair][1]+". Reason = "+fail_reason[0]+
							" ("+getFailReason(fail_reason[0])+")");
				} else {
					scenes_xyzatr[fpn_pairs[ipair][0]] = rel_xyzatr; // save directly, no need to combine
					double [][] adjusted_xyzatr_dt = QuadCLTCPU.scaleDtFromErs(
							clt_parameters,
							quadCLTs[fpn_pairs[ipair][0]].getErsCorrection().getErsXYZATR_dt());
					ers_reference.addScene(quadCLTs[fpn_pairs[ipair][0]].getImageName(),
							scenes_xyzatr[fpn_pairs[ipair][0]][0],
							scenes_xyzatr[fpn_pairs[ipair][0]][1],
							adjusted_xyzatr_dt[0], // ZERO3, // ers_scene.getErsXYZ_dt(),		
							adjusted_xyzatr_dt[1]); // ZERO3 // ers_scene.getErsATR_dt()		
				    rmse_stats.add(lma_rms[0]);
					if (debugLevel > -3) {
						System.out.println("Pass multi scene "+fpn_pairs[ipair][0]+" (of "+ quadCLTs.length+") "+
								quadCLTs[fpn_pairs[ipair][1]].getImageName() + "/" + quadCLTs[fpn_pairs[ipair][0]].getImageName()+
				                " Done. RMS="+lma_rms[0]+
				                ", maximal so far was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
					}
				}
			}
		}
		
		if (earliest_scene > 0) {
			System.out.println("setInitialOrientationsIms(): Not all scenes matched, earliest useful scene = "+earliest_scene+
					" (total number of scenes = "+(ref_index - earliest_scene + 1)+
					"). Maximal RMSE was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
		} else if (debugLevel > -4) {
			System.out.println("All multi scene passes are Done. Maximal RMSE was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
		}
		quadCLTs[ref_index].set_orient(1); // first orientation
		quadCLTs[ref_index].set_accum(0);  // reset accumulations ("build_interscene") number
		quadCLTs[ref_index].saveInterProperties( // save properties for interscene processing (extrinsics, ers, ...)  // null pointer
				null, // String path,             // full name with extension or w/o path to use x3d directory
				debugLevel+1);
		// Add to log
    	StringBuffer sb = new StringBuffer();
    	sb.append(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime())+"\n");
		sb.append("Finished setInitialOrientationsIms():\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("earliest_scene= "+earliest_scene+"\n");
    	sb.append("ref_index=      "+ref_index+"\n");
    	sb.append("Maximal RMSE=   "+rmse_stats.getMax()+"\n");
    	sb.append("Average RMSE=   "+rmse_stats.getAverage()+"\n");
    	sb.append("------------------------\n\n");
		quadCLTs[ref_index].saveStringInModelDirectory(sb.toString(), QuadCLT.ORIENTATION_LOGS_SUFFIX); // String  suffix)
		return earliest_scene;
	}

	public static void testPIMU(
    		final CLTParameters clt_parameters,
    		final QuadCLT[]     quadCLTs,
    		final int           ref_index,
    		final int           debugLevel
			) {
		double [][][] ims_xyzatr = quadCLTs[ref_index].getXyzatrIms(
				clt_parameters, // CLTParameters clt_parameters,
				quadCLTs,       // QuadCLT[]  quadCLTs,
				null,           // double []  quat_corr, // only applies to rotations - verify!
				debugLevel) ;   // int        debugLevel)
		double [][][] pimu_xyzatr = QuadCLT.integratePIMU(
				clt_parameters, // final CLTParameters clt_parameters,
				quadCLTs,       // final QuadCLT[]     quadCLTs,
				ref_index,      // final int           ref_index,
				null,           // double [][][]       dxyzatr,
	    		0, // final int           early_index,
	    		(quadCLTs.length -1) // int           last_index,
//	    		(new double[2][3]) //final double [][]   offsets // null - will not subtract from velocities
	    		);
    	double [][][] dxyzatr =     QuadCLT.getDxyzatrPIMU(
        		clt_parameters,
        		quadCLTs); //,
//        		quadCLTs[ref_index].getPimuOffsets());
    	double [] timestamps = new double [quadCLTs.length];
    	for (int nscene = 0; nscene < quadCLTs.length; nscene++) {
    		timestamps[nscene] = quadCLTs[nscene].getTimeStamp();
    	}
	
    	System.out.println("N\tTimestamp\timsX\timsY\timsZ\timsA\timsT\timsR"+
    			"\tpimuX\tpimuY\tpimuZ\tpimuA\tpimuT\tpimuR"+
    			"\tvelX\tvelY\tvelZ\tomegaA\tomegaT\tomegaR");
    	for (int nscene = 0; nscene < quadCLTs.length; nscene++) if (quadCLTs[nscene] != null) {
       		System.out.println(
    				nscene+"\t"+timestamps[nscene]+"\t"+
    				ims_xyzatr[nscene][0][0]+ "\t"+ims_xyzatr[nscene][0][1]+ "\t"+ims_xyzatr[nscene][0][2]+"\t"+
    				ims_xyzatr[nscene][1][0]+ "\t"+ims_xyzatr[nscene][1][1]+ "\t"+ims_xyzatr[nscene][1][2]+"\t"+
    				pimu_xyzatr[nscene][0][0]+"\t"+pimu_xyzatr[nscene][0][1]+"\t"+pimu_xyzatr[nscene][0][2]+"\t"+
    				pimu_xyzatr[nscene][1][0]+"\t"+pimu_xyzatr[nscene][1][1]+"\t"+pimu_xyzatr[nscene][1][2]+"\t"+
    				dxyzatr[nscene][0][0]+    "\t"+dxyzatr[nscene][0][1]+    "\t"+dxyzatr[nscene][0][2]+"\t"+
    				dxyzatr[nscene][1][0]+    "\t"+dxyzatr[nscene][1][1]+    "\t"+dxyzatr[nscene][1][2]+"\t");
    	}
    	System.out.println();
	}
	
	public static int [][] getFPNPairs(
			ArrayList<Integer> fpn_list,
			double             fpn_mitigate_dist,
			boolean            rectilinear,
			QuadCLT []         quadCLTs,
			double [][][]      scenes_xyzatr,
			double             avg_z,
			int                latest_scene, // >= earliest_scene 
			int                earliest_scene){
		int [][] fpn_pairs = new int [fpn_list.size()][2];
//		int [] fpn_ref_scenes = new int [quadCLTs.length];
		boolean [] need_fpn_mitigate = new boolean [quadCLTs.length];
		for (int i:fpn_list) {
			need_fpn_mitigate[i] = true;
		}
//		Arrays.fill(fpn_ref_scenes, -1);
		int fpn_index = latest_scene; //  - 1;
		for (int ipair = 0; ipair < fpn_pairs.length; ipair++)	{ 
			// find first needed
			for (;(fpn_index >= 0) && !need_fpn_mitigate[fpn_index]; fpn_index--);
			// find closest but farther than fpn_mitigate_dist;
			fpn_pairs[ipair][0] = fpn_index; 
			fpn_pairs[ipair][1] = -1;
			double max_dist = 0.0;
			int best_cond_pair = -1;
			double best_dist = Double.NaN;
			for (int i = latest_scene; i >= earliest_scene; i--) {
				double fpn_dist = quadCLTs[latest_scene].estimateAverageShift(
						scenes_xyzatr[i], // double [][] xyzatr0,
						scenes_xyzatr[fpn_index],    // double [][] xyzatr1,
						avg_z,                    // double average_z,
						false,                    // boolean use_rot,
						rectilinear);                   // boolean rectilinear)
				
				if (fpn_dist < fpn_mitigate_dist) {
					fpn_dist = quadCLTs[i].estimateAverageShift(
							scenes_xyzatr[i], // double [][] xyzatr0,
							scenes_xyzatr[fpn_index],    // double [][] xyzatr1,
							avg_z,                    // double average_z,
							true,                    // boolean use_rot,
							rectilinear);                   // boolean rectilinear)
				}
				if (fpn_dist >= fpn_mitigate_dist) {
					if (!(fpn_dist >= best_dist)) { // Double.NaN OK
						best_dist = fpn_dist;
						fpn_pairs[ipair][1] = i;
						need_fpn_mitigate[fpn_pairs[ipair][0]] = false;
					}
				} else if (fpn_dist > max_dist) {
					max_dist = fpn_dist;
					best_cond_pair = i;
				}
			}
			if (fpn_pairs[ipair][1] < 0) {
				System.out.print("Failed to find a good pair for scene #"+fpn_pairs[ipair][0]+". ");
				System.out.println("Using #"+best_cond_pair+" with distance = "+max_dist+" pix.");
				fpn_pairs[ipair][1] = best_cond_pair;
				need_fpn_mitigate[fpn_pairs[ipair][0]] = false;
			}
		}
		return fpn_pairs;
	}
	
	public static int getFPNUnResolved(int [][] pairs) {
		int n = 0;
		for (int [] pair:pairs) {
			if (pair[1] < 0) n++;
		}
		return n;
	}
	
	public static int setInitialOrientations(
			final CLTParameters          clt_parameters,
			int                          min_num_scenes,
			final ColorProcParameters    colorProcParameters,
			final QuadCLT[]              quadCLTs, //
			final int                    ref_index,
			final QuadCLT.SetChannels [] set_channels,
			final boolean                batch_mode,
			int                          earliest_scene,
			int []                       start_ref_pointers, // [0] - earliest valid scene, [1] ref_index
			final int                    threadsMax,  // int               threadsMax,
			final boolean                updateStatus,
			final int                    debugLevel) {
		int num_scene_retries = 10;
		int retry_radius =      2;
		double  scale_extrap_atr =       clt_parameters.imp.scale_extrap_atr;
		double  scale_extrap_xyz =       clt_parameters.imp.scale_extrap_xyz;
		int     avg_len	=                clt_parameters.imp.avg_len;
		RMSEStats rmse_stats =      new RMSEStats();
		double  min_ref_str =       clt_parameters.imp.min_ref_str;
		boolean ref_need_lma =      clt_parameters.imp.ref_need_lma;
		double  min_ref_frac=       clt_parameters.imp.min_ref_frac;
		boolean sfm_filter =        clt_parameters.imp.sfm_filter;  //true;   // use SfM filtering if available 
		double  sfm_minmax =        clt_parameters.imp.sfm_minmax;  //10.0;   // minimal value of the SfM gain maximum to consider available
		double  sfm_fracmax =       clt_parameters.imp.sfm_fracmax; // 0.75;  // minimal fraction of the SfM maximal gain
		double  sfm_fracall =       clt_parameters.imp.sfm_fracall; // 0.3;   // minimal relative area of the SfM-e
		
//    	int min_num_scenes =        clt_parameters.imp.min_num_scenes; // abandon series if there are less than this number of scenes in it 
    	int max_num_scenes =        clt_parameters.imp.max_num_scenes; // cut longer series
    	
    	double boost_max_short = 2.0; // 
    	double boost_zoom_short = 1.5; // 
    	
    	
    	
		double [] lma_rms = new double[2];
		double [] use_atr = null;

		int tilesX =  quadCLTs[ref_index].getTileProcessor().getTilesX();
//        int tilesY =  quadCLTs[ref_index].getTileProcessor().getTilesY();
        int tile_size = quadCLTs[ref_index].getTileProcessor().getTileSize();
		double min_offset =         0.0; // clt_parameters.imp.min_offset;
		double max_offset =         clt_parameters.imp.max_rel_offset * tilesX * tile_size;
		double max_roll =           clt_parameters.imp.max_roll_deg*Math.PI/180.0;
		double max_zoom_diff =      clt_parameters.imp.max_zoom_diff;
		boolean fpn_skip =          clt_parameters.imp.fpn_skip; // if false - fail as before
		boolean fpn_rematch =       clt_parameters.imp.fpn_rematch; // if false - keep previous
		double [] min_max = {min_offset, max_offset, 0.0} ; // {min, max, actual rms)
		int []    fail_reason = new int[1];   // null or int[1]: 0 - OK, 2 - LMA, 3 - min, 4 - max
		for (int scene_index =  ref_index - 1; scene_index >= 0 ; scene_index--) {
			// to include ref scene photometric calibration
			quadCLTs[scene_index] = quadCLTs[ref_index].spawnNoModelQuadCLT(
					set_channels[scene_index].set_name,
					clt_parameters,
					colorProcParameters, //
					threadsMax,
					debugLevel-2);
		} // split cycles to remove output clutter
		ErsCorrection ers_reference = quadCLTs[ref_index].getErsCorrection();		
		int debug_scene = -15;
		boolean debug2 = !batch_mode; // false; // true;
		boolean [] reliable_ref = null;
		boolean use_lma_dsi =      clt_parameters.imp.use_lma_dsi;
        double [] reduced_strength = new double[1];
		if (min_ref_str > 0.0) {
			reliable_ref = quadCLTs[ref_index].getReliableTiles( // will be null if does not exist.
					false, // boolean use_combo,
					min_ref_str,   // double min_strength,
					min_ref_frac,  // double min_ref_frac,
					ref_need_lma,  // boolean needs_lma);
					true,          // boolean needs_lma_combo);
					sfm_filter,  // boolean sfm_filter, // use SfM filtering if available                          
					sfm_minmax,  // double  sfm_minmax, // minimal value of the SfM gain maximum to consider available
					sfm_fracmax, // double  sfm_fracmax,// minimal fraction of the SfM maximal gain
					sfm_fracall, // double  sfm_fracall,// minimal relative area of the SfM-enabled tiles (do not apply filter if less)						
					reduced_strength,  // if not null will return >0 if had to reduce strength (no change if did not reduce)
					debugLevel); // int       debugLevel)
			if (reduced_strength[0] > 0) {
				use_lma_dsi = false; // too few points
			}
			if (debug2) {
				double [] dbg_img = new double [reliable_ref.length];
				for (int i = 0; i < dbg_img.length; i++) {
					dbg_img[i] = reliable_ref[i]?1:0;
				}
				ShowDoubleFloatArrays.showArrays(
						dbg_img,
						quadCLTs[ref_index].getTileProcessor().getTilesX(),
						quadCLTs[ref_index].getTileProcessor().getTilesY(),
						"reliable_ref");
			}
		}
		
		double max_z_change = Double.NaN; // only applicable for drone images
		if (max_zoom_diff > 0) { // ignore if set to
			double avg_z = quadCLTs[ref_index].getAverageZ(true); // use lma
			max_z_change = avg_z * max_zoom_diff;
			if (debugLevel > -3) {
				System.out.println("Setting maximal Z-direction movement to "+ max_z_change+" m.");
			}
		}

		
		double [][][] scenes_xyzatr =      new double [quadCLTs.length][][]; // previous scene relative to the next one
		scenes_xyzatr[ref_index] =         new double[2][3]; // all zeros
		boolean after_spiral = false;
		boolean got_spiral = false;
		int    search_rad =        clt_parameters.imp.search_rad;    // 10;
		ers_reference.addScene(quadCLTs[ref_index].getImageName(), // add reference scene (itself) too
				scenes_xyzatr[ref_index][0],
				scenes_xyzatr[ref_index][1],
				ZERO3, // ers_scene.getErsXYZ_dt(),		
				ZERO3); // ers_scene.getErsATR_dt()		
		for (int scene_index =  ref_index - 1; scene_index >= earliest_scene ; scene_index--) {
			if ((ref_index - scene_index) >= max_num_scenes){
				earliest_scene = scene_index + 1;
				if (debugLevel > -3) {
					System.out.println("Cutting too long series at scene "+scene_index+" (of "+ quadCLTs.length+". excluding) ");
				}
				// set this and all previous to null
				for (; scene_index >= 0 ; scene_index--) {
					ers_reference.addScene(quadCLTs[scene_index].getImageName()); // remove
				}
				break;
			}
			if (scene_index == debug_scene) {
				System.out.println("scene_index = "+scene_index);
				System.out.println("scene_index = "+scene_index);
			}
			QuadCLT scene_QuadClt = quadCLTs[scene_index];
			// get initial xyzatr:
			// TODO:repeat spiralSearchATR if found scene was too close
//			if (scene_index ==  ref_index - 1) { // search around for the best fit
			if (!got_spiral) { // search around for the best fit
				use_atr = spiralSearchATR(
						clt_parameters, // CLTParameters            clt_parameters,
				    	use_lma_dsi,    // may be turned off by the caller if there are too few points
						search_rad,     // int    search_rad
						quadCLTs[ref_index], // QuadCLT             reference_QuadClt,
						scene_QuadClt, // QuadCLT                   scene_QuadClt,
						reliable_ref, // ********* boolean []                reliable_ref,
						debugLevel);
				if (use_atr == null) {
					if (num_scene_retries-- > 0) {
						search_rad = retry_radius; // faster, no need/sense to look far
						continue;
					}
					earliest_scene = scene_index + 1;
					if (debugLevel > -3) {
						System.out.println("Pass multi scene "+scene_index+" (of "+ quadCLTs.length+") "+
								quadCLTs[ref_index].getImageName() + "/" + scene_QuadClt.getImageName()+
								" spiralSearchATR() FAILED. Setting earliest_scene to "+earliest_scene);
					}
					// set this and all previous to null
					for (; scene_index >= 0 ; scene_index--) {
						if (quadCLTs[scene_index] == null) {
							System.out.println("setInitialOrientations(): quadCLTs["+scene_index+"] is already null!");
						} else {
							ers_reference.addScene(quadCLTs[scene_index].getImageName()); // remove
						}
					}
					break; // failed with even first before reference
				}
				after_spiral = true;
				got_spiral = true;
				scenes_xyzatr[scene_index] = new double [][] {new double[3], use_atr};
			} else { // assume linear motion
				after_spiral = false;
				if (scene_index == debug_scene) {
					System.out.println("adjusting orientation, scene_index="+scene_index);
					System.out.println("adjusting orientation, scene_index="+scene_index);
				}
				int na = avg_len;
				if ((scene_index + 1 + na) > ref_index) {
					na = ref_index - (scene_index + 1); 
				}
				
				double [][] last_diff = ErsCorrection.combineXYZATR(
						scenes_xyzatr[scene_index + 1],
						ErsCorrection.invertXYZATR(scenes_xyzatr[scene_index+1 + na]));
				for (int i = 0; i < 3; i++) {
					last_diff[0][i] /= na;
					last_diff[1][i] /= na;
				}
				for (int i = 0; i < 3; i++) {
					last_diff[0][i] *= scale_extrap_xyz;
					last_diff[1][i] *= scale_extrap_atr;
				}					
				scenes_xyzatr[scene_index] = ErsCorrection.combineXYZATR(
						scenes_xyzatr[scene_index+1],
						last_diff);
			}
			// Refine with LMA
			double [][] initial_pose = new double[][]{
				scenes_xyzatr[scene_index][0].clone(),
				scenes_xyzatr[scene_index][1].clone()};
			boolean rot_to_transl = after_spiral && clt_parameters.ilp.ilma_translation_priority;
			double [] reg_weights = rot_to_transl? clt_parameters.ilp.ilma_regularization_weights0 : clt_parameters.ilp.ilma_regularization_weights;
			min_max[1] = max_offset;
			//boost_zoom_short
			double max_z_change_scaled = max_z_change;
			if ((ref_index - scene_index) < min_num_scenes) {
				min_max[1] = boost_max_short * max_offset;
				max_z_change_scaled = max_z_change * boost_zoom_short;
				if (debugLevel > -3) {
					System.out.println("As the current series ("+(ref_index - scene_index)+
							" scenes) is shorter than minimal ("+min_num_scenes+"), the maximal shift is scaled by "+
							boost_max_short + " to " + (boost_max_short * max_offset)+
							" pixels");
				}
			}
			if ((scene_index - earliest_scene) < min_num_scenes) {
				min_max[1] = boost_max_short * max_offset;
				max_z_change_scaled = max_z_change * boost_zoom_short;
				if (debugLevel > -3) {
					System.out.println("As the remaining number of scenes to process ("+(scene_index - earliest_scene + 1)+
							") is less than minimal ("+min_num_scenes+"), the maximal shift is scaled by "+
							boost_max_short + " to " + (boost_max_short * max_offset)+
							" pixels");
				}
			}
			// Does not use motion blur for reference scene here!
			scenes_xyzatr[scene_index] = adjustPairsLMAInterscene(
					clt_parameters,      // CLTParameters  clt_parameters,
					use_lma_dsi,         // clt_parameters.imp.use_lma_dsi,
					false,               //	boolean        fpn_disable,   // disable fpn filter if images are known to be too close
					true,                // boolean        disable_ers,
					min_max,             // double []      min_max,       // null or pair of minimal and maximal offsets
					fail_reason,         // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
					quadCLTs[ref_index], // QuadCLT reference_QuadCLT,
					null,                // double []        ref_disparity, // null or alternative reference disparity
					reliable_ref,        // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
					scene_QuadClt,                                  // QuadCLT scene_QuadCLT,
					initial_pose[0],        // xyz
					initial_pose[1],        // atr
					initial_pose[0],        // double []      scene_xyz_pull, // if both are not null, specify target values to pull to 
					rot_to_transl? (new double[3]):initial_pose[1],        // double []      scene_atr_pull, // 
					clt_parameters.ilp.ilma_lma_select,                                  // final boolean[]   param_select,
					reg_weights, // clt_parameters.ilp.ilma_regularization_weights,                              //  final double []   param_regweights,
					lma_rms,                                        // double []      rms, // null or double [2]
					clt_parameters.imp.max_rms,                     // double         max_rms,
					clt_parameters.imp.debug_level);                // 1); // -1); // int debug_level);

			boolean adjust_OK = scenes_xyzatr[scene_index] != null;
			if (adjust_OK) { // check only for initial orientation, do not check on readjustments
				if (Math.abs(scenes_xyzatr[scene_index][1][2]) > max_roll) {
					fail_reason[0] = FAIL_REASON_ROLL;
					adjust_OK = false;
				}
				if (max_zoom_diff > 0) { // ignore if set to 
					if (Math.abs(scenes_xyzatr[scene_index][0][2]) > max_z_change) {
						if (Math.abs(scenes_xyzatr[scene_index][0][2]) > max_z_change_scaled) {
							fail_reason[0] = FAIL_REASON_ZOOM;
							adjust_OK = false;
						} else {
							System.out.println("Z-change "+Math.abs(scenes_xyzatr[scene_index][0][2])+
							"m exceeds limit of "+max_z_change+"m, but as it is beginning/end of the series, limit is raised to "+
							max_z_change_scaled+"m");
						}
					}
				}
			}
			// FAIL_REASON_ROLL
			handle_failure: {
				if (!adjust_OK) {
//					boolean OK = false;
					System.out.println("LMA failed at nscene = "+scene_index+". Reason = "+fail_reason[0]+
							" ("+getFailReason(fail_reason[0])+")");
					if ((fail_reason[0]==FAIL_REASON_MIN) || ((fail_reason[0]==FAIL_REASON_LMA) && !got_spiral)) {
						if (fpn_skip) {
							System.out.println("fpn_skip is set, just using initial pose");
							scenes_xyzatr[scene_index] = initial_pose;
							break handle_failure; // to next scene
						} else {
							System.out.println("fpn_skip is not set, aborting series and adjusting earliest_scene");
							// set this and all previous to null
						}
					}
					// all other reasons lead to failure
					earliest_scene = scene_index + 1;
					if (debugLevel > -4) {
						System.out.println("Pass multi scene "+scene_index+" (of "+ quadCLTs.length+") "+
								quadCLTs[ref_index].getImageName() + "/" + scene_QuadClt.getImageName()+
								" FAILED. Setting earliest_scene to "+earliest_scene + " Failure reason: " + fail_reason[0]+
								" ("+getFailReason(fail_reason[0])+")");
					}
					// set this and all previous to null
					for (; scene_index >= 0 ; scene_index--) {
						ers_reference.addScene(quadCLTs[scene_index].getImageName()); // remove
						//					quadCLTs[scene_index] = null; // completely remove early scenes?
					}
					break;
				}
			}
			if (after_spiral && (scene_index < (ref_index-1))) { // need to interpolate skipped scenes
				// here we expect very small translations/angles, so linear scaling is OK
				double s = 1.0 / (ref_index - scene_index);
				for (int interp_index = ref_index-1; interp_index > scene_index; interp_index--) {
					scenes_xyzatr[interp_index] = new double[2][3];
					for (int i = 0; i < 2; i++) {
						for (int j = 0; j < 3; j++) {
							scenes_xyzatr[interp_index][i][j] =
									scenes_xyzatr[scene_index][i][j] * s *(interp_index - scene_index); 
						}
					}
					ers_reference.addScene(scene_QuadClt.getImageName(),
							scenes_xyzatr[interp_index][0],
							scenes_xyzatr[interp_index][1],
							ZERO3, // ers_scene.getErsXYZ_dt(),		
							ZERO3 // ers_scene.getErsATR_dt()		
							);
				}
			}
			
			/* old version
			if (scenes_xyzatr[scene_index] == null) {
				earliest_scene = scene_index + 1;
				if (debugLevel > -3) {
					System.out.println("Pass multi scene "+scene_index+" (of "+ quadCLTs.length+") "+
							quadCLTs[ref_index].getImageName() + "/" + scene_QuadClt.getImageName()+
							" FAILED. Setting earliest_scene to "+earliest_scene);
				}
				// set this and all previous to null
				for (; scene_index >= 0 ; scene_index--) {
					ers_reference.addScene(quadCLTs[scene_index].getImageName(), null);
				}
				break;
			}
			*/
			// TODO: Maybe after testing high-ers scenes - restore velocities from before/after scenes
			ers_reference.addScene(scene_QuadClt.getImageName(),
					scenes_xyzatr[scene_index][0],
					scenes_xyzatr[scene_index][1],
					ZERO3, // ers_scene.getErsXYZ_dt(),		
					ZERO3 // ers_scene.getErsATR_dt()		
					);
		    rmse_stats.add(lma_rms[0]);
			if (debugLevel > -3) {
				System.out.println("Pass multi scene "+scene_index+" (of "+ quadCLTs.length+") "+
						quadCLTs[ref_index].getImageName() + "/" + scene_QuadClt.getImageName()+
		                " Done. RMS="+lma_rms[0]+
		                ", maximal so far was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
			}
		} // for (int scene_index =  ref_index - 1; scene_index >= 0 ; scene_index--)
		if ((ref_index - earliest_scene + 1) < min_num_scenes) {
			System.out.println("Total number of useful scenes = "+(ref_index - earliest_scene + 1)+
					" < "+min_num_scenes+". Scrapping this series.");
			if (start_ref_pointers != null) {
				start_ref_pointers[0] = earliest_scene;
			}
			return -1;
		}
		if (earliest_scene > 0) {
			System.out.println("setInitialOrientations(): Not all scenes matched, earliest useful scene = "+earliest_scene+
					" (total number of scenes = "+(ref_index - earliest_scene + 1)+
					").Maximal RMSE was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
		} else if (debugLevel > -4) {
			System.out.println("All multi scene passes are Done. Maximal RMSE was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
		}
		quadCLTs[ref_index].set_orient(1); // first orientation
		quadCLTs[ref_index].set_accum(0);  // reset accumulations ("build_interscene") number
		quadCLTs[ref_index].saveInterProperties( // save properties for interscene processing (extrinsics, ers, ...)  // null pointer
				null, // String path,             // full name with extension or w/o path to use x3d directory
				debugLevel+1);
		return earliest_scene;
	}
	
	public static void readjustShowCorrected(
			CLTParameters  clt_parameters,
			QuadCLT []     quadCLTs,
			double [][]    dxyzatr_dt_nscene, 
			double [][]    dbg_mb_img, // initialize to double[quadCLTs.length][]
			double         mb_max_gain,
			double []      mb_ref_disparity,
			int            ref_index,
			int            nscene,
			int            debug_scene, //  = 68			
			int            debugLevel) {
		boolean show_corrected = false;
		double  mb_tau =      clt_parameters.imp.mb_tau;      // 0.008; // time constant, sec
		int tilesX =  quadCLTs[ref_index].getTileProcessor().getTilesX();
        int tilesY =  quadCLTs[ref_index].getTileProcessor().getTilesY();
		ErsCorrection ers_reference = quadCLTs[ref_index].getErsCorrection();
		if (nscene == debug_scene) {
			System.out.println("2.nscene = "+nscene);
			System.out.println("3.nscene = "+nscene);
		}
		dbg_mb_img[nscene] = new double [tilesX*tilesY*2];
		Arrays.fill(dbg_mb_img[nscene],Double.NaN);
		String ts = quadCLTs[nscene].getImageName();
		double [] mb_scene_xyz = (nscene != ref_index)? ers_reference.getSceneXYZ(ts):ZERO3;
		double [] mb_scene_atr = (nscene != ref_index)? ers_reference.getSceneATR(ts):ZERO3;
		double [][] ref_pXpYD = OpticalFlow.transformToScenePxPyD( // full size - [tilesX*tilesY], some nulls
        		null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
        		mb_ref_disparity, // dls[0],  // final double []   disparity_ref, // invalid tiles - NaN in disparity (maybe it should not be masked by margins?)
        		ZERO3,         // final double []   scene_xyz, // camera center in world coordinates
        		ZERO3,         // final double []   scene_atr, // camera orientation relative to world frame
        		quadCLTs[ref_index],     // final QuadCLT     scene_QuadClt,
        		quadCLTs[ref_index]);    // final QuadCLT     reference_QuadClt)

		double [][] motion_blur = OpticalFlow.getMotionBlur( // dxyzatr_dt[][] should not be scaled here
				quadCLTs[ref_index],   // QuadCLT        ref_scene,
				quadCLTs[nscene],      // QuadCLT        scene,         // can be the same as ref_scene
				ref_pXpYD,             // double [][]    ref_pXpYD,     // here it is scene, not reference!
				mb_scene_xyz,          // double []      camera_xyz,
				mb_scene_atr,          // double []      camera_atr,
				dxyzatr_dt_nscene[0], // double []      camera_xyz_dt,
				dxyzatr_dt_nscene[1], // double []      camera_atr_dt,
				-1,                    // int            shrink_gaps,  // will gaps, but not more that grow by this
				debugLevel); // int            debug_level)
		for (int nTile = 0; nTile < motion_blur[0].length; nTile++) {
			int tx = nTile % tilesX;
			int ty = nTile / tilesX;
			dbg_mb_img[nscene][tx + tilesX * (ty*2 +0)] = mb_tau * motion_blur[0][nTile];
			dbg_mb_img[nscene][tx + tilesX * (ty*2 +1)] = mb_tau * motion_blur[1][nTile];
		}
		while (show_corrected) {
			ImagePlus imp_mbc = QuadCLT.renderGPUFromDSI(
					-1,                  // final int         sensor_mask,
					false,               // final boolean     merge_channels,
					null,                // final Rectangle   full_woi_in,      // show larger than sensor WOI (or null)
					clt_parameters,      // CLTParameters     clt_parameters,
					mb_ref_disparity,    // double []         disparity_ref,
					// motion blur compensation 
					mb_tau,              // double            mb_tau,      // 0.008; // time constant, sec
					mb_max_gain,         // double            mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
					motion_blur,         // double [][]       mb_vectors,  //
					
					mb_scene_xyz,        // ZERO3,               // final double []   scene_xyz, // camera center in world coordinates
					mb_scene_atr,        // final double []   scene_atr, // camera orientation relative to world frame
					quadCLTs[nscene],    // final QuadCLT     scene,
					quadCLTs[ref_index], // final QuadCLT     ref_scene, // now - may be null - for testing if scene is rotated ref
					false, // toRGB,               // final boolean     toRGB,
					clt_parameters.imp.show_color_nan,
					quadCLTs[nscene].getImageName()+"-MOTION_BLUR_CORRECTED", // String            suffix,
					THREADS_MAX,          // int               threadsMax,
					debugLevel);         // int         debugLevel)
			imp_mbc.show();
			ImagePlus imp_mbc_merged = QuadCLT.renderGPUFromDSI(
					-1,                  // final int         sensor_mask,
					true,               // final boolean     merge_channels,
					null,                // final Rectangle   full_woi_in,      // show larger than sensor WOI (or null)
					clt_parameters,      // CLTParameters     clt_parameters,
					mb_ref_disparity,    // double []         disparity_ref,
					// motion blur compensation 
					mb_tau,              // double            mb_tau,      // 0.008; // time constant, sec
					mb_max_gain,         // double            mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
					motion_blur,         // double [][]       mb_vectors,  //
					
					mb_scene_xyz,        // ZERO3,               // final double []   scene_xyz, // camera center in world coordinates
					mb_scene_atr,        // final double []   scene_atr, // camera orientation relative to world frame
					quadCLTs[nscene],    // final QuadCLT     scene,
					quadCLTs[ref_index], // final QuadCLT     ref_scene, // now - may be null - for testing if scene is rotated ref
					false, // toRGB,               // final boolean     toRGB,
					clt_parameters.imp.show_color_nan,
					quadCLTs[nscene].getImageName()+"-MOTION_BLUR_CORRECTED-MERGED", // String            suffix,
					THREADS_MAX,          // int               threadsMax,
					debugLevel);         // int         debugLevel)
			imp_mbc_merged.show();
			show_corrected = false; // manually set/reset
		}
	}	
	
//    		double        avg_rlen,// >= 1.0
	/**
	 * Re-adjust camera poses with LMA after initial poses are already known.
	 * Fitting can be done for "reliable_ref" tiles only, they may be filtered
	 * by SfM gain external to this method).
	 * Fitting can be performed with ERS for Azimuth and Tilt Rotations (other
	 * ERS components are preserved intact).
	 * By default X,Y,Z and Roll are adjusted (with or without 2 angular velocities).
	 * Optionally one of the 2 refinement methods is used:
	 *   a) lpf_xy true:
	 *   previous X and Y are low-pass filtered, by running-average with cosine window
	 *   of avg_rlen half-width     
	 *   b) readjust_xy_ims && !lpf_xy:
	 *   X and Y are refined with IMS linear velocities shifted (by adding ax+b to
	 *   coordinates) to best fit previously determined coordinates (which in turn
	 *   depended on IMS angles that are more accurate than coordinates). The result
	 *   still has absolute AGL accuracy determined by the intrascene measurements
	 *   that are good to ~1% at 100m if properly field-calibrated
	 *   In both a) and b) all of X,Y,Z,A,T, and R are adjusted (with optional dA/dt
	 *   and dT/dt as ERS), and X and Y are pulled to new calculated values (by
	 *   averaging or IMS) with strength reg_weight_xy, other parameters (Z,A,T,R) are
	 *   not regularized.
	 *   Special cases:
	 *   c) (reg_weight_xy == 0.0) && readjust_xy_ims - freeze X,Y, freely adjust
	 *   Z,A,T,R (optional subsequent runs after X,Y are "pulled" regardless of IMU or
	 *   not
	 *   d) (reg_weight_xy == 0.0) && lpf_xy - freely adjust X,Y,Z, A,T,R (typical mode
	 *   for the forward-looking applications
	 * @param clt_parameters   configuration parameters
	 * @param mb_max_gain      maximal motion blur gain
	 * @param disable_ers      disable adjustment of the angular velocities through ERS.
	 *                         ERS may be disabled inside adjustDiffPairsLMAInterscene()
	 *                         if not all 4 image quadrants have enough "reliable" tiles.
	 * @param disable_ers_y    disable adjustment of the angular tilt velocities through ERS.
	 *                         It can be disabled until SfM as it conflicts with SfM - both
	 *                         tilt and tilt rotation lead to change of the vertical image scale.
	 * @param configured_lma   Ignore calculated LMA parameter selection and regularization
	 *                         weights, use ones from the configuration parameters
	 * @param lpf_xy           Low pass filter X and Y "pull" values (derived from the
	 *                         current ones). Overrides readjust_xy_ims as they are mutually
	 *                         exclusive.
	 * @param avg_rlen         half-width of the LPF window, used with lpf_xy==true
	 * @param readjust_xy_ims  readjust X and Y from current values and IMS linear velocities
	 * @param reg_weight_xy    regularization weights for X and Y, valid with any of lpf_xy or
	 *                         readjust_xy_ims.
	 * @param reliable_ref     tile mask in line-scan order. LMA fitting ignores data for
	 *                         unselected tiles.  
	 * @param quadCLTs         array of scene instances. Only scenes between range[0] and
	 *                         range[1] (inclusive) will be used.
	 * @param ref_index        index of the reference frame in the scenes array
	 * @param range            {earliest_scene_index, latest_scene_index};
	 * @param ers_mode         0 - keep, 1 - set from velocity, 2 - set from IMS. Now always 0
	 * @param test_motion_blur motion blur debugging and images
	 * @param debugLevel       debug level
	 * @return earliest scene - may be higher than range[0] if LMA failed (should not happen at
	 *         this pass, should fail during initial orientation.
	 */
	public static int  reAdjustPairsLMAInterscene( // after combo dgi is available and preliminary poses are known
			CLTParameters  clt_parameters,
			double         mb_max_gain,
			boolean        use_Z,
			boolean        use_R,
			boolean        disable_ers,
			boolean        disable_ers_y,
			boolean        disable_ers_r,
			boolean        lma_xyzatr,
			boolean        configured_lma,
			boolean        lpf_xy,       // lpf x and y, re-adjust X,Y,Z,A,T,R with pull for X,Y. Disables  
			double         avg_rlen,
			boolean        readjust_xy_ims,  // readjust X,Y fromIMS linear velocities and full X,Y movement
			                                 // from the previous adjustment. Adjust A,T,R,Z (and optionally
			                                 // angular velocities) freely
			double         reg_weight_xy,    // regularization weight for X and Y when lpf_xy or readjust_xy_ims
			boolean []     reliable_ref,     // null or bitmask of reliable reference tiles			
    		QuadCLT []     quadCLTs,
    		int            ref_index,
    		int     []     range,
    		int            ers_mode, // 0 - keep, 1 - set from velocity, 2 - set from IMS
    		boolean        test_motion_blur,
			int            debugLevel)
	{ 
		System.out.println("reAdjustPairsLMAInterscene(): using mb_max_gain="+mb_max_gain+
				", disable_ers="+disable_ers+", disable_ers_y="+disable_ers_y+
				", use_Z="+use_Z+", use_R="+use_R);
		boolean freeze_xy_pull =    clt_parameters.imp.freeze_xy_pull;    // true; // false; // true; // debugging freezing xy to xy_pull
		boolean copy_pull_current = clt_parameters.imp.copy_pull_current; //false; // true;
		boolean restore_imu =       clt_parameters.imp.restore_imu;       //false; // restore imu omega-az and omega-tl, freeze ERS, adjust X,Y,Z,Az,Tl,Rl
		boolean[] param_select =  configured_lma? clt_parameters.ilp.ilma_lma_select :
			ErsCorrection.getParamSelect(
					lma_xyzatr || use_Z,   // boolean use_Z,
					lma_xyzatr || use_R,   // boolean use_R,
					lma_xyzatr || ( !freeze_xy_pull && (!readjust_xy_ims || (reg_weight_xy != 0))), // false only in mode c): freeze X,Y// boolean use_XY
					lma_xyzatr || ( readjust_xy_ims || lpf_xy),  // boolean use_AT,
					!disable_ers, //boolean use_ERS);//clt_parameters.ilp.ilma_lma_select;
					!disable_ers_y, // boolean use_ERS_tilt,
					!disable_ers_r); // boolean use_ERS_roll);
		final double []    param_regweights =  configured_lma? clt_parameters.ilp.ilma_regularization_weights :
			ErsCorrection.getParamRegWeights(
					(freeze_xy_pull? 0.0: reg_weight_xy)); // double  reg_weight,
		boolean fail_on_zoom_roll=false; // it should fail on initial adjustment
    	boolean fmg_reorient_en =    clt_parameters.imp.fmg_reorient_en; // enable IMS-based FPN mitigation for readjustmnet orientation
    	double  fmg_distance =       clt_parameters.imp.fmg_distance; // try to find other reference scene not closer than this pixels
    	double  fmg_max_quad =       clt_parameters.imp.fmg_max_quad; // estimate offset by 4 points (rooll-aware, 25% from center) if center
    	boolean fmg_rectilinear =    clt_parameters.imp.fmg_rectilinear;// use rectilinear model for scene offset estimation 
		boolean use_precomp =        clt_parameters.imp.use_precomp;// try to predict initial error from previous scenes
		
//		int     avg_len	=                clt_parameters.imp.avg_len;
        // Set up velocities from known coordinates, use averaging
        double  half_run_range =    clt_parameters.ilp.ilma_motion_filter; // 3.50; // make a parameter
		double [][] dbg_scale_dt = {clt_parameters.ilp.ilma_scale_xyz, clt_parameters.ilp.ilma_scale_atr};
		boolean debug_ers =         clt_parameters.ilp.ilma_debug_ers ; // true;
        double avg_z = quadCLTs[ref_index].getAverageZ(true); // for offset estimation		
		if (debugLevel > -3) {
			System.out.println("reAdjustPairsLMAInterscene(): avg_z="+avg_z);
		}
		String dbg_ers_string = 
				((dbg_scale_dt[1][0] > 0)?"a":((dbg_scale_dt[1][0] < 0) ? "A":"0"))+
				((dbg_scale_dt[1][1] > 0)?"t":((dbg_scale_dt[1][1] < 0) ? "T":"0"))+
				((dbg_scale_dt[1][2] > 0)?"r":((dbg_scale_dt[1][2] < 0) ? "R":"0"))+
				((dbg_scale_dt[0][0] > 0)?"x":((dbg_scale_dt[0][0] < 0) ? "X":"0"))+
				((dbg_scale_dt[0][1] > 0)?"y":((dbg_scale_dt[0][1] < 0) ? "Y":"0"))+
				((dbg_scale_dt[0][2] > 0)?"z":((dbg_scale_dt[0][2] < 0) ? "Z":"0"));
		boolean mb_en =       clt_parameters.imp.mb_en;
		double  mb_tau =      clt_parameters.imp.mb_tau;      // 0.008; // time constant, sec
		int     margin =      clt_parameters.imp.margin;
        double second_margin = 1.1; // allow slightly larger offset for readjsutments (to be limited by the first pass)
        double boost_max_short = 5.0; // 2.0; // Some bug?
		int earliest_scene = range[0];
		int last_scene =     range[1];
	    boolean use_combo_dsi =        clt_parameters.imp.use_combo_dsi;
	    boolean use_lma_dsi =          clt_parameters.imp.use_lma_dsi;
	    
		int tilesX =  quadCLTs[ref_index].getTileProcessor().getTilesX();
        int tilesY =  quadCLTs[ref_index].getTileProcessor().getTilesY();
        int tile_size = quadCLTs[ref_index].getTileProcessor().getTileSize();
		double min_offset =         clt_parameters.imp.min_offset;
		double max_offset =         boost_max_short*second_margin*clt_parameters.imp.max_rel_offset * tilesX * tile_size;
		double max_roll =           second_margin*clt_parameters.imp.max_roll_deg*Math.PI/180.0;
		double max_zoom_diff =      second_margin*clt_parameters.imp.max_zoom_diff;
		boolean fpn_skip =          clt_parameters.imp.fpn_skip; // if false - fail as before
		boolean fpn_rematch =       clt_parameters.imp.fpn_rematch; // if false - keep previous
		double [] min_max = {min_offset, max_offset, 0.0} ; // {min, max, actual rms)
		int []    fail_reason = new int[1];   // null or int[1]: 0 - OK, 2 - LMA, 3 - min, 4 - max
		System.out.println("reAdjustPairsLMAInterscene(): min_offset="+min_max[0]+", max_offset="+min_max[1]);
        double [] disparity_raw = new double [tilesX * tilesY];
        Arrays.fill(disparity_raw,clt_parameters.disparity);
        
        double [][] combo_dsn_final = quadCLTs[ref_index].restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky

        double [][] dls = { // Update to use FG? Or FG/no BG?
        		combo_dsn_final[OpticalFlow.COMBO_DSN_INDX_DISP],
        		combo_dsn_final[OpticalFlow.COMBO_DSN_INDX_LMA],
        		combo_dsn_final[OpticalFlow.COMBO_DSN_INDX_STRENGTH]
        };
        double [][] ds = OpticalFlow.conditionInitialDS(
        		true,                // boolean        use_conf,       // use configuration parameters, false - use following  
        		clt_parameters,      // CLTParameters  clt_parameters,
        		dls,                 // double [][]    dls
        		quadCLTs[ref_index], // QuadCLT        scene,
        		debugLevel);			
        double [] interscene_ref_disparity = null; // keep null to use old single-scene disparity for interscene matching
        if (use_combo_dsi) {
        	interscene_ref_disparity = ds[0].clone(); // use_lma_dsi ?
        	if (use_lma_dsi) {
        		for (int i = 0; i < interscene_ref_disparity.length; i++) {
        			if (Double.isNaN(dls[1][i])) {
        				interscene_ref_disparity[i] = Double.NaN;
        			}
        		}
        	}
        }
        
        double [][] dbg_mb_img = null;
        double [] mb_ref_disparity =null;
        //        if (test_motion_blur) {
        mb_ref_disparity = interscene_ref_disparity;
        if (mb_ref_disparity == null) {
        	mb_ref_disparity = quadCLTs[ref_index].getDLS()[use_lma_dsi?1:0];
        }
        
        if (test_motion_blur) {
        	dbg_mb_img = new double[quadCLTs.length][];
        }
        
		ErsCorrection ers_reference = quadCLTs[ref_index].getErsCorrection();
		double [][][] scenes_xyzatr =      new double [quadCLTs.length][][];
		double [][][] scenes_xyzatr_pull = new double [quadCLTs.length][][];
        double [][][] dxyzatr_dt =         new double [quadCLTs.length][][];

		scenes_xyzatr[ref_index] = new double[2][3]; // all zeros
		// should have at least next or previous non-null
		int debug_scene = 69; // -68; // -8;
		RMSEStats rmse_stats = new RMSEStats();
    	if (debug_ers) {
    		System.out.println("ERS velocities scale mode = '"+dbg_ers_string+"'");
    	}
    	
        for (int nscene = last_scene; nscene >= earliest_scene; nscene--) {
			// just checking it is not isolated
        	if (quadCLTs[nscene] == null) {
        		earliest_scene = nscene + 1;
        		break;
        	}
        	// now reference scene should also be in ers_reference.getSceneXYZ(ts)
        	String ts = quadCLTs[nscene].getImageName();
        	if ((ers_reference.getSceneXYZ(ts)== null) || (ers_reference.getSceneATR(ts)== null)) {
        		earliest_scene = nscene + 1;
        		break;
        	}
        	scenes_xyzatr[nscene] = ers_reference.getSceneXYZATR(ts);
        	scenes_xyzatr_pull[nscene] = scenes_xyzatr[nscene].clone();
        	dxyzatr_dt[nscene] = ers_reference.getSceneErsXYZATR_dt(quadCLTs[nscene].getImageName());
        }
        
        boolean set_line_time = debugLevel>1000;
        if (set_line_time) {
        	double line_time = 27.778E-6;
        	System.out.println("line_time="+line_time);
        	for (int nscene = earliest_scene; nscene <= last_scene; nscene++) {
        		ErsCorrection ec = quadCLTs[nscene].getErsCorrection();
        		ec.line_time = line_time;
        	}
        }
        
        
        switch (ers_mode) {
        case 1 : 
        	dxyzatr_dt = OpticalFlow.getVelocitiesFromScenes(
        			quadCLTs,        // QuadCLT []     scenes, // ordered by increasing timestamps
        			ref_index,
        			earliest_scene,  // int            start_scene,
        			last_scene,       // int            end1_scene,
        			scenes_xyzatr,   // double [][][]  scenes_xyzatr,
        			half_run_range, // double         half_run_range
					debugLevel); // int            debugLevel);
        	break;
        default: // do nothing - already read
        }
        
        if (restore_imu) { // resore omegas from IMU 
        	double [][][] dxyzatr_dt_pimu = QuadCLTCPU.getDxyzatrPIMU( // does not depend on correction
    				clt_parameters, // CLTParameters clt_parameters,
    				quadCLTs); //       // QuadCLT[]     quadCLTs,
//    				quadCLTs[ref_index].getPimuOffsets()); // boolean       scale)
        	for (int i = 0; i < dxyzatr_dt.length; i++) {
        		if ((dxyzatr_dt[i]!=null) && (dxyzatr_dt[i][1]!=null) && (dxyzatr_dt_pimu[i]!=null) && (dxyzatr_dt_pimu[i][1]!=null)) {
        			dxyzatr_dt[i][1][0] = dxyzatr_dt_pimu[i][1][0];
        			dxyzatr_dt[i][1][1] = dxyzatr_dt_pimu[i][1][1];
        		}
        	}
    		param_select =   ErsCorrection.getParamSelect(
					true,   // boolean use_Z,
					true,   // boolean use_R,
    				true,   // boolean use_XY
    				true,   // boolean use_AT,
    				false,  // boolean use_ERS);//clt_parameters.ilp.ilma_lma_select;
					false,  // boolean use_ERS_tilt);
					false); // boolean use_ERS_roll);
        }

        // optionally filter by averaging and "IMU" (actually current dxyzatr_dt).
        // double         avg_rlen
//        readjust_xy_ims = false;
        
        if (lpf_xy && (reg_weight_xy > 0.0)) {
        	scenes_xyzatr_pull = QuadCLT.refineFromLPF(
        			clt_parameters, // CLTParameters clt_parameters,
        			quadCLTs,       // QuadCLT[]     quadCLTs,
        			scenes_xyzatr,  // double [][][] xyzatr,
        			avg_rlen,       // double        avg_rlen,// >= 1.0
            		false,          // boolean       filter_rot, // false - translation. Do twice for both
            		true,           // boolean       keep_rz, // keep Z and Roll
            		ref_index,      // int           ref_index,
            		earliest_scene, // int           early_index,
            		last_scene);    // int           last_index)
//        } else if (readjust_xy_ims && (reg_weight_xy > 0.0)) {
        } else if (readjust_xy_ims) {
        	// optionally run adjustment here (QuadCLT.rotateImsToCameraXYZ()? Or only 
        	scenes_xyzatr_pull = QuadCLT.refineXYZFromIMU(
        			clt_parameters, // CLTParameters clt_parameters,
        			false, // true, // false,          // boolean       common_scale_only // false - individual by direction
        			use_Z,          // boolean       keepZ, // if adjusting Z, qill use its old value
        			quadCLTs,       // QuadCLT[]     quadCLTs,
        			scenes_xyzatr,  // double [][][] xyzatr,
            		null,           // double [][][] pimu_xyzatr, // if null - will be recalculated
            		ref_index,      // int           ref_index,
            		earliest_scene, // int           early_index,
            		last_scene,     // int           last_index,
            		debugLevel);    //int           debugLevel)
    		for (int nscene = earliest_scene; nscene <= last_scene; nscene++) if (scenes_xyzatr[nscene] != null) {
    			scenes_xyzatr_pull[nscene] =  modifyATRtoXYZ(
    					scenes_xyzatr[nscene],         // double [][] cur_xyzatr, // careful with Z - using the new one
    					scenes_xyzatr_pull[nscene][0], // double []   new_xyz,
    					avg_z);                        // double      avg_z
    		}
        }
        if (copy_pull_current) { // freeze_xy_pull) {
        	System.out.println("reAdjustPairsLMAInterscene(): freezing X,Y to X,Y pull values");
            for (int nscene = last_scene; nscene >= earliest_scene; nscene--) {
            	if (scenes_xyzatr_pull[nscene] != null) {
            		// only X,Y, keep Z.
///            		scenes_xyzatr[nscene][0] = scenes_xyzatr_pull[nscene][0];
            		scenes_xyzatr[nscene][0][0] = scenes_xyzatr_pull[nscene][0][0];
            		scenes_xyzatr[nscene][0][1] = scenes_xyzatr_pull[nscene][0][1];
// A,T,R - keep previous            		
///            		scenes_xyzatr[nscene][1] = scenes_xyzatr_pull[nscene][1];
            	}
            }
        }
        if (debug_ers) {
    		System.out.println(String.format(
    				"%3s,%9s,%9s,%9s,,%9s,%9s,%9s,,%9s,%9s,%9s,,%s,%9s,%9s,,%9s,%9s,%9s,%9s",
    				"N","X","Y","Z","A","T","R","X-pull","Y-pull","Z-pull","A-pull","T-pull","R-pull",
    				"cent-dist","quad-dist","cent-rect","quad-rect"));
        	for (int nscene = last_scene; nscene >= earliest_scene; nscene--) {
        		// All 4 variants - just for testing. Normally should try center first and use
        		// quad if there is no significant movement
        		double est_center = quadCLTs[ref_index].estimateAverageShift(
        				scenes_xyzatr[ref_index], // double [][] xyzatr0,
        				scenes_xyzatr[nscene],    // double [][] xyzatr1,
        				avg_z,                    // double average_z,
        				false,                    // boolean use_rot,
        				false);                   // boolean rectilinear)
        		double est_quad = quadCLTs[ref_index].estimateAverageShift(
        				scenes_xyzatr[ref_index], // double [][] xyzatr0,
        				scenes_xyzatr[nscene],    // double [][] xyzatr1,
        				avg_z,                    // double average_z,
        				true,                    // boolean use_rot,
        				false);                   // boolean rectilinear)
        		double est_center_rectilinear = quadCLTs[ref_index].estimateAverageShift(
        				scenes_xyzatr[ref_index], // double [][] xyzatr0,
        				scenes_xyzatr[nscene],    // double [][] xyzatr1,
        				avg_z,                    // double average_z,
        				false,                    // boolean use_rot,
        				true);                   // boolean rectilinear)
        		double est_quad_rectilinear = quadCLTs[ref_index].estimateAverageShift(
        				scenes_xyzatr[ref_index], // double [][] xyzatr0,
        				scenes_xyzatr[nscene],    // double [][] xyzatr1,
        				avg_z,                    // double average_z,
        				true,                    // boolean use_rot,
        				true);                   // boolean rectilinear)
        		System.out.println(String.format("%3d,%9.5f,%9.5f,%9.5f,,%9.5f,%9.5f,%9.5f,,%9.5f,%9.5f,%9.5f,,%9.5f,%9.5f,%9.5f,,%9.4f, %9.4f, %9.4f, %9.4f",
        				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],
        				scenes_xyzatr_pull[nscene][0][0], scenes_xyzatr_pull[nscene][0][1], scenes_xyzatr_pull[nscene][0][2],
        				scenes_xyzatr_pull[nscene][1][0], scenes_xyzatr_pull[nscene][1][1], scenes_xyzatr_pull[nscene][1][2],
        				est_center, est_quad, est_center_rectilinear, est_quad_rectilinear));
        	}
        	System.out.println();
        	for (int nscene = last_scene; nscene >= earliest_scene; nscene--) {
        		System.out.println(String.format("%3d xyz_dt: %9.5f %9.5f %9.5f   atr_dt: %9.5f %9.5f %9.5f",
        				nscene,
        				dxyzatr_dt[nscene][0][0], dxyzatr_dt[nscene][0][1], dxyzatr_dt[nscene][0][2],
        				dxyzatr_dt[nscene][1][0], dxyzatr_dt[nscene][1][1], dxyzatr_dt[nscene][1][2]));
        	}
        	System.out.println();
        }
        double max_z_change = Double.NaN; // only applicable for drone images
        if (max_zoom_diff > 0) { // ignore if set to
        	max_z_change = avg_z * max_zoom_diff;
        	if (fail_on_zoom_roll) {
        		if (debugLevel > -3) {
        			System.out.println("Setting maximal Z-direction movement to "+ max_z_change+" m.");
        		}
        	}
        }
    	
    	int [] scene_seq = new int [last_scene-earliest_scene+1];
    	{
    		int indx=0;
    		for (int i = ref_index; i >= earliest_scene; i--) {
    			scene_seq[indx++] = i;
    		}
    		for (int i = ref_index+1; i <= last_scene; i++) {
    			scene_seq[indx++] = i;
    		}
    	}
    	
		TpTask[][]         tp_tasks_ref = new TpTask[2][];
		double [][]        pXpYD_ref = new double [tilesX*tilesY][];
    	ArrayList<Integer> fpn_list = new ArrayList<Integer>();
		boolean            fpn_disable = false; // estimate they are too close
		final double       max_rms =          clt_parameters.imp.max_rms;
		
		//add test
		boolean test_adjust = debugLevel > 1000;
		while (test_adjust) {
			int [] test_pair = {ref_index, 0};
			double []      test_lma_rms = new double[2];
			TpTask[][]     tp_tasks_test_rel_ref = new TpTask[2][];
			double [][] test_xyzatr = adjustDiffPairsLMAInterscene( // compare two scenes, first may be reference, use motion blur
					clt_parameters,          //CLTParameters  clt_parameters,
					use_lma_dsi,             //,boolean        use_lma_dsi,
					fpn_disable,             // boolean        fpn_disable,   // disable fpn filter if images are known to be too close
					disable_ers,             // boolean        disable_ers,
					min_max,                 // double []      min_max,       // null or pair of minimal and maximal offsets
					fail_reason,             // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
					quadCLTs,                // QuadCLT []     quadCLTs,
					ref_index,               // int            ref_index,
					tp_tasks_test_rel_ref,   // TpTask[][]     tp_tasks_ref,  // Should be TpTask[2][*]. If tp_tasks_ref[0] == null will calculate
					// at set first scene to the GPU
					pXpYD_ref,               // double [][]    pXpYD_ref,     // should be se or at least double [num_tiles][] if tp_tasks_ref[0] == null
					// will be recalculated when  tp_tasks_ref[0] == null, but for reference frame
					test_pair[0],            // int            nscene0,       // may be == ref_index
					test_pair[1],                  // int            nscene1,       // compares to nscene0
					interscene_ref_disparity,// double []      ref_disparity, // null or alternative reference disparity
					reliable_ref,            // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
					scenes_xyzatr[test_pair[0]],// double [][]    scene0_xyzatr,
					scenes_xyzatr[test_pair[1]],            // double [][]    scene1_xyzatr,
					avg_z,                   //  double         average_z,     
					scenes_xyzatr_pull [test_pair[1]],              // double [][]    scene1_xyzatr_pull,
					param_select,            // boolean[]      param_select,
					param_regweights,        // double []      param_regweights,
					test_lma_rms,            // double []      rms_out, // null or double [2]
					max_rms,                 // double         max_rms,
					mb_en,                   // boolean        mb_en,
					mb_tau,                  // double         mb_tau,      // 0.008; // time constant, sec
					mb_max_gain,             // double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
					clt_parameters.imp.debug_level); // int            debugLevel)
			boolean adjust_OK_test = test_xyzatr != null;
			System.out.println("adjust_OK_test = "+adjust_OK_test);			
			System.out.println("test_adjust = "+test_adjust);			
		}		
		
		// Main cycle - first goes down from the center, then up, and finally processes "fpn" scenes (close to reference)
		double [][] precomp_xyzatr = new double [2][3];
		int last_processed_scene = -2; // none
		/*
		 * There is still a problem with a large mismatch at start position for far off-reference scenes,
		 * especiall those that were not refined after inversion - reversing order of earlier processed "later"
		 * half (after the reference in the center) earlier processed as the bottom half of the other sub-sequence.
		 * 
		 * For mitigation we'll use that both half-sequences start from the center with small mismatches. The
		 * precomp_xyzatr will be used to store how much LMA corrected from the original estimation and add it
		 * to the next scene (only when it is the next), similar to the initial orientation.   
		 */
		
    	for (int nscene:scene_seq) {
			if (nscene == debug_scene) {
				System.out.println("nscene = "+nscene);
				System.out.println("nscene = "+nscene);
			}
    		double est_shift = quadCLTs[ref_index].estimateAverageShift(
    				scenes_xyzatr[ref_index], // double [][] xyzatr0,
    				scenes_xyzatr[nscene],    // double [][] xyzatr1,
    				avg_z,                    // double average_z,
    				false,                    // boolean use_rot,
    				fmg_rectilinear);                   // boolean rectilinear)
    		if (est_shift < fmg_max_quad) {
    			est_shift = quadCLTs[ref_index].estimateAverageShift(
    					scenes_xyzatr[ref_index], // double [][] xyzatr0,
    					scenes_xyzatr[nscene],    // double [][] xyzatr1,
    					avg_z,                    // double average_z,
    					true,                    // boolean use_rot,
    					fmg_rectilinear);                   // boolean rectilinear)
    		}
    		if (debugLevel > -4) {
    			System.out.println("nscene="+nscene+": est offset "+
    					est_shift+"pix");
    		}
			// just checking it is not isolated
        	if ((quadCLTs[nscene] == null) ||
        			((nscene != ref_index) &&
        			((ers_reference.getSceneXYZ(quadCLTs[nscene].getImageName())== null) ||
        			(ers_reference.getSceneATR(quadCLTs[nscene].getImageName())== null)))) {
        		earliest_scene = nscene + 1;
        		break;
        	}
			String ts = quadCLTs[nscene].getImageName();
			// it will be modified inside LMA, so needs to be backuped/restored to undo
			quadCLTs[nscene].getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
					QuadCLTCPU.scaleDtToErs(
							clt_parameters,
							dxyzatr_dt[nscene]));	
			if (dbg_mb_img != null) { // fill in 1 slice of dbg_mb_img, show all at once later
				readjustShowCorrected(
						clt_parameters,     // CLTParameters  clt_parameters,
						quadCLTs,           // QuadCLT []     quadCLTs,
						dxyzatr_dt[nscene], // double [][]    dxyzatr_dt_nscene, 
						dbg_mb_img,         //double [][]    dbg_mb_img, // initialize to double[quadCLTs.length][]
						mb_max_gain,        // double         mb_max_gain,
						mb_ref_disparity,   // double []      mb_ref_disparity,
						ref_index,          // int            ref_index,
						nscene,             // int            nscene,
						debug_scene,        //  = 68 int            debug_scene, //  = 68			
						debugLevel);        // int            debugLevel);				
			}

			if (nscene == ref_index) { // set GPU reference CLT data - should be before other scenes
				ers_reference.addScene(ts,
						scenes_xyzatr[nscene], // ref_index
						dxyzatr_dt[nscene]
						);
			} else { // if (nscene == ref_index)
				double []      lma_rms = new double[2];
				double [][] initial_pose = scenes_xyzatr[nscene].clone();
	    		boolean adjust_OK = false;
	    		if (est_shift < min_max[0]) {
	    			fail_reason[0]=FAIL_REASON_MIN;
	    		} else {
	    			double [][] corr_xyzatr_pull = new double [][] {scenes_xyzatr_pull[nscene][0].clone(), scenes_xyzatr_pull[nscene][1].clone()};
	    			/*
	    			 * scenes_xyzatr[ref_index],// double [][]    scene0_xyzatr, - scene to compare to
	    			 * scenes_xyzatr[nscene],   // double [][]    scene1_xyzatr - previous known pose?
	    			 * scenes_xyzatr_pull[nscene], // double [][]    scene1_xyzatr_pull, - now not a pull,
	    			 *  but a rigid target set as initial approximation 
	    			 */
	    			boolean applied_precomp = false;
	    			if (use_precomp && (last_processed_scene >= nscene-1) && (last_processed_scene <= nscene+1)) {
	    				corr_xyzatr_pull=ErsCorrection.combineXYZATR(
	    						scenes_xyzatr_pull[nscene],
	    						precomp_xyzatr);
	    				applied_precomp = true;
	    				if (debugLevel > -2) {
	    					System.out.println(String.format(
	    							"Applied precompensation: [%9.6f, %9.6f, %9.6f] [%9.6f, %9.6f, %9.6f]",
	    							precomp_xyzatr[0][0], precomp_xyzatr[0][1], precomp_xyzatr[0][2], 
	    							precomp_xyzatr[1][0], precomp_xyzatr[1][1], precomp_xyzatr[1][2]));
	    				}
	    			}
	    			scenes_xyzatr[nscene] = adjustDiffPairsLMAInterscene( // compare two scenes, first may be reference, use motion blur
	    					clt_parameters,          //CLTParameters  clt_parameters,
	    					use_lma_dsi,             //,boolean        use_lma_dsi,
	    					fpn_disable,             // boolean        fpn_disable,   // disable fpn filter if images are known to be too close
	    					disable_ers,             // boolean        disable_ers,
	    					min_max,                 // double []      min_max,       // null or pair of minimal and maximal offsets
	    					fail_reason,             // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
	    					quadCLTs,                // QuadCLT []     quadCLTs,
	    					ref_index,               // int            ref_index,
	    					tp_tasks_ref,            // TpTask[][]     tp_tasks_ref,  // Should be TpTask[2][*]. If tp_tasks_ref[0] == null will calculate
	    					// at set first scene to the GPU
	    					pXpYD_ref,               // double [][]    pXpYD_ref,     // should be se or at least double [num_tiles][] if tp_tasks_ref[0] == null
	    					// will be recalculated when  tp_tasks_ref[0] == null, but for reference frame
	    					ref_index,               // int            nscene0,       // may be == ref_index
	    					nscene,                  // int            nscene1,       // compares to nscene0
	    					interscene_ref_disparity,// double []      ref_disparity, // null or alternative reference disparity
	    					reliable_ref,            // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
	    					scenes_xyzatr[ref_index],// double [][]    scene0_xyzatr,
	    					scenes_xyzatr[nscene],   // double [][]    scene1_xyzatr,
	    					avg_z,                   // double         average_z,     
	    					corr_xyzatr_pull, // scenes_xyzatr_pull[nscene], // double [][]    scene1_xyzatr_pull,
	    					param_select,            // boolean[]      param_select,
	    					param_regweights,        // double []      param_regweights,
	    					lma_rms,                 // double []      rms_out, // null or double [2]
	    					max_rms,                 // double         max_rms,
	    					mb_en,                   // boolean        mb_en,
	    					mb_tau,                  // double         mb_tau,      // 0.008; // time constant, sec
	    					mb_max_gain,             // double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
	    					clt_parameters.imp.debug_level); // int            debugLevel)
	    			adjust_OK = scenes_xyzatr[nscene] != null;
	    			if (!adjust_OK) {
	    				last_processed_scene = -2;
	    			} else if (use_precomp) {
	    				double [][] last_corr_xyzatr = ErsCorrection.combineXYZATR(
	    						scenes_xyzatr[nscene],
	    						ErsCorrection.invertXYZATR(corr_xyzatr_pull));
	    				// add precomp_xyzatr if it was applied:
	    				if (applied_precomp) {
	    					precomp_xyzatr = ErsCorrection.combineXYZATR(
	    							precomp_xyzatr,
	    							last_corr_xyzatr);
	    				} else { // just use difference
	    					precomp_xyzatr = last_corr_xyzatr;
	    				}
	    				last_processed_scene = nscene;
	    			}
	    		}
				if (adjust_OK && fail_on_zoom_roll) { // check only for initial orientation, do not check on readjustments
					if (Math.abs(scenes_xyzatr[nscene][1][2]) > max_roll) {
						fail_reason[0] = FAIL_REASON_ROLL;
						adjust_OK = false;
					}
					if (max_zoom_diff > 0) { // ignore if set to 
						if (Math.abs(scenes_xyzatr[nscene][0][2]) > max_z_change) {  // NaN OK - will not fail
							fail_reason[0] = FAIL_REASON_ZOOM;
							adjust_OK = false;
						}
					}
				}
				// FAIL_REASON_ROLL
				handle_failure: {
					if (!adjust_OK) {
						scenes_xyzatr[nscene] = initial_pose.clone(); // restore pre-adjust
						boolean OK = false;
						System.out.println("LMA failed at nscene = "+nscene+". Reason = "+fail_reason[0]+
								" ("+getFailReason(fail_reason[0])+")");
						if ((fail_reason[0]==FAIL_REASON_MIN) || ((fail_reason[0]==FAIL_REASON_LMA))) {
							fpn_list.add(nscene);
							quadCLTs[nscene].getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
									QuadCLTCPU.scaleDtToErs(
											clt_parameters,
											dxyzatr_dt[nscene]));		
							if (fpn_skip) {
								System.out.println("fpn_skip is set, just skipping this scene");
								break handle_failure;
							} else {
								System.out.println("fpn_skip is not set, aborting series and adjusting earliest_scene");
								// set this and all previous to null
							}
						}
						// all other reasons lead to failure
						earliest_scene = nscene + 1;
						if (debugLevel > -4) {
							System.out.println("reAdjustPairsLMAInterscene "+nscene+" (of "+ quadCLTs.length+") "+
									quadCLTs[ref_index].getImageName() + "/" + ts+
									" FAILED. Setting earliest_scene to "+earliest_scene + " Failure reason: " + fail_reason[0]+
									" ("+getFailReason(fail_reason[0])+")");
						}
						// set this and all previous to null
						for (; nscene >= 0 ; nscene--) {
							ers_reference.addScene(quadCLTs[nscene].getImageName()); // remove
							quadCLTs[nscene] = null; // completely remove early scenes?
						}
						break;
					}
				}
				// overwrite old data
				// undo velocities->ers scaling
				double [][] adjusted_xyzatr_dt = QuadCLTCPU.scaleDtFromErs(
						clt_parameters,
						quadCLTs[nscene].getErsCorrection().getErsXYZATR_dt(
								));
				ers_reference.addScene(ts,
						scenes_xyzatr[nscene][0],
						scenes_xyzatr[nscene][1],
						adjusted_xyzatr_dt[0],	
						adjusted_xyzatr_dt[1]		
						);

			    rmse_stats.add(lma_rms[0]);
				if (debugLevel > -3) {
					System.out.println("reAdjustPairsLMAInterscene "+nscene+" (of "+ quadCLTs.length+") "+
							quadCLTs[ref_index].getImageName() + "/" + ts+
			                " Done. RMS="+lma_rms[0]+
			                ", maximal so far was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
				}
			} // if (nscene == ref_index) else
        } // for (int nscene = ref_index; nscene > earliest_scene; nscene--) {

		if (debugLevel > -4) {
			System.out.println("num_fpn_mitigate= "+fpn_list.size());
		}
		if (fmg_reorient_en && !fpn_list.isEmpty()) {
			if (fmg_distance < (min_max[0] + 2)) {
				fmg_distance = min_max[0] + 2;
			}
			int [][] fpn_pairs = getFPNPairs(
					fpn_list,          // ArrayList<Integer> fpn_list,
					fmg_distance,      // double             fpn_mitigate_dist,
					fmg_rectilinear,   // boolean            rectilinear,
					quadCLTs,          // QuadCLT []         quadCLTs,
					scenes_xyzatr,     // double [][][]      scenes_xyzatr,
					avg_z,             // double             avg_z,
					last_scene,        // latest_scene,         // int                ref_index, // >= earliest_scene 
					earliest_scene);   // int                earliest_scene)
			// mitigating problem, that in the process of adjusting offset can fall below
			// the minimum and coordinates will be NaN:
			/*
				Mitigating FPN for scene 361 being too close to reference 344, using scene 367 as a reference
				interCorrPair(): avg_offs = 10.755562139763402 <= 256.0, sw = 4217.0 iter=3, RMS=0.5364197453455559 (Pure RMS=0.5364197453455559)
				interCorrPair(): avg_offs = 7.795390182218127 < 8.0, sw = 4219.0
				adjustPairsLMAInterscene() returned null
			 */
			double [] min_max_fpn = {0,min_max[1]}; 
			boolean test_adjust1 = debugLevel > 1000;
			if (test_adjust1) {
				int [][] fpn_pairs_dbg = new int [fpn_pairs.length+3][2];
				int ipair = 0;
				for (; ipair < fpn_pairs.length; ipair++) {
					fpn_pairs_dbg[ipair] = fpn_pairs[ipair].clone();
				}
				fpn_pairs_dbg[ipair++] = new int[] {41,30};
				fpn_pairs_dbg[ipair++] = new int[] {30,45};
				fpn_pairs_dbg[ipair++] = new int[] {41,45};
				for (int i = fpn_pairs.length; i < fpn_pairs_dbg.length; i++) {
					int nscene = fpn_pairs_dbg[i][0];
					quadCLTs[nscene].getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
							QuadCLTCPU.scaleDtToErs(
									clt_parameters,
									dxyzatr_dt[nscene]));		

				}

				fpn_pairs = fpn_pairs_dbg;
			}
			for (int ipair = 0; ipair < fpn_pairs.length; ipair++) if (fpn_pairs[ipair][1] >= 0) {
				if (debugLevel > -4) {
					System.out.println("Mitigating FPN for scene "+fpn_pairs[ipair][0]+
							" being too close to reference "+ref_index+
							", using scene "+fpn_pairs[ipair][1]+" as a reference");
				}
				double []      lma_rms = new double[2];
				TpTask[][]     tp_tasks_rel_ref = new TpTask[2][];
				double [][] rel_xyzatr = adjustDiffPairsLMAInterscene( // compare two scenes, first may be reference, use motion blur
						clt_parameters,          //CLTParameters  clt_parameters,
						use_lma_dsi,             //,boolean        use_lma_dsi,
						fpn_disable,             // boolean        fpn_disable,   // disable fpn filter if images are known to be too close
						disable_ers,             // boolean        disable_ers,
						min_max_fpn, // min_max, // double []      min_max,       // null or pair of minimal and maximal offsets
						fail_reason,             // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
						quadCLTs,                // QuadCLT []     quadCLTs,
						ref_index,               // int            ref_index,
						tp_tasks_rel_ref,        // TpTask[][]     tp_tasks_ref,  // Should be TpTask[2][*]. If tp_tasks_ref[0] == null will calculate
						// at set first scene to the GPU
						pXpYD_ref,               // double [][]    pXpYD_ref,     // should be se or at least double [num_tiles][] if tp_tasks_ref[0] == null
						// will be recalculated when  tp_tasks_ref[0] == null, but for reference frame
						fpn_pairs[ipair][1],               // int            nscene0,       // may be == ref_index
						fpn_pairs[ipair][0],                  // int            nscene1,       // compares to nscene0
						interscene_ref_disparity,// double []      ref_disparity, // null or alternative reference disparity
						reliable_ref,            // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
						scenes_xyzatr[fpn_pairs[ipair][1]],// double [][]    scene0_xyzatr,
						scenes_xyzatr[fpn_pairs[ipair][0]],            // double [][]    scene1_xyzatr,
						avg_z,                   //  double         average_z,     
						scenes_xyzatr_pull [fpn_pairs[ipair][0]],              // double [][]    scene1_xyzatr_pull,
    					param_select,            // boolean[]      param_select,
    					param_regweights,        // double []      param_regweights,
						lma_rms,                 // double []      rms_out, // null or double [2]
						max_rms,                 // double         max_rms,
						mb_en,                   // boolean        mb_en,
						mb_tau,                  // double         mb_tau,      // 0.008; // time constant, sec
						mb_max_gain,             // double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
						clt_parameters.imp.debug_level); // int            debugLevel)
				boolean adjust_OK = rel_xyzatr != null;
				if (!adjust_OK) {
					System.out.println("LMA failed at scene pair = "+fpn_pairs[ipair][0]+
							" referenced from scene "+fpn_pairs[ipair][1]+". Reason = "+fail_reason[0]+
							" ("+getFailReason(fail_reason[0])+")");
					quadCLTs[fpn_pairs[ipair][0]].getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
							QuadCLTCPU.scaleDtToErs(
									clt_parameters,
									dxyzatr_dt[fpn_pairs[ipair][0]]));		
				} else {
					scenes_xyzatr[fpn_pairs[ipair][0]] = rel_xyzatr; // save directly, no need to combine
					double [][] adjusted_xyzatr_dt = QuadCLTCPU.scaleDtFromErs(
							clt_parameters,
							quadCLTs[fpn_pairs[ipair][0]].getErsCorrection().getErsXYZATR_dt());
					ers_reference.addScene(quadCLTs[fpn_pairs[ipair][0]].getImageName(),
							scenes_xyzatr[fpn_pairs[ipair][0]][0],
							scenes_xyzatr[fpn_pairs[ipair][0]][1],
							adjusted_xyzatr_dt[0],		
							adjusted_xyzatr_dt[1]);		
				    rmse_stats.add(lma_rms[0]);
					if (debugLevel > -3) {
						System.out.println("reAdjustPairsLMAInterscene "+fpn_pairs[ipair][0]+" (of "+ quadCLTs.length+") "+
								quadCLTs[fpn_pairs[ipair][1]].getImageName() + "/" + quadCLTs[fpn_pairs[ipair][0]].getImageName()+
				                " Done. RMS="+lma_rms[0]+
				                ", maximal so far was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
					}
				}
			}
		}  	
    	
		boolean test_adjust1 = debugLevel > 1000;
		if (test_adjust1) {
			int [] test_pair = {0, ref_index};
			while (test_adjust1) {
				double []      test_lma_rms = new double[2];
				TpTask[][]     tp_tasks_test_rel_ref = new TpTask[2][];
				System.out.println("test_adjust scene = "+test_pair[0]+", reference="+test_pair[1]);			
				double [][] test_xyzatr = adjustDiffPairsLMAInterscene( // compare two scenes, first may be reference, use motion blur
						clt_parameters,          //CLTParameters  clt_parameters,
						use_lma_dsi,             //,boolean        use_lma_dsi,
						fpn_disable,             // boolean        fpn_disable,   // disable fpn filter if images are known to be too close
						disable_ers,             // boolean        disable_ers,
						min_max,                 // double []      min_max,       // null or pair of minimal and maximal offsets
						fail_reason,             // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
						quadCLTs,                // QuadCLT []     quadCLTs,
						ref_index,               // int            ref_index,
						tp_tasks_test_rel_ref,   // TpTask[][]     tp_tasks_ref,  // Should be TpTask[2][*]. If tp_tasks_ref[0] == null will calculate
						// at set first scene to the GPU
						pXpYD_ref,               // double [][]    pXpYD_ref,     // should be se or at least double [num_tiles][] if tp_tasks_ref[0] == null
						// will be recalculated when  tp_tasks_ref[0] == null, but for reference frame
						test_pair[1],            // int            nscene0,       // may be == ref_index
						test_pair[0],                  // int            nscene1,       // compares to nscene0
						interscene_ref_disparity,// double []      ref_disparity, // null or alternative reference disparity
						reliable_ref,            // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
						scenes_xyzatr[test_pair[1]],// double [][]    scene0_xyzatr,
						scenes_xyzatr[test_pair[0]],            // double [][]    scene1_xyzatr,
						avg_z,                   //  double         average_z,     
						scenes_xyzatr_pull [test_pair[0]],              // double [][]    scene1_xyzatr_pull,
						param_select,            // boolean[]      param_select,
						param_regweights,        // double []      param_regweights,
						test_lma_rms,            // double []      rms_out, // null or double [2]
						max_rms,                 // double         max_rms,
						mb_en,                   // boolean        mb_en,
						mb_tau,                  // double         mb_tau,      // 0.008; // time constant, sec
						mb_max_gain,             // double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
						clt_parameters.imp.debug_level); // int            debugLevel)
				boolean adjust_OK_test = test_xyzatr != null;
				System.out.println("adjust_OK_test = "+adjust_OK_test);		
				System.out.println("reAdjustPairsLMAInterscene "+test_pair[0]+
						quadCLTs[test_pair[1]].getImageName() + "/" + quadCLTs[test_pair[0]].getImageName()+
						" Done. RMS="+test_lma_rms[0]);
				System.out.println("test_adjust = "+test_adjust1);			
			}		
		}
        // TODO: after all scenes done, see if any of scenes_xyzatr[] is null, and if fpn_rematch - rematch
		if (dbg_mb_img != null) {
			String [] dbg_mb_titles = new String[quadCLTs.length];
			for (int i = 0; i < quadCLTs.length; i++) if (quadCLTs[i] != null) {
				dbg_mb_titles[i] = quadCLTs[i].getImageName();
			}
			ShowDoubleFloatArrays.showArrays(
					dbg_mb_img,
					tilesX * 2,
					tilesY,
					true,
					quadCLTs[ref_index].getImageName()+"-MOTION_BLUR",
					dbg_mb_titles);
		}
        
		if (debugLevel > -4) {
			System.out.println("All multi scene passes are Done. Maximal RMSE was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
		}

		quadCLTs[ref_index].saveInterProperties( // save properties for interscene processing (extrinsics, ers, ...)
				null, // String path,             // full name with extension or w/o path to use x3d directory
				debugLevel+1);
		
		// Add to log
    	StringBuffer sb = new StringBuffer();
    	sb.append(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime())+"\n");
		sb.append("Finished reAdjustPairsLMAInterscene():\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("earliest_scene= "+range[0]+"\n");
    	sb.append("ref_index=      "+ref_index+"\n");
    	sb.append("last_index=     "+range[1]+"\n");
    	sb.append("Maximal RMSE=   "+rmse_stats.getMax()+"\n");
    	sb.append("Average RMSE=   "+rmse_stats.getAverage()+"\n");
    	sb.append("lpf_xy=         "+lpf_xy+"\n");
    	sb.append("readjust_xy_ims="+readjust_xy_ims+"\n");
    	sb.append("lma_xyzatr=     "+lma_xyzatr+"\n");
    	sb.append("use_R=          "+use_R+"\n");
    	sb.append("use_R=          "+use_R+"\n");
    	sb.append("mb_max_gain=    "+mb_max_gain+"\n");
    	sb.append("avg_z=          "+avg_z+"m\n");
    	sb.append("reg_weight_xy=  "+reg_weight_xy+"\n");
    	sb.append("disable_ers=    "+disable_ers+"\n");
    	sb.append("disable_ers_y=  "+disable_ers_y+"\n");
    	sb.append("disable_ers_r=  "+disable_ers_r+"\n");
    	sb.append("use_precomp=    "+use_precomp+"\n");
    	sb.append("------------------------\n\n");
		quadCLTs[ref_index].saveStringInModelDirectory(sb.toString(), QuadCLT.ORIENTATION_LOGS_SUFFIX); // String  suffix)
		return earliest_scene;
	}
	
	/**
	 * Modify initial ATR to match change of the XYZ from the already adjusted pose.
	 * Uses the fact that movement in X is approximate equivalent to Azimuth rotation
	 * (positive X has the same effect as negative A multiplied by Z) and Y-movement
	 * has the same sign to Tilt.
	 * @param cur_xyzatr current (already adjusted by the image-based LMA) {{x,y,z},{a,t,r}
	 * @param new_xyz new (target from the IMS) {x,y,z} 
	 * @param avg_z average altitude (Z)
	 * @return new pose {{x,y,z},{a,t,r} expected to have a close match between scenes
	 */
	public static double [][] modifyATRtoXYZ(
			double [][] cur_xyzatr, // careful with Z - using the new one
			double []   new_xyz,
			double      avg_z
			){
		double dx = new_xyz[0] - cur_xyzatr[0][0];
		double dy = new_xyz[1] - cur_xyzatr[0][1];
		double [][] diff_xyzatr = new double[][] {{dx, dy, 0},{dx/avg_z, -dy/avg_z,0}};
		double [][] new_xyzatr = ErsCorrection.combineXYZATR(cur_xyzatr,diff_xyzatr); 
		return new_xyzatr;
	}

	
	
	
	
	// Make it the only entry point
	// uses *_dt from
	// quadCLTs[nscene*].getErsCorrection().getErsXYZATR_dt()
	public static double[][]  adjustDiffPairsLMAInterscene( // compare two scenes, first may be reference, use motion blur
			CLTParameters  clt_parameters,
		    boolean        use_lma_dsi,
			boolean        fpn_disable,   // disable fpn filter if images are known to be too close
			boolean        disable_ers,
			double []      min_max,       // null or pair of minimal and maximal offsets
			int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
			QuadCLT []     quadCLTs,
			int            ref_index,
			TpTask[][]     tp_tasks_ref,  // Should be TpTask[2][*]. If tp_tasks_ref[0] == null will calculate
			                              // at set first scene to the GPU
			double [][]    pXpYD_ref,     // should be se or at least double [num_tiles][] if tp_tasks_ref[0] == null
			               // will be recalculated when  tp_tasks_ref[0] == null, but for reference frame
			int            nscene0,       // may be == ref_index
			int            nscene1,       // compares to nscene0
			double []      ref_disparity, // null or alternative reference disparity
			boolean []     reliable_ref, // null or bitmask of reliable reference tiles
			double [][]    scene0_xyzatr,
			double [][]    scene1_xyzatr,
			double         average_z,     
			double [][]    scene1_xyzatr_pull,
			boolean[]      param_select,
			double []      param_regweights,
			double []      rms_out, // null or double [2]
			double         max_rms,
			boolean        mb_en,
			double         mb_tau,      // 0.008; // time constant, sec
			double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
			int            debugLevel)
	{
//	    boolean use_lma_dsi =          clt_parameters.imp.use_lma_dsi;
		if (ref_disparity == null) { 
			ref_disparity = quadCLTs[ref_index].getDLS()[use_lma_dsi?1:0];
		}
		// TODO: set reference as new version of adjustPairsLMAInterscene() assumes it set
		if ((tp_tasks_ref == null) || (tp_tasks_ref[0] == null)) { // calculate and setup reference (for comparison) scene to GPU
			if (tp_tasks_ref == null) {
				tp_tasks_ref = new TpTask[2][];
			}
			int margin = clt_parameters.imp.margin;
			double [][] pXpYD_ref1 = OpticalFlow.transformToScenePxPyD( // full size - [tilesX*tilesY], some nulls
					null,               // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
					ref_disparity,      // dls[0],  // final double []   disparity_ref, // invalid tiles - NaN in disparity (maybe it should not be masked by margins?)
					ZERO3,              // final double []   scene_xyz, // camera center in world coordinates
					ZERO3,              // final double []   scene_atr, // camera orientation relative to world frame
					quadCLTs[ref_index],  // final QuadCLT     scene_QuadClt,
					quadCLTs[ref_index]); // final QuadCLT     reference_QuadClt)
			if (pXpYD_ref == null) {
				pXpYD_ref = pXpYD_ref1;
			} else {
				System.arraycopy(pXpYD_ref1, 0, pXpYD_ref, 0, pXpYD_ref1.length);
			}
			double [][] pXpYD_scene0 =pXpYD_ref;
			if (nscene0 != ref_index) { // same as in sfm
				pXpYD_scene0 = OpticalFlow.transformToScenePxPyD( // will be null for disparity == NaN, total size - tilesX*tilesY
						null,                 // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
						ref_disparity,        // dls[0], // final double []   disparity_ref, // invalid tiles - NaN in disparity (maybe it should not be masked by margins?)
						scene0_xyzatr[0],    // final double []   scene_xyz, // camera center in world coordinates
						scene0_xyzatr[1],    // final double []   scene_atr, // camera orientation relative to world frame
						quadCLTs[nscene0],    // final QuadCLT     scene_QuadClt,
						quadCLTs[ref_index]); // final QuadCLT     reference_QuadClt)
			}
			double [][] mb_vectors_scene0 = null;
			if (mb_en) {
				double [][] scene0_xyzatr_dt = QuadCLTCPU.scaleDtFromErs(
						clt_parameters,
						quadCLTs[nscene0].getErsCorrection().getErsXYZATR_dt());
				mb_vectors_scene0 = OpticalFlow.getMotionBlur( // same as in sfm
						quadCLTs[ref_index],   // QuadCLT        ref_scene,
						quadCLTs[nscene0],     // QuadCLT        scene,         // can be the same as ref_scene
						pXpYD_ref,             // double [][]    ref_pXpYD,     // here it is scene, not reference!
						scene0_xyzatr[0],     // double []      camera_xyz,
						scene0_xyzatr[1],     // double []      camera_atr,
						scene0_xyzatr_dt[0],   // double []      camera_xyz_dt,
						scene0_xyzatr_dt[1],   // double []      camera_atr_dt,
						0,                     // int            shrink_gaps,  // will gaps, but not more that grow by this
						debugLevel);           // int            debug_level)
			}
			TpTask [][] tp_tasks_ref2 = setReferenceGPU (
					clt_parameters,     // CLTParameters      clt_parameters,			
					quadCLTs[nscene0],  // QuadCLT            ref_scene,
					ref_disparity,// null      // double []          ref_disparity, // null or alternative reference disparity
					pXpYD_scene0,       // double [][]        ref_pXpYD,
					reliable_ref,       // final boolean []   selection, // may be null, if not null do not  process unselected tiles
					margin,             // final int          margin,
					// motion blur compensation 
					mb_tau,             // double             mb_tau,      // 0.008; // time constant, sec
					mb_max_gain,        // double             mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
					mb_vectors_scene0,  // double [][]        mb_vectors,  // now [2][ntiles];
					debugLevel);        // int                debug_level)
			System.arraycopy(tp_tasks_ref2,0, tp_tasks_ref, 0, tp_tasks_ref2.length);
		}
		double [][] mb_vectors_scene1 = null;
		if (mb_en) {
			double [][] scene1_xyzatr_dt = QuadCLTCPU.scaleDtFromErs(
					clt_parameters,
					quadCLTs[nscene1].getErsCorrection().getErsXYZATR_dt());
			
			mb_vectors_scene1 = OpticalFlow.getMotionBlur(
					quadCLTs[ref_index],   // QuadCLT        ref_scene,
					quadCLTs[nscene1],      // QuadCLT        scene,         // can be the same as ref_scene
					pXpYD_ref,              // double [][]    ref_pXpYD,     // here it is scene, not reference!
					scene1_xyzatr[0],     // double []      camera_xyz,
					scene1_xyzatr[1],     // double []      camera_atr,
					scene1_xyzatr_dt[0],   // double []      camera_xyz_dt,
					scene1_xyzatr_dt[1],   // double []      camera_atr_dt,
					0,                     // int            shrink_gaps,  // will gaps, but not more that grow by this
					debugLevel);           // int            debug_level)
		}
// avg_z		
		double pull_offset = 0;
		if (!Double.isNaN(average_z) && (scene1_xyzatr_pull != null) && (scene1_xyzatr_pull[0] != null) && (scene1_xyzatr_pull[1] != null)){
			pull_offset = quadCLTs[ref_index].estimateAverageShift(
					scene1_xyzatr,      // double [][] xyzatr0,
					scene1_xyzatr_pull, // double [][] xyzatr1,
					average_z,                    // double average_z,
    				false,                    // boolean use_rot,
    				true);                   // boolean rectilinear)
			if (debugLevel > -3) {
				System.out.println("adjustDiffPairsLMAInterscene(): pull_offset="+pull_offset);
				double dx = scene1_xyzatr_pull[0][0]-scene1_xyzatr[0][0];
				double dy = scene1_xyzatr_pull[0][1]-scene1_xyzatr[0][1];
				double da = (scene1_xyzatr_pull[1][0]-scene1_xyzatr[1][0])*average_z;
				double dt = (scene1_xyzatr_pull[1][1]-scene1_xyzatr[1][1])*average_z;
//				System.out.println(String.format("modifyATRtoXYZ(): dx=%8.4f, dy=%8.4f"+
//						 ", hda=%8.4f, hdt=%8.4f, dx+hda=%8.4f, dy-hdt=%8.4f"
//						, dx, dy, da, dt, dx+da, dy-dt));				
			}
			
			while (pull_offset > clt_parameters.imp.max_pull_jump) {
				// so far assuming only X and Y to be modified. Z - won't harm, angles are not used now, 
				// but if yes - quaternions are needed. Note, that angles are updated and will not now match pull
				// here update all parameters that are NOT adjusted. For angles use individual angles, just avoid
				// crossing 2pi 
				/// XYZ ErsCorrection.DP_DSX + i
				for (int i = 0; i < 3; i++) if (!param_select[ErsCorrection.DP_DSX + i]){
					scene1_xyzatr[0][i] += (scene1_xyzatr_pull[0][i] - scene1_xyzatr[0][i]) * clt_parameters.imp.max_pull_jump / pull_offset;   
				}
				// Az [-pi,+pi), Tl [-pi/2,+pi/2), Rl[-pi.+pi], but only roll can cross +/-pi
				for (int i = 0; i < 3; i++) if (!param_select[ErsCorrection.DP_DSAZ + i]){
					if (i==2) {
						double da = scene1_xyzatr_pull[1][i] - scene1_xyzatr[1][i];
						if      (da >  Math.PI) da -= 2 * Math.PI;
						else if (da < -Math.PI) da += 2 * Math.PI;
						scene1_xyzatr[0][i] += da * clt_parameters.imp.max_pull_jump / pull_offset;
						if      (scene1_xyzatr[1][i] >=  Math.PI) scene1_xyzatr[1][i] -= 2 * Math.PI;
						else if (scene1_xyzatr[1][i] <  -Math.PI) scene1_xyzatr[1][i] += 2 * Math.PI;
					} else {
						scene1_xyzatr[0][i] += (scene1_xyzatr_pull[0][i] - scene1_xyzatr[0][i]) * clt_parameters.imp.max_pull_jump / pull_offset;
					}
				}				
				/*
				for (int i = 0; i < 2; i++) {
					scene1_xyzatr[0][i] += (scene1_xyzatr_pull[0][i] - scene1_xyzatr[0][i]) * clt_parameters.imp.max_pull_jump / pull_offset;   
				}
				*/
				// apply small scene1_xyzatr jump towards scene1_xyzatr_pull, run adjustPairsLMAInterscene, return if failed
				double [][] new_xyzatr = adjustPairsLMAInterscene( // assumes reference GPU data is already set - once for multiple scenes
						clt_parameters,    // CLTParameters  clt_parameters,
						true,              // boolean        initial_adjust,
						false,             // boolean        fpn_disable,   // disable fpn filter if images are known to be too close
						disable_ers,       // boolean        disable_ers,
						min_max,           // double []      min_max,       // null or pair of minimal and maximal offsets
						fail_reason,       // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
						quadCLTs[ref_index],// QuadCLT        ref_QuadClt,   // reference scene for ref_disparity, zero xyz and atr
						ref_disparity,     // double []      ref_disparity, // null or alternative reference disparity
						quadCLTs[nscene0], // QuadCLT        reference_QuadClt,
						pXpYD_ref,         // double [][]    pXpYD_ref,     // pXpYD for the reference scene
						reliable_ref,      // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
						tp_tasks_ref[0],   // TpTask[]       tp_tasks_ref,  // only (main if MB correction) tasks for FPN correction
						quadCLTs[nscene1], // QuadCLT        scene_QuadClt,
						scene1_xyzatr,     // double [][]    camera_xyzatr,
						scene1_xyzatr_pull,// double [][]    scene_xyzatr_pull,  // if both are not null, specify target values to pull to
						scene0_xyzatr,     // double [][]    ref_xyzatr, 
						param_select,      // boolean[]      param_select,
						param_regweights,  // double []      param_regweights,
						rms_out,           // double []      rms_out, // null or double [2]
						max_rms,           // double         max_rms,
						// motion blur compensation 
						mb_tau,            // double         mb_tau,      // 0.008; // time constant, sec
						mb_max_gain,       // double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
						mb_vectors_scene1, // double [][]    mb_vectors,  // now [2][ntiles];
						debugLevel);       // int            debug_level)
				if (new_xyzatr == null ) {
					return null; // failed
				}
				double 	new_pull_offset = quadCLTs[ref_index].estimateAverageShift(
						new_xyzatr,         // double [][] xyzatr0,
						scene1_xyzatr_pull, // double [][] xyzatr1,
						average_z,                    // double average_z,
	    				false,                    // boolean use_rot,
	    				true);                   // boolean rectilinear)
				if (new_pull_offset < pull_offset) {
					scene1_xyzatr = new_xyzatr;
					// later - watch which parameters are updated by LMA, update those
					for (int i = 0; i < 3; i++) {
						if (param_select[ErsCorrection.DP_DSX + i]){
							scene1_xyzatr_pull[0][i] = scene1_xyzatr[0][i];   
						}
						if (param_select[ErsCorrection.DP_DSAZ + i]){
							scene1_xyzatr_pull[1][i] = scene1_xyzatr[1][i];   
						}
					}
//					scene1_xyzatr_pull[1] =    new_xyzatr[1];      // update adjusted orientation
//					scene1_xyzatr_pull[0][2] = new_xyzatr[0][2];// update Z 
					pull_offset = new_pull_offset;
					if (debugLevel > -3) {
						System.out.println("adjustDiffPairsLMAInterscene(): new pull_offset="+pull_offset);
					}
				} else {
					System.out.println("adjustDiffPairsLMAInterscene(): new pull_offset worsened = "+pull_offset+", using old one");
					break;
				}
			}
			scene1_xyzatr[0] = scene1_xyzatr_pull[0].clone(); // maybe no-cloning?
			scene1_xyzatr[1] = scene1_xyzatr_pull[1].clone();
			
		}
		
		
		
		return  adjustPairsLMAInterscene( // assumes reference GPU data is already set - once for multiple scenes
				clt_parameters,    // CLTParameters  clt_parameters,
				true,              // boolean        initial_adjust,
				false,             // boolean        fpn_disable,   // disable fpn filter if images are known to be too close
				disable_ers,       // boolean        disable_ers,
				min_max,           // double []      min_max,       // null or pair of minimal and maximal offsets
				fail_reason,       // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
				quadCLTs[ref_index],//QuadCLT        ref_QuadClt,   // reference scene for ref_disparity, zero xyz and atr
				ref_disparity,     // double []      ref_disparity, // null or alternative reference disparity
				quadCLTs[nscene0], // QuadCLT        first_QuadClt, // First in comparison pair
				pXpYD_ref,         // double [][]    pXpYD_ref,     // pXpYD for the reference scene
				reliable_ref,      // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
				tp_tasks_ref[0],   // TpTask[]       tp_tasks_ref,  // only (main if MB correction) tasks for FPN correction
				quadCLTs[nscene1], // QuadCLT        scene_QuadClt,
				scene1_xyzatr,     // double [][]    camera_xyzatr,
				scene1_xyzatr_pull,// double [][]    scene_xyzatr_pull,  // if both are not null, specify target values to pull to
				scene0_xyzatr,     // double [][]    ref_xyzatr, 
				param_select,      // boolean[]      param_select,
				param_regweights,  // double []      param_regweights,
				rms_out,           // double []      rms_out, // null or double [2]
				max_rms,           // double         max_rms,
				// motion blur compensation 
				mb_tau,            // double         mb_tau,      // 0.008; // time constant, sec
				mb_max_gain,       // double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
				mb_vectors_scene1, // double [][]    mb_vectors,  // now [2][ntiles];
				debugLevel);       // int            debug_level)
	}
	
	
	
	@Deprecated
	// Trying to replace with universal adjustDiffPairsLMAInterscene() 
	public static double[][]  adjustPairsLMAInterscene(
			CLTParameters  clt_parameters,
		    boolean        use_lma_dsi,
			boolean        fpn_disable,   // disable fpn filter if images are known to be too close
			boolean        disable_ers,
			double []      min_max,       // null or pair of minimal and maximal offsets
			int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
			QuadCLT        reference_QuadClt,
			double []      ref_disparity, // null or alternative reference disparity
			boolean []     reliable_ref, // null or bitmask of reliable reference tiles
			QuadCLT        scene_QuadClt,
			double []      camera_xyz,
			double []      camera_atr,
			double []      scene_xyz_pull, // if both are not null, specify target values to pull to 
			double []      scene_atr_pull, // 
			boolean[]      param_select,
			double []      param_regweights,
			double []      rms_out, // null or double [2]
			double         max_rms,
			int            debug_level)
	{
//	    boolean use_lma_dsi =          clt_parameters.imp.use_lma_dsi;
		if (ref_disparity == null) { 
			ref_disparity = reference_QuadClt.getDLS()[use_lma_dsi?1:0];
		}

		// TODO: set reference as new version of adjustPairsLMAInterscene() assumes it set
		int        margin = clt_parameters.imp.margin;
		double [][] pXpYD_ref = OpticalFlow.transformToScenePxPyD( // full size - [tilesX*tilesY], some nulls
				null,               // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
				ref_disparity,      // dls[0],  // final double []   disparity_ref, // invalid tiles - NaN in disparity (maybe it should not be masked by margins?)
				ZERO3,              // final double []   scene_xyz, // camera center in world coordinates
				ZERO3,              // final double []   scene_atr, // camera orientation relative to world frame
				reference_QuadClt,  // final QuadCLT     scene_QuadClt,
				reference_QuadClt); // final QuadCLT     reference_QuadClt)
		
		/* Does not use motion blur for reference scene here! */
		
		TpTask[][] tp_tasks_ref2 = setReferenceGPU (
				clt_parameters,     // CLTParameters      clt_parameters,			
				reference_QuadClt,  // QuadCLT            ref_scene,
				ref_disparity,      // double []          ref_disparity, // null or alternative reference disparity
				pXpYD_ref,          // double [][]        ref_pXpYD,
				reliable_ref,       // final boolean []   selection, // may be null, if not null do not  process unselected tiles
				margin,             // final int          margin,
				// motion blur compensation 
				0.0,                // double             mb_tau,      // 0.008; // time constant, sec
				0.0,                // double             mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
				null,               // double [][]        mb_vectors,  // now [2][ntiles];
				debug_level);       // int                debug_level)
		double [][] scene_xyzatr =      new double[][] {camera_xyz, camera_atr}; // 
		double [][] scene_xyzatr_pull = new double[][] {scene_xyz_pull, scene_atr_pull}; //
		double [][] ref_xyzatr = new double[2][3];
		return  adjustPairsLMAInterscene( // assumes reference GPU data is already set - once for multiple scenes
				clt_parameters,    // CLTParameters  clt_parameters,
				true,              // boolean        initial_adjust,
				false,             // boolean        fpn_disable,   // disable fpn filter if images are known to be too close
				disable_ers,       // boolean        disable_ers,
				min_max,           // double []      min_max,       // null or pair of minimal and maximal offsets
				fail_reason,       // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
				reference_QuadClt, // QuadCLT        ref_QuadClt,   // reference scene for ref_disparity, zero xyz and atr
				ref_disparity,     // double []      ref_disparity, // null or alternative reference disparity
				reference_QuadClt, // QuadCLT        first_QuadClt, // First in comparison pair
				pXpYD_ref,         //				double [][]    pXpYD_ref,     // pXpYD for the reference scene
				reliable_ref,      // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
				tp_tasks_ref2[0],  // TpTask[]       tp_tasks_ref,  // only (main if MB correction) tasks for FPN correction
				scene_QuadClt,     // QuadCLT        scene_QuadClt,
				scene_xyzatr,      // double [][]    scene_xyzatr,
				scene_xyzatr_pull, // double [][]    scene_xyzatr_pull,  // if both are not null, specify target values to pull to
				ref_xyzatr,        // double [][]    ref_xyzatr 
				param_select,      // boolean[]      param_select,
				param_regweights,  // double []      param_regweights,
				rms_out,           // double []      rms_out, // null or double [2]
				max_rms,           // double         max_rms,
				// motion blur compensation 
				0.0,               // double         mb_tau,      // 0.008; // time constant, sec
				0.0,               // double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
				null,              // double [][]    mb_vectors,  // now [2][ntiles];
				debug_level);      // int            debug_level)
	}

	public static double[][]  adjustPairsLMAInterscene( // assumes reference scene already set in GPU.
			CLTParameters  clt_parameters,
			boolean        initial_adjust, // will remember adjustment parameters for comparison
			boolean        fpn_disable,   // disable fpn filter if images are known to be too close
			boolean        disable_ers,
			double []      min_max,       // null or pair of minimal and maximal offsets
			int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
			QuadCLT        ref_QuadClt,   // reference scene for ref_disparity, zero xyz and atr
			double []      ref_disparity, // null or alternative reference disparity
			QuadCLT        first_QuadClt, // First in comparison pair, does not need to be reference?
			double [][]    pXpYD_ref,     // pXpYD for the reference scene
			boolean []     reliable_ref, // null or bitmask of reliable reference tiles
			TpTask[]       tp_tasks_ref,  // only (main if MB correction) tasks for FPN correction
			QuadCLT        scene_QuadClt,
			double [][]    scene_xyzatr,
			double [][]    scene_xyzatr_pull, // if both are not null, specify target values to pull to 
			double [][]    ref_xyzatr,        // 
			boolean[]      param_select,
			double []      param_regweights,
			double []      rms_out, // null or double [2]
			double         max_rms,
			// motion blur compensation 
			double         mb_tau,      // 0.008; // time constant, sec
			double         mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
			double [][]    mb_vectors,  // now [2][ntiles];
			int            debug_level)
	{
		boolean show_initial = true; // debug feature
//		boolean debug_corr_fpn = false;
		boolean use3D = clt_parameters.ilp.ilma_3d;
		boolean filter_by_ers =   clt_parameters.ilp.ilma_ers_quads;
		double  gap_frac =        clt_parameters.ilp.ilma_gap_frac ; // 0.25;
		int     min_quad_tiles =  clt_parameters.ilp.ilma_min_quad_tiles ; // 10;
		double  min_quad_weight = clt_parameters.ilp.ilma_min_quad_weight ; // 0.01;
		
		double disparity_weight = use3D? clt_parameters.ilp.ilma_disparity_weight : 0.0;
		int        margin = clt_parameters.imp.margin;
		int        sensor_mask_inter = clt_parameters.imp.sensor_mask_inter ; //-1;
		float [][][] facc_2d_img = new float [1][][]; // set it to null?
		IntersceneLma intersceneLma = new IntersceneLma(
				clt_parameters.ilp.ilma_thread_invariant,
				disparity_weight);
		int lmaResult = -1;
		boolean last_run = false;
		double[][] scene_xyzatr0 = new double [][] {scene_xyzatr[0].clone(),scene_xyzatr[1].clone()};
		double [][][] coord_motion = null;
		boolean show_corr_fpn = (!clt_parameters.multiseq_run) && (debug_level > -1); //  -3; *********** Change to debug FPN correleation *** 
//		float [][] dbg_corr_fpn = debug_corr_fpn ? (new float [34][]) : null;
		int nlma = 0;
		// TODO: save ers_scene.ers_watr_center_dt and ers_scene.ers_wxyz_center_dt before first run lma, restore on failure
		// it is saved to backup parameters when first lma run (nlma == 0),         // boolean           first_run,
		for (; nlma < clt_parameters.imp.max_cycles; nlma ++) {
			boolean near_important = nlma > 0;
			// FIXME: not re-calculating motion blur. Maybe do that after each parameter update?
			// pass dxyzatr_dt, not mb_vectors? Or update velocities too?
			coord_motion = interCorrPair( // new double [tilesY][tilesX][][];
					clt_parameters,      // CLTParameters  clt_parameters,
					use3D,               // boolean        use3D,         // generate disparity difference
					fpn_disable,         //	boolean        fpn_disable,   // disable fpn filter if images are known to be too close
					mb_max_gain,         // double         mb_max_gain,					
					min_max,             // double []      min_max,       // null or pair of minimal and maximal offsets
					fail_reason,         // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
					ref_QuadClt,         // QuadCLT        ref_scene,     // Scene for which DSI (ref_disparity) is calculated 
					ref_disparity,       // double []      ref_disparity, // null or alternative reference disparity
					first_QuadClt,       // QuadCLT        reference_QuadCLT,
					pXpYD_ref,           // double [][]    pXpYD_ref,     // pXpYD for the reference scene			
					tp_tasks_ref,        // TpTask[]       tp_tasks_ref,  // only (main if MB correction) tasks for FPN correction
					scene_QuadClt,       // QuadCLT        scene_QuadCLT,
					scene_xyzatr0[0],    //                xyz
					scene_xyzatr0[1],    // pose[1],      // atr
					reliable_ref,        // ****null,     // final boolean [] selection, // may be null, if not null do not  process unselected tiles
					margin,              // final int      margin,
					sensor_mask_inter,   // final int      sensor_mask_inter, // The bitmask - which sensors to correlate, -1 - all.
					facc_2d_img,         // final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)
					null, // dbg_corr_fpn,        //	final float [][] dbg_corr_fpn,
					near_important,      // boolean            near_important, // do not reduce weight of the near tiles
					false,               // boolean            all_fpn,        // do not lower thresholds for non-fpn (used during search)
					initial_adjust,      // boolean             initial_adjust,
					mb_vectors,          // double [][]        mb_vectors,  // now [2][ntiles];
					clt_parameters.imp.debug_level, // int                imp_debug_level,
					debug_level); // 1); // -1); // int debug_level);
	        if (coord_motion == null) {
	        	System.out.println("adjustPairsLMAInterscene() returned null");
	        	return null;
	        }
	        boolean ers_lma = false;
	        for (int i:ErsCorrection.DP_ERS_INDICES) { // does it want to use ERS-related parameters
	        	ers_lma |= param_select[i];
	        }
			boolean [] param_select_mod = param_select; 
			if (ers_lma && filter_by_ers && !disable_ers) {
				double [][] quad_strengths = getQuadStrengths(
						coord_motion,                                      // double [][][] coord_motion,
						gap_frac,                                          //double           gap_frac, // 0.25 
						first_QuadClt.getTileProcessor().getTilesX()); // int           tilesX);
				for (int i = 0; i < quad_strengths[0].length; i++) {
					if ((quad_strengths[0][i] < min_quad_tiles) || (quad_strengths[1][i] < min_quad_weight)) {
						disable_ers = true;
						if (clt_parameters.imp.debug_level > -2) {
							System.out.print("adjustPairsLMAInterscene(): insufficient data for ERS, skipping. ");
							System.out.println ("quad_defined = ["+((int)quad_strengths[0][0])+","
									+((int)quad_strengths[0][1])+","+((int)quad_strengths[0][2])+","+((int)quad_strengths[0][3])+"] (needed = "+
									min_quad_tiles +"), rel_strengths = ["
									+quad_strengths[1][0]+","+quad_strengths[1][1]+","
									+quad_strengths[1][2]+","+quad_strengths[1][3]+"] (needed "
									+min_quad_weight+")");
						}
						break;
					}
				}
			}
			
			// update setting ers_scene
			
			if (disable_ers) {
				param_select_mod = param_select.clone();
				for (int i:ErsCorrection.DP_ERS_INDICES) {
					param_select_mod[i] = false;
				}
				// TODO Removed on 11/23/2023 for use with IMS. Set reasonable data if IMS is not used
				/*
				// now just copy ers from the reference 
				ErsCorrection ers_ref =   reference_QuadClt.getErsCorrection();
				ErsCorrection ers_scene = scene_QuadClt.getErsCorrection();
				// when disabled - scene ers from the same as reference one
				ers_scene.ers_watr_center_dt = ers_ref.ers_watr_center_dt.clone();
				ers_scene.ers_wxyz_center_dt = ers_ref.ers_wxyz_center_dt.clone();
				*/
			}
//			double [][] ref_xyzatr_inv = ErsCorrection.invertXYZATR(ref_xyzatr);
			// TODO: save ers_scene.ers_watr_center_dt and ers_scene.ers_wxyz_center_dt
			intersceneLma.prepareLMA(
					scene_xyzatr0,         // final double []   scene_xyzatr0,     // camera center in world coordinates (or null to use instance)
					scene_xyzatr_pull,     // final double []   scene_xyz_pull, // if both are not null, specify target values to pull to
					ref_xyzatr, // _inv, // ref_xyzatr, 
					// reference atr, xyz are considered 0.0 not anymore?
					scene_QuadClt,       // final QuadCLT     scene_QuadClt,
					first_QuadClt,   // final QuadCLT     reference_QuadClt,
					param_select_mod, // param_select,        // final boolean[]   param_select,
					param_regweights,    // final double []   param_regweights,
					coord_motion[1],     // final double [][] vector_XYS, // optical flow X,Y, confidence obtained from the correlate2DIterate()
					coord_motion[0],     // final double [][] centers,    // macrotile centers (in pixels and average disparities
					(nlma == 0),         // boolean           first_run,
					clt_parameters.imp.debug_level);           // final int         debug_level)
			lmaResult = intersceneLma.runLma(
					clt_parameters.ilp.ilma_lambda, // double lambda,           // 0.1
					clt_parameters.ilp.ilma_lambda_scale_good, //  double lambda_scale_good,// 0.5
					clt_parameters.ilp.ilma_lambda_scale_bad,  // double lambda_scale_bad, // 8.0
					clt_parameters.ilp.ilma_lambda_max,        // double lambda_max,       // 100
					clt_parameters.ilp.ilma_rms_diff,          // double rms_diff,         // 0.001
					clt_parameters.imp.max_LMA,                // int    num_iter,         // 20
					last_run,                                  // boolean last_run,
					debug_level);           // int    debug_level)
			if (lmaResult < 0) {
				System.out.println("Interscene adjustment failed, lmaResult="+lmaResult+" < 0");
				if (fail_reason != null) {
					fail_reason[0]=FAIL_REASON_LMA;
				}
				// TODO: Restore *_dt from backup_parameters_full
				scene_QuadClt.getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
						intersceneLma.getSceneERSXYZ(true), // true for initial values
						intersceneLma.getSceneERSATR(true)); // true for initial values
				return null;
			}
			if (debug_level > -3){
				System.out.println ("iter="+lmaResult+", RMS="+intersceneLma.getLastRms()[0]+
						" (Pure RMS="+intersceneLma.getLastRms()[1]+")");
			}
			scene_xyzatr0 = intersceneLma.getSceneXYZATR(false); // true for initial values
			double [] diffs_atr = intersceneLma.getV3Diff(ErsCorrection.DP_DSAZ);
			double [] diffs_xyz = intersceneLma.getV3Diff(ErsCorrection.DP_DSX);
			if ((diffs_atr[0] < clt_parameters.imp.exit_change_atr) &&
					(diffs_xyz[0] < clt_parameters.imp.exit_change_xyz)) {
				break;
			}
			// TODO: Is ers_scene.ers_wxyz_center_dt updated after running LMA?
			// No, update it here !
			scene_QuadClt.getErsCorrection().setErsDt( // set for ref also (should be set before non-ref!)
					intersceneLma.getSceneERSXYZ(false), // true for initial values
					intersceneLma.getSceneERSATR(false)); // true for initial values
					
//					scaled_dxyzatr_dt[0], // (ers_use_xyz? dxyzatr_dt[nscene][0]: ZERO3), //, // dxyzatr_dt[nscene][0], // double []    ers_xyz_dt,
//					scaled_dxyzatr_dt[1]); // dxyzatr_dt[nscene][1]); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
		}
		if (show_corr_fpn && (debug_level > -1)) { // now not needed, restore if needed
//			int num_components = intersceneLma.getNumComponents();
			int num_components = 2;
			String [] fpn_dbg_titles = new String[num_components + scene_QuadClt.getNumSensors() * 2];
			fpn_dbg_titles[0] = "X_avg";
			fpn_dbg_titles[1] = "Y_avg";
			if (num_components > 2) {
				fpn_dbg_titles[2] = "Disp";
			}
			for (int i = 0; i < scene_QuadClt.getNumSensors(); i++) {
				fpn_dbg_titles[num_components + 0 + 2*i] = "X-"+i;
				fpn_dbg_titles[num_components + 1 + 2*i] = "Y-"+i;
			}
			float [][] dbg_corr_fpn = new float [fpn_dbg_titles.length][];
			coord_motion = interCorrPair( // new double [tilesY][tilesX][][];
					clt_parameters,      // CLTParameters  clt_parameters,
					false,               // boolean        use3D,         // generate disparity difference
					false, // 			boolean            fpn_disable,   // disable fpn filter if images are known to be too close
					mb_max_gain,         // double         mb_max_gain,	
					min_max,             // double []      min_max,       // null or pair of minimal and maximal offsets
					fail_reason,         // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
					first_QuadClt,   // QuadCLT        ref_scene,     // Scene for which DSI (ref_disparity) is calculated 
					ref_disparity,       // double []      ref_disparity, // null or alternative reference disparity
					first_QuadClt,   // QuadCLT        first_scene,
					pXpYD_ref,           // double [][]        pXpYD_ref,     // pXpYD for the reference scene			
					tp_tasks_ref,        // TpTask[]           tp_tasks_ref,  // only (main if MB correction) tasks for FPN correction
					scene_QuadClt,       // QuadCLT scene_QuadCLT,
					scene_xyzatr0[0],    // xyz
					scene_xyzatr0[1],    // pose[1], // atr
					reliable_ref,        // ****null,                // final boolean [] selection, // may be null, if not null do not  process unselected tiles
					margin,              // final int        margin,
					sensor_mask_inter,   // final int        sensor_mask_inter, // The bitmask - which sensors to correlate, -1 - all.
					facc_2d_img,         // final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)
					dbg_corr_fpn,        //	final float [][] dbg_corr_fpn,
					true,                // boolean            near_important, // do not reduce weight of the near tiles
					false,               // boolean            all_fpn,        // do not lower thresholds for non-fpn (used during search)
					initial_adjust,      // boolean            initial_adjust,					
					mb_vectors,          // double [][]        mb_vectors,  // now [2][ntiles];
					2,                   // int                imp_debug_level,
					debug_level); // 1); // -1); // int debug_level);
			TileProcessor tp = first_QuadClt.getTileProcessor();
			int tilesX = tp.getTilesX();
			int tilesY = tp.getTilesY();

			ShowDoubleFloatArrays.showArrays(
					dbg_corr_fpn,
					tilesX,
					tilesY,
					true,
					scene_QuadClt.getImageName()+"-"+first_QuadClt.getImageName()+"-CORR-FPN",
					fpn_dbg_titles);
		}
		if (show_corr_fpn) { // repeat after last adjustment to get images
//			int num_components = intersceneLma.getNumComponents();
			int num_components = 2; // here - no disparity
			String [] fpn_dbg_titles = new String[num_components + scene_QuadClt.getNumSensors() * 2];
			fpn_dbg_titles[0] = "X_avg";
			fpn_dbg_titles[1] = "Y_avg";
			if (num_components > 2) {
				fpn_dbg_titles[2] = "Disp";
			}
			for (int i = 0; i < scene_QuadClt.getNumSensors(); i++) {
				fpn_dbg_titles[num_components + 0 + 2*i] = "X-"+i;
				fpn_dbg_titles[num_components + 1 + 2*i] = "Y-"+i;
			}
			float [][] dbg_corr_fpn = new float [fpn_dbg_titles.length][];
			coord_motion = interCorrPair( // new double [tilesY][tilesX][][];
					clt_parameters,      // CLTParameters  clt_parameters,
					false,               // boolean        use3D,         // generate disparity difference
					false, // 			boolean            fpn_disable,   // disable fpn filter if images are known to be too close
					mb_max_gain,         // double         mb_max_gain,
					null,                // double []      min_max,       // null or pair of minimal and maximal offsets
					null,                // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
					first_QuadClt,   // QuadCLT        ref_scene,     // Scene for which DSI (ref_disparity) is calculated 
					ref_disparity,       // double []      ref_disparity, // null or alternative reference disparity
					first_QuadClt,   // QuadCLT        first_scene,
					pXpYD_ref,           // double [][]        pXpYD_ref,     // pXpYD for the reference scene			
					tp_tasks_ref,        // TpTask[]           tp_tasks_ref,  // only (main if MB correction) tasks for FPN correction
					scene_QuadClt,       // QuadCLT scene_QuadCLT,
					scene_xyzatr0[0],    // xyz
					scene_xyzatr0[1],    // pose[1], // atr
					reliable_ref,        // ****null,                // final boolean [] selection, // may be null, if not null do not  process unselected tiles
					margin,              // final int        margin,
					sensor_mask_inter,   // final int        sensor_mask_inter, // The bitmask - which sensors to correlate, -1 - all.
					facc_2d_img,         // final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)
					dbg_corr_fpn,        //	final float [][] dbg_corr_fpn,
					true,                // boolean            near_important, // do not reduce weight of the near tiles
					false,               // boolean            all_fpn,        // do not lower thresholds for non-fpn (used during search)
					initial_adjust,      // boolean            initial_adjust,					
					mb_vectors,          // double [][]        mb_vectors,  // now [2][ntiles];
					2,                   // int                imp_debug_level,
					debug_level); // 1); // -1); // int debug_level);
			TileProcessor tp = first_QuadClt.getTileProcessor();
			int tilesX = tp.getTilesX();
			int tilesY = tp.getTilesY();

			ShowDoubleFloatArrays.showArrays(
					dbg_corr_fpn,
					tilesX,
					tilesY,
					true,
					scene_QuadClt.getImageName()+"-"+first_QuadClt.getImageName()+"-CORR-FPN",
					fpn_dbg_titles);
		}
		if (clt_parameters.imp.debug_level > -2) {
			String [] lines1 = intersceneLma.printOldNew((clt_parameters.imp.debug_level > -1)); // false); // boolean allvectors)
			System.out.println("Adjusted interscene, iteration="+nlma+
					", last RMS = "+intersceneLma.getLastRms()[0]+
					" (pure RMS = "+intersceneLma.getLastRms()[1]+")"+
					", results:");
			for (String line : lines1) {
				System.out.println(line);
			}
			if (show_initial) {
				lines1 = intersceneLma.printOldNew(true, 1); // force "was"
				for (String line : lines1) {
					System.out.println(line);
				}
			}
		}
		if ((rms_out != null) && (intersceneLma.getLastRms() != null)) {
			rms_out[0] = intersceneLma.getLastRms()[0];
			rms_out[1] = intersceneLma.getLastRms()[1];
			//if (lmaResult < 0) { last_rms[0]
		}
		if (max_rms > 0.0) {
			if (lmaResult < 0) { // = 0) {
				return null;
			}
			if (!(intersceneLma.getLastRms()[0] <= max_rms)) {
				System.out.println("RMS failed: "+intersceneLma.getLastRms()[0]+" >= " + max_rms);
				return null;
			}
		}
		return scene_xyzatr0;
	}	
	
	
	
	
	
    public static double [] spiralSearchATR(
    		CLTParameters             clt_parameters,
        	boolean                   use_lma_dsi, // may be turned off by the caller if there are too few points
    		int                       search_rad, // to reduce for the next scenes on failure (FPN filter)
    		QuadCLT                   reference_QuadClt,
    		QuadCLT                   scene_QuadClt,
    		boolean []                reliable_ref,
    		int                       debugLevel
    		) {
//		boolean use3D = clt_parameters.ilp.ilma_3d && (clt_parameters.ilp.ilma_disparity_weight > 0);
//		double disparity_weight = use3D? clt_parameters.ilp.ilma_disparity_weight : 0.0;
    	int [][] offset_start_corner = {{-1,-1},{1,-1},{1,1},{-1,1}}; //{x,y}
    	int [][] offset_move = {{1,0},{0,1},{-1,0},{0,-1}};
    	int    margin =            clt_parameters.imp.margin;
    	int    sensor_mask_inter = clt_parameters.imp.sensor_mask_inter ; //-1;
    	float [][][] facc_2d_img = new float [1][][];
    	int    pix_step =          clt_parameters.imp.pix_step;      // 4;
    	// int    search_rad =        clt_parameters.imp.search_rad;    // 10;
    	double maybe_sum =         clt_parameters.imp.maybe_sum;     // 8.0;
    	double sure_sum =          clt_parameters.imp.sure_sum;     // 30.0;
    	double maybe_avg =         clt_parameters.imp.maybe_avg;     // 0.005;
    	double sure_avg =          clt_parameters.imp.sure_avg;     // 0.015;
    	
    	double  max_search_rms =   clt_parameters.imp.max_search_rms; //             1.2;   // good - 0.34, so-so - 0.999
    	double  maybe_fom =        clt_parameters.imp.maybe_fom;      //             3.0;   // good - 30, second good - 5
    	double  sure_fom =         clt_parameters.imp.sure_fom;       //            10.0;   // good - 30, second good - 10
    	boolean treat_serch_fpn =  clt_parameters.imp.treat_serch_fpn;// use FPN (higher) thresholds during search (even if offset is not small)

//    	boolean use_lma_dsi =      clt_parameters.imp.use_lma_dsi;
    	
		double [][] pose = new double [2][3];
    	double angle_per_step = reference_QuadClt.getGeometryCorrection().getCorrVector().getTiltAzPerPixel() * pix_step;
    	double [][] atrs = new double [(2*search_rad+1)*(2*search_rad+1)][];
    	boolean [] good_offset =    new boolean [atrs.length];
    	double [] confidences_sum = new double [atrs.length];
    	double [] confidences_avg = new double [atrs.length];
    	double [] confidences_fom = new double [atrs.length];
    	int min_defined =           75;
    	int rad = 0, dir=0, n=0;
    	int ntry = 0;
    	int num_good=0;
    	double [] use_atr = null;
    	int dbg_ntry = 61;
    	
    	double []ref_disparity = reference_QuadClt.getDLS()[use_lma_dsi?1:0];
    	double [][]  pXpYD_ref = OpticalFlow.transformToScenePxPyD( // full size - [tilesX*tilesY], some nulls
    			null,               // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
    			ref_disparity,      // dls[0],  // final double []   disparity_ref, // invalid tiles - NaN in disparity (maybe it should not be masked by margins?)
    			ZERO3,              // final double []   scene_xyz, // camera center in world coordinates
    			ZERO3,              // final double []   scene_atr, // camera orientation relative to world frame
    			reference_QuadClt,  // final QuadCLT     scene_QuadClt,
    			reference_QuadClt); // final QuadCLT     reference_QuadClt)
    	TpTask[] tp_tasks_ref = setReferenceGPU (
    			clt_parameters,     // CLTParameters      clt_parameters,			
    			reference_QuadClt,  // QuadCLT            ref_scene,
    			ref_disparity,      // double []          ref_disparity, // null or alternative reference disparity
    			pXpYD_ref,          // double [][]        ref_pXpYD,
    			reliable_ref,       // final boolean []   selection, // may be null, if not null do not  process unselected tiles
    			margin,             // final int          margin,
    			// motion blur compensation 
    			0.0,                // double             mb_tau,      // 0.008; // time constant, sec
    			0.0,                // double             mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
    			null,               // double [][]        mb_vectors,  // now [2][ntiles];
    			debugLevel)[0];       // int                debug_level)
    	
    	try_around:
    		for (rad = 00; rad <= search_rad; rad++) {
    			for (dir = 0; dir < ((rad==0)?1:4); dir++) {
    				int n_range = (rad > 0) ? (2* rad) : 1;
    				for (n = 0; n < n_range; n++) {
    					int ix = rad*offset_start_corner[dir][0] + n * offset_move[dir][0];
    					int iy = rad*offset_start_corner[dir][1] + n * offset_move[dir][1];;
    					double [] atr = {
    							pose[1][0]+ix * angle_per_step,
    							pose[1][1]+iy * angle_per_step,
    							pose[1][2]};
    					if (debugLevel > -1) {
    						System.out.println("buildSeries(): trying adjustPairsLMA() with initial offset azimuth: "+
    								atr[0]+", tilt ="+atr[1]);
    					}
    					if (ntry==dbg_ntry) {
    						System.out.println("ntry="+ntry);
    					}
    					double [][][] coord_motion = interCorrPair( // new double [tilesY][tilesX][][];
    							clt_parameters,      // CLTParameters  clt_parameters,
    							false, // use3D,               // boolean        use3D,         // generate disparity difference
    							false, // 			boolean            fpn_disable,   // disable fpn filter if images are known to be too close
    							0,                   // mb_max_gain,         // double  mb_max_gain,
    							null,                // double []      min_max,       // null or pair of minimal and maximal offsets
    							null,                // int []         fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
    							reference_QuadClt,   // QuadCLT ref_scene,     // Scene for which DSI (ref_disparity) is calculated 
    							ref_disparity,       // null, // double []        ref_disparity, // null or alternative reference disparity
    							reference_QuadClt,   // QuadCLT first_scene,
    							pXpYD_ref,   // 			double [][]        pXpYD_ref,     // pXpYD for the reference scene	11653		
    							tp_tasks_ref, // 			TpTask[]           tp_tasks_ref,  // only (main if MB correction) tasks for FPN correction
    							scene_QuadClt,       // QuadCLT scene_QuadCLT,
    							pose[0],             // camera_xyz0,         // xyz
    							atr,                 // camera_atr0,         // pose[1], // atr
    							reliable_ref, // null,                // final boolean [] selection, // may be null, if not null do not  process unselected tiles
    							margin,              // final int        margin,
    							sensor_mask_inter,   // final int        sensor_mask_inter, // The bitmask - which sensors to correlate, -1 - all.
    							facc_2d_img,         // final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)
    							null,                //	final float [][][] dbg_corr_fpn,
    							false,               // boolean            near_important, // do not reduce weight of the near tiles
    							treat_serch_fpn,     // true,                // boolean            all_fpn,        // do not lower thresholds for non-fpn (used during search)
    							true,                // boolean             initial_adjust,
    							null,                // double [][]        mb_vectors,  // now [2][ntiles];
    							clt_parameters.imp.debug_level, // int                imp_debug_level,
    							clt_parameters.imp.debug_level); // 1); // -1); // int debug_level);
    					// may return null if failed to match
    					if (coord_motion == null) {
    			    		System.out.println("Failed to find a match between the reference scene ("+reference_QuadClt.getImageName() +
    			    				") and a previous one ("+scene_QuadClt.getImageName()+"), coord_motion == null");
    			    		return null;
    					}
    					int num_defined = 0;
    					double sum_strength = 0.0;
    					// calculate average vector and standard deviation for its components
    					double swx=0.0,swy = 0.0, swx2 = 0.0, swy2 = 0.0, sw2=0.0 ; 
    					// coord_motion may be null here
    					for (int i = 0; i < coord_motion[1].length; i++) if (coord_motion[1][i] != null){
    						swx +=  coord_motion[1][i][2] * coord_motion[1][i][0];
    						swx2 += coord_motion[1][i][2] * coord_motion[1][i][0] * coord_motion[1][i][0];
    						swy +=  coord_motion[1][i][2] * coord_motion[1][i][1];
    						swy2 += coord_motion[1][i][2] * coord_motion[1][i][1] * coord_motion[1][i][1];
    						sum_strength += coord_motion[1][i][2];
    						sw2+=coord_motion[1][i][2]*coord_motion[1][i][2];
    						num_defined++;
    					}
    					double vx_avg = swx/sum_strength;
    					double vy_avg = swy/sum_strength;
    					double l2x = swx2/sum_strength - vx_avg * vx_avg;
    					double l2y = swy2/sum_strength - vy_avg * vy_avg;
    					double rms_x = Math.sqrt(l2x);
    					double rms_y= Math.sqrt(l2y);
    					double rms = Math.sqrt(l2x+l2y);
    					double fom = sum_strength/rms/Math.sqrt(0.01 * num_defined); // reduce influence of number defined
    					double avg_strength = (num_defined > 0)?(sum_strength/num_defined) : 00;
    					atrs[ntry] = atr;
    					confidences_sum[ntry] = sum_strength;
    					confidences_avg[ntry] = avg_strength;
    					confidences_fom[ntry] = fom;
    					boolean good = (num_defined > min_defined) && (rms < max_search_rms) && (fom > maybe_fom) && (sum_strength > maybe_sum) && (avg_strength > maybe_avg);
    					good_offset[ntry] = good;
    					if (debugLevel > -2) {
    						System.out.println ((good?"* ":"  ")+"ntry = "+ntry+", num_defined = "+num_defined+" ("+min_defined+")"+
    								", FOM="+fom+
    								", vx_avg = "+vx_avg+", vy_avg="+vy_avg+
    								", sum_strength = "+sum_strength+", rms="+rms+
    								", str2="+Math.sqrt(sw2)+ ", str2_avg="+Math.sqrt(sw2/num_defined)+
    								", avg_strength = "+avg_strength+
    								", rms_x="+rms_x+", rms_y="+rms_y);
    					}
    					if (good) {
    						if ((fom > sure_fom) || (sum_strength > sure_sum) || (avg_strength > sure_avg)) {
    							use_atr = atr;
    							break try_around;
    						}
    						num_good++;
    					}
//    					if (ntry == 105) {
//    						System.out.println("spiralSearchATR(): ntry="+ntry);
//    					}
    					ntry++;
    				}
    			}
    		}
    	if ((use_atr == null) && (num_good == 0)) {
    		System.out.println("Failed to find a match between the reference scene ("+reference_QuadClt.getImageName() +
    				") and a previous one ("+scene_QuadClt.getImageName()+")");
    		return null;
    	}
    	if (use_atr == null) {
    		int best_ntry = -1;
    		for (int i = 0; i <  atrs.length; i++) if (good_offset[i]){
       			if ((best_ntry < 0) || (confidences_fom[i] > confidences_fom[best_ntry])) {
    				best_ntry = i;
    			}
    		}
    		use_atr = atrs[best_ntry];
    	}
    	return use_atr;
    }
	
	
	/**
	 * 
	 * @param clt_parameters
	 * @param use3D           when true, generate disparity difference in addition to average {pX,pY} 
	 * @param mb_max_gain
	 * @param first_scene
	 * @param ref_disparity
	 * @param pXpYD_ref
	 * @param tp_tasks_ref
	 * @param scene
	 * @param scene_xyz
	 * @param scene_atr
	 * @param selection
	 * @param margin
	 * @param sensor_mask_inter
	 * @param accum_2d_corr
	 * @param dbg_corr_fpn
	 * @param near_important
	 * @param all_fpn
	 * @param mb_vectors
	 * @param imp_debug_level
	 * @param debug_level
	 * @return
	 */
	
	public static double [][][]  interCorrPair( // return [tilesX*telesY]{ref_pXpYD, dXdYS}
			CLTParameters      clt_parameters,
			boolean            use3D,         // generate disparity difference
			boolean            fpn_disable,   // disable fpn filter if images are known to be too close
			double             mb_max_gain,
			double []          min_max,       // null or pair of minimal and maximal offsets
			int []             fail_reason,   // null or int[1]: 0 - OK, 1 - LMA, 2 - min, 3 - max
			QuadCLT            ref_scene,     // Scene for which DSI (ref_disparity) is calculated 
			double []          ref_disparity, // null or alternative reference disparity
			QuadCLT            first_scene,   // first in a pair, scene to compare to
			double [][]        pXpYD_ref,     // pXpYD for the reference scene			
			TpTask[]           tp_tasks_ref,  // only (main if MB correction) tasks for FPN correction.
			QuadCLT            scene,
			double []          scene_xyz,
			double []          scene_atr,
			final boolean []   selection, // may be null, if not null do not  process unselected tiles
			final int          margin,
			final int          sensor_mask_inter, // The bitmask - which sensors to correlate, -1 - all.
			final float [][][] accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)
			final float [][]   dbg_corr_fpn,
			boolean            near_important, // do not reduce weight of the near tiles
			boolean            all_fpn,        // do not lower thresholds for non-fpn (used during search)
			boolean            initial_adjust,
			// motion blur compensation 
			double [][]        mb_vectors,  // now [2][ntiles];
			int                imp_debug_level, // MANUALLY change to 2 for debug!
			int                debug_level)
	{
		boolean use3D_lma = clt_parameters.ilp.ilma_3d_lma;
		boolean use3D_lma_tilt_only = clt_parameters.ilp.ilma_3d_tilt_only;
		if (!first_scene.hasGPU()) {
			throw new IllegalArgumentException ("interCorrPair(): CPU mode not supported");
		}
		TileProcessor tp = first_scene.getTileProcessor();
		// Temporary reusing same ref scene ******
		boolean scene_is_ref_test =    clt_parameters.imp.scene_is_ref_test; // false; // true;
		boolean show_2d_correlations = clt_parameters.imp.show2dCorrelations(imp_debug_level); // true;
		boolean show_motion_vectors =  clt_parameters.imp.showMotionVectors(imp_debug_level); // true;
		boolean show_render_ref =      clt_parameters.imp.renderRef(imp_debug_level); // false; //true;
		boolean show_render_scene =    clt_parameters.imp.renderScene(imp_debug_level); // false; // true;
		boolean toRGB =                clt_parameters.imp.toRGB  ; // true;
		boolean show_coord_motion =    clt_parameters.imp.showCorrMotion(imp_debug_level); // mae its own
	    int     erase_clt = (toRGB? clt_parameters.imp.show_color_nan : clt_parameters.imp.show_mono_nan) ? 1:0;
	    boolean use_lma_dsi =          clt_parameters.imp.use_lma_dsi;
		boolean mov_en =               clt_parameters.imp.mov_en; // true;  // enable detection/removal of the moving objects during pose matching
	    boolean mov_debug_images =     clt_parameters.imp.showMovementDetection(imp_debug_level);
	    int mov_debug_level =          clt_parameters.imp.movDebugLevel(imp_debug_level);

	    boolean fpn_remove =           !fpn_disable && clt_parameters.imp.fpn_remove;
	    double  fpn_max_offset =       clt_parameters.imp.fpn_max_offset;
	    double  fpn_radius =           clt_parameters.imp.fpn_radius;
	    boolean fpn_ignore_border =    clt_parameters.imp.fpn_ignore_border; // only if fpn_mask != null - ignore tile if maximum touches fpn_mask
	    boolean show_tptask_ref =      false;
	    boolean show_tptask_scene =    false;
	    boolean eq_debug =             false;
	    boolean transform_debug =      false;
	    boolean eq_en = near_important && clt_parameters.imp.eq_en; //  true;// equalize "important" FG tiles for better camera XYZ fitting	    
        int     eq_stride_hor =        clt_parameters.imp.eq_stride_hor; //  8;
		int     eq_stride_vert =       clt_parameters.imp.eq_stride_vert; // 8;
		double  eq_min_stile_weight =  clt_parameters.imp.eq_min_stile_weight; // 0.2; // 1.0;
		int     eq_min_stile_number =  clt_parameters.imp.eq_min_stile_number; // 10;
		double  eq_min_stile_fraction =clt_parameters.imp.eq_min_stile_fraction; // 0.02; // 0.05;
		double  eq_min_disparity =     clt_parameters.imp.eq_min_disparity; //  5;
		double  eq_max_disparity =     clt_parameters.imp.eq_max_disparity; // 100;
		double  eq_weight_add =        clt_parameters.imp.eq_weight_add; //  0.05;
		double  eq_weight_scale =      clt_parameters.imp.eq_weight_scale; // 10;
		double  eq_level =             clt_parameters.imp.eq_level; //  0.8; // equalize to (log) fraction of average/this strength      
	   
		final double scene_disparity_cor = clt_parameters.imp.disparity_corr; // 04/07/2023 // 0.0;		
		final double gpu_sigma_corr =     clt_parameters.getGpuCorrSigma(first_scene.isMonochrome());
		final double gpu_sigma_rb_corr =  first_scene.isMonochrome()? 1.0 : clt_parameters.gpu_sigma_rb_corr;
		final double gpu_sigma_log_corr = clt_parameters.getGpuCorrLoGSigma(first_scene.isMonochrome());
		
		boolean mb_en =       clt_parameters.imp.mb_en ;
		double  mb_tau =      clt_parameters.imp.mb_tau;      // 0.008; // time constant, sec
///		double  mb_max_gain = clt_parameters.imp.mb_max_gain_inter; // 2.0;   // motion blur maximal gain (if more - move second point more than a pixel
		
		int tilesX =         tp.getTilesX();
		int tilesY =         tp.getTilesY();
		double [][][] coord_motion = null; // new double [2][tilesX*tilesY][];
		final double [][][] motion_vectors = show_motion_vectors?new double [tilesY *tilesX][][]:null;
		final float  [][][] fclt_corr = ((accum_2d_corr != null) || show_2d_correlations) ?
				(new float [tilesX * tilesY][][]) : null;
		if (debug_level > 1) {
			System.out.println("interCorrPair():   "+IntersceneLma.printNameV3("ATR",scene_atr)+
					" "+IntersceneLma.printNameV3("XYZ",scene_xyz));
		}
		if (show_tptask_ref) {
			if (tp_tasks_ref != null){
				String dbg_title = first_scene.getImageName()+"-ref";
				TpTask.showTpTask(
						tp_tasks_ref, // TpTask[] tp_tasks,
						tilesX,       // int      tilesX,
						tilesY,       // int      tilesY,
						dbg_title);   // String   title)
			}
		}
		
		ImageDtt image_dtt;
		image_dtt = new ImageDtt(
				first_scene.getNumSensors(), // ,
				clt_parameters.transform_size,
				clt_parameters.img_dtt,
				first_scene.isAux(),
				first_scene.isMonochrome(),
				first_scene.isLwir(),
				clt_parameters.getScaleStrength(first_scene.isAux()),
				first_scene.getGPU());
		image_dtt.getCorrelation2d(); // initiate image_dtt.correlation2d, needed if disparity_map != null  
		if (first_scene.getGPU() != null) {
			first_scene.getGPU().setGpu_debug_level(debug_level - 4); // monitor GPU ops >=-1
		}
		final double disparity_corr = clt_parameters.imp.disparity_corr; // 04/07/2023 // 0.0; // (z_correction == 0) ? 0.0 : geometryCorrection.getDisparityFromZ(1.0/z_correction);
//ref_disparity	
		if (ref_disparity == null) {
			ref_disparity = ref_scene.getDLS()[use_lma_dsi?1:0];
		}
		
		double [][] scene_pXpYD = OpticalFlow.transformToScenePxPyD( // will be null for disparity == NaN, total size - tilesX*tilesY
				null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
				ref_disparity, // dls[0], // final double []   disparity_ref, // invalid tiles - NaN in disparity (maybe it should not be masked by margins?)
				scene_xyz,    // final double []   scene_xyz, // camera center in world coordinates
				scene_atr,    // final double []   scene_atr, // camera orientation relative to world frame
				scene,        // final QuadCLT     scene_QuadClt,
				scene_is_ref_test ? null: ref_scene);   // final QuadCLT     reference_QuadClt)
		double [][] scene_disparity_strength = null; // calculate if needed	
		if (use3D) { //(scene_disparity_strength != null)
			final boolean [] valid_tiles = new boolean[tilesX * tilesY]; // or use null?
			float [][][][] fcorr_td = new float[tilesY][tilesX][][];
			int mcorr_sel = Correlation2d.corrSelEncode(clt_parameters.img_dtt, scene.getNumSensors());
			double [][] disparity_map = new double [ImageDtt.getDisparityTitles(scene.getNumSensors(), scene.isMonochrome()).length][];
			TpTask[]  tp_tasks_disp =  GpuQuad.setInterTasks( // just to calculate valid_tiles
					scene.getNumSensors(),
					scene.getGeometryCorrection().getSensorWH()[0],
					!scene.hasGPU(), // final boolean             calcPortsCoordinatesAndDerivatives, // GPU can calculate them centreXY
					scene_pXpYD, // final double [][]         pXpYD, // per-tile array of pX,pY,disparity triplets (or nulls)
					null,          // final boolean []          selection, // may be null, if not null do not  process unselected tiles
					scene.getGeometryCorrection(), // final GeometryCorrection  geometryCorrection,
					scene_disparity_cor, // final double              disparity_corr,
	    			margin, // final int                 margin,      // do not use tiles if their centers are closer to the edges
	    			valid_tiles, // final boolean []          valid_tiles,            
	    			THREADS_MAX); // final int                 threadsMax)  // maximal number of threads to launch
			
			  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_disp, // *** 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
					  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
					  THREADS_MAX,       // maximal number of threads to launch
					  debug_level);
			
			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)       
					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(scene.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_disp,                  // 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 
					scene.getGeometryCorrection().getRXY(false), // final double [][]         rXY,             // from geometryCorrection
					// next both can be nulls
					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
					null, // 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][]
					use3D_lma, // 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,
					THREADS_MAX,                    // final int                 threadsMax,      // maximal number of threads to launch
					null,                          // final String              debug_suffix,
					debug_level); // + 2+1); // -1 );              // final int                 globalDebugLevel)\
			if (use3D_lma) {
				scene_disparity_strength=new double[][]{
					disparity_map[ImageDtt.DISPARITY_INDEX_POLY],
					disparity_map[ImageDtt.DISPARITY_INDEX_POLY+1]};
			} else {
				scene_disparity_strength=new double[][]{
					disparity_map[ImageDtt.DISPARITY_INDEX_CM],
					disparity_map[ImageDtt.DISPARITY_INDEX_CM+1]};
			}
			// remove disparity average, only detect tilts
			double average_disparity = 0;
			double average_absolute_disparity = 0;
			{
				double sw = 0, swd = 0;
				for (int i = 0; i < scene_pXpYD.length; i++) {
					if ((scene_pXpYD[i]!=null) && !Double.isNaN(scene_pXpYD[i][2])) {
						sw+=1.0;
						swd += scene_pXpYD[i][2];
					}
				}
				average_absolute_disparity = swd/sw;
				// remove average disparity and multiply by average disparity to have the same influence ratio to tild 
				sw = 0;
				swd = 0;
				for (int i = 0; i < scene_disparity_strength[0].length; i++) {
					if (Double.isNaN(scene_disparity_strength[0][i]) || Double.isNaN(scene_disparity_strength[1][i])) {
						scene_disparity_strength[0][i]=0;
						scene_disparity_strength[1][i]=0;
					}
					sw +=  scene_disparity_strength[1][i];
					swd += scene_disparity_strength[0][i] * scene_disparity_strength[1][i];
				}
				average_disparity = swd/sw;
				double magic_scale = use3D_lma? 1.0 : first_scene.getTileProcessor().getMagicScale();
				for (int i = 0; i < scene_disparity_strength[0].length; i++) {
//					scene_disparity_strength[0][i] = (scene_disparity_strength[0][i] - average_disparity) / average_absolute_disparity;
					scene_disparity_strength[0][i] = (scene_disparity_strength[0][i] - average_disparity) /magic_scale;
					scene_disparity_strength[1][i] = 1.0 / average_absolute_disparity;
				}				
			}
		}
		if (transform_debug) {
			// calculate wi/media/elphel/SSD3-4GB/lwir16-proc/berdich3/sel-mod1/1697879036_777649/debug/v15_12A/th no transform
			double [][] dbg_ref_pXpYD = OpticalFlow.transformToScenePxPyD( // will be null for disparity == NaN, total size - tilesX*tilesY
					null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
					ref_disparity, // dls[0], // final double []   disparity_ref, // invalid tiles - NaN in disparity (maybe it should not be masked by margins?)
					ZERO3, // scene_xyz,    // final double []   scene_xyz, // camera center in world coordinates
					ZERO3, // scene_atr,    // final double []   scene_atr, // camera orientation relative to world frame
					first_scene,    // final QuadCLT     scene_QuadClt,
					first_scene);   // final QuadCLT     reference_QuadClt)
			double [][] dbg_ref_pXpYD2 = OpticalFlow.transformToScenePxPyD( // will be null for disparity == NaN, total size - tilesX*tilesY
					null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
					ref_disparity, // dls[0], // final double []   disparity_ref, // invalid tiles - NaN in disparity (maybe it should not be masked by margins?)
					ZERO3, // scene_xyz,    // final double []   scene_xyz, // camera center in world coordinates
					new double[] {0,0,0.00001}, // scene_atr,    // final double []   scene_atr, // camera orientation relative to world frame
					first_scene,    // final QuadCLT     scene_QuadClt,
					first_scene);   // final QuadCLT     reference_QuadClt)
			double [][] dbg_img = new double[6][dbg_ref_pXpYD.length];
			for (int i = 0; i < dbg_img.length; i++) {
				Arrays.fill(dbg_img[i], Double.NaN);
			}
			for (int nt = 0; nt < dbg_ref_pXpYD.length; nt++) {
				if (dbg_ref_pXpYD[nt] != null) {
					for (int i = 0; i < dbg_ref_pXpYD[nt].length; i++){
						dbg_img[2*i][nt] = dbg_ref_pXpYD[nt][i];
					}
				}
				if (dbg_ref_pXpYD2[nt] != null) {
					for (int i = 0; i < dbg_ref_pXpYD2[nt].length; i++){
						dbg_img[2*i+1][nt] = dbg_ref_pXpYD2[nt][i];
					}
				}
			}
			String [] dbg_titles = {"pX0","pX2","pY0","pY2","disp0","disp2"};
			ShowDoubleFloatArrays.showArrays( // out of boundary 15
					dbg_img,
					tilesX,
					tilesY,
					true,
					scene.getImageName()+"-"+first_scene.getImageName()+"-transform_test",
					dbg_titles);
			System.out.println("transform_test ref_scene: "+ first_scene.getImageName());
			first_scene.getErsCorrection().printVectors(scene_xyz, scene_atr);
		}
		
		
		TpTask[][] tp_tasks;
		if (mb_en && (mb_vectors!=null)) {
			tp_tasks  =  GpuQuad.setInterTasksMotionBlur(
					scene.getNumSensors(),
					scene.getErsCorrection().getSensorWH()[0],
					!scene.hasGPU(),          // final boolean             calcPortsCoordinatesAndDerivatives, // GPU can calculate them centreXY
					scene_pXpYD,              // final double [][]         pXpYD, // per-tile array of pX,pY,disparity triplets (or nulls)
					selection,                // final boolean []          selection, // may be null, if not null do not  process unselected tiles
					// motion blur compensation 
					mb_tau,                   // final double              mb_tau,      // 0.008; // time constant, sec
					mb_max_gain,              // final double              mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
					mb_vectors,               // final double [][]         mb_vectors,  //
					scene.getErsCorrection(), // final GeometryCorrection  geometryCorrection,
					disparity_corr,           // final double              disparity_corr,
					margin,                   // final int                 margin,      // do not use tiles if their centers are closer to the edges
					null,                     // final boolean []          valid_tiles,            
					THREADS_MAX);              // final int                 threadsMax)  // maximal number of threads to launch
		} else {
			tp_tasks = new TpTask[1][];
			tp_tasks[0]  =  GpuQuad.setInterTasks(
					scene.getNumSensors(),
					scene.getErsCorrection().getSensorWH()[0],
					!scene.hasGPU(),          // final boolean             calcPortsCoordinatesAndDerivatives, // GPU can calculate them centreXY
					scene_pXpYD,              // final double [][]         pXpYD, // per-tile array of pX,pY,disparity triplets (or nulls)
					selection,                // final boolean []          selection, // may be null, if not null do not  process unselected tiles
					scene.getErsCorrection(), // final GeometryCorrection  geometryCorrection,
					disparity_corr,           // final double              disparity_corr,
					margin,                   // final int                 margin,      // do not use tiles if their centers are closer to the edges
					null,                     // final boolean []          valid_tiles,            
					THREADS_MAX);              // final int                 threadsMax)  // maximal number of threads to launch
		}
		
		scene.saveQuadClt(); // to re-load new set of Bayer images to the GPU (do nothing for CPU) and Geometry
		float  [][][][]     fcorr_td =  null; // no accumulation, use data in GPU
		// Generate 2D phase correlations from the CLT representation
		// generates sum of the per-channel correlations as the last slot.
		// updates gpuQuad.gpu_corr_indices, gpuQuad.gpu_corrs_td and some other
		if (mb_en && (mb_vectors!=null)) {
			image_dtt.interCorrTDMotionBlur(
					clt_parameters.img_dtt,     // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
					tp_tasks,                // final TpTask[][]            tp_tasks,
					fcorr_td,                   // final float  [][][][]     fcorr_td,        // [tilesY][tilesX][pair][4*64] transform domain representation of 6 corr pairs
					clt_parameters.gpu_sigma_r, // final double              gpu_sigma_r,     // 0.9, 1.1
					clt_parameters.gpu_sigma_b, // final double              gpu_sigma_b,     // 0.9, 1.1
					clt_parameters.gpu_sigma_g, // final double              gpu_sigma_g,     // 0.6, 0.7
					clt_parameters.gpu_sigma_m, // final double              gpu_sigma_m,     //  =       0.4; // 0.7;
					gpu_sigma_rb_corr,          // final double              gpu_sigma_rb_corr,    //  = 0.5; // apply LPF after accumulating R and B correlation before G, monochrome ? 1.0 :
					gpu_sigma_corr,             // final double              gpu_sigma_corr,       //  =    0.9;gpu_sigma_corr_m
					gpu_sigma_log_corr,         // final double              gpu_sigma_log_corr,   // hpf to reduce dynamic range for correlations
					clt_parameters.corr_red,    // final double              corr_red, // +used
					clt_parameters.corr_blue,   // final double              corr_blue,// +used
					sensor_mask_inter,          // final int                 sensor_mask_inter, // The bitmask - which sensors to correlate, -1 - all.
					THREADS_MAX,                 // final int                 threadsMax,       // maximal number of threads to launch
					debug_level);               // final int                 globalDebugLevel);
		} else {
			image_dtt.interCorrTD(
					clt_parameters.img_dtt,     // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
					tp_tasks[0],                // final TpTask[]            tp_tasks,
					fcorr_td,                   // final float  [][][][]     fcorr_td,        // [tilesY][tilesX][pair][4*64] transform domain representation of 6 corr pairs
					clt_parameters.gpu_sigma_r, // final double              gpu_sigma_r,     // 0.9, 1.1
					clt_parameters.gpu_sigma_b, // final double              gpu_sigma_b,     // 0.9, 1.1
					clt_parameters.gpu_sigma_g, // final double              gpu_sigma_g,     // 0.6, 0.7
					clt_parameters.gpu_sigma_m, // final double              gpu_sigma_m,     //  =       0.4; // 0.7;
					gpu_sigma_rb_corr,          // final double              gpu_sigma_rb_corr,    //  = 0.5; // apply LPF after accumulating R and B correlation before G, monochrome ? 1.0 :
					gpu_sigma_corr,             // final double              gpu_sigma_corr,       //  =    0.9;gpu_sigma_corr_m
					gpu_sigma_log_corr,         // final double              gpu_sigma_log_corr,   // hpf to reduce dynamic range for correlations
					clt_parameters.corr_red,    // final double              corr_red, // +used
					clt_parameters.corr_blue,   // final double              corr_blue,// +used
					sensor_mask_inter,          // final int                 sensor_mask_inter, // The bitmask - which sensors to correlate, -1 - all.
					THREADS_MAX,                // final int                 threadsMax,       // maximal number of threads to launch
					debug_level);               // final int                 globalDebugLevel);
		}
		if (show_tptask_scene) {
			for (int i = 0; i < tp_tasks.length; i++) if (tp_tasks_ref[i] != null){
				String dbg_title = scene.getImageName()+"-scn-"+i;
				TpTask.showTpTask(
						tp_tasks[i], // TpTask[] tp_tasks,
						tilesX, // int      tilesX,
						tilesY, // int      tilesY,
						dbg_title); // String   title)
			}
		}
		
		if (show_render_ref) {
			ImagePlus imp_render_ref = first_scene.renderFromTD (
					-1,                  // final int         sensor_mask,
					false,               // boolean             merge_channels,
					clt_parameters,                                 // CLTParameters clt_parameters,
					clt_parameters.getColorProcParameters(first_scene.isAux()), //ColorProcParameters colorProcParameters,
					clt_parameters.getRGBParameters(),              //EyesisCorrectionParameters.RGBParameters rgbParameters,
					null, // int []  wh,
					toRGB, // boolean toRGB,
					true, //boolean use_reference
					"GPU-SHIFTED-REF"); // String  suffix)
			imp_render_ref.show();
		}
		if (show_render_scene) { //set toRGB=false
			ImagePlus imp_render_scene = scene.renderFromTD (
					-1,                  // final int         sensor_mask,
					false,               // boolean             merge_channels,
					clt_parameters,                                 // CLTParameters clt_parameters,
					clt_parameters.getColorProcParameters(first_scene.isAux()), //ColorProcParameters colorProcParameters,
					clt_parameters.getRGBParameters(),              //EyesisCorrectionParameters.RGBParameters rgbParameters,
					null, // int []  wh,
					toRGB, // boolean toRGB,
					false, //boolean use_reference
					"GPU-SHIFTED-SCENE"); // String  suffix)

			imp_render_scene.show();
		}
		if (dbg_corr_fpn != null) { // 2*16 or 2*17 (average, individual)
			float [][] foffsets = getInterCorrOffsetsDebug(
					tp_tasks_ref, // final TpTask[] tp_tasks_ref,
					tp_tasks[0], // final TpTask[] tp_tasks,
					first_scene.getNumSensors(),
					tilesX, // final int      tilesX,
					tilesY); // final int      tilesY
			for (int i = 0; (i < dbg_corr_fpn.length) && (i < foffsets.length); i++) {
				dbg_corr_fpn[i] = foffsets[i];
			}
		}

		double [][] fpn_offsets = null;
		if (fpn_remove) {
			fpn_offsets = getInterCorrOffsets(
					fpn_max_offset, // final double   max_offset,
					tp_tasks_ref,   // final TpTask[] tp_tasks_ref,
					tp_tasks[0],       // final TpTask[] tp_tasks,
					first_scene.getNumSensors(), // final int      numSens,
					tilesX,         // final int      tilesX,
					tilesY);        // final int      tilesY);
		}
		// Verify offsets before running LMA, return null and reason if test fails.
		if (min_max != null) {
			double [][] offsets = getInterCorrOffsets(
					Double.NaN, // final double   max_offset,
					tp_tasks_ref,   // final TpTask[] tp_tasks_ref,
					tp_tasks[0],       // final TpTask[] tp_tasks,
					first_scene.getNumSensors(), // final int      numSens,
					tilesX,         // final int      tilesX,
					tilesY);        // final int      tilesY);
			if (offsets == null) {
				if (fail_reason != null) {
				    fail_reason[0]=FAIL_REASON_NULL;
				}
				return null;
			}
			double avg_offs2 = 0;
			double sw = 0;
			for (double [] offs:offsets) if (offs != null) {
				double w = 1.0;
				avg_offs2 += w * (offs[0] * offs[0] + offs[1] * offs[1]);
				sw += w;
			}
			if (sw == 0) {
				if (fail_reason != null) {
				    fail_reason[0]=FAIL_REASON_EMPTY;
				}
			}
			double avg_offs = Math.sqrt(avg_offs2 / sw);
			if (min_max.length>2) {
				min_max[2] = avg_offs;
			}
			if (avg_offs < min_max[0]) {// NaN - do not check;
				if (fail_reason != null) {
				    fail_reason[0]=FAIL_REASON_MIN;
				}
				if (debug_level > -3){
					System.out.println ("interCorrPair(): avg_offs = "+avg_offs+
								" < "+min_max[0]+", sw = "+sw);
				}
				return null;
			}
			
			if (avg_offs > min_max[1]) {// NaN - do not check;
				if (fail_reason != null) {
				    fail_reason[0]=FAIL_REASON_MAX;
				}
				if (debug_level > -3){
					System.out.println ("interCorrPair(): avg_offs = "+avg_offs+
								" > "+min_max[1]+", sw = "+sw);
				}

				return null;
			} else {
				if (debug_level > -3){
					System.out.print ("interCorrPair(): avg_offs = "+avg_offs+
								" <= "+min_max[1]+", sw = "+sw+" ");
				}
			}
		}
		double            half_disparity = near_important ? 0.0 : clt_parameters.imp.half_disparity;
		double [][][]     dcorr_tiles = (fclt_corr != null)? (new double [tp_tasks[0].length][][]):null;
		// will use num_acc with variable number of accumulations (e.g. clusters)
		//all_fpn
		double min_str =      all_fpn ? clt_parameters.imp.min_str_fpn : clt_parameters.imp.min_str;
		double min_str_sum =  all_fpn ? clt_parameters.imp.min_str_sum_fpn : clt_parameters.imp.min_str_sum;
		double min_str_neib = all_fpn ? clt_parameters.imp.min_str_neib_fpn : clt_parameters.imp.min_str_neib;
		double corr_fz_inter = clt_parameters.getGpuFatZeroInter(first_scene.isMonochrome());
		if (mb_en && (mb_vectors!=null)) { // increase fat zero when there is motion blur
			corr_fz_inter *= 8;
		}
		
		boolean use_neibs =        clt_parameters.imp.use_neibs;                  // false; // true;
		boolean use_neibs_pd =     true;		
		boolean neibs_nofpn_only = clt_parameters.imp.neibs_nofpn_only |
				(initial_adjust && clt_parameters.imp.neibs_nofpn_init);           // consolidate neighbors for non-fpn tiles only!
		boolean redo_both =        clt_parameters.imp.redo_both;                  // use average of neighbors for both pd,td if any of the center tile tests (td, pd) fails
		int min_num_neibs =        clt_parameters.imp.min_num_neibs;              // plus center, total number >= (min_num_neibs+1)
		double scale_neibs_pd = use_neibs_pd? clt_parameters.imp.scale_neibs_pd : 0; // scale threshold for the pixel-domain average maximums		
		double scale_neibs_td = use_neibs_pd? clt_parameters.imp.scale_neibs_td : 0; // scale threshold for the transform-domain average maximums
		double scale_avg_weight =  clt_parameters.imp.scale_avg_weight;           // reduce influence of the averaged correlations compared to the single-tile ones
		
		int [] corr_indices_dbg = show_2d_correlations? image_dtt.getGPU().getCorrIndices() : null;
		boolean use_partial = clt_parameters.imp.use_partial;
		coord_motion = image_dtt.clt_process_tl_interscene(       // 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
				// only used here to keep extra array element for disparity difference
				use3D,                             // boolean        use3D,         // generate disparity difference
				use_neibs,				           // boolean                   use_neibs,
				fcorr_td,                          // final float  [][][][]     fcorr_td,        // [tilesY][tilesX][pair][4*64] transform domain representation of all selected corr pairs
				null,                              // float [][][]              num_acc,         // number of accumulated tiles [tilesY][tilesX][pair] (or null). Can be inner null if not used in tp_tasks
				null,                              // 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
				corr_fz_inter,                     // 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
				// should it be reference or scene? Or any?
				tp_tasks[0],                       // final TpTask []           tp_tasks,        // data from the reference frame - will be applied to LMA for the integrated correlations
				// to be converted to float (may be null)
				dcorr_tiles,                       // final double  [][][]      dcorr_tiles,     // [tile][pair_abs, sparse][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
				pXpYD_ref, // ref_pXpYD,           // final double [][]         pXpYD,           // pXpYD for the reference scene
				fpn_offsets,                       // final double [][]         fpn_offsets,     // null, or per-tile X,Y offset to be blanked
				fpn_radius,                        // final double              fpn_radius,      // radius to be blanked around FPN offset center
				fpn_ignore_border,                 // final boolean             fpn_ignore_border, // only if fpn_mask != null - ignore tile if maximum touches fpn_mask			
				motion_vectors,                    // final double [][][]       motion_vectors,  // [tilesY*tilesX][][] -> [][][num_sel_sensors+1][2]
				clt_parameters.imp.run_poly,       // final boolean             run_poly,        // polynomial max, if false - centroid
				use_partial,                       // final boolean             use_partial,     // find motion vectors for individual pairs, false - for sum only
				clt_parameters.imp.centroid_radius,// final double              centroid_radius, // 0 - use all tile, >0 - cosine window around local max
				clt_parameters.imp.n_recenter,     // final int                 n_recenter,      // when cosine window, re-center window this many times
				clt_parameters.imp.td_weight,      // final double              td_weight,       // mix correlations accumulated in TD with 
				clt_parameters.imp.td_neib_weight, // final double              td_neib_weight,  // mix correlations accumulated in TD (neibs)  
				clt_parameters.imp.pd_weight,      // final double              pd_weight,       // correlations (post) accumulated in PD
				clt_parameters.imp.td_nopd_only,   // final boolean             td_nopd_only   , // only use TD accumulated data if no safe PD is available for the tile.
				clt_parameters.imp.neib_notd_only, // final boolean             neib_notd_only,  // use neighbors only if individual TD is too weak 
				min_str,                           // final double              min_str_nofpn,         //  = 0.25;
				min_str_sum,                       // final double              min_str_sum_nofpn,     // = 0.8; // 5;
				min_str_neib,                      // final double              min_str_neib_nofpn,
				clt_parameters.imp.min_str_fpn,    // final double              min_str,         //  = 0.25;
				clt_parameters.imp.min_str_sum_fpn,// final double              min_str_sum,     // = 0.8; // 5;
				clt_parameters.imp.min_str_neib_fpn,//final double              min_str_neib_fpn, 
				clt_parameters.imp.min_neibs,      // final int                 min_neibs,       //   2;	   // minimal number of strong neighbors (> min_str)
				clt_parameters.imp.weight_zero_neibs, // final double              weight_zero_neibs,//  0.2;   // Reduce weight for no-neib (1.0 for all 8)
				half_disparity,                    // final double              half_disparity,  //   5.0;   // Reduce weight twice for this disparity
				clt_parameters.imp.half_avg_diff,  // final double              half_avg_diff,   //   0.2;   // when L2 of x,y difference from average of neibs - reduce twice
				
				neibs_nofpn_only,                  // final boolean             neibs_nofpn_only, // consolidate neighbors fot non-fpn tiles only!
				redo_both,                         //final boolean             redo_both,        // use average of neighbors for both pd,td if any of the center tile tests (td, pd) fails
				min_num_neibs,                     //final int                 min_num_neibs,    // plus center, total number >= (min_num_neibs+1)
				scale_neibs_pd,                    //final double              scale_neibs_pd,   // scale threshold for the pixel-domain average maximums  		
				scale_neibs_td,                    //final double              scale_neibs_td,   // scale threshold for the transform-domain average maximums
				scale_avg_weight,                  //final double              scale_avg_weight,  // reduce influence of the averaged correlations compared to the single-tile ones
				
				clt_parameters.tileX,              // final int                 debug_tileX,
				clt_parameters.tileY,      	       // final int                 debug_tileY,
				THREADS_MAX,                       // final int                 threadsMax,       // maximal number of threads to launch
				debug_level);
		// optional coord_motion[1][3..4] is reserved for disparity difference and strength
		// final int                 globalDebugLevel);
		if (coord_motion == null) {
			System.out.println("clt_process_tl_interscene() returned null");
			if (fail_reason != null) {
			    fail_reason[0]=FAIL_REASON_INTERSCENE;
			}
			return null;
		}
		if (use3D) {//(scene_disparity_strength != null)
			// combine motion vector with disparity_diff/strength
			int num_slices = scene_disparity_strength.length;
			int coord_motion_slice = coord_motion.length - 1; // used for motionvector - last index
			for (int tile = 0; tile < coord_motion[coord_motion_slice].length; tile++ ) if ( coord_motion[coord_motion_slice][tile] != null){
				for (int i = 0; i < num_slices; i++) {
					coord_motion[coord_motion_slice][tile][i+3] = scene_disparity_strength[i][tile];
				}
			}
		}
		if (eq_en) {
			//		   		double  eq_weight_add = (min_str * clt_parameters.imp.pd_weight +  min_str_sum * clt_parameters.imp.td_weight) /
			//			   	   		 (clt_parameters.imp.pd_weight +  clt_parameters.imp.td_weight);
			double [] strength_backup = null;
			if (eq_debug) { // **** Set manually in debugger ****
				strength_backup = new double [coord_motion[1].length];
				for (int i = 0; i < strength_backup.length; i++) if (coord_motion[1][i] != null) {
					strength_backup[i] = coord_motion[1][i][2];
				}else {
					strength_backup[i] = Double.NaN;
				}
			}
			do {
				// restore
				if (strength_backup != null) {
					for (int i = 0; i < strength_backup.length; i++) if (coord_motion[1][i] != null) {
						coord_motion[1][i][2] = strength_backup[i];
					}
				}
				equalizeMotionVectorsWeights(
						coord_motion,          // final double [][][] coord_motion,
						tilesX,                // final int           tilesX,
						eq_stride_hor,         // final int           stride_hor,
						eq_stride_vert,        // final int           stride_vert,
						eq_min_stile_weight,   // final double        min_stile_weight,
						eq_min_stile_number,   // final int           min_stile_number,
						eq_min_stile_fraction, // final double        min_stile_fraction,
						eq_min_disparity,      // final double        min_disparity,
						eq_max_disparity,      // final double        max_disparity,
						eq_weight_add,         // final double        weight_add,
						eq_weight_scale,       // final double        weight_scale)
						eq_level);             // equalize to (log) fraction of average/this strength
				if (eq_debug) {
					String [] mvTitles = {"dx", "dy","conf", "conf0", "pX", "pY","Disp","defined"}; // ,"blurX","blurY", "blur"};
					double [][] dbg_img = new double [mvTitles.length][tilesX*tilesY];
					for (int l = 0; l < dbg_img.length; l++) {
						Arrays.fill(dbg_img[l], Double.NaN);
					}
					for (int nTile = 0; nTile <  coord_motion[0].length; nTile++) {
						if (coord_motion[0][nTile] != null) {
							for (int i = 0; i <3; i++) {
								dbg_img[4+i][nTile] = coord_motion[0][nTile][i];
							}
						}
						dbg_img[3] = strength_backup;
						if (coord_motion[1][nTile] != null) {
							for (int i = 0; i <3; i++) {
								dbg_img[0+i][nTile] = coord_motion[1][nTile][i];
							}
						}
						dbg_img[7][nTile] = ((coord_motion[0][nTile] != null)?1:0)+((coord_motion[0][nTile] != null)?2:0);
					}
					ShowDoubleFloatArrays.showArrays( // out of boundary 15
							dbg_img,
							tilesX,
							tilesY,
							true,
							scene.getImageName()+"-"+first_scene.getImageName()+"-coord_motion-eq",
							mvTitles);

				}
			} while (eq_debug);
		}

		if (mov_en) {
			String debug_image_name = mov_debug_images ? (scene.getImageName()+"-"+first_scene.getImageName()+"-movements"): null;
			boolean [] move_mask = getMovementMask(
					clt_parameters, // CLTParameters clt_parameters,
					coord_motion[1], // double [][]   motion, // only x,y,w components
					tilesX, // int           tilesX,
					debug_image_name, // String        debug_image_name,
					mov_debug_level); // int           debug_level);
			if (move_mask != null) {
				for (int nTile=0; nTile < move_mask.length; nTile++) if (move_mask[nTile]) {
					coord_motion[1][nTile]= null;
				}
			}
		}
		// seems that fclt_corr and fclt_corr1 are both not needed w/o debug
		if (show_2d_correlations) { // visualize prepare ref_scene correlation data
			float [][][] fclt_corr1 = ImageDtt.convertFcltCorr( // partial length, matching corr_indices = gpuQuad.getCorrIndices(); // also sets num_corr_tiles
					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
			if (use_neibs) {
				
			}
			float [][] dbg_corr_rslt_partial = ImageDtt.corr_partial_dbg( // not used in lwir
					fclt_corr1, // final float  [][][]     fcorr_data,       // [tile][pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
					corr_indices_dbg, // image_dtt.getGPU().getCorrIndices(), // 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,
					THREADS_MAX, // final int               threadsMax,     // maximal number of threads to launch
					debug_level); // final int               globalDebugLevel)

			String [] titles = new String [dbg_corr_rslt_partial.length]; // dcorr_tiles[0].length];
			for (int i = 0; i < titles.length; i++) {
				if (i== (titles.length - 1)) {
					titles[i] = "sum";
				} else {
					titles[i] = "sens-"+i;
				}
			}

			// titles.length = 15, corr_rslt_partial.length=16!
			ShowDoubleFloatArrays.showArrays( // out of boundary 15
					dbg_corr_rslt_partial,
					tilesX*(2*image_dtt.transform_size),
					tilesY*(2*image_dtt.transform_size),
					true,
					scene.getImageName()+"-"+first_scene.getImageName()+"-interscene",
					titles);
		}



		if (motion_vectors != null) {
			int num_sens = 0;
			int num_rslt = 0;
			find_num_layers:
				for (int nt = 0; nt <motion_vectors.length; nt++) {
					if (motion_vectors[nt] != null) {
						num_sens = motion_vectors[nt].length;
						for (int i = 0; i < num_sens; i++) {
							if (motion_vectors[nt][i] != null) {
								num_rslt = motion_vectors[nt][i].length;
								break find_num_layers;
							}
						}
					}
				}
			double [][] dbg_img = new double [num_sens * num_rslt][tilesX*tilesY];
			String [] titles = new String [dbg_img.length];
			for (int ns = 0; ns < num_sens; ns++) {
				String ss = (ns == (num_sens - 1))?"sum":(""+ns);
				for (int nr = 0; nr < num_rslt; nr++) {
					int indx = ns*num_rslt+nr;
					Arrays.fill(dbg_img[indx],Double.NaN);
					String sr="";
					switch (nr) {
					case 0:sr ="X";break;
					case 1:sr ="Y";break;
					case 2:sr ="S";break;
					default:
						sr = ""+nr;
					}
					titles[indx] = ss+":"+sr;
					for (int nt = 0; nt <motion_vectors.length; nt++) {
						if ((motion_vectors[nt] != null) && (motion_vectors[nt][ns] != null) ) {
							dbg_img[indx][nt] = motion_vectors[nt][ns][nr];
						}
					}
				}
			}
			ShowDoubleFloatArrays.showArrays( // out of boundary 15
					dbg_img,
					tilesX,
					tilesY,
					true,
					scene.getImageName()+"-"+first_scene.getImageName()+"-motion_vectors",
					titles);
		}
		if (show_coord_motion) {
			//coord_motion
			String [] mvTitles = {"dx", "dy", "conf","disp_diff", "disp_str", "pX", "pY","Disp","defined"}; // ,"blurX","blurY", "blur"};
			double [][] dbg_img = new double [mvTitles.length][]; 
			for (int l = 0; l < dbg_img.length; l++) {
				if (use3D || (l < 3) || (l > 4) ) {
					dbg_img[l] = new double [tilesX*tilesY]; // keep unused null
					Arrays.fill(dbg_img[l], Double.NaN);
				}
			}
			for (int nTile = 0; nTile <  coord_motion[0].length; nTile++) {
				if (coord_motion[0][nTile] != null) {
					for (int i = 0; i < 3; i++) {
						dbg_img[5+i][nTile] = coord_motion[0][nTile][i];
					}
				}
				if (coord_motion[1][nTile] != null) {
					for (int i = 0; i < (use3D? 5 : 3); i++) {
						dbg_img[0+i][nTile] = coord_motion[1][nTile][i];
					}
				}
				dbg_img[8][nTile] = ((coord_motion[0][nTile] != null)?1:0)+((coord_motion[0][nTile] != null)?2:0);
			}
			ShowDoubleFloatArrays.showArrays( // out of boundary 15
					dbg_img,
					tilesX,
					tilesY,
					true,
					scene.getImageName()+"-"+first_scene.getImageName()+"-coord_motion",
					mvTitles);
		}
		if (debug_level > 0){
			double  gap_frac = 0.25; 
			double [][] quad_strengths = getQuadStrengths(
					coord_motion,    // double [][][] coord_motion,
					gap_frac,        //double           gap_frac, // 0.25 
					tilesX);         // int           tilesX);
			
//			int num_defined = 0;
//			double sum_strength = 0.0;
			System.out.println ("interCorrPair(): quad_defined = ["+quad_strengths[0][0]+","
					+quad_strengths[0][1]+","+quad_strengths[0][2]+","+quad_strengths[0][3]+"]"
					+"), rel_strengths = ["
					+quad_strengths[1][0]+","+quad_strengths[1][1]+","
					+quad_strengths[1][2]+","+quad_strengths[1][3]+"] ");
		}
		if (fail_reason != null) {
			fail_reason[0]= 0;
		}
		return coord_motion; // here non-null
	}
	

	static float [][] getInterCorrOffsetsDebug(
			final TpTask[] tp_tasks_ref,
			final TpTask[] tp_tasks,
			final int      numSens,
			final int      tilesX,
			final int      tilesY
			){
		
		final int tiles = tilesX * tilesY;
		final float [][] offsets = new float [2*numSens+2][tiles];
		final int [] num_inp = new int[tiles];
		for (int i = 0; i < offsets.length; i++) {
			Arrays.fill(offsets[i], Float.NaN);
		}
		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() {
					for (int nTile = ai.getAndIncrement(); nTile <tp_tasks.length; nTile = ai.getAndIncrement()) {
						TpTask task = tp_tasks[nTile];
						int tile = task.ty * tilesX + task.tx;
						num_inp[tile] = 1;
						for (int i = 0; i < numSens; i++) {
							offsets[2*i+2][tile] = task.xy[i][0];
							offsets[2*i+3][tile] = task.xy[i][1];
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile <tp_tasks_ref.length; nTile = ai.getAndIncrement()) {
						TpTask task = tp_tasks_ref[nTile];
						int tile = task.ty * tilesX + task.tx;
						if (num_inp[tile] == 1) {
							num_inp[tile] = 2;
							float sx=0,sy=0;
							for (int i = 0; i < numSens; i++) {
								offsets[2*i+2][tile] -= task.xy[i][0];
								offsets[2*i+3][tile] -= task.xy[i][1];
								sx += offsets[2*i+2][tile];
								sy += offsets[2*i+3][tile];
							}
							offsets[0][tile] = sx/numSens;
							offsets[1][tile] = sy/numSens;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return offsets;
	}
	/**
	 * Get average X,Y offsets between inter-scene correlated tiles
	 * to eliminate correlation between FPN of the same sensor.   
	 * @param max_offset maximal X, Y offset to keep (normally tile size == 8) 
	 * @param tp_tasks_ref reference tasks (should be updated from GPU to have 
	 *        actual X,Y for each sensor
	 * @param tp_tasks scene correlated to the reference, should also have
	 *        per-sensor coordinates
	 * @param numSens number of sensors
	 * @param tilesX number of tile columns
	 * @param tilesY number of tile rows.
	 * @return array of X, Y pairs ([tilesX*tilesY][2]), each may be null if 
	 *         not defined or out of tile. Returns null if no tiles contain a valid offset
	 */
	static double [][] getInterCorrOffsets(
			final double   max_offset,
			final TpTask[] tp_tasks_ref,
			final TpTask[] tp_tasks,
			final int      numSens,
			final int      tilesX,
			final int      tilesY
			){
		final int tiles = tilesX * tilesY;
		final double [][] offset_pairs = new double [2*numSens][tiles];
		final int [] num_inp = new int[tiles];
		for (int i = 0; i < offset_pairs.length; i++) {
			Arrays.fill(offset_pairs[i], Float.NaN);
		}
		double [][] offsets = new double[tiles][]; 
		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() {
					for (int nTile = ai.getAndIncrement(); nTile <tp_tasks.length; nTile = ai.getAndIncrement()) {
						TpTask task = tp_tasks[nTile];
						int tile = task.ty * tilesX + task.tx;
						num_inp[tile] = 1;
						for (int i = 0; i < numSens; i++) {
							offset_pairs[2*i+0][tile] = task.xy[i][0];
							offset_pairs[2*i+1][tile] = task.xy[i][1];
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		final AtomicInteger anum_pairs = new AtomicInteger(0);
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile <tp_tasks_ref.length; nTile = ai.getAndIncrement()) {
						TpTask task = tp_tasks_ref[nTile];
						int tile = task.ty * tilesX + task.tx;
						if (num_inp[tile] == 1) {
							num_inp[tile] = 2;
							double sx=0,sy=0;
							for (int i = 0; i < numSens; i++) {
								sx += offset_pairs[2*i+0][tile] - task.xy[i][0];
								sy += offset_pairs[2*i+1][tile] - task.xy[i][1];
							}
							sx /= numSens;
							sy /= numSens;
//							if ((Math.abs(sx) <= max_offset) && (Math.abs(sy) <= max_offset )) {
							if (!(Math.abs(sx) > max_offset) && !(Math.abs(sy) > max_offset )) { // max_offset== NaN OK 
								offsets[tile] = new double[] {sx,sy};
								anum_pairs.getAndIncrement();
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		if (anum_pairs.get() > 0) {
			return offsets;
		}
		return null;
	}

	
	public static boolean [] getMovementMask(
			CLTParameters clt_parameters,
			double [][]   motion, // only x,y,w components
			int           tilesX,
			String        debug_image_name,
			int           debug_level)
	{
		boolean       show_debug_images = (debug_image_name != null);

		String [] mvTitles = {"dx", "dy", "conf", "blur", "blurX","blurY","clust","filtclust","re-clust","mask"};
		boolean mov_en =         clt_parameters.imp.mov_en; // true;  // enable detection/removal of the moving objects during pose matching
		if (!mov_en) {
			return null;
		}
		double  mov_sigma =      clt_parameters.imp.mov_sigma; // 1.5;   // pix - weighted-blur offsets before detection
		// next two to prevent motion detection while errors are too big
		double  mov_max_std =    clt_parameters.imp.mov_max_std; // 0.5;   // pix
		double  mov_thresh_rel = clt_parameters.imp.mov_thresh_rel; // 5.0;   // exceed average error
		double  mov_thresh_abs=  clt_parameters.imp.mov_thresh_abs; // 1.0;   // sqrt(dx^2+dy^2) in moving areas 
		double  mov_clust_max =  clt_parameters.imp.mov_clust_max; // 1.5;   // cluster maximum should exceed threshold this times
		int     mov_grow =       clt_parameters.imp.mov_grow; // 4;     // grow detected moving area
		int     mov_max_len =    clt_parameters.imp.mov_max_len; // 0 - no limit 
		
		double frac_clust =     0.25;//  cluster tiles compared to cluster max
		double  mov_max_std2 =   mov_max_std * mov_max_std;
		double  mov_thresh_rel2 = mov_thresh_rel* mov_thresh_rel;
		double  mov_thresh_abs2 = mov_thresh_abs * mov_thresh_abs;
		int    tilesY = motion.length / tilesX;
		double [] wa = new double[3];
		for (int nTile = 0; nTile <  motion.length; nTile++) if (motion[nTile] != null) {
			double w = motion[nTile][2]; //?  1.0; //
			wa[0]+=w;
			wa[1]+=w*motion[nTile][0];
			wa[2]+=w*motion[nTile][1];
		}
		wa[1] /= wa[0];
		wa[2] /= wa[0];
		double [][] mov_obj = new double[show_debug_images?4:2][tilesX*tilesY];
		double sl2 = 0;
		for (int nTile = 0; nTile <  motion.length; nTile++) if (motion[nTile] != null) {
			double w = motion[nTile][2]; //?  1.0; // 
			mov_obj[0][nTile]= w;
			double x = motion[nTile][0]-wa[1];
			double y = motion[nTile][1]-wa[2];
			double l2 = x*x + y*y;
			mov_obj[1][nTile]= w*l2;
			if (show_debug_images) {
				mov_obj[2][nTile]= w*x;
				mov_obj[3][nTile]= w*y;
			}
			sl2+=w*l2;
		}
		sl2/=wa[0];
		if (debug_level>0) {
			System.out.println("getMovementMask(): std="+Math.sqrt(sl2));
		}
		double [][] dbg_img = null;
		if(show_debug_images) {		
			dbg_img = new double [mvTitles.length][tilesX*tilesY];
			for (int l = 0; l < dbg_img.length; l++) {
				Arrays.fill(dbg_img[l], Double.NaN);
			}
		}		
		boolean [] move_mask = null; 
		double [] clust_max = null;
		int [][] minx_maxx_miny_maxy = null;
		process_movements:
		{	
			if (sl2 > mov_max_std2) {
				if (debug_level>0) {
					System.out.println("getMovementMask(): Too high scene mismatch: std="+Math.sqrt(sl2)+" > "+mov_max_std+", aborting movement processing");
				}
				break process_movements;
			}
			DoubleGaussianBlur gb = new DoubleGaussianBlur();
			gb.blurDouble(mov_obj[0], tilesX, tilesY, mov_sigma, mov_sigma, 0.01);
			gb.blurDouble(mov_obj[1], tilesX, tilesY, mov_sigma, mov_sigma, 0.01);
			for (int nTile = 0; nTile <  motion.length; nTile++) {
				mov_obj[1][nTile] /= mov_obj[0][nTile];
			}		
			if (show_debug_images) {
				gb.blurDouble(mov_obj[2], tilesX, tilesY, mov_sigma, mov_sigma, 0.01);
				gb.blurDouble(mov_obj[3], tilesX, tilesY, mov_sigma, mov_sigma, 0.01);
				for (int nTile = 0; nTile <  motion.length; nTile++) {
					mov_obj[2][nTile] /= mov_obj[0][nTile];
					mov_obj[3][nTile] /= mov_obj[0][nTile];
				}
				for (int nTile = 0; nTile <  motion.length; nTile++) {
					 if (motion[nTile] != null) {
						 dbg_img[0][nTile] =  motion[nTile][0];
						 dbg_img[1][nTile] =  motion[nTile][1];
						 dbg_img[2][nTile] =  motion[nTile][2];
					 }
					 dbg_img[3][nTile] =  mov_obj[1][nTile];				
					 dbg_img[4][nTile] =  mov_obj[2][nTile];				
					 dbg_img[5][nTile] =  mov_obj[3][nTile];
				}				
			}
			double mov_thresh = sl2 * mov_thresh_rel2;
			if (mov_thresh < mov_thresh_abs2) {
				mov_thresh = mov_thresh_abs2;
			}
			int num_over = 0;
			boolean [] over_thresh = new boolean [mov_obj[0].length];
			for (int nTile = 0; nTile <  motion.length; nTile++) if (mov_obj[1][nTile] > mov_thresh){
				over_thresh[nTile] = true;
				num_over ++;
			}
			if (num_over == 0) {
				if (debug_level>0) {
					System.out.println("getMovementMask(): No moving tiles exceed ="+Math.sqrt(mov_thresh)+", aborting movement processing");
				}
				break process_movements;
			}
			// Find clusters
			TileNeibs tn = new TileNeibs(tilesX,tilesY);

			int [] clusters = tn.enumerateClusters(
					over_thresh, // boolean [] tiles,
					null,        // int []     num_clusters,
					false);      // boolean ordered)
			// check clusters contain super-threshold tile
			double max_cluster = mov_thresh*mov_clust_max; // each cluster should have larger tile value to survive
			int max_clust_num = 0;
			for (int nTile=0; nTile < clusters.length;nTile++) if (clusters[nTile] > max_clust_num) {
				max_clust_num = clusters[nTile];
			}
			clust_max = new double [max_clust_num];
			for (int nTile=0; nTile < clusters.length;nTile++) if (clusters[nTile] > 0) {
				int i = clusters[nTile]-1;
				if (mov_obj[1][nTile] > clust_max[i]) {
					clust_max[i] = mov_obj[1][nTile];
				}
			}
			int num_good_clusters = 0;
			boolean [] good_clusters = new boolean[clust_max.length];
			for (int i = 0; i < clust_max.length; i++) if (clust_max[i] >= max_cluster){
				good_clusters[i] = true;
				num_good_clusters ++;
			}		
			if (debug_level>0) {
				System.out.println("getMovementMask(): Got "+max_clust_num+" clusters, of them good (>"+max_cluster+") - "+num_good_clusters );
				for (int i = 0; i < clust_max.length; i++) {
					System.out.println("getMovementMask(): Cluster "+i+(good_clusters[i]?"*":" ")+", max = "+clust_max[i]);
				}
			}
			
			if(show_debug_images) {		
				for (int nTile = 0; nTile <  motion.length; nTile++) if (clusters[nTile] > 0){
					 dbg_img[6][nTile] = clusters[nTile];
				}
			}
			// remove tiles much lower than cluster max
			for (int nTile=0; nTile < clusters.length;nTile++) if (clusters[nTile] > 0) {
				int iclust = clusters[nTile] - 1;
				double threshold = clust_max[iclust] * frac_clust;
				if (!good_clusters[iclust] || (mov_obj[1][nTile] < threshold)) {
					clusters[nTile] = 0;
				}
			}
			
			if(show_debug_images) {		
				for (int nTile = 0; nTile <  motion.length; nTile++) if (clusters[nTile] > 0){
					 dbg_img[7][nTile] = clusters[nTile];
				}
			}
			
			if (num_good_clusters == 0) {
				if (debug_level>0) {
					System.out.println("getMovementMask(): No strong enough moving clusters (of total "+clust_max.length+"), aborting movement processing");
				}
				break process_movements;
			}
			
			move_mask = new boolean [clusters.length];
			for (int nTile=0; nTile < clusters.length; nTile++) if (clusters[nTile] > 0) {
				move_mask[nTile] = good_clusters[clusters[nTile] -1];
			}
			
			tn.growSelection(
					mov_grow,  // int        grow,           // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
					move_mask, // boolean [] tiles,
					null);     // boolean [] prohibit);
			
			// Re-clusterize

			clusters = tn.enumerateClusters(
					move_mask, // boolean [] tiles,
					null,      // int []     num_clusters,
					false);    // boolean ordered)
			if(show_debug_images) {		
				for (int nTile = 0; nTile <  motion.length; nTile++) if (clusters[nTile] > 0){
					 dbg_img[8][nTile] = clusters[nTile];
				}
			}
			
			// Measure cluster sizes and remove too big ones
			if (mov_max_len > 0) {
				max_clust_num = 0;
				for (int nTile=0; nTile < clusters.length;nTile++) if (clusters[nTile] > max_clust_num) {
					max_clust_num = clusters[nTile];
				}
				minx_maxx_miny_maxy = new int [max_clust_num][];
				for (int nTile = 0; nTile <  motion.length; nTile++) if (clusters[nTile] > 0){
					int x = nTile % tilesX;
					int y = nTile / tilesX;
					int iclust = clusters[nTile] - 1;
					if (minx_maxx_miny_maxy[iclust] == null) {
						minx_maxx_miny_maxy[iclust] = new int[] {x,x,y,y};
					}
					if (x < minx_maxx_miny_maxy[iclust][0]) minx_maxx_miny_maxy[iclust][0] = x;
					if (x > minx_maxx_miny_maxy[iclust][1]) minx_maxx_miny_maxy[iclust][1] = x;
					if (y < minx_maxx_miny_maxy[iclust][2]) minx_maxx_miny_maxy[iclust][2] = y;
					if (y > minx_maxx_miny_maxy[iclust][3]) minx_maxx_miny_maxy[iclust][3] = y;
				}
				
				num_good_clusters = 0;
				good_clusters = new boolean[max_clust_num];
				for (int iclust = 0; iclust < max_clust_num; iclust++) {
					if (    (minx_maxx_miny_maxy[iclust] != null) &&
							((minx_maxx_miny_maxy[iclust][1] - minx_maxx_miny_maxy[iclust][0]) <= mov_max_len) &&
							((minx_maxx_miny_maxy[iclust][3] - minx_maxx_miny_maxy[iclust][2]) <= mov_max_len)) {
						good_clusters[iclust] = true;
						num_good_clusters ++;
					} else {
						minx_maxx_miny_maxy[iclust] = null;
					}
				}
				if (num_good_clusters == 0) {
					if (debug_level>0) {
						System.out.println("getMovementMask(): No small enough moving clusters (of total "+clust_max.length+"), aborting movement processing");
					}
					break process_movements;
				}
				
				if (debug_level>0) { // -1) {
					System.out.println("getMovementMask(): Got "+max_clust_num+" clusters, of them good - "+num_good_clusters );
					for (int i = 0; i < minx_maxx_miny_maxy.length; i++) {
						int sizex = minx_maxx_miny_maxy[i][1] - minx_maxx_miny_maxy[i][0];
						int sizey = minx_maxx_miny_maxy[i][3] - minx_maxx_miny_maxy[i][2];
						int max_size =  (sizex>sizey)? sizex:sizey;
						System.out.println("getMovementMask(): Cluster "+i+(good_clusters[i]?"*":" ")+
								", max_size="+max_size+" ("+sizex+", "+sizey+")"+
								", xmin = "+minx_maxx_miny_maxy[i][0]+
								", xmax = "+minx_maxx_miny_maxy[i][1]+
								", ymin = "+minx_maxx_miny_maxy[i][2]+
								", ymax = "+minx_maxx_miny_maxy[i][3]);
					}
				}
				// remove too large clusters
				if (mov_max_len > 0) {
					for (int nTile=0; nTile < clusters.length; nTile++) if (clusters[nTile] > 0) {
						int iclust = clusters[nTile] - 1;
						if (!good_clusters[iclust]) {
							clusters[nTile] = 0;
						}
					}
				}
				move_mask = new boolean [clusters.length];
				for (int nTile=0; nTile < clusters.length; nTile++) if (clusters[nTile] > 0) {
					move_mask[nTile] = good_clusters[clusters[nTile] -1];
				}
			}
			
			if(show_debug_images) {		
				for (int nTile = 0; nTile <  motion.length; nTile++) if (move_mask[nTile]){
					 dbg_img[9][nTile] = 1.0;
				}
			}

		}
		if(show_debug_images) {	
			ShowDoubleFloatArrays.showArrays( // out of boundary 15
					dbg_img,
					tilesX,
					tilesY,
					true,
					debug_image_name,
					mvTitles);
		}
		if (debug_level > -2) { // 0
			if (move_mask != null) {
				int num_mov = 0;
				for (int nTile=0; nTile<move_mask.length;nTile++) if (move_mask[nTile]) {
					num_mov++;
				}
				System.out.print("getMovementMask(): Created moving objects mask of "+num_mov+" tiles. Sizes:");
				if (minx_maxx_miny_maxy != null) {
					for (int iclust = 0; iclust < minx_maxx_miny_maxy.length; iclust++) if (minx_maxx_miny_maxy[iclust] != null){
						int sizex = minx_maxx_miny_maxy[iclust][1] - minx_maxx_miny_maxy[iclust][0];
						int sizey = minx_maxx_miny_maxy[iclust][3] - minx_maxx_miny_maxy[iclust][2];
						int max_size =  (sizex>sizey)? sizex:sizey;
						System.out.print(" "+max_size);
					}
				}
				System.out.println();

			} else {
				System.out.println("getMovementMask(): No moving objects mask is created.");
			}
		}
		return move_mask;
	}
	
	/**
	 * Equalize weights of the motion vectors to boost that of important buy weak one.
	 * Process overlapping (by half, using shifted cosine weight function) supertiles
	 * independently and if it qualifies, increase its tiles weights equlaizing (with
	 * certain limitations) total supertile weights.
	 * @param coord_motion       [2][tilesX*tilesY][3] input/output arrays.[0][tile][] is
	 *                           pXpYD triplet (
	 * @param stride_hor         half of a supertile width
	 * @param stride_vert        half of a supertile height
	 * @param min_stile_weight   minimal total weight of the tiles in a supertile (lower
	 *                           will not be modified)
	 * @param min_stile_number   minimal number of defined tiles in a supertile
	 * @param min_stile_fraction minimal total tile strength compared to the average one
	 * @param min_disparity      minimal disparity of tiles to consider (after filtering)
	 * @param max_disparity      maximal disparity of tiles to consider (after filtering)
	 * @param weight_add         add to each tile after scaling (if total weight < average)
	 * @param weight_scale       scale each tile (if total weight < average)
	 * @param eq_level           equalize (log scale) to this ratio between average and this
	 *                           strength (0.0 - leave as is, equalize weak to average strength 
	 * If total new weight of a supertile exceeds average - scale each tile to match. If
	 * lower - keep as is. Only after this step remove tiles (replace with original weight)
	 * that are discarded by the disparity filter. Multiply result by the the window and
	 * accumulate (in 4 passes to prevent contentions for the same destination array  
	 */
	
	public static void equalizeMotionVectorsWeights(
			final double [][][] coord_motion,
			final int           tilesX,
			final int           stride_hor,
			final int           stride_vert,
			final double        min_stile_weight,
			final int           min_stile_number,
			final double        min_stile_fraction,
			final double        min_disparity,
			final double        max_disparity,
			final double        weight_add,
			final double        weight_scale,
			final double        eq_level) // equalize to (log) fraction of average/this strength      
	{
		final int tiles = coord_motion[0].length;
		final int tilesY = tiles/tilesX;
		final int stile_width =  2 * stride_hor;
		final int stile_height = 2 * stride_vert;
		double [] whor =  new double [stride_hor];
		double [] wvert = new double [stride_vert];
		for (int i = 0; i < stride_hor;  i++) whor[i] =  0.5 *(1.0 - Math.cos(Math.PI*(i+0.5)/stride_hor)); 
		for (int i = 0; i < stride_vert; i++) wvert[i] = 0.5 *(1.0 - Math.cos(Math.PI*(i+0.5)/stride_vert));
		final int stilesX = (tilesX - 1)/stride_hor + 2; // 10  // 9
		final int stilesY = (tilesY - 1)/stride_vert + 2; // 8 // 7
		final int stiles = stilesX * stilesY;
		int indx = 0;
		final double[] wind = new double[stile_height*stile_width];
		for (int iy = 0; iy < stile_height;iy++) {
			int iy1 = (iy >= stride_vert) ? (stile_height - 1 - iy) : iy;
			for (int ix = 0; ix < stile_width; ix++) {
				int ix1 = (ix >= stride_hor) ? (stile_width - 1 - ix) : ix;
				wind[indx++] = wvert[iy1] * whor[ix1];
			}
		}
		final double [] stile_weight = new double [stiles];
		final int [] stile_number = new int [stiles];
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final int dbg_stile =  -1;
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int sTile = ai.getAndIncrement(); sTile < stiles; sTile = ai.getAndIncrement()) {
						if (sTile == dbg_stile) {
							System.out.println("normalizeMotionVectorsWeights (): dbg_stile = "+dbg_stile);
						}
						int sTileX = sTile % stilesX;
						int sTileY = sTile / stilesX;
						stile_weight[sTile] = 0.0;
						int num_tiles = 0; // for partial stiles
						for (int iy = 0; iy < stile_height;iy++) {
							int tileY = (sTileY - 1) * stride_vert + iy;
							if ((tileY < 0) || (tileY >= tilesY)) continue;
							for (int ix = 0; ix < stile_width; ix++) {
								int tileX = (sTileX - 1) * stride_hor + ix;
								if ((tileX < 0) || (tileX >= tilesX)) continue;
								num_tiles++; // for partial stiles
								int tile = tileX + tilesX*tileY;
								if ((coord_motion[1][tile] != null) && !Double.isNaN(coord_motion[0][tile][2])) {
									stile_weight[sTile] += coord_motion[1][tile][2];
									stile_number[sTile]++;
								}
							}
						}
						stile_weight[sTile] *= 1.0 * stile_height * stile_height / num_tiles; // increase for partiaL stiles
						stile_number[sTile] *= stile_height*stile_height;
						stile_number[sTile] /= num_tiles;
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		double sum_stile_str = 0.0;
		int num_stiles_defined = 0;
		for (int sTile = 0; sTile < stiles; sTile++) {
			if (stile_weight[sTile] > 0) {
				num_stiles_defined++;
				sum_stile_str+=stile_weight[sTile];
			}
		}
		if (num_stiles_defined <=0) {
			System.out.println("normalizeMotionVectorsWeights(): no defined supertiles, bailing out");
			return;
		}
		final boolean [] dbg_mod = new boolean [stiles];
		final double [] tile_new_strength = new double [tiles]; // accumulate new strengths here
		final double avg_stile_str = sum_stile_str / num_stiles_defined;
		final double min_combo_weight = Math.max(min_stile_weight, min_stile_fraction * avg_stile_str);
		for (int offsy = 0; offsy < 2; offsy++) { // avoiding overlap
			final int foffsy = offsy;
			final int sty2 = (stilesY + 1 - offsy) / 2;
			for (int offsx = 0; offsx < 2; offsx++) {
				final int foffsx = offsx;
				final int stx2 = (stilesX + 1 - offsx) / 2;
				final int st2 = sty2*stx2;
				ai.set(0);
				for (int ithread = 0; ithread < threads.length; ithread++) {
					threads[ithread] = new Thread() {
						public void run() {
							double [] mod_weights =  new double [stile_height*stile_width];
							for (int indx = ai.getAndIncrement(); indx < st2; indx = ai.getAndIncrement()) {
								int stileY = (indx/stx2)*2+foffsy;
								int stileX = (indx%stx2)*2+foffsx;
								int sTile = stileX + (stilesX * stileY);
								if (sTile == dbg_stile) {
									System.out.println("normalizeMotionVectorsWeights (): dbg_stile = "+dbg_stile);
								}
								boolean keep_old = false;
								// check this tile qualifies:
								//stile_number
								if (stile_number[sTile] < min_stile_number) {
									keep_old = true;
								}
								if (stile_weight[sTile] < min_combo_weight) {
									keep_old = true;
								}
								if (stile_weight[sTile] >= avg_stile_str) { // already strong enough
									keep_old = true;
								}
								double target_str=Math.exp(
										eq_level*Math.log(avg_stile_str) +
										(1.0-eq_level)*Math.log(stile_weight[sTile]));
								Arrays.fill(mod_weights,0);
								double sum_weights = 0.0;
								int num_tiles = 0; // for partial stiles
								for (int iy = 0; iy < stile_height;iy++) {
									int tileY = (stileY - 1) * stride_vert + iy;
									if ((tileY < 0) || (tileY >= tilesY)) continue;
									for (int ix = 0; ix < stile_width; ix++) {
										int tileX = (stileX - 1) * stride_hor + ix;
										if ((tileX < 0) || (tileX >= tilesX)) continue;
										int tile = tileX + tilesX*tileY;
										num_tiles ++;
										if ((coord_motion[1][tile] != null) && !Double.isNaN(coord_motion[0][tile][2])) {
											int ltile = ix + iy * stile_width;
											mod_weights[ltile] = keep_old ?
													coord_motion[1][tile][2] :
														(weight_add + coord_motion[1][tile][2] ) * weight_scale;
											sum_weights += mod_weights[ltile];
										}
									}
								}
								sum_weights *= 1.0 * stile_height * stile_height / num_tiles  ; // increase for partial tiles 
								if (!keep_old) {
									if (sum_weights > target_str) { // scale back
										double s = target_str/sum_weights;
										for (int ltile = 0; ltile < mod_weights.length; ltile++) {
											mod_weights[ltile] *= s;
										}
									}
									for (int iy = 0; iy < stile_height;iy++) {
										int tileY = (stileY - 1) * stride_vert + iy;
										if ((tileY < 0) || (tileY >= tilesY)) continue;
										for (int ix = 0; ix < stile_width; ix++) {
											int tileX = (stileX - 1) * stride_hor + ix;
											if ((tileX < 0) || (tileX >= tilesX)) continue;
											int tile = tileX + tilesX*tileY;
											int ltile = ix + iy * stile_width;
											// remove out of range disparity
											if (coord_motion[0][tile] != null) {
												double disp = coord_motion[0][tile][2]; // shopuld be non-null
												if ((disp < min_disparity) || (disp > max_disparity)) { // min/max = NaN - OK
													mod_weights[ltile] = coord_motion[1][tile][2]; // use original value
												}
											}
										}
									}
									dbg_mod[sTile] = true;
								}
								// multiply by window and accumulate
								for (int iy = 0; iy < stile_height;iy++) {
									int tileY = (stileY - 1) * stride_vert + iy;
									if ((tileY < 0) || (tileY >= tilesY)) continue;
									for (int ix = 0; ix < stile_width; ix++) {
										int tileX = (stileX - 1) * stride_hor + ix;
										if ((tileX < 0) || (tileX >= tilesX)) continue;
										int tile = tileX + tilesX*tileY;
										int ltile = ix + iy * stile_width;
										// wind
										tile_new_strength[tile] += wind[ltile] * mod_weights[ltile];
									}
								}
							}
						}
					};
				}		      
				ImageDtt.startAndJoin(threads);
			}
		}
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < tiles; nTile = ai.getAndIncrement()) {
						if (tile_new_strength[nTile] > 0.0) { // assuming ((coord_motion[1][nTile] != null) && !Double.isNaN(coord_motion[0][nTile][2]))
							if (coord_motion[1][nTile] == null) {
								System.out.println("coord_motion[1]["+nTile+"] == null, tileX="+(nTile%tilesX)+", tileY="+(nTile/tilesX));
							} else {
								coord_motion[1][nTile][2] = tile_new_strength[nTile]; // replace modified
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return;
	}
	
	   /**
     * Calculates data availability in 4 corners (excluding center gap)
     * To disable ERS calculation in LMA 
     * @param coord_motion interCorrPair() output - here only defined (non-null)
     *                     and strength are used
     * @param gap_frac     fraction of width and height to remove in the center
     *                     0.25 - remove from 3/8 to 5/8 "cross" in the center
     * @param tilesX       number of tiles in a row
     * @return             [2][4] - [0][]: number of non-null tiles in each quadrant,
     *                     [1][] - fraction of weight of total weight (total does not exclude center)  
     */
	public static double [][] getQuadStrengths(
			double [][][] coord_motion,
			double        gap_frac, // 4 
			int           tilesX) {
		int tilesY = coord_motion[1].length/ tilesX;
		int num_defined = 0;
		double sum_strength = 0.0;
		double [][] quad_strengths = new double[2][4];
		int igapX = (int) (gap_frac * tilesX), igapY = (int) (gap_frac * tilesY);
		int igapX0 = (tilesX - igapX)/2;
		int igapX1 = igapX0 + igapX; 
		int igapY0 = (tilesY - igapY)/2;
		int igapY1 = igapY0 + igapY; 
		
		for (int i = 0; i < coord_motion[1].length; i++) if (coord_motion[1][i] != null){
			sum_strength += coord_motion[1][i][2];
			num_defined++;
			int ty = i / tilesX;
			int tx = i % tilesX;
			if (((ty > igapY0) && (ty < igapY1)) || ((tx > igapX0) && (tx < igapX1))) {
				continue;
			}
			int iy = ty/(tilesY/2);
			int ix = tx/(tilesX/2);
			int ixy = 2*iy+ix;
			quad_strengths[0][ixy] += 1.0;
			quad_strengths[1][ixy] += coord_motion[1][i][2];
		}
		if (sum_strength > 0) {
			for (int i = 0; i< quad_strengths[1].length; i++) {
//				quad_strengths[0][i]/=num_defined;
				quad_strengths[1][i]/=sum_strength;
			}
		}
		return quad_strengths;
	}
	/**
	 * Prepare and set GPU reference TD data to be used for interscene correlation. Optionally uses
	 * motion blur correction (if mb_vectors != null)
	 * @param clt_parameters general parameters
	 * @param ref_scene     reference scene QuadCLT instance
	 * @param ref_disparity either alternative disparity array or null to use it from the reference scene itself
	 * @param ref_pXpYD - precalculated or null (will be calculated)
	 * @param selection optional selection to ignore unselected tiles)
	 * @param margin do not use tiles with centers closer than this to the edges. Measured in pixels.
	 * @param mb_tau Sensor time constant in seconds (only needed if mb_vectors != null)
	 * @param mb_max_gain maximal gain for MB correction (higher MB corrected by increasing offset)
	 * @param mb_vectors [2][tiles] {dx/dt[tiles],dx/dt[tiles]} motion blur vectors or null 
	 * @param debug_level debug level
	 * @return TpTask[][] arrays used to program GPU. With no MB has only one element, with MB - two
	 */
	public static TpTask[][] setReferenceGPU (
			CLTParameters      clt_parameters,			
			QuadCLT            ref_scene,
			double []          ref_disparity, // null or alternative reference disparity
			double [][]        ref_pXpYD,
			final boolean []   selection, // may be null, if not null do not  process unselected tiles
			final int          margin,
			// motion blur compensation 
			double             mb_tau,      // 0.008; // time constant, sec
			double             mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
			double [][]        mb_vectors,  // now [2][ntiles];
			int                debug_level)
	{
		if (!ref_scene.hasGPU()) {
			throw new IllegalArgumentException ("setReferenceGPU(): CPU mode not supported");
		}
		boolean toRGB =       clt_parameters.imp.toRGB  ; // true;
	    int     erase_clt =   (toRGB? clt_parameters.imp.show_color_nan : clt_parameters.imp.show_mono_nan) ? 1:0;
	    boolean use_lma_dsi = clt_parameters.imp.use_lma_dsi;

		if (ref_scene.getGPU() != null) {
			ref_scene.getGPU().setGpu_debug_level(debug_level - 4); // monitor GPU ops >=-1
		}
		final double disparity_corr = clt_parameters.imp.disparity_corr; // 04/07/2023 // 0.0; // (z_correction == 0) ? 0.0 : geometryCorrection.getDisparityFromZ(1.0/z_correction);
//ref_disparity	
		if (ref_pXpYD == null) {
			if (ref_disparity == null) {
				ref_disparity = ref_scene.getDLS()[use_lma_dsi?1:0];
			}
			ref_pXpYD = OpticalFlow.transformToScenePxPyD( // full size - [tilesX*tilesY], some nulls
					null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
					ref_disparity, // dls[0],  // final double []   disparity_ref, // invalid tiles - NaN in disparity (maybe it should not be masked by margins?)
					ZERO3,         // final double []   scene_xyz, // camera center in world coordinates
					ZERO3,         // final double []   scene_atr, // camera orientation relative to world frame
					ref_scene,     // final QuadCLT     scene_QuadClt,
					ref_scene,    // final QuadCLT     reference_QuadClt)
					QuadCLT.THREADS_MAX);
		}
		ImageDtt image_dtt = new ImageDtt(
				ref_scene.getNumSensors(),
				clt_parameters.transform_size,
				clt_parameters.img_dtt,
				ref_scene.isAux(),
				ref_scene.isMonochrome(),
				ref_scene.isLwir(),
				clt_parameters.getScaleStrength(ref_scene.isAux()),
				ref_scene.getGPU());
        TpTask[][] tp_tasks_ref;
		if (mb_vectors!=null) {
			tp_tasks_ref = GpuQuad.setInterTasksMotionBlur( // "true" reference, with stereo actual reference will be offset
					ref_scene.getNumSensors(),
					ref_scene.getErsCorrection().getSensorWH()[0],
					!ref_scene.hasGPU(),          // final boolean             calcPortsCoordinatesAndDerivatives, // GPU can calculate them centreXY
					ref_pXpYD,                    // final double [][]         pXpYD, // per-tile array of pX,pY,disparity triplets (or nulls)
					selection,                    // final boolean []          selection, // may be null, if not null do not  process unselected tiles
					// motion blur compensation 
					mb_tau,                       // final double              mb_tau,      // 0.008; // time constant, sec
					mb_max_gain,                  // final double              mb_max_gain, // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
					mb_vectors,                   //final double [][]         mb_vectors,  //
					ref_scene.getErsCorrection(), // final GeometryCorrection  geometryCorrection,
					disparity_corr,               // final double              disparity_corr,
					margin,                       // final int                 margin,      // do not use tiles if their centers are closer to the edges
					null,                         // final boolean []          valid_tiles,            
					QuadCLT.THREADS_MAX);                  // final int                 threadsMax)  // maximal number of threads to launch
			ref_scene.saveQuadClt(); // to re-load new set of Bayer images to the GPU (do nothing for CPU) and Geometry
			image_dtt.setReferenceTDMotionBlur( // tp_tasks_ref will be updated
					erase_clt,
					null,                       // final int []              wh,               // null (use sensor dimensions) or pair {width, height} in pixels
					clt_parameters.img_dtt,     // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
					true, // final boolean             use_reference_buffer,
					tp_tasks_ref,               // final TpTask[]            tp_tasks,
					clt_parameters.gpu_sigma_r, // final double              gpu_sigma_r,     // 0.9, 1.1
					clt_parameters.gpu_sigma_b, // final double              gpu_sigma_b,     // 0.9, 1.1
					clt_parameters.gpu_sigma_g, // final double              gpu_sigma_g,     // 0.6, 0.7
					clt_parameters.gpu_sigma_m, // final double              gpu_sigma_m,     //  =       0.4; // 0.7;
					QuadCLT.THREADS_MAX,                 // final int                 threadsMax,       // maximal number of threads to launch
					debug_level);               // final int                 globalDebugLevel);
			
		} else {
			tp_tasks_ref = new TpTask[1][];
			tp_tasks_ref[0] =  GpuQuad.setInterTasks( // "true" reference, with stereo actual reference will be offset
					ref_scene.getNumSensors(),
					ref_scene.getErsCorrection().getSensorWH()[0],
					!ref_scene.hasGPU(),          // final boolean             calcPortsCoordinatesAndDerivatives, // GPU can calculate them centreXY
					ref_pXpYD,                    // final double [][]         pXpYD, // per-tile array of pX,pY,disparity triplets (or nulls)
					selection,                    // final boolean []          selection, // may be null, if not null do not  process unselected tiles
					ref_scene.getErsCorrection(), // final GeometryCorrection  geometryCorrection,
					disparity_corr,               // final double              disparity_corr,
					margin,                       // final int                 margin,      // do not use tiles if their centers are closer to the edges
					null,                         // final boolean []          valid_tiles,            
					QuadCLT.THREADS_MAX);                  // final int                 threadsMax)  // maximal number of threads to launch
			ref_scene.saveQuadClt(); // to re-load new set of Bayer images to the GPU (do nothing for CPU) and Geometry
			image_dtt.setReferenceTD( // tp_tasks_ref will be updated
					erase_clt,
					null,                       // final int []              wh,               // null (use sensor dimensions) or pair {width, height} in pixels
					clt_parameters.img_dtt,     // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
					true, // final boolean             use_reference_buffer,
					tp_tasks_ref[0],               // final TpTask[]            tp_tasks,
					clt_parameters.gpu_sigma_r, // final double              gpu_sigma_r,     // 0.9, 1.1
					clt_parameters.gpu_sigma_b, // final double              gpu_sigma_b,     // 0.9, 1.1
					clt_parameters.gpu_sigma_g, // final double              gpu_sigma_g,     // 0.6, 0.7
					clt_parameters.gpu_sigma_m, // final double              gpu_sigma_m,     //  =       0.4; // 0.7;
					QuadCLT.THREADS_MAX,                 // final int                 threadsMax,       // maximal number of threads to launch
					debug_level);               // final int                 globalDebugLevel);
		}

		ref_scene.saveQuadClt(); // to re-load new set of Bayer images to the GPU (do nothing for CPU) and Geometry
		return tp_tasks_ref;
    }
	
    public static void generateEgomotionTable(
			CLTParameters  clt_parameters,
			QuadCLT []     quadCLTs,
			int            ref_index,
			int            earliest_scene,
			String         path,
			String         comment,
			int            debugLevel) {
		double [] ims_ortho =     clt_parameters.imp.ims_ortho;
		double [] ims_mount_atr = clt_parameters.imp.getImsMountATR(); // converts to radians
		QuadCLT ref_scene = quadCLTs[ref_index];
		ErsCorrection ers_reference = ref_scene.getErsCorrection();
		int num_processed = 0;
		double [][][] scenes_xyzatr =    new double [quadCLTs.length][][];
		double [][][] scenes_xyzatr_dt = new double [quadCLTs.length][][];
		// for testing QuaternionLma
		double [] rms = new double [2];
		double []      enu_corr = new double[3];
		double [] quatCorr = getQuaternionCorrection(
				clt_parameters, // CLTParameters  clt_parameters,
				quadCLTs,       // QuadCLT []     quadCLTs,
				ref_index,      // int            ref_index,
				earliest_scene, // int            earliest_scene,
				rms,            // double []      rms // null or double[2];
				enu_corr,       //double []      enu_corr,
				debugLevel);    // int            debugLevel
		if (debugLevel > -3) {
        	Rotation rot = new Rotation(quatCorr[0],quatCorr[1],quatCorr[2],quatCorr[3], false); // no normalization - see if can be scaled
			System.out.println("Applying correction to the IMS to world orientation (rotating around IMS vertical):");
			double []  corr_angles = rot.getAngles(RotationOrder.YXZ, ErsCorrection.ROT_CONV);
			double []  corr_degrees = new double[3];
			for (int i = 0; i < 3; i++) corr_degrees[i]=corr_angles[i]*180/Math.PI;
			System.out.println("generateEgomotionTable(): quatCorr=["+quatCorr[0]+", "+quatCorr[1]+", "+quatCorr[2]+", "+quatCorr[3]+"]");
			System.out.println("generateEgomotionTable(): ATR(rad)=["+corr_angles[0]+", "+corr_angles[1]+", "+corr_angles[2]+"]");
			System.out.println("generateEgomotionTable(): ATR(deg)=["+corr_degrees[0]+", "+corr_degrees[1]+", "+corr_degrees[2]+"]");
			System.out.println("generateEgomotionTable(): ENU corr=["+enu_corr[0]+", "+enu_corr[1]+", "+enu_corr[2]+"]");
			
		}
		
		for (int nscene = earliest_scene; nscene < quadCLTs.length; nscene++) {
			QuadCLT scene = quadCLTs[nscene];
			String ts = scene.getImageName();
			scenes_xyzatr[nscene] =    new double [][] {ers_reference.getSceneXYZ(ts),ers_reference.getSceneATR(ts)};
			scenes_xyzatr_dt[nscene] = ers_reference.getSceneErsXYZATR_dt(ts);
			if (scenes_xyzatr[nscene] != null) {
				num_processed++;
			}
		}
		boolean use_processed = num_processed > 1;
		String header_ts="#\ttimestamp";
		String header_img="\tx(m)\ty(m)\tz(m)\ta(rad)\ttilt(rad)\troll(rad)"+
			"\tVx(m/s)\tVy(m/s)\tVz(m/s)\tVa(rad/s)\tVt(rad/s)\tVr(rad/s)"+
			"\tEVx(m/s)\tEVy(m/s)\tEVz(m/s)\tEVa(rad/s)\tEVt(rad/s)\tEVr(rad/s)";
		String header_ins1="\ttow\ttheta0\ttheta1\ttheta2\tu(m/s)\tv(m/s)\tw(m/s)"+
			"\tlat\tlong\talt\tned0\tned1\tned2";
		String header_ins2="\ttow\tqn2b0\tqn2b1\tqn2b2\tqn2b3\tu(m/s)\tv(m/s)\tw(m/s)\tlat\tlong\talt";
		String header_ins2_extra="\tned_N\tned_E\tned_D\timu_X\timu_Y\timu_Z"+
				"\tcam_X1\tcam_Y1\tcam_Z1\tcam_X2\tcam_Y2\tcam_Z2"+
				"\tned->X\tned->Y\tned->Z\tenu->X\tenu->Y\tenu->Z"+
				"\tINS->X\tINS->Y\tINS->Z"+
				"\tabs_A_ned\tabs_T_ned\tabs_R_ned\trel_A_ned\trel_T_ned\trel_R_ned"+
				"\tabs_A_enu\tabs_T_enu\tabs_R_enu\trel_A_enu\trel_T_enu\trel_R_enu"+
				"\traw_A_enu\traw_T_enu\traw_R_enu"+
				"\tu_dir\tv_dir\tw_dir\tu_inv\tv_inv\tw_inv";
		
		String header_pimu="\to0\to1\to2\ta0\ta1\ta2";
		
		String header_camv="\tcam_vx\tcam_vy\tcam_vz\tcam_va\tcam_vt\tcam_vr";
		String header_testpimu = "\timsX\timsY\timsZ\timsA\timsT\timsR"+
    			"\tpimuX\tpimuY\tpimuZ\tpimuA\tpimuT\tpimuR"+
    			"\tpimuX-C\tpimuY-C\tpimuZ-C\tpimuA-C\tpimuT-C\tpimuR-C";		
		String header = header_ts+(use_processed?header_img:"")+header_ins1+
				header_ins2+header_ins2_extra+header_pimu+header_camv+header_testpimu;
		
		StringBuffer sb = new StringBuffer();
		double [][][] dxyzatr_dt = null;
		if (use_processed) {
			dxyzatr_dt = OpticalFlow.getVelocitiesFromScenes(
					quadCLTs,          // QuadCLT []     scenes, // ordered by increasing timestamps
					ref_index,
					earliest_scene,    // int            start_scene,
					quadCLTs.length-1, // int            end1_scene,
					scenes_xyzatr,     // double [][][]  scenes_xyzatr, // 5.0
					clt_parameters.ofp.lpf_series, // half_run_range); // double         half_run_range
					debugLevel); // int            debugLevel);
		}
		
				
		Did_ins_2   d2_ref = quadCLTs[ref_index].did_ins_2;
		double [] cam_quat_ref =Imx5.quaternionImsToCam(d2_ref.getQn2b() ,
				ims_mount_atr, // new double[] {0, 0.13, 0},
				ims_ortho);
		double [] ref_abs_atr = Imx5.quatToCamAtr(cam_quat_ref); //
		double [][] ims_ref_xyzatr = {ZERO3, ref_abs_atr};
		
		
		double [] cam_quat_ref_enu =Imx5.quaternionImsToCam(d2_ref.getQEnu() ,
				ims_mount_atr, // new double[] {0, 0.13, 0},
				ims_ortho);
		double [] ref_abs_atr_enu = Imx5.quatToCamAtr(cam_quat_ref_enu);
		double [][] ims_ref_xyzatr_enu = {ZERO3, ref_abs_atr_enu};
		double [][][] scenes_dxyzatr = QuadCLTCPU.getDxyzatrPIMU( // velocities and omegas from IMU 
				clt_parameters, // CLTParameters clt_parameters,
				quadCLTs); // ,       // QuadCLT[]     quadCLTs,
//				quadCLTs[ref_index].getPimuOffsets()); // boolean       scale)
		
		
		double [][][] ims_xyzatr = quadCLTs[ref_index].getXyzatrIms(
				clt_parameters, // CLTParameters clt_parameters,
				quadCLTs,       // QuadCLT[]  quadCLTs,
				null,           // double []  quat_corr, // only applies to rotations - verify!
				-1); // debugLevel) ;   // int        debugLevel)
		double [][][] pimu_xyzatr = QuadCLT.integratePIMU(
				clt_parameters, // final CLTParameters clt_parameters,
				quadCLTs,       // final QuadCLT[]     quadCLTs,
				ref_index,      // final int           ref_index,
				null,           // double [][][]       dxyzatr,
				earliest_scene, // final int           early_index,
	    		(quadCLTs.length -1) // int           last_index,
	    		);
		int cent_index =        earliest_scene + (quadCLTs.length - earliest_scene) / 2;
		double [][] cent_xyzatr = pimu_xyzatr[cent_index];
//		int            ref_index,
//		int            earliest_scene,
		
		
		for (int nscene = earliest_scene; nscene < quadCLTs.length; nscene++) {
			QuadCLT scene = quadCLTs[nscene];
			if (scene == null) {
				for (int i = 0; i < 26; i++) {
					sb.append("\t---");
				}
				sb.append("\n");
				continue;
			}
			double timestamp = scene.getTimeStamp();
			Did_ins_1   d1 = scene.did_ins_1;
			Did_ins_2   d2 = scene.did_ins_2;
			Did_pimu    d3 = scene.did_pimu;
			
			sb.append(nscene+"\t"+timestamp);
			if (use_processed) {
				double [] nan3 = new double[] {Double.NaN, Double.NaN,Double.NaN};
				double [][] nan23 = new double[][] {nan3,nan3};
				if (scenes_xyzatr[nscene] != null) {
					if (scenes_xyzatr[nscene][0] == null) scenes_xyzatr[nscene][0]= nan3;
					if (scenes_xyzatr[nscene][1] == null) scenes_xyzatr[nscene][1]= nan3;
					if (dxyzatr_dt[nscene]== null) dxyzatr_dt[nscene] = nan23;
					if (dxyzatr_dt[nscene][0] == null) dxyzatr_dt[nscene][0]= nan3;
					if (dxyzatr_dt[nscene][0] == null) dxyzatr_dt[nscene][0]= nan3;
					if (scenes_xyzatr_dt[nscene]== null) scenes_xyzatr_dt[nscene] = nan23;
					if (scenes_xyzatr_dt[nscene][0] == null) scenes_xyzatr_dt[nscene][0]= nan3;
					if (scenes_xyzatr_dt[nscene][0] == null) scenes_xyzatr_dt[nscene][0]= nan3;
					
					// x,y,z - scene position in reference scene frame (reference scene is all 0-s)
					sb.append("\t"+scenes_xyzatr[nscene][0][0]+"\t"+scenes_xyzatr[nscene][0][1]+"\t"+scenes_xyzatr[nscene][0][2]);
					// a,tilt,roll - scene orientation in reference scene frame (reference scene is all 0-s)
					sb.append("\t"+scenes_xyzatr[nscene][1][0]+"\t"+scenes_xyzatr[nscene][1][1]+"\t"+scenes_xyzatr[nscene][1][2]);
					// Vx, Vy, Vz - linear velocities calculated from coordinates by running average (now +/- 5 scenes
					sb.append("\t"+dxyzatr_dt[nscene][0][0]+"\t"+dxyzatr_dt[nscene][0][1]+"\t"+dxyzatr_dt[nscene][0][2]);
					// Va, Vt, Vr - angular velocities calculated from coordinates by running average (now +/- 5 scenes
					sb.append("\t"+dxyzatr_dt[nscene][1][0]+"\t"+dxyzatr_dt[nscene][1][1]+"\t"+dxyzatr_dt[nscene][1][2]);
					
					sb.append("\t"+scenes_xyzatr_dt[nscene][0][0]+"\t"+scenes_xyzatr_dt[nscene][0][1]+"\t"+scenes_xyzatr_dt[nscene][0][2]);
					sb.append("\t"+scenes_xyzatr_dt[nscene][1][0]+"\t"+scenes_xyzatr_dt[nscene][1][1]+"\t"+scenes_xyzatr_dt[nscene][1][2]);
				} else {
					for (int i = 0; i < 12; i++) {
						sb.append("\t---");
					}
				}
			}
			sb.append("\t"+d1.timeOfWeek);
			sb.append("\t"+d1.theta[0]+"\t"+d1.theta[1]+"\t"+d1.theta[2]);
			sb.append("\t"+d1.uvw[0]+  "\t"+d1.uvw[1]+  "\t"+d1.uvw[2]);
			sb.append("\t"+d1.lla[0]+  "\t"+d1.lla[1]+  "\t"+d1.lla[2]);
			sb.append("\t"+d1.ned[0]+  "\t"+d1.ned[1]+  "\t"+d1.ned[2]);
//String header_ins2="\ttow\tqn2b0\tqn2b1\tqn2b2\tqn2b3\tu(m/s)\tv(m/s)\tw(m/s)\tlat\tlong\talt";
			sb.append("\t"+d2.timeOfWeek);
			sb.append("\t"+d2.qn2b[0]+"\t"+d2.qn2b[1]+"\t"+d2.qn2b[2]+"\t"+d2.qn2b[3]);
			sb.append("\t"+d2.uvw[0]+  "\t"+d2.uvw[1]+  "\t"+d2.uvw[2]);
			sb.append("\t"+d2.lla[0]+  "\t"+d2.lla[1]+  "\t"+d2.lla[2]);
			double [] double_theta = d1.getTheta();
//imsToCamRotations(double [] ims_theta, boolean rev_order, boolean rev_matrix )
			double [] double_qn2b = d2.getQn2b();
			double [] double_uvw =  d2.getUvw();
			double [] uvw_dir = Imx5.applyQuaternionTo(double_qn2b, double_uvw, false); // bad
			double [] uvw_inv = Imx5.applyQuaternionTo(double_qn2b, double_uvw, true); // good
//Converting from local uvw to NED: (qn2b).applyInverseTo(uvw,ned) results in [vNorth, vEast, vUp] 			
			
			double [] ned = Imx5.nedFromLla (d2.lla, d2_ref.lla);
			double [] enu = Imx5.enuFromLla (d2.lla, d2_ref.lla);
			double [] ims_xyz = Imx5.applyQuaternionTo(double_qn2b, ned, false);
//"\tned_N\tned_E\tned_D\timu_X\timu_Y\timu_Z"			
			sb.append("\t"+ned[0]+  "\t"+ned[1]+  "\t"+ned[2]);             // global axes
			sb.append("\t"+ims_xyz[0]+  "\t"+ims_xyz[1]+  "\t"+ims_xyz[2]); // imu axes
			
			double [] cam_quat1 =Imx5.quaternionImsToCam(double_qn2b, new double[3], ims_ortho);
			double [] cam_quat2 =Imx5.quaternionImsToCam(double_qn2b,
					ims_mount_atr, // new double[] {0, 0.13, 0},
					ims_ortho);
			double [] cam_quat_enu =Imx5.quaternionImsToCam(d2.getQEnu(),
					ims_mount_atr, // new double[] {0, 0.13, 0},
					ims_ortho);
			double [] cam_quat_enu_raw =Imx5.quaternionImsToCam(d2.getQEnu(),
					new double[3], // new double[] {0, 0.13, 0},
					ims_ortho);
			
			double [] cam_xyz1 = Imx5.applyQuaternionTo(cam_quat1, ned, false);
			double [] cam_xyz2 = Imx5.applyQuaternionTo(cam_quat2, ned, false);
			double [] cam_xyz_ned = test_xyz_ned(
					Imx5.nedFromLla (d2.lla, d2_ref.lla), // double [] ned,double [] ned,
					d2_ref.getQn2b(), // double[]  quat_ned,
					ims_mount_atr, // double [] ims_mount_atr,
					ims_ortho); //double [] ims_ortho)
			double [] cam_xyz_enu = Imx5.applyQuaternionTo(
					Imx5.quaternionImsToCam(d2_ref.getQEnu(), // double[]  quat_enu,
							ims_mount_atr,
							ims_ortho),
					enu,
					false);
			double [] quat_lma_enu_xyz = (quatCorr != null) ?
					Imx5.applyQuaternionTo(quatCorr,cam_xyz_enu,false):
						cam_xyz_enu;
//"\tcam_X1\tcam_Y1\tcam_Z1\tcam_X2\tcam_Y2\tcam_Z2"			
			// NED without ims_mount_atr correction \tcam_X1\tcam_Y1\tcam_Z1
			sb.append("\t"+cam_xyz1[0]+  "\t"+cam_xyz1[1]+  "\t"+cam_xyz1[2]); // 
			// NED with ims_mount_atr correction \tcam_X2\tcam_Y2\tcam_Z2
			sb.append("\t"+cam_xyz2[0]+  "\t"+cam_xyz2[1]+  "\t"+cam_xyz2[2]); //
//"\tned->X\tned->Y\tned->Z\tenu->X\tenu->Y\tenu->Z"
			sb.append("\t"+cam_xyz_ned[0]+      "\t"+cam_xyz_ned[1]+      "\t"+cam_xyz_ned[2]); // \tned->X\tned->Y\tned->Z
			sb.append("\t"+cam_xyz_enu[0]+      "\t"+cam_xyz_enu[1]+      "\t"+cam_xyz_enu[2]); // WITH ims_mount_atr, NO quatCorr \tenu->X\tenu->Y\tenu->Z
//"\tINS->X\tINS->Y\tINS->Z"			
			sb.append("\t"+quat_lma_enu_xyz[0]+ "\t"+quat_lma_enu_xyz[1]+ "\t"+quat_lma_enu_xyz[2]); // WITH ims_mount_atr, WITH quatCorr "\tINS->X\tINS->Y\tINS->Z"
			
			double [] scene_abs_atr = Imx5.quatToCamAtr(cam_quat2);
			
			double [][] ims_scene_xyzatr = {ZERO3, scene_abs_atr};
			double [] scene_rel_atr=ErsCorrection.combineXYZATR(
					ims_scene_xyzatr,
					ErsCorrection.invertXYZATR(ims_ref_xyzatr))[1];
			
			double [] scene_abs_atr_enu = Imx5.quatToCamAtr(cam_quat_enu);
			double [][] ims_scene_xyzatr_enu = {ZERO3, scene_abs_atr_enu};
			double [] scene_raw_atr_enu = Imx5.quatToCamAtr(cam_quat_enu_raw); // not corrected by ims_mount_atr

			double [] scene_rel_atr_enu=ErsCorrection.combineXYZATR(
					ims_scene_xyzatr_enu,
					ErsCorrection.invertXYZATR(ims_ref_xyzatr_enu))[1];
//			"\tabs_A_ned\tabs_T_ned\tabs_R_ned\trel_A_ned\trel_T_ned\trel_R_ned"+			
			sb.append("\t"+scene_abs_atr[0]+  "\t"+scene_abs_atr[1]+  "\t"+scene_abs_atr[2]); // 
			sb.append("\t"+scene_rel_atr[0]+  "\t"+scene_rel_atr[1]+  "\t"+scene_rel_atr[2]); //
//          "\tabs_A_enu\tabs_T_enu\tabs_R_enu\trel_A_enu\trel_T_enu\trel_R_enu"			
			sb.append("\t"+scene_abs_atr_enu[0]+  "\t"+scene_abs_atr_enu[1]+  "\t"+scene_abs_atr_enu[2]); // 
			sb.append("\t"+scene_rel_atr_enu[0]+  "\t"+scene_rel_atr_enu[1]+  "\t"+scene_rel_atr_enu[2]); //
//			"\traw_A_enu\traw_T_enu\traw_R_enu"+
			sb.append("\t"+scene_raw_atr_enu[0]+  "\t"+scene_raw_atr_enu[1]+  "\t"+scene_raw_atr_enu[2]); //
//			"\tu_dir\tv_dir\tw_dir\tu_inv\tv_inv\tw_inv"
			sb.append("\t"+uvw_dir[0]+  "\t"+uvw_dir[1]+  "\t"+uvw_dir[2]); // wrong
			sb.append("\t"+uvw_inv[0]+  "\t"+uvw_inv[1]+  "\t"+uvw_inv[2]); // correct
//			String header_pimu="\to0\to1\to2\ta0\ta1\ta2";
			sb.append("\t"+(d3.theta[0]/d3.dt)+"\t"+(d3.theta[1]/d3.dt)+"\t"+(d3.theta[2]/d3.dt));
			sb.append("\t"+(d3.vel[0]/d3.dt)+"\t"+(d3.vel[1]/d3.dt)+"\t"+(d3.vel[2]/d3.dt));
//			String header_camv="\tcam_vx\tcam_vy\tcam_vz\tcam_va\tcam_vt\tcam_vr";
//          velocities and omegas from IMU 			
			sb.append("\t"+scenes_dxyzatr[nscene][0][0]+ "\t"+scenes_dxyzatr[nscene][0][1]+ "\t"+scenes_dxyzatr[nscene][0][2]);
			sb.append("\t"+scenes_dxyzatr[nscene][1][0]+ "\t"+scenes_dxyzatr[nscene][1][1]+ "\t"+scenes_dxyzatr[nscene][1][2]);
			//scenes_dxyzatr
			

			sb.append(
//					getXyzatrIms () "\timsX\timsY\timsZ\timsA\timsT\timsR"
					"\t"+ims_xyzatr[nscene][0][0]+ "\t"+ims_xyzatr[nscene][0][1]+ "\t"+ims_xyzatr[nscene][0][2]+
					"\t"+ims_xyzatr[nscene][1][0]+ "\t"+ims_xyzatr[nscene][1][1]+ "\t"+ims_xyzatr[nscene][1][2]+
//    	 			integratePIMU() "\tpimuX\tpimuY\tpimuZ\tpimuA\tpimuT\tpimuR";		
					"\t"+pimu_xyzatr[nscene][0][0]+"\t"+pimu_xyzatr[nscene][0][1]+"\t"+pimu_xyzatr[nscene][0][2]+
					"\t"+pimu_xyzatr[nscene][1][0]+"\t"+pimu_xyzatr[nscene][1][1]+"\t"+pimu_xyzatr[nscene][1][2]);
			double [][] cent_diff = ErsCorrection.combineXYZATR(
					pimu_xyzatr[nscene],
					ErsCorrection.invertXYZATR(cent_xyzatr));
			sb.append(
//					"\tpimuX-C\tpimuY-C\tpimuZ-C\tpimuA-C\tpimuT-C\tpimuR-C" // difference to the center. XYZ - wrong (fix it)?
					"\t"+cent_diff[0][0]+ "\t"+cent_diff[0][1]+ "\t"+cent_diff[0][2]+
					"\t"+cent_diff[1][0]+ "\t"+cent_diff[1][1]+ "\t"+cent_diff[1][2]);			
			
			// add lpf 
			sb.append("\n");
		}
		// test QuaternionLMA here
		// Add another data
		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("New ATR mount (rad):[\t"+new_atr[0]+"\t"+new_atr[1]+"\t"+new_atr[2]+"]\n");
		sb.append("New ATR mount (deg):[\t"+degrees[0]+"\t"+degrees[1]+"\t"+degrees[2]+"]\n");
//		double [] quatCorr
    	Rotation rot = new Rotation(quatCorr[0],quatCorr[1],quatCorr[2],quatCorr[3], false); // no normalization - see if can be scaled
		double []  corr_angles = rot.getAngles(RotationOrder.YXZ, ErsCorrection.ROT_CONV);
		double []  corr_degrees = new double[3];
		for (int i = 0; i < 3; i++) corr_degrees[i]=corr_angles[i]*180/Math.PI;
		sb.append("quatCorr=[\t"+quatCorr[0]+"\t"+quatCorr[1]+"\t"+quatCorr[2]+"\t"+quatCorr[3]+"]\n");
		sb.append("ATR(rad)=[\t"+corr_angles[0]+"\t"+corr_angles[1]+"\t"+corr_angles[2]+"]\n");
		sb.append("ATR(deg)=[\t"+corr_degrees[0]+"\t"+corr_degrees[1]+"\t"+corr_degrees[2]+"]\n");
		
		if (path!=null) {
			String footer=(comment != null) ? ("Comment: "+comment): "";
			CalibrationFileManagement.saveStringToFile (
					path,
					header+"\n"+sb.toString()+"\n"+footer);
		}else{
			new TextWindow("Sharpness History", header, sb.toString(), 1000,900);
		}
    }
    @Deprecated
    // adjusts by all 3 axis rotation 
    public static double [] getQuaternionCorrection_Old(
			CLTParameters  clt_parameters,
			QuadCLT []     quadCLTs,
			int            ref_index,
			int            earliest_scene,
			double []      rms // null or double[2];
    		) {
		double []     ims_ortho =     clt_parameters.imp.ims_ortho;
		double []     ims_mount_atr = clt_parameters.imp.getImsMountATR(); // converts to radians
		QuadCLT       ref_scene = quadCLTs[ref_index];
		ErsCorrection ers_reference = ref_scene.getErsCorrection();
		double [][][]   quat_lma_xyzatr =     new double [quadCLTs.length][2][3];
		double [][][]   quat_lma_enu_xyzatr = new double [quadCLTs.length][2][3];
		Did_ins_2     d2_ref = quadCLTs[ref_index].did_ins_2;
		for (int nscene = earliest_scene; nscene < quadCLTs.length; nscene++) {
			QuadCLT scene = quadCLTs[nscene];
			if (nscene == ref_index) {
				quat_lma_xyzatr[nscene][0] =    new double[3];
			} else {
				String ts = scene.getImageName();
				quat_lma_xyzatr[nscene][0] = ers_reference.getSceneXYZ(ts);
			}
			Did_ins_2   d2 = scene.did_ins_2;
			double [] enu = Imx5.enuFromLla (d2.lla, d2_ref.lla);
			quat_lma_enu_xyzatr[nscene][0] = Imx5.applyQuaternionTo(
					Imx5.quaternionImsToCam(d2_ref.getQEnu(), // double[]  quat_enu,
							ims_mount_atr,
							ims_ortho),
					enu,
					false);
		}
		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 []   quat0 = new double [] {1.0, 0.0, 0.0, 0.0}; // identity
		int         debug_level = 1;
		QuaternionLma quaternionLma = new QuaternionLma();
		quaternionLma.prepareLMA(
				quat_lma_enu_xyzatr, // quat_lma_xyz,     // double [][] vect_x,
				quat_lma_xyzatr,     // 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,
				debug_level);     // 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,
				debug_level);     // 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;
					}					
				}
			}
			return quaternionLma.getQuaternion();
		}
    }

    // relative to the GPS/compass
    public static double [] getQuaternionCorrection(
			CLTParameters  clt_parameters,
			QuadCLT []     quadCLTs,
			int            ref_index,
			int            earliest_scene,
			double []      rms, // null or double[2];
			double []      enu_corr,
			int            debugLevel
    		) {
		double []     ims_ortho =     clt_parameters.imp.ims_ortho;
		double []     ims_mount_atr = clt_parameters.imp.getImsMountATR(); // converts to radians
		QuadCLT       ref_scene = quadCLTs[ref_index];
		ErsCorrection ers_reference = ref_scene.getErsCorrection();
		double [][]   quat_lma_xyz =     new double [quadCLTs.length][];
		double [][]   quat_lma_enu_xyz = new double [quadCLTs.length][];
		Did_ins_2     d2_ref = quadCLTs[ref_index].did_ins_2;
		for (int nscene = earliest_scene; nscene < quadCLTs.length; nscene++) {
			QuadCLT scene = quadCLTs[nscene];
			if (nscene == ref_index) {
				quat_lma_xyz[nscene] =    new double[3];
			} else {
				String ts = scene.getImageName();
				quat_lma_xyz[nscene] = ers_reference.getSceneXYZ(ts);
			}
			Did_ins_2   d2 = scene.did_ins_2;
			double [] enu = Imx5.enuFromLla (d2.lla, d2_ref.lla);
			quat_lma_enu_xyz[nscene] = Imx5.applyQuaternionTo(
					Imx5.quaternionImsToCam(d2_ref.getQEnu(), // double[]  quat_enu,
							ims_mount_atr,
							ims_ortho),
					enu,
					false);
		}
		double [] up_axis = Imx5.getUpAxis(
				ims_mount_atr); // double [] ims_atr )
		
		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;
		int     debug_level = debugLevel;
		QuaternionLma quaternionLma = new QuaternionLma();
		quaternionLma.prepareCompassLMA(
				quat_lma_enu_xyz, // quat_lma_xyz,     // double [][] vect_x,
				quat_lma_xyz,     // double [][] vect_y,
				null,             // double [][] vect_w, all same weight
				up_axis,            // double []   vector_up, // Up in the IMS axes nearest to the camera Z (rotated by - ims_mount_atr)
				debug_level);     // 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,
				debug_level);     // 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 (debugLevel > -3) {
				System.out.println("getQuaternionCorrection(): Rotated around IMS-vertical by "+quaternionLma.getQuaternion()[0]+" rad");
				System.out.println("getQuaternionCorrection(): Rotated around IMS-vertical by "+quaternionLma.getQuaternion()[0]*180/Math.PI+" degrees");
			}
			double [][] camera_enu = quaternionLma.cameraToENU(quaternionLma.getQuaternion());
			double sw = 0;
			double [] swd = new double[3];
			for (int nscene = 0; nscene < camera_enu.length; nscene++) if (camera_enu[nscene] != null) {
				double w = 1.0;
				sw += w;
				for (int i = 0; i < swd.length; i++) {
					swd[i]+=w*(camera_enu[nscene][i]-quat_lma_enu_xyz[nscene][i]);
				}
			}
			for (int i = 0; i < swd.length; i++) {
				swd[i] /= sw;
			}
			
			if (enu_corr== null) {
				enu_corr=new double[3]; // will generate, just not return
			}
			double [] enu= Imx5.applyQuaternionTo(// metric E,N,U 
					Imx5.quaternionImsToCam(d2_ref.getQEnu(), // double[]  quat_enu,
							ims_mount_atr,
							ims_ortho),
					swd,
					true); // inverse
			for (int i = 0; i < 3; i++) {
				enu_corr[i] = enu[i];
			}
			if (debug_level > -3) {
				System.out.println("GNSS correction in camera X,Y,Z = ["+swd[0]+"]["+swd[1]+"]["+swd[2]+"]");
				System.out.println("GNSS correction in camera E,N,U = ["+enu_corr[0]+"]["+enu_corr[1]+"]["+enu_corr[2]+"]");
			}
			if (debug_level > 0) {
				System.out.println(String.format("%3s"+
						"\t%9s\t%9s\t%9s"+
						"\t%9s\t%9s\t%9s",
						"N","GNSS-X","GNSS-Y","GNSS-Z",
						"CAM-X","CAM-Y","CAM-Z"));
				
				for (int nscene = 0; nscene < camera_enu.length; nscene++) if (camera_enu[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,
							quat_lma_enu_xyz[nscene][0],quat_lma_enu_xyz[nscene][1],quat_lma_enu_xyz[nscene][2],
							camera_enu[nscene][0],camera_enu[nscene][1],camera_enu[nscene][2]));
				}
			}
			
			
			
			return quaternionLma.getAxisQuat();
		}
    }
    
    static double [] test_xyz_ned(
    		double [] ned,
			double[]  quat_ned,
			double [] ims_mount_atr,
			double [] ims_ortho) {
		
		double [] cam_quat2 =Imx5.quaternionImsToCam(quat_ned,
				ims_mount_atr,
				ims_ortho);
		return Imx5.applyQuaternionTo(cam_quat2, ned, false);
    }
      
    static double [] test_xyz_enu(
    		double [] enu,
			double[]  quat_enu,
			double [] ims_mount_atr,
			double [] ims_ortho) {
		
		double [] cam_quat2 =Imx5.quaternionImsToCam(quat_enu,
				ims_mount_atr,
				ims_ortho);
		return Imx5.applyQuaternionTo(cam_quat2, enu, false);
    }
   
    
}
