/**
 **
 ** OpticalFlow - Process scene pairs
 **
 ** Copyright (C) 2020 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  OpticalFlow.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.awt.Color;
import java.awt.Font;
import java.awt.Rectangle;
import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.DoubleAccumulator;

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

import java.util.concurrent.ThreadLocalRandom;

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.LogTee;
import com.elphel.imagej.common.PolynomialApproximation;
import com.elphel.imagej.common.ShowDoubleFloatArrays;
import com.elphel.imagej.correction.CorrectionColorProc;
import com.elphel.imagej.correction.SyncCommand;
import com.elphel.imagej.cuas.CorrectionFPN;
import com.elphel.imagej.cuas.Cuas;
import com.elphel.imagej.cuas.CuasCenterLma;
import com.elphel.imagej.cuas.CuasMotion;
import com.elphel.imagej.cuas.CuasRanging;
import com.elphel.imagej.gpu.GPUTileProcessor;
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 com.elphel.imagej.ims.UasLogReader;
import com.elphel.imagej.jp4.JP46_Reader_camera;
import com.elphel.imagej.tileprocessor.sfm.StructureFromMotion;
import com.elphel.imagej.vegetation.VegetationModel;

import Jama.Matrix;
import ij.ImagePlus;
import ij.ImageStack;
import ij.Prefs;
import ij.gui.Line;
import ij.io.FileSaver;
import ij.process.FloatProcessor;
import ij.process.ImageConverter;
import ij.process.ImageProcessor;
import ij.text.TextWindow;
import ij.plugin.filter.GaussianBlur;

public class OpticalFlow {
	public static String [] COMBO_DSN_TITLES = {"disp", "strength","disp_lma","num_valid","change",
			"disp_bg", "strength_bg","disp_lma_bg","change_bg","disp_fg","disp_bg_all","blue_sky","sfm_gain", "ground","terrain"};
	public static int COMBO_DSN_INDX_DISP =        0; // cumulative disparity (from CM or POLY), FG
	public static int COMBO_DSN_INDX_STRENGTH =    1; // strength, FG
	public static int COMBO_DSN_INDX_LMA =         2; // masked copy from 0 - cumulative disparity
	public static int COMBO_DSN_INDX_VALID =       3; // initial only
	public static int COMBO_DSN_INDX_CHANGE =      4; // increment
	public static int COMBO_DSN_INDX_DISP_BG =     5; // cumulative BG disparity (from CM or POLY)
	public static int COMBO_DSN_INDX_STRENGTH_BG = 6; // background strength
	public static int COMBO_DSN_INDX_LMA_BG =      7; // masked copy from BG disparity 
	public static int COMBO_DSN_INDX_CHANGE_BG =   8; // increment, BG
	public static int COMBO_DSN_INDX_DISP_FG =     9; // cumulative disparity (from CM or POLY), FG
	public static int COMBO_DSN_INDX_DISP_BG_ALL =10; // cumulative BG disparity (Use FG where no BG is available)
	public static int COMBO_DSN_INDX_BLUE_SKY =   11; // Detected featureless infinity (sky)
	public static int COMBO_DSN_INDX_SFM_GAIN =   12; // SfM disparity gain pixel/pixel
	public static int COMBO_DSN_INDX_GROUND =     13; // Smooth ground surface w/o vegetation
	public static int COMBO_DSN_INDX_TERRAIN =    14; // Smooth ground surface w/o vegetation
	
	public static int [] COMBO_DSN_NONNAN = {COMBO_DSN_INDX_STRENGTH, COMBO_DSN_INDX_STRENGTH_BG,COMBO_DSN_INDX_SFM_GAIN};
	public static int [] COMBO_DSN_DISPARITY = {COMBO_DSN_INDX_DISP,COMBO_DSN_INDX_LMA,COMBO_DSN_INDX_DISP_BG,COMBO_DSN_INDX_LMA_BG,
			COMBO_DSN_INDX_DISP_FG,COMBO_DSN_INDX_DISP_BG_ALL,COMBO_DSN_INDX_GROUND,COMBO_DSN_INDX_TERRAIN};
	
	
// move to Interscene class?	
	// 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 final int RESULTS_BUILD_SEQ_DEFAULT =  0; // Build sequence normal result in build_series_result[0] - doe not need to be assigned
	public static final int RESULTS_BUILD_SEQ_LY =       1; // LY field calibration performed
	
	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;
		}
	}

	private static int findSceneIndexByTimestamp(
			final QuadCLT[] quadCLTs,
			final String timestamp) {
		if ((quadCLTs == null) || (timestamp == null) || timestamp.isEmpty()) {
			return -1;
		}
		for (int i = 0; i < quadCLTs.length; i++) {
			if ((quadCLTs[i] != null) && timestamp.equals(quadCLTs[i].getImageName())) {
				return i;
			}
		}
		return -1;
	}

	private static String getInterIntraLmaPath(
			final QuadCLT scene,
			final String timestamp) {
		if ((scene == null) || (timestamp == null) || timestamp.isEmpty()) {
			return null;
		}
		return scene.getX3dDirectory() + Prefs.getFileSeparator() + timestamp +
				QuadCLTCPU.DSI_SUFFIXES[QuadCLTCPU.INDEX_INTER_LMA] + ".tiff";
	}

	private static String getDsiMainPath(
			final QuadCLT scene,
			final String timestamp) {
		if ((scene == null) || (timestamp == null) || timestamp.isEmpty()) {
			return null;
		}
		return scene.getX3dDirectory() + Prefs.getFileSeparator() + timestamp +
				QuadCLTCPU.DSI_SUFFIXES[QuadCLTCPU.INDEX_DSI_MAIN] + ".tiff";
	}

	private static String resolveQuarterInterIntraPath(
			final QuadCLT[] quadCLTs,
			final int refIndex,
			final int fallbackIndex,
			final String timestamp) {
		QuadCLT dirScene = ((refIndex >= 0) && (refIndex < quadCLTs.length)) ? quadCLTs[refIndex] : null;
		String ts = timestamp;
		if ((ts == null) || ts.isEmpty()) {
			if ((fallbackIndex >= 0) && (fallbackIndex < quadCLTs.length) && (quadCLTs[fallbackIndex] != null)) {
				ts = quadCLTs[fallbackIndex].getImageName();
				dirScene = quadCLTs[fallbackIndex];
			}
		} else {
			final int idx = findSceneIndexByTimestamp(quadCLTs, ts);
			if ((idx >= 0) && (idx < quadCLTs.length) && (quadCLTs[idx] != null)) {
				dirScene = quadCLTs[idx];
			}
		}
		return getInterIntraLmaPath(dirScene, ts);
	}

	private static int resolveQuarterRefIndex(
			final QuadCLT[] quadCLTs,
			final int rangeStart,
			final int rangeEnd,
			final int fallbackIndex,
			final String timestamp) {
		int idx = findSceneIndexByTimestamp(quadCLTs, timestamp);
		if ((idx < rangeStart) || (idx > rangeEnd)) {
			idx = fallbackIndex;
		}
		if (idx < rangeStart) {
			idx = rangeStart;
		}
		if (idx > rangeEnd) {
			idx = rangeEnd;
		}
		return idx;
	}

	private static boolean ensureQuarterReferenceProducts(
			final CLTParameters clt_parameters,
			final ColorProcParameters colorProcParameters,
			final EyesisCorrectionParameters.RGBParameters rgbParameters,
			final QuadCLT quadCLT_main,
			final QuadCLT[] quadCLTs,
			final SetChannels[] set_channels,
			final int centerRefIndex,
			final int quarterRefIndex,
			final int rangeStart,
			final int rangeEnd,
			final boolean forceRecalculateQuarterRefs,
			final boolean quarterSfmOnly,
			final boolean batch_mode,
			final int threadsMax,
			final boolean updateStatus,
			final int debugLevel) {
		if ((quadCLTs == null) || (set_channels == null) ||
				(centerRefIndex < 0) || (centerRefIndex >= quadCLTs.length) ||
				(quarterRefIndex < 0) || (quarterRefIndex >= quadCLTs.length) ||
				(rangeStart > rangeEnd)) {
			return false;
		}
		if (quadCLTs[centerRefIndex] == null) {
			return false;
		}
		if (quadCLTs[quarterRefIndex] == null) {
			quadCLTs[quarterRefIndex] = quadCLTs[centerRefIndex].spawnNoModelQuadCLT(
					set_channels[quarterRefIndex].set_name,
					clt_parameters,
					colorProcParameters,
					threadsMax,
					debugLevel - 2);
		}
		if (quadCLTs[quarterRefIndex] == null) {
			return false;
		}
		if (forceRecalculateQuarterRefs) {
			final String quarterTs = quadCLTs[quarterRefIndex].getImageName();
			final String quarterInterPath = getInterIntraLmaPath(quadCLTs[quarterRefIndex], quarterTs);
			final String quarterDsiMainPath = getDsiMainPath(quadCLTs[quarterRefIndex], quarterTs);
			if (debugLevel > -3) {
				System.out.println("buildSeries(): forcing quarter-reference regeneration for " + quarterTs);
			}
			if (quarterInterPath != null) {
				final File fInter = new File(quarterInterPath);
				if (fInter.exists() && !fInter.delete()) {
					System.out.println("buildSeries(): WARNING failed to delete quarter INTER-INTRA-LMA " + quarterInterPath);
				}
			}
			if (quarterDsiMainPath != null) {
				final File fDsi = new File(quarterDsiMainPath);
				if (fDsi.exists() && !fDsi.delete()) {
					System.out.println("buildSeries(): WARNING failed to delete quarter DSI_MAIN " + quarterDsiMainPath);
				}
			}
			quadCLTs[quarterRefIndex].setDSI(null);
		}
		if (!quadCLTs[quarterRefIndex].dsiExists()) {
			if (debugLevel > -3) {
				System.out.println("buildSeries(): generating -DSI_MAIN for quarter scene " +
						quadCLTs[quarterRefIndex].getImageName());
			}
			buildRefDSI(
					clt_parameters,
					false,
					false,
					colorProcParameters,
					rgbParameters,
					batch_mode,
					set_channels[quarterRefIndex].set_name,
					quadCLT_main,
					quadCLTs[quarterRefIndex],
					threadsMax,
					updateStatus,
					debugLevel);
		}
		if (!quadCLTs[quarterRefIndex].dsiExists()) {
			return false;
		}
		final EstimateSceneRange quarterRange = new EstimateSceneRange();
		quarterRange.earliest = rangeStart;
		quarterRange.latest = rangeEnd;
		quarterRange.center = quarterRefIndex;
		quarterRange.earliest_prescan = rangeStart;
		quarterRange.latest_prescan = rangeEnd;
		quarterRange.needs_prescan = false;
			if (debugLevel > -3) {
				System.out.println("buildSeries(): generating quarter INTER-INTRA-LMA with scanSfmIMS(), center=" +
						quarterRefIndex + ", range=[" + rangeStart + "," + rangeEnd + "], sfm_only=" + quarterSfmOnly);
			}
			final boolean sfmOk = EstimateSceneRange.scanSfmIMS(
					clt_parameters,
					quarterRange,
					quadCLTs,
				quadCLT_main,
				colorProcParameters,
					set_channels,
					batch_mode,
					updateStatus,
					debugLevel,
					quarterSfmOnly);
		if (!sfmOk) {
			return false;
		}
		return quadCLTs[quarterRefIndex].interDsiExists();
	}

	public static 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
	// 4 variants for 8 neighbors (N,NE,E...NW) to split into 2 opposite groups to compare with the center tile
	public static int [][] KERN_FG = {
			{1,1,3,2,2,2,3,1},
			{3,2,2,2,3,1,1,1},
			{1,3,2,2,2,3,1,1},
			{1,1,1,3,2,2,2,3}};
	public static double W = 0.25; // 0.5
	public static double [][] KERN_FG_W = { // corresponding weights
			{1,W,W,W,1,W,W,W},
			{W,W,1,W,W,W,1,W},
			{W,W,W,1,W,W,W,1},
			{W,1,W,W,W,1,W,W}};
	// Same for a larger kernel (5x5)
	public static int [][] KERN_FG2 = {
		//   0 1 2 3 4 5 6 7 8 91011121314151617181920212223
			{1,1,3,2,2,2,3,1,1,1,0,0,3,0,0,2,2,2,0,0,3,0,0,1},	
			{3,2,2,2,3,1,1,1,3,0,0,2,2,2,0,0,3,0,0,1,1,1,0,0}, 
			{1,3,2,2,2,3,1,1,0,0,3,0,0,2,2,2,0,0,3,0,0,1,1,1},
			{1,1,1,3,2,2,2,3,0,1,1,1,0,0,3,0,0,2,2,2,0,0,3,0}};
	public static double [][] KERN_FG_W2 = {
		//   0 1 2 3 4 5 6 7 8 91011121314151617181920212223
			{1,W,W,W,1,W,W,W,W,W,0,0,W,0,0,W,W,W,0,0,W,0,0,W},	
			{W,W,1,W,W,W,1,W,W,0,0,W,W,W,0,0,W,0,0,W,W,W,0,0}, 
			{W,W,W,1,W,W,W,1,0,0,W,0,0,W,W,W,0,0,W,0,0,W,W,W},
			{W,1,W,W,W,1,W,W,0,W,W,W,0,0,W,0,0,W,W,W,0,0,W,0}};
	
	
	public boolean        updateStatus = true;
	public int            numSens;
    // not configured yet
    public double         scale_no_lma_disparity = 1.0; // multiply strength were disparity_lma = NaN; 
	public long                                            startTime;     // start of batch processing
	
	public SyncCommand sync_command = null;

	public OpticalFlow (
			SyncCommand    sync_command,
			int            numSens,
			double         scale_no_lma_disparity,
			int            threadsMax,  // maximal number of threads to launch
			boolean        updateStatus) {
		this.sync_command = sync_command;
		this.numSens =                numSens;
//		this.THREADS_MAX =             threadsMax;
		this.updateStatus =           updateStatus;
		this.scale_no_lma_disparity = scale_no_lma_disparity; // currently not used (applied directly to setDSRBG) May be removed
	}

	/**
	 * Fill gaps in scene tile values (encoded as Double.NaN) from neighbors
	 * @param nan_tiles [macrotiles][layer(disparity, strength, r,b,g)][tile-in-macrotile], has nulls at first index
	 * @param qthis scene (QuadCLT instance)
	 * @param num_passes maximal number of passes to run Laplacian
	 * @param max_change threshold change of the tile value
	 * @param debug_level debug level
	 */
	
	public static void fillTilesNans(
			final double [][][] nan_tiles,
			final QuadCLT     qthis,
			final int         num_passes,
			final double      max_change,
			final int         debug_level)
	{
		final double   diagonal_weight = 0.5 * Math.sqrt(2.0); // relative to ortho
		final int margin =               0; // 1; // extra margins over 16x16 tiles to accommodate distorted destination tiles
		final TileProcessor tp =         qthis.getTileProcessor();
		final int transform_size =       tp.getTileSize();
		final int fullTileSize =         2 * (transform_size + margin);
		double wdiag = 0.25 *diagonal_weight / (diagonal_weight + 1.0);
		double wortho = 0.25 / (diagonal_weight + 1.0);
		final double [] neibw = {wortho, wdiag, wortho, wdiag, wortho, wdiag, wortho, wdiag}; 
		
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final int dbg_mtile = (debug_level > 1)? 203 : -1;
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int iMTile = ai.getAndIncrement(); iMTile < nan_tiles.length; iMTile = ai.getAndIncrement()) {
						if (iMTile == dbg_mtile) {
							System.out.println("fillTilesNans (): iMTile = "+iMTile);
						}
						if (nan_tiles[iMTile] != null) {
							tilesFillNaN(
									neibw, // final double []   neibw,
									nan_tiles[iMTile], // final double [][] slices,
									num_passes, // final int         num_passes,
									max_change, // final double      max_change,
									fullTileSize); // final int         width
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		
		if (debug_level > 1) {
			// show debug image
			String title = qthis.getImageName()+"-NO-NaN";
			showMacroTiles(
					title,        // String title,
					nan_tiles, // double [][][] source_tiles,
					qthis,        // final QuadCLT qthis,
					margin);      // final int     margin); // extra margins over 16x16 tiles to accommodate distorted destination tiles
		}
		if (debug_level > 0) {
			System.out.println("fillTilesNans() DONE.");
		}
	}

	// 
	/**
	 * Helper to be called from thread of fillTilesNans()
	 * @param neibw array of 8 weights of neighbors, CW, starting with up, sum = 1.0
	 * @param slices per channel (disparity, strength, r, b, g) array of macrotile slices 
	 * @param num_passes number of times to replace value by a weighted average of 8 neighbors
	 * @param max_change break if the absolute value of the change falls below this threshold
	 * @param width width of a macrotile in tiles (height is .length/width)
	 */
	
	private static void tilesFillNaN(
			final double []   neibw,
			final double [][] slices,
			final int         num_passes,
			final double      max_change,
			final int         width
			) {
		final int tiles = slices[0].length;
		final int height = tiles/width;
		double [] strength = slices[QuadCLT.DSRBG_STRENGTH]; 
		final TileNeibs tn =  new TileNeibs(width, height);
		double [] slice_in =  new double [tiles];
		double [] slice_out = new double [tiles];
		//first - process strength, then use calculated strength to fill other slices
		boolean [] fixed = new boolean [tiles]; 
		for (int i = 0; i < tiles; i++) {
			if (strength[i] > 0.0) {
				fixed[i] = true;
			} else {
				strength[i] = 0.0; // get rid of NaN; for non-strength will use average
			}
		}
		System.arraycopy(strength, 0, slice_in, 0, tiles);
		for (int pass = 0; pass < num_passes; pass ++) {
			double pass_change = 0.0;
			for (int nt = 0; nt < tiles; nt++) if (!fixed[nt]){
				double s = 0.0;
				double sw = 0.0;
				double d;
				for (int dir = 0; dir < 8; dir++) {
					int nt1 = tn.getNeibIndex(nt, dir);
					if (nt1 >=0) {
						if (fixed[nt1]) {
							d = strength[nt1];
						}else {
							d = slice_in[nt1];
						}
						s += d * neibw[dir];
						sw += neibw[dir];
					}
				}
				// sw should never be 0;
				s /= sw;
				pass_change = Math.max(pass_change, Math.abs(slice_out[nt] - s));
				slice_out[nt] = s;
			}
			if (pass_change < max_change) {
				break;
			}
			System.arraycopy(slice_out, 0, slice_in, 0, tiles);
		}
		for (int i = 0; i < fixed.length; i++) if (!fixed[i]){
			strength[i] = slice_out[i];
		}
		//non-strength							
		for (int iSlice = 0; iSlice < slices.length; iSlice++) if (iSlice != QuadCLT.DSRBG_STRENGTH){
			double [] slice =    slices[iSlice];
			System.arraycopy(slice, 0, slice_in, 0, tiles);
			double fs =0.0;
			double fsw = 0.0;
			for (int i = 0; i < fixed.length; i++) {
				if (!Double.isNaN(slice[i])  &&  (strength[i] > 0.0)) { //  - now already non-null
					fixed[i] = true;
					fs +=  slice[i] * strength[i];
					fsw += strength[i];
				}
			}
			if (fsw <= 0.0) {
				continue; // should not happen
			}
			fs /= fsw; // average value
			for (int i = 0; i < fixed.length; i++) if (! fixed[i]){
				slice_in[i] = fs;
			}								
			for (int pass = 0; pass < num_passes; pass ++) {
				double pass_change = 0.0;
				for (int nt = 0; nt < tiles; nt++) if (!fixed[nt]){
					double s = 0.0;
					double sw = 0.0;
					double d;
					for (int dir = 0; dir < 8; dir++) {
						int nt1 = tn.getNeibIndex(nt, dir);
						if (nt1 >=0) {
							if (fixed[nt1]) {
								d = slice[nt1];
							}else {
								d = slice_in[nt1];
							}
							double w = neibw[dir]; //  * strength[nt1];
							s += d * w ;
							sw += w;
						}
					}
					if (sw > 0) {
						s /= sw;
					}
					pass_change = Math.max(pass_change, Math.abs(slice_out[nt] - s));
					slice_out[nt] = s;
				}
				if (pass_change < max_change) {
					break;
				}
				System.arraycopy(slice_out, 0, slice_in, 0, tiles);
			}
			for (int i = 0; i < fixed.length; i++) if (!fixed[i]){
				slice[i] = slice_out[i];
			}
		}
	}
	
	
	
	
	/**
	 * Calculate confidence for the interscene X,Y correlation
	 * @param flowXY per-macrotile array of per-tile X,Y of the optical flow vectors. May have nulls
	 * @param width width of the macrotile array 
	 * @param best_num select this number of tghe closest matches among 8 neighbors
	 * @param ref_stdev  confidence formula: (ref_stdev ^ 2) / (neib_std_dev^2 + ref_stdev^2)  
	 * @param debug_title debug image title null - no image)
	 * @return per-tile array of triplets {x,y, confidence}. May have nulls (original and those without enough neighbors
	 */
	
	public double [][] attachVectorConfidence(
			final double [][] flowXY,
			final int         width,
			final int         best_num,
			final double      ref_stdev,
			final String      debug_title)
	{
		int height = flowXY.length/width;
		final double [][] flowXYS = new double[flowXY.length][];
		final TileNeibs tn =  new TileNeibs(width, height);
		final double ref_stdev2 =ref_stdev * ref_stdev;
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final int dbg_mtile = -1; // 203;
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int iMTile = ai.getAndIncrement(); iMTile < flowXY.length; iMTile = ai.getAndIncrement()) if (flowXY[iMTile] != null){
						if (iMTile == dbg_mtile) {
							System.out.println("attachVectorConfidence(): iMTile = "+iMTile);
						}
						double [] r2s = new double [8];
						for (int dir = 0; dir < r2s.length; dir++) {
							int indx =  tn.getNeibIndex(iMTile, dir);
							if ((indx >= 0) &&  (flowXY[indx] != null)){
								double  dx = flowXY[indx][0] - flowXY[iMTile][0];
								double  dy = flowXY[indx][1] - flowXY[iMTile][1];
								r2s[dir] = dx*dx + dy*dy;
							} else {
								r2s[dir] =Double.NaN;
							}
						}
						Arrays.sort(r2s); //  Double.NaN is considered greater than any other value and all Double.NaN values are considered equal.
						if (!Double.isNaN(r2s[best_num-1])) {
							double s1=0.0, s2 =0.0;
							for (int i = 0; i < best_num; i++) {
								s1 += r2s[i];
								s2 += r2s[i] * r2s[i];
							}
							double sd2 = (best_num * s2 - s1*s1)/(best_num * best_num);
							double confidence =  (ref_stdev * ref_stdev) / (sd2 + ref_stdev2);
							flowXYS[iMTile] = new double[] {flowXY[iMTile][0],flowXY[iMTile][1],confidence};
						}
					}
				}
			};
		}
		
		ImageDtt.startAndJoin(threads);
		if (debug_title != null) {
			showVectorXYConfidence(
					debug_title, // String      title,
					flowXYS, // double [][] flowXYS,
					width); // int         width)	
		}
		return flowXYS;
	}
	
	private static int removeOutliers(
			double nsigma,
			double [][] flowXYS)
	{
		if (nsigma < 0.0) {
			return 0;
		}
		double swx = 0.0, swy = 0.0, sw = 0.0;
		for (int i = 0; i < flowXYS.length; i++) if (flowXYS[i] != null) {
			double w = flowXYS[i][2];
			swx += flowXYS[i][0]* w;
			swy += flowXYS[i][1]* w;
			sw += w;
		}
		if (sw > 0.0) {
			swx /= sw;
			swy /= sw;
			// calculate deviation regardless of weight
			int n = 0;
			double s2 = 0.0;
			for (int i = 0; i < flowXYS.length; i++) if (flowXYS[i] != null) {
				double dx = flowXYS[i][0] - swx;
				double dy = flowXYS[i][1] - swy;
				s2 += dx*dx+dy*dy;
				n++;
			}
			s2/= n;
			n=0;
			double s2_max = s2 * nsigma * nsigma;
			for (int i = 0; i < flowXYS.length; i++) if (flowXYS[i] != null) {
				double dx = flowXYS[i][0] - swx;
				double dy = flowXYS[i][1] - swy;
				if ((dx*dx+dy*dy) > s2_max) {
					flowXYS[i] = null;
					n++;
				}
			}
			return n;
			/*
			double err = 4.0;
			for (int i = 0; i < flowXYS.length; i++) if (flowXYS[i] != null){
				flowXYS[i][0] += err;
				flowXYS[i][1] += err;
			}
			*/
			
		}
		return -1;
	}
	
	
	/**
	 * Show a 2/3-slice image for the optical flow (only X,Y or X,Y,Confidence)
	 * @param title image title (null or empty - will not show)
	 * @param flowXYS
	 * @param width number of macrotiles in a row
	 */
	private static void showVectorXYConfidence(
			String      title,
			double [][] flowXYS,
			int         width)
	{
		if ((title != null) && !title.equals("")) {
			int height = flowXYS.length/width;
//			String [] titles0 ={"dX","dY","Strength"};
			String [] titles ={"dX","dY","Strength","Err","dX-weighted","dY-weighted","Werr"};
//			int nslices = titles0.length;
			int nslices = 0;
			for (int i = 0; i < flowXYS.length; i++) if (flowXYS[i] != null) {
				nslices = flowXYS[i].length;
				break;
			}
			/*
			if (nslices > titles0.length) {
				nslices = titles0.length;
			}

			String [] titles =new String [nslices];
			for (int i = 0; i < nslices; i++) {
				titles[i] = titles0[i]; 
			}
			*/
			final double [][] dbg_img = new double [titles.length][width * height];
			for (int l = 0; l < dbg_img.length; l++) {
				Arrays.fill(dbg_img[l],  Double.NaN);
			}
			for (int mtile = 0; mtile < flowXYS.length; mtile++) if (flowXYS[mtile] != null){
//				for (int l = 0; l < dbg_img.length; l++) {
				for (int l = 0; l < nslices; l++) {
					dbg_img[l][mtile] = flowXYS[mtile][l];
				}
				if (nslices > 2) {
					dbg_img[3][mtile] = Math.sqrt(
							flowXYS[mtile][0] * flowXYS[mtile][0] + 
							flowXYS[mtile][1] * flowXYS[mtile][1]);
					dbg_img[4][mtile] = flowXYS[mtile][0] * flowXYS[mtile][2];
					dbg_img[5][mtile] = flowXYS[mtile][1] * flowXYS[mtile][2];
					dbg_img[6][mtile] = Math.sqrt(
							flowXYS[mtile][0] * flowXYS[mtile][0] + 
							flowXYS[mtile][1] * flowXYS[mtile][1]) * flowXYS[mtile][2];
				}
			}
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					width,
					height,
					true,
					title,
					titles);
		}
	}
	private static void showVectorXYConfidence(
			String        title,
			double [][][] flowXYSs,
			int           width)
	{
		if ((title != null) && !title.equals("")) {
			int height = flowXYSs[0].length/width;
			String [] titles0 ={"dX","dY","S"};
			int nslices = titles0.length;
			for (int i = 0; i < flowXYSs[0].length; i++) if (flowXYSs[0][i] != null) {
				nslices = flowXYSs[0][i].length;
				break;
			}
			if (nslices > titles0.length) {
				nslices = titles0.length;
			}

			String [] titles =new String [nslices];
			for (int i = 0; i < nslices; i++) {
				titles[i] = titles0[i]; 
			}
			
			int slices = flowXYSs.length;
			String [] dbg_titles = new String[slices * nslices];
			double [][] dbg_img = new double [dbg_titles.length][width*height];
			for (int slice = 0; slice < flowXYSs.length; slice++) {
				for (int n = 0; n < nslices; n++) {
					dbg_titles[slice + n * slices] = titles0[n]+slice;
					Arrays.fill(dbg_img[slice + slices * n], Double.NaN);
					for (int i = 0; i < flowXYSs[slice].length; i++) if (flowXYSs[slice][i] != null){
						dbg_img[slice + n * slices][i] = flowXYSs[slice][i][n];
					}
				}

			}
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					width,
					height,
					true,
					title,
					dbg_titles);			
		}
	}
	
	
	
	
	/**
	 * Calculate optical flow vectors for macrotiles (vX, vY, confidence) for each non-null macrotile
	 * by multiple iterations of 2D phase correlations, finding 2D argmax (currently by windowed center of masses)
	 * and adding the corrections to the initial offsets (flowXY). Correlation takes place between reference and scene tiles,
	 * where reference macrotiles use original scene data without any interpolation, and scene macrotiles try to
	 * minimize interpolation by finding the best-fit offset in the [-0.5,0.5) range for each of X and Y directions,
	 * and then applying residual fractional shifts (flowXY_frac) as rotations in the frequency domain.
	 *   
	 * @param scene_xyz Scene X (right),Y (up), Z (negative away from camera) in the reference camera coordinates
	 *        or null to use scene instance coordinates.
	 * @param scene_atr Scene azimuth, tilt and roll (or null to use scene instance).
	 * @param scene_QuadClt Scene QuadCLT instance.
	 * @param reference_QuadClt Reference QuadCLT instance.
	 * @param reference_tiles_macro 
	 * @param reference_center_occupancy Fraction of non-null tiles in the center 8x8 area of the reference macrotiles after disparity
	 *        filtering (see tolerance_absolute,  tolerance_relative). Below this threshold - skip that macrotile.
	 * @param flowXY0 Initial offset of scene tiles (in image pixels) in x (right) and y (down) directions or null (to use all zeros)
	 * @param tolerance_absolute Filter reference macrotiles by same disparity (within a disparity range) consisting of the sum 
	 *        of absolute disparity (tolerance_absolute) and a proportional to the average disparity (tolerance_relative).  
	 * @param tolerance_relative Relative to the average disparity part of the disparity filtering.
	 * @param scene_macrotile_occupancy Skip scene macrotile if less than this fraction of all tiles in a macrotile remain
	 *        after filtering.
	 * @param num_laplacian Number of Laplacian passes while replacing undefined (NaN) tiles from neighbors for reference and scene
	 *        macrotiles.
	 * @param change_laplacian Break the loop of Laplacian passes if the maximal absolute value of the last pass changes falls below
	 *        this threshold.
	 * @param chn_weights A 4-element array of the correlation weights for strength, red, blue and green channels
	 * @param corr_sigma A low-pass sigma for the 2-d correlation (in tiles)
	 * @param fat_zero 2D correlation relative "fat zero" to damp phase correlation normalization.
	 * @param late_normalize True - normalize after combining all channels, false - normalize each channel separately.
	 *        When consolidating multiple tile late_normalize is considered true. 
	 * @param iradius Used for argmax() center of mass window (1 - 3x3, 2 - 5x5)
	 * @param dradius Radius argmax() window radius
	 * @param refine_num For argmax() based on center of masses. In each iteration center window around previously found
	 *        fractional-pixel argmax. 
	 * @param num_ignore_worsening During first num_ignore_worsening iterations, do not reduce applied correction
	 * @param max_tries Limit of the number of correlation iterations.
	 * @param magic_scale 0.85 - measured argmax has a bias caused by fading of the 2D correlation away from the center.
	 * @param min_change Stop refining offset vector when the correction falls below this value (in image pixels)
	 * @param best_num When calculating the confidence level, calculate correction vector standard deviation among
	 *        8 neighbors and use best_num of the best (closest to the current macrotile) of them. Disregard macrotile
	 *        if number of available neighbors is less than best_num. 
	 * @param ref_stdev Average/expected standard deviation of differences between the current macrotile and its best
	 *        neighbors. Confidence formula is confidence= (ref_stdev ^ 2)/(ref_stdev ^2 + stdev^2)
	 * @param debug_level
	 * @return An array of per-macrotile triplets: {X, Y, Confidence}, where X and Y are expressed in image pixels.
	 *         some macrotiles may have nulls.
	 *         Return depends on threads(?), has random variations when restarted with the same data
	 */
	public double [][] correlate2DIterate( // returns optical flow and confidence
			final ImageDttParameters  imgdtt_params,
			// for prepareSceneTiles()			
			final double []   scene_xyz,     // camera center in world coordinates
			final double []   scene_atr,     // camera orientation relative to world frame
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt,
			final double [][] reference_tiles_macro,
			final double      reference_center_occupancy,   // fraction of remaining  tiles in the center 8x8 area (<1.0)
//			final double [][][] reference_tiles, // prepared with prepareReferenceTiles() + fillTilesNans();
			// flowXY should be initialized to all pairs of zeros (or deliberate pixel offset pairs if initial error is too high, will be modified with each iteration
			final double [][] flowXY0, // per macro tile initial {mismatch in image pixels in X and Y directions} or null
			final double      tolerance_absolute, // absolute disparity half-range in each tile
			final double      tolerance_relative, // relative disparity half-range in each tile
			final double      scene_macrotile_occupancy,          // fraction of remaining  tiles (<1.0)
			final int         num_laplacian,
			final double      change_laplacian,
			// for correlate2DSceneToReference ()
			final double []   chn_weights, // absolute, starting from strength (strength,r,b,g)
			final double      corr_sigma,
			final double      fat_zero,
			final boolean     late_normalize,
			// for correlation2DToVectors_CM()
			final int         iradius,      // half-size of the square to process 
			final double      dradius,      // weight calculation (1/(r/dradius)^2 + 1)
			final int         refine_num,   // number of iterations to apply weights around new center
			
			final int         num_ignore_worsening, // run all tiles for few iterations before filtering
			final int         max_tries,
			// for recalculateFlowXY()
			final double      magic_scale, // 0.85 for CM
			final double      min_change,
			
			final int         best_num,
			final double      ref_stdev,
			final int         debug_level, //1
			final boolean     enable_debug_images) // true
	{
		boolean debug_mismatch = (debug_level > -10); // && enable_debug_images);
//		int debug_corr2d = 0;// 10
		
		final TileProcessor tp =         reference_QuadClt.getTileProcessor();
		final int transform_size =       tp.getTileSize();

		int dbg_mtilesX = tp.getTilesX()/transform_size;
		int dbg_mtilesY = tp.getTilesY()/transform_size;
		int dbg_mtiles = dbg_mtilesX * dbg_mtilesY; 
		int dbg_width = 3*256; // largest
		int dbg_height= dbg_mtiles * 5;
		
//		double [][] dbg_img = debug_mismatch ? (new double [max_tries][dbg_width*dbg_height]) : null; 
		double [][] dbg_img = debug_mismatch ? (new double [max_tries][]) : null; 
		
		
		double [][][] reference_tiles = prepareReferenceTiles(
				reference_QuadClt,        // final QuadCLT     qthis,
				tolerance_absolute, // final double      tolerance_absolute, // absolute disparity half-range in each tile
				tolerance_relative, // final double      tolerance_relative, // relative disparity half-range in each tile
				reference_center_occupancy,   // final double      center_occupancy,   // fraction of remaining  tiles in the center 8x8 area (<1.0)
				-1); // -1); // 2); // final int         debug_level)
		
		fillTilesNans(
				reference_tiles,          // final double [][][] nan_tiles,
				reference_QuadClt,                 // final QuadCLT     qthis,
				num_laplacian,            // final int         num_passes,
				change_laplacian,            // final double      max_change,
				-1); //-1); // 2);                    // final int         debug_level)
		if (reference_tiles_macro != null) {
			double [][] macro_centers =  getMacroPxPyDisp(
					reference_QuadClt, // final QuadCLT     reference_QuadClt,
					reference_tiles    //final double [][][] reference_tiles // prepared with prepareReferenceTiles() + fillTilesNans();
					);
			for (int i = 0; i < reference_tiles_macro.length; i++) {
				reference_tiles_macro[i] = macro_centers[i];
			}
		}
		
		final double [][] flowXY = (flowXY0 == null) ? (new double [reference_tiles.length][2]):flowXY0;
		final double [][] flowXY_frac = new double [reference_tiles.length][]; // Will contain fractional X/Y shift for CLT
		double [][] flowXY_run = flowXY; // only non-nulls for the tiles to correlate
		final double []   abs_change = new double [reference_tiles.length]; // updated 
		Arrays.fill(abs_change, Double.NaN);
		final double [] step_scale =  new double [reference_tiles.length]; // multiply increment if change exceeds previous
		Arrays.fill(step_scale, 1.0);
		final double [][] flowXY_prev =  new double [reference_tiles.length][]; // multiply increment if change exceeds previous
		
		
		for (int ntry = 0; ntry < max_tries; ntry++) {
			double [][][] scene_tiles = prepareSceneTiles(// to match to reference
					// null for {scene,reference}{xyz,atr} uses instances globals 
					scene_xyz,                // final double []   scene_xyz,     // camera center in world coordinates
					scene_atr,                // final double []   scene_atr,     // camera orientation relative to world frame
					scene_QuadClt,            // final QuadCLT     scene_QuadClt,
					reference_QuadClt,        // final QuadCLT     reference_QuadClt,
					reference_tiles,          // final double [][][] reference_tiles, // prepared with prepareReferenceTiles() + fillTilesNans();
					flowXY_run,               // final double [][] flowXY, // per macro tile {mismatch in image pixels in X and Y directions
					flowXY_frac,              // final double [][] flowXY_frac, // should be initialized as [number of macro tiles][] - returns fractional shifts [-0.5, 0.5)
					tolerance_absolute,       // final double      tolerance_absolute, // absolute disparity half-range in each tile
					tolerance_absolute,       // final double      tolerance_relative, // relative disparity half-range in each tile
					tolerance_relative,       // final double      occupancy,          // fraction of remaining  tiles (<1.0)
					num_laplacian,               // final int         num_passes,
					change_laplacian,               // final double      max_change,
					-1); //-1); // 1); // 2);                       // final int         debug_level)
			// undefine tiles in flowXY that are never used
			if (ntry == 0) {
				for (int i = 0; i <flowXY.length; i++) {
					if ((scene_tiles[i] == null) || (reference_tiles[i] == null)) {
						flowXY[i] = null;	
					}
				}
			}
			double [][] corr2dscene_ref = correlate2DSceneToReference(// to match to reference
					imgdtt_params, // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
					scene_QuadClt,          // final QuadCLT     scene_QuadClt,
					reference_QuadClt,      // final QuadCLT     reference_QuadClt,
					scene_tiles,            // final double [][][] scene_tiles,     // prepared with prepareSceneTiles()
					reference_tiles,        // final double [][][] reference_tiles, // prepared with prepareReferenceTiles() + fillTilesNans(); - combine?
					flowXY_frac,            // final double [][] flowXY_frac, // X, YH fractional shift [-0.5,0.5) to implement with FD rotations
					chn_weights,            // final double []   chn_weights, // absolute, starting from strength (strength,r,b,g)
					corr_sigma,             // final double      corr_sigma,
					fat_zero,               // final double      fat_zero,
					late_normalize,         // final boolean     late_normalize,
					false,                  // final boolean     combine_empty_only, // only use neighbor correlations for empty tiles (false - any)
					0.0,                    // final double      combine_dradius
					0.0,                    // final double      tolerance_absolute, // absolute disparity half-range to consolidate tiles
					0.0,                    // final double      tolerance_relative, // relative disparity half-range to consolidate tiles
					-1); // 1); // final int         debug_level)
			
			double [][] vectorsXYS = correlation2DToVectors_CM(
					corr2dscene_ref,        // final double [][] corr2d_tiles, // per 2d calibration tiles (or nulls)
					transform_size,         // final int         transform_size,
					iradius,                // final int         iradius,      // half-size of the square to process 
					dradius,                // final double      dradius,      // weight calculation (1/(r/dradius)^2 + 1)
					refine_num,             // final int         refine_num,   // number of iterations to apply weights around new center
					-1);                    //final int         debug_level)
			double      this_min_change = min_change; //  (ntry < num_run_all)? 0.0: min_change;
			boolean     ignore_worsening = ntry < num_ignore_worsening; // (num_run_all + 10);
			if (debug_level > 0) { //-2) { // was >0
				System.out.println("======== NTRY "+ntry +" ========");
			}
			double [][] flowXY_dbg=null;
			double [][] flowXY_prev_dbg=null;
			double [][] vectorsXYS_dbg=null;
			double [] abs_change_dbg=null;
			double [] step_scale_dbg=null;
			if (dbg_img != null) {
				flowXY_dbg =      new double [flowXY.length][];
				flowXY_prev_dbg = new double [flowXY_prev.length][];
				vectorsXYS_dbg =  new double [vectorsXYS.length][];
				for (int ii = 0; ii < vectorsXYS.length; ii++) {
					if (flowXY[ii] != null) flowXY_dbg[ii] = flowXY[ii].clone();
					if (flowXY_prev[ii] != null) flowXY_prev_dbg[ii] = flowXY_prev[ii].clone();
					if (vectorsXYS[ii] != null) vectorsXYS_dbg[ii] = vectorsXYS[ii].clone();
				}
				abs_change_dbg = abs_change.clone();
				step_scale_dbg = step_scale.clone();
			}
			if (dbg_img != null) {//
				System.out.println("Before recalculateFlowXY() ntry = "+ntry+" debug_level="+debug_level);
			}
			flowXY_run = recalculateFlowXY(
					flowXY,                     // final double [][] flowXY, // will update
					flowXY_prev,                // final double [][] flowXY_prev, // previous flowXY (may be null for tiles)   
					vectorsXYS,                 // final double [][] corr_vectorsXY,
					abs_change,                 // final double []   abs_change, // updated
					step_scale,                 // final double []   step_scale, // multiply increment if change exceeds previous
					ignore_worsening,           // final boolean     boolean     ignore_worsening 
					magic_scale/transform_size, // final double      magic_scale, // 0.85 for CM
					this_min_change,            // final double      min_change,
					((dbg_img != null)? 2: debug_level));                         // final int         debug_level);
			if (dbg_img != null) {
				dbg_img[ntry] = new double [dbg_width*dbg_height];
				Arrays.fill(dbg_img[ntry], Double.NaN);
				for (int ii = 0; ii < scene_tiles.length; ii++) if (scene_tiles[ii] != null){
					for (int jj = 0; jj < scene_tiles[ii].length; jj++) {
						System.arraycopy(
								scene_tiles[ii][jj],
								0,
								dbg_img[ntry],
								((dbg_mtiles * 0) + ii)* dbg_width + jj * scene_tiles[ii][jj].length,
								scene_tiles[ii][jj].length); // 256
					}
				}
				for (int ii = 0; ii < corr2dscene_ref.length; ii++) if (corr2dscene_ref[ii] != null){
					System.arraycopy(
							corr2dscene_ref[ii],
							0,
							dbg_img[ntry],
							((dbg_mtiles * 1) + ii)* dbg_width,
							 corr2dscene_ref[ii].length); // 225
				}
				for (int ii = 0; ii < vectorsXYS.length; ii++) if (vectorsXYS[ii] != null){
					for (int jj = 0; jj < vectorsXYS[ii].length; jj++) {
						System.arraycopy(
								vectorsXYS[ii],
								0,
								dbg_img[ntry],
								((dbg_mtiles * 2) + ii)* dbg_width,
								vectorsXYS[ii].length); // 3
					}
				}
				if (flowXY_run != null) {
					for (int ii = 0; ii < flowXY_run.length; ii++) if (flowXY_run[ii] != null){
						System.arraycopy(
								flowXY_run[ii],
								0,
								dbg_img[ntry],
								((dbg_mtiles * 3) + ii)* dbg_width,
								flowXY_run[ii].length); // 2
					}
				}
				for (int ii = 0; ii < abs_change.length; ii++){
					if (flowXY_frac[ii] != null){
						System.arraycopy(
								flowXY_frac[ii],
								0,
								dbg_img[ntry],
								((dbg_mtiles * 4) + ii)* dbg_width,
								flowXY_frac[ii].length); // 2
					}					
					if (flowXY_prev[ii] != null){
						System.arraycopy(
								flowXY_prev[ii],
								0,
								dbg_img[ntry],
								((dbg_mtiles * 4) + ii)* dbg_width + 2, // 2 pixels right 
								flowXY_prev[ii].length); // 2
					}						
					dbg_img[ntry][((dbg_mtiles * 4) + ii)* dbg_width + 4] = abs_change[ii];
					dbg_img[ntry][((dbg_mtiles * 4) + ii)* dbg_width + 5] = step_scale[ii];

					if (flowXY_dbg[ii] != null){
						System.arraycopy(
								flowXY_dbg[ii],
								0,
								dbg_img[ntry],
								((dbg_mtiles * 4) + ii)* dbg_width + 6, // 2 pixels right 
								flowXY_dbg[ii].length); // 2
					}
					if (flowXY_prev_dbg[ii] != null){
						System.arraycopy(
								flowXY_prev_dbg[ii],
								0,
								dbg_img[ntry],
								((dbg_mtiles * 4) + ii)* dbg_width + 8, // 2 pixels right 
								flowXY_prev_dbg[ii].length); // 2
					}
					if (vectorsXYS_dbg[ii] != null){
						System.arraycopy(
								vectorsXYS_dbg[ii],
								0,
								dbg_img[ntry],
								((dbg_mtiles * 4) + ii)* dbg_width + 10, // 2 pixels right
								vectorsXYS_dbg[ii].length); // 2
					}
					dbg_img[ntry][((dbg_mtiles * 4) + ii)* dbg_width + 13] = abs_change_dbg[ii];
					dbg_img[ntry][((dbg_mtiles * 4) + ii)* dbg_width + 14] = step_scale_dbg[ii];
				}
			}
			if (flowXY_run == null) { // nothing to do left
				break; 
			}
		}
		if (dbg_img != null) {
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					dbg_width,
					dbg_height,
					true,
					"lma_data_"+debug_level);
		}
		final int macroTilesX =          tp.getTilesX()/transform_size;

		String flowXYS_title =  (enable_debug_images && (debug_level > 0))?("vectorXYS_"+scene_QuadClt.getImageName()+"-ref"+reference_QuadClt.getImageName()):null;
		
		double [][] vectorXYConfidence =  attachVectorConfidence(
				flowXY,         // final double [][] flowXY,
				macroTilesX,    // final int         width,
				best_num,       // final int         best_num,
				ref_stdev,      // final double      ref_stdev,
				flowXYS_title); // final String      debug_title);    
	
		return vectorXYConfidence; // it is also in input arguments
	}
	
	/**
	 * Recalculate optical flow vectors from the new 2D correlation results 
	 * @param currentFlowXY Previous optical flow vectors (are not modified) in image pixels. May have null-s.
	 * @param corr_vectorsXY Results of the 2D correlation.
	 * @param magic_scale 0.85 - measured argmax has a bias caused by fading of the 2D correlation away from the center.
	 * @return Updated optical flow vectors in image pixels. May have null-s.
	 */
	
	static double [][] recalculateFlowXY(
			final double [][] currentFlowXY,
			final double [][] corr_vectorsXY,
			final double      magic_scale) // 0.85 for CM
	{
		
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final double [][]   flowXY =  new double [currentFlowXY.length][];
		final int dbg_mtile = -620; // 453; // 500;
		final double rmagic_scale = 1.0/magic_scale;

		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int iMTile = ai.getAndIncrement(); iMTile < currentFlowXY.length; iMTile = ai.getAndIncrement())
						if ((currentFlowXY[iMTile] != null) && (corr_vectorsXY[iMTile] != null)){
							if (iMTile == dbg_mtile) {
								System.out.println("recalculateFlowXY(): iMTile = "+iMTile);
							}
							flowXY[iMTile]= new double[] {
									currentFlowXY[iMTile][0] + rmagic_scale * corr_vectorsXY[iMTile][0],
									currentFlowXY[iMTile][1] + rmagic_scale * corr_vectorsXY[iMTile][1]};
						}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return flowXY;
	}
	/**
	 * Recalculate optical flow vector (in image pixels)
	 * @param flowXY current per-tile vectors (null for undefined), updated
	 * @param flowXY_prev  previous flowXY (may be null for tiles) 
	 * @param corr_vectorsXY correction vector from correlation to apply
	 * @param abs_change absolute value of last coordinate change for each tile
	 * @param step_scale  multiply increment if change exceeds previous
	 * @param ignore_worsening continue even if the change exceeds previous
	 * @param magic_scale divide correlation vector (typically 0.85/8 for CM argmax) 
	 * @param min_change minimal vector coordinate difference to repeat correlations
	 * @param debug_level if > 0; print number of tiles to correlate
	 * @return flowXY vectors only for tiles to be updated or null if no tiles left
	 */
	static double [][] recalculateFlowXY(
			final double [][] flowXY, // will update
			final double [][] flowXY_prev, // previous flowXY (may be null for tiles)   
			final double [][] corr_vectorsXY,
			final double []   abs_change, // updated
			final double []   step_scale, // multiply increment if change exceeds previous
			final boolean     ignore_worsening, 
			final double      magic_scale, // 0.85 for CM
			final double      min_change,
			final int         debug_level)  
	{
		
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final double [][]   flowXY_task =  new double [flowXY.length][];
		final int dbg_mtile = (debug_level > 1)? 40 : -1; // 473; // 295; // 15/7 620; // 453; // 500;
		final double rmagic_scale = 1.0/magic_scale;
		final AtomicInteger aupdate = new AtomicInteger(0); //number of tiles to recalculate
		final double reduce_step = 0.5; //multiply step if calculated difference is larger thart the previous
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int iMTile = ai.getAndIncrement(); iMTile < flowXY.length; iMTile = ai.getAndIncrement()) {
  						if (iMTile == dbg_mtile) {
							System.out.println("recalculateFlowXY() 1: iMTile = "+iMTile);
						}
  						if (flowXY[iMTile] != null){
  							if (corr_vectorsXY[iMTile] == null) {
  								if (min_change <= 0.0) { // ==0.1
  									if (flowXY_prev[iMTile] != null) {
  										flowXY_prev[iMTile][0] = flowXY[iMTile][0];
  										flowXY_prev[iMTile][1] = flowXY[iMTile][1];
  									} else {
  										flowXY_prev[iMTile] = flowXY[iMTile].clone();
  									}
  									flowXY_task[iMTile] = flowXY[iMTile];
  									abs_change[iMTile] = Double.NaN;
  									aupdate.getAndIncrement();
  								}
  							} else { // if (corr_vectorsXY[iMTile] == null)
  								double dx = step_scale[iMTile] * rmagic_scale * corr_vectorsXY[iMTile][0];
  								double dy = step_scale[iMTile] * rmagic_scale * corr_vectorsXY[iMTile][1];
  								double new_diff = Math.sqrt(dx*dx + dy*dy);
  								
  								double last_change = abs_change[iMTile]; // may be NaN;
  								abs_change[iMTile] = new_diff;
  								
  								if ((debug_level >2) && (new_diff > last_change) && (min_change > 0.0)) {
  									System.out.println("recalculateFlowXY() 2: iMTile="+iMTile+", new_diff="+ new_diff+", last_change="+last_change);
  								}

  								
  								if ((debug_level > 1) && (iMTile == dbg_mtile))  {
//  									System.out.println(String.format("recalculateFlowXY() 2A: iMTile = %4d (%2d / %2d) flowXY = [%f/%f] step_scale = %8.6f dx = %f dy =  %f  abs= %f previous = %f, ignore_worsening = %b",
//  											iMTile, (iMTile %10), (iMTile / 10), flowXY[iMTile][0], flowXY[iMTile][1], step_scale[iMTile], dx,dy,new_diff, last_change,ignore_worsening));
  									System.out.println("recalculateFlowXY() 2A: iMTile = "+iMTile+
  											" flowXY = "+flowXY[iMTile][0]+"/"+flowXY[iMTile][0] +
  											" step_scale = "+step_scale[iMTile]+
  											"dx = "+dx+" dy =  "+dy+" abs= "+new_diff +
  											"previous = "+ last_change+" ignore_worsening ="+ignore_worsening+
  											" corr_vectorsXY[iMTile][0]="+corr_vectorsXY[iMTile][0]+" corr_vectorsXY[iMTile][1]="+corr_vectorsXY[iMTile][1]);
  								}
  								
  								if (new_diff < min_change) {
  									if (flowXY_prev[iMTile] != null) {
  										flowXY_prev[iMTile][0] = flowXY[iMTile][0];
  										flowXY_prev[iMTile][1] = flowXY[iMTile][1];
  									} else {
  										flowXY_prev[iMTile] = flowXY[iMTile].clone();
  									}
  									flowXY[iMTile][0] += dx;
  									flowXY[iMTile][1] += dy;
  	  								if ((debug_level > 1) && (iMTile == dbg_mtile))  {
  	  									System.out.println(String.format("recalculateFlowXY() 2B: iMTile = %4d (%2d / %2d) flowXY = [%f/%f]",
  	  											iMTile, (iMTile %10), (iMTile / 10), flowXY[iMTile][0], flowXY[iMTile][1]));
  	  								}
  								} else {
  									if (ignore_worsening || !(new_diff >= last_change)) { // better or ignore - continue iterations
  										//
  										if ((debug_level > 1) && (iMTile == dbg_mtile))  {
  											System.out.println(String.format("recalculateFlowXY() 3: iMTile = %4d (%2d / %2d) flowXY = [%f/%f] step_scale = %f dx = %f dy =  %f  abs= %f previous = %f CONTINUE",
  													iMTile, (iMTile %10), (iMTile / 10), flowXY[iMTile][0], flowXY[iMTile][1], step_scale[iMTile], dx,dy,new_diff, last_change));
  										}
  	  									if (flowXY_prev[iMTile] != null) {
  	  										flowXY_prev[iMTile][0] = flowXY[iMTile][0];
  	  										flowXY_prev[iMTile][1] = flowXY[iMTile][1];
  	  									} else {
  	  										flowXY_prev[iMTile] = flowXY[iMTile].clone();
  	  									}
  										flowXY[iMTile][0] += dx;
  										flowXY[iMTile][1] += dy;
  										flowXY_task[iMTile] = flowXY[iMTile]; // set to measure
  										abs_change[iMTile] = new_diff;
  										aupdate.getAndIncrement();
  	  	  								if ((debug_level > 1) && (iMTile == dbg_mtile))  {
  	  	  									System.out.println(String.format("recalculateFlowXY() 3A: iMTile = %4d (%2d / %2d) flowXY = [%f/%f]",
  	  	  											iMTile, (iMTile %10), (iMTile / 10), flowXY[iMTile][0], flowXY[iMTile][1]));
  	  	  								}
  									} else if ((new_diff >= last_change) && (min_change > 0)) { // worse - reduce step, but still apply
  	  	  								if ((debug_level > 1) && (iMTile == dbg_mtile))  {
  	  	  									System.out.println(String.format("recalculateFlowXY() 4A: iMTile = %4d (%2d / %2d) flowXY = [%f/%f]",
  	  	  											iMTile, (iMTile %10), (iMTile / 10), flowXY[iMTile][0], flowXY[iMTile][1]));
  	  	  								}
  										
   										if (debug_level > 2) {
   											System.out.println(String.format("recalculateFlowXY() 4: iMTile = %4d (%2d / %2d) flowXY = [%f/%f] step_scale = %f dx = %f dy =  %f  abs= %f previous = %f REDUCED STEP",
   													iMTile, (iMTile %40), (iMTile / 40), flowXY[iMTile][0], flowXY[iMTile][1], step_scale[iMTile], dx,dy,new_diff, last_change));
   										}
   										// do not update previous (it should be not null
  	  									if (flowXY_prev[iMTile] == null) { // should not happen
  	  										System.out.println("BUG!");
  	  										flowXY_prev[iMTile] = flowXY[iMTile].clone();
  	  									}
  	  									dx = flowXY[iMTile][0] - flowXY_prev[iMTile][0];
  	  									dy = flowXY[iMTile][1] - flowXY_prev[iMTile][1];
  	  									flowXY[iMTile][0] = flowXY_prev[iMTile][0];
  	  									flowXY[iMTile][1] = flowXY_prev[iMTile][1];
  	    								if ((debug_level > 1) && (iMTile == dbg_mtile))  {
  	    									System.out.println(String.format("recalculateFlowXY() 4B: iMTile = %4d (%2d / %2d) flowXY = [%f/%f] step_scale = %f dx = %f dy =  %f  abs= %f previous = %f",
  	    											iMTile, (iMTile %10), (iMTile / 10), flowXY[iMTile][0], flowXY[iMTile][1], step_scale[iMTile], dx,dy,new_diff, last_change));
  	    								}
  	  									
  	  									step_scale[iMTile] *= reduce_step;
   										dx *= reduce_step;
   										dy *= reduce_step; // was wrong 03/16/2022: // dx *= reduce_step;
   										
   										flowXY[iMTile][0] += dx;
   										flowXY[iMTile][1] += dy;
   		  								if ((debug_level > 1) && (iMTile == dbg_mtile))  {
   		  									System.out.println(String.format("recalculateFlowXY() 4C: iMTile = %4d (%2d / %2d) flowXY = [%f/%f] step_scale = %f dx = %f dy =  %f  abs= %f previous = %f",
   		  											iMTile, (iMTile %10), (iMTile / 10), flowXY[iMTile][0], flowXY[iMTile][1], step_scale[iMTile], dx,dy,new_diff, last_change));
   		  								}
   										flowXY_task[iMTile] = flowXY[iMTile]; // set to measure
   										abs_change[iMTile] = last_change; // restore previous step
   										aupdate.getAndIncrement();
  									}
  								}
  							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		if (debug_level > 0) {
			System.out.println("  recalculateFlowXY(): tiles to correlate: "+aupdate.get());
		}
		if (aupdate.get() > 0) {
			return flowXY_task;
		} else {
			return null; // nothing to measure left
		}
	}
	
	/**
	 * Convert 2D correlation tiles to 2D argmax using center of masses (CM) method.
	 * @param corr2d_tiles Array of 2d correlation tiles (or nulls). Each tile is typically 225
	 *        (2 * transform_size-1) * (2 * transform_size-1). 
	 * @param transform_size CLT transform size (8)
	 * @param iradius Used for argmax() center of mass window (1 - 3x3, 2 - 5x5)
	 * @param dradius Radius argmax() window radius
	 * @param refine_num For argmax() based on center of masses. In each iteration center window around previously found
	 *        fractional-pixel argmax. 
	 * @param debug_level Debug level (now > 0 print for programmed tile, can have a breakpoint).
	 * @return A per-macrotile array of {X,Y,strength} triplets. X,Y are in tiles (not image pixels), may have nulls.
	 *         Strength is a ratio of (max - average)/stdev. There is no interpolation, so strength is influenced by
	 *         a fractional part of argmax.
	 */
	public double [][] correlation2DToVectors_CM(
			final double [][] corr2d_tiles, // per 2d correlation tiles (or nulls)
			final int         transform_size,
			final int         iradius,      // half-size of the square to process 
			final double      dradius,      // weight calculation (1/(r/dradius)^2 + 1)
			final int         refine_num,   // number of iterations to apply weights around new center
			final int         debug_level)
	{
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final double [][]   vectors_xys =    new double [corr2d_tiles.length][];
		final int dbg_mtile = (debug_level > 0) ? 620 : -1; // 453; // 500;

		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int iMTile = ai.getAndIncrement(); iMTile < corr2d_tiles.length; iMTile = ai.getAndIncrement()) if (corr2d_tiles[iMTile] != null) {
  						if (iMTile == dbg_mtile) {
							System.out.println("correlation2DToVectors_CM (): iMTile = "+iMTile);
						}
						vectors_xys[iMTile] = getCorrCenterXYS_CM(
								corr2d_tiles[iMTile], // double []   corr2d_tile,
								transform_size,       // int         transform_size,
								iradius,              // int         iradius,
								dradius,              // double      dradius,
								refine_num);          // int         refine_num); 
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return vectors_xys;
	}
	
	
	/**
	 * Single-tile 2D correlation tile to 2D argmax pair using center of masses (CM) method.
	 * @param corr2d_tile A 2D correlation tile, typically of 225 elements: 
	 *        (2 * transform_size-1) * (2 * transform_size-1).
	 * @param transform_size CLT transform size (8)
	 * @param iradius Used for argmax() center of mass window (1 - 3x3, 2 - 5x5)
	 * @param dradius Radius argmax() window radius
	 * @param refine_num For argmax() based on center of masses. In each iteration center window around previously found
	 *        fractional-pixel argmax. 
	 * @return A {X,Y,strength} triplet. X,Y are in tiles (not image pixels), may have nulls.
	 *         Strength is a ratio of (max - average)/stdev. There is no interpolation, so strength is influenced by
	 *         a fractional part of argmax.
	 */
	private static double [] getCorrCenterXYS_CM(
			double []   corr2d_tile,
			int         transform_size,
			int         iradius,
			double      dradius,
			int         refine_num) // [2 * iradius + 1][2 * iradius + 1] 
	{
		// strength - (maximum - average)/stdev?
		int corr_size = 2* transform_size - 1;
		int imax = 0;
		for (int i = 1; i < corr2d_tile.length; i++) {
			if (corr2d_tile[i] > corr2d_tile[imax]) {
				imax = i;
			}
		}
		double xMax = imax % corr_size;
		double yMax = imax / corr_size;
		double k2 = 1.0/dradius*dradius;
		for (int pass = 0; pass < refine_num; pass ++) {
			int iXMax = (int) Math.floor(xMax);
			int iYMax = (int) Math.floor(yMax);
			int iY0 = iYMax - iradius;     if (iY0 < 0) iY0 = 0;
			int iY1 = iYMax + iradius + 1; if (iY1 >= corr_size) iY1 = corr_size -1;
			int iX0 = iXMax - iradius;     if (iX0 < 0) iX0 = 0;
			int iX1 = iXMax + iradius + 1; if (iX1 >= corr_size) iX1 = corr_size -1;
			double s = 0.0, sx = 0.0, sy = 0.0;
			for (int iy =  iY0; iy <= iY1; iy++) {
				double r2y = (iy - yMax)*(iy - yMax); 
				for (int ix =  iX0; ix <= iX1; ix++) {
					double d = corr2d_tile[ix + iy * corr_size];
					double r2 = r2y + (ix - xMax)*(ix - xMax);
					double w = 1.0/(k2*r2 + 1);
					double wd = w * d;
					s += wd;
					sx += wd * ix;
					sy += wd * iy;
				}
			}
			xMax = sx/s;
			yMax = sy/s;
		}
		int iYMmax = (int) Math.round(yMax); 
		int iXMmax = (int) Math.round(xMax); 
		if (iYMmax < 0)	iYMmax = 0;
		if (iYMmax >= transform_size) iYMmax = transform_size -1;
		if (iXMmax < 0)	iXMmax = 0;
		if (iXMmax >= transform_size) iXMmax = transform_size -1;

		double dMax = corr2d_tile[iYMmax * corr_size + iXMmax]; // negative
		double s1=0.0, s2 =0.0;
		for (int i = 0; i < corr2d_tile.length; i++) {
			s1 += corr2d_tile[i];
			s2 += corr2d_tile[i] * corr2d_tile[i];
		}
		double avg = s1/corr2d_tile.length;
		double sd = Math.sqrt(corr2d_tile.length * s2 - s1*s1)/corr2d_tile.length;
		double strength = (dMax - avg)/sd;
		
		return new double [] {xMax - transform_size +1, yMax - transform_size +1, strength};
	}
	
	/**
	 * 2D correlation of scene to reference tiles.
	 *  
	 * @param scene_QuadClt scene QuadCLT instance.
	 * @param reference_QuadClt reference QuadCLT instance.
	 * @param scene_tiles Scene tiles (per macrotile, per channel (disparity, strength, r, b, g)
	 *  (2*transform_size) *  (2*transform_size), currently 256. 
	 * @param reference_tiles Reference tiles, same format as scene_tiles.
	 * @param flowXY_frac Per-tile fractional X,Y offsets in the range of [-0.5, 0.5)
	 * @param chn_weights A 4-element array of the correlation weights for strength, red, blue and green channels
	 * @param corr_sigma A low-pass sigma for the 2-d correlation (in tiles)
	 * @param fat_zero 2D correlation relative "fat zero" to damp phase correlation normalization.
	 * @param late_normalize True - normalize after combining all channels, false - normalize each channel separately.
	 *        When consolidating multiple tile late_normalize is considered true. 
	 * @param combine_empty_only If true, use neighbors consolidation for undefined tiles only.
	 * @param combine_dradius  Radius for the consolidation weights half-cosine (weight is zero outside of dradius. 
	 * @param tolerance_absolute Used for consolidation only absolute tolerance to the difference between the disparity
	 *        of the central reference macrotile and the scene macrotiles to consolidate.  
	 * @param tolerance_relative Relative disparity tolerance - add a product of tolerance_relative by the average
	 *        macrotile disparity to the tolerance_absolute for disparity filtering of the macrotiles.
	 * @param debug_level Debug level.
	 * @return Per macrotile correlation tiles (now 225-long). May have nulls for the empty tiles.
	 */
	public double [][] correlate2DSceneToReference(// to match to reference
			final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt,
			final double [][][] scene_tiles,     // prepared with prepareSceneTiles()
			final double [][][] reference_tiles, // prepared with prepareReferenceTiles() + fillTilesNans(); - combine?
			final double [][] flowXY_frac, // X, YH fractional shift [-0.5,0.5) to implement with FD rotations
			final double []   chn_weights0, // absolute, starting from strength (strength,r,b,g)
			final double      corr_sigma,
			final double      fat_zero,
			final boolean     late_normalize,
			final boolean     combine_empty_only, // only use neighbor correlations for empty corr tiles (false - any)
			// reference tile should still be defined
			final double      combine_dradius, // 1 - 3x3, 2 - 5x5
			final double      tolerance_absolute, // absolute disparity half-range to consolidate tiles
			final double      tolerance_relative, // relative disparity half-range to consolidate tiles
			final int         debug_level)
	// returns per-tile 2-d correlations (15x15)
	{
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final TileProcessor tp =         reference_QuadClt.getTileProcessor();
		final int tilesX =               tp.getTilesX();
		final int tilesY =               tp.getTilesY();
		final int transform_size =       tp.getTileSize();
		final int tile_length =          transform_size * transform_size;
		final int macroTilesX =          tilesX/transform_size;
		final int macroTilesY =          tilesY/transform_size;
		final int macroTiles =           macroTilesX * macroTilesY; 
		
		final double [][]   corr_tiles =    new double [macroTiles][];
		final double [][][] corr_tiles_TD = new double [macroTiles][][];
		
		final int dbg_mtile = (debug_level >0) ? 203 : -1;
		
		final int chn_offset = QuadCLT.DSRBG_STRENGTH; // start scene_tiles, reference tiles with this 2-nd index
		int dsrbg_len = reference_QuadClt.getDSRBG().length;
		final int num_channels = (chn_weights0.length < (dsrbg_len - QuadCLT.DSRBG_STRENGTH))? chn_weights0.length: (dsrbg_len - QuadCLT.DSRBG_STRENGTH);
		final double []   chn_weights = new double  [num_channels];
		for (int i = 0; i < num_channels; i++) {
			chn_weights[i] = chn_weights0[i];
		}
		final ImageDtt image_dtt = new ImageDtt(
				numSens,
				transform_size,
				imgdtt_params, // null, // FIXME: Needs  ImageDttParameters (clt_parameters.img_dtt),
				reference_QuadClt.isAux(),
				reference_QuadClt.isMonochrome(),
				reference_QuadClt.isLwir(),
				1.0);

		final double [] filter =     image_dtt.doubleGetCltLpfFd(corr_sigma);
		final int      combine_radius = (int) Math.floor(combine_dradius); // 1 - 3x3, 2 - 5x5
		final double [][] rad_weights = new double [2 * combine_radius + 1][2 * combine_radius + 1];
		for (int dY = -combine_radius; dY <= combine_radius; dY ++) {
			for (int dX = -combine_radius; dX <= combine_radius; dX ++) {
				rad_weights[dY + combine_radius][dX + combine_radius] =
						Math.cos(0.5 * Math.PI * dY / combine_dradius) *
						Math.cos(0.5 * Math.PI * dX / combine_dradius);
			}
		}
		final double [] avg_disparity_ref =   new double [macroTiles];
		final double [] avg_disparity_scene = new double [macroTiles];
		
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					DttRad2 dtt = new DttRad2(transform_size);
					dtt.set_window(1);
					double [][][] clt_tiles_ref =   new double [num_channels][4][];
					double [][][] clt_tiles_scene = new double [num_channels][4][];
					
					Correlation2d corr2d = new Correlation2d(
							2, // numSens,// does it matter?
							transform_size,             // int transform_size,
							false,                      // boolean monochrome,
							false);                     //   boolean debug)
					
					for (int iMTile = ai.getAndIncrement(); iMTile < macroTiles; iMTile = ai.getAndIncrement()) {
						if (reference_tiles[iMTile] != null) { // to calculate average reference disparity
							double sw = 0.0, sdw=0.0;
							double [] disparity = reference_tiles[iMTile][QuadCLT.DSRBG_DISPARITY]; 
							double [] strength =  reference_tiles[iMTile][QuadCLT.DSRBG_STRENGTH]; 
							for (int i = 0; i < strength.length; i++) {
								if (!Double.isNaN(disparity[i]) && (strength[i] > 0.0)) {
									sw += strength[i];
									sdw += strength[i]*(disparity[i]);
								}
							}
							if (sw > 0.0) avg_disparity_ref[iMTile] = sdw/sw;
						}
						if (scene_tiles[iMTile] != null) { // to calculate average scene disparity
							double sw = 0.0, sdw=0.0;
							double [] disparity = scene_tiles[iMTile][QuadCLT.DSRBG_DISPARITY]; 
							double [] strength =  scene_tiles[iMTile][QuadCLT.DSRBG_STRENGTH]; 
							for (int i = 0; i < strength.length; i++) {
								if (!Double.isNaN(disparity[i]) && (strength[i] > 0.0)) {
									sw += strength[i];
									sdw += strength[i]*(disparity[i]);
								}
							}
							if (sw > 0.0) avg_disparity_scene[iMTile] = sdw/sw;
						}
						if ((scene_tiles[iMTile] != null) && (reference_tiles[iMTile] != null)) {
							if (iMTile == dbg_mtile) {
								System.out.println("correlate2DSceneToReference(): iMTile = "+iMTile);
							}
							// convert reference tile
							double [][][] fold_coeff_ref = dtt.get_shifted_fold_2d ( // get_shifted_fold_2d(
									transform_size,
									0.0,
									0.0,
									0); // debug level
							double [][][] fold_coeff_scene = dtt.get_shifted_fold_2d ( // get_shifted_fold_2d(
									transform_size,
									flowXY_frac[iMTile][0],
									flowXY_frac[iMTile][1],
									0); // debug level
							for (int chn = 0; chn < num_channels; chn++) {
								double [] tile_in_ref =   reference_tiles[iMTile][chn + chn_offset];
								double [] tile_in_scene = scene_tiles[iMTile][chn + chn_offset];
								// unfold and convert both reference and scene
								for (int dct_mode = 0; dct_mode < 4; dct_mode++) {
									clt_tiles_ref[chn][dct_mode] = dtt.fold_tile (tile_in_ref, transform_size, dct_mode, fold_coeff_ref);
									clt_tiles_ref[chn][dct_mode] = dtt.dttt_iv   (clt_tiles_ref[chn][dct_mode], dct_mode, transform_size);
									clt_tiles_scene[chn][dct_mode] = dtt.fold_tile (tile_in_scene, transform_size, dct_mode, fold_coeff_scene);
									clt_tiles_scene[chn][dct_mode] = dtt.dttt_iv   (clt_tiles_scene[chn][dct_mode], dct_mode, transform_size);
								}
								// Apply shift to scene only (reference is not shifted)
						        image_dtt.fract_shift(    // fractional shift in transform domain. Currently uses sin/cos - change to tables with 2? rotations
						        		clt_tiles_scene[chn], // double  [][]  clt_tile,
						        		flowXY_frac[iMTile][0],            // double        shiftX,
						        		flowXY_frac[iMTile][1],            // double        shiftY,
						                false); // debug);
							}
							if (late_normalize) {
								corr_tiles_TD[iMTile] = corr2d.correlateCompositeTD( // correlate, do not normalize, stay in TD
										clt_tiles_ref,   // double [][][] clt_data1,
										clt_tiles_scene, // double [][][] clt_data2,
										null,            // double []     lpf,
										1.0,             // double        scale_value, // scale correlation value
										chn_weights);    // double []     col_weights_in, // should have the same dimension as clt_data1 and clt_data2
							} else {
								corr_tiles[iMTile] = corr2d.correlateCompositeFD( // 
										clt_tiles_ref,   // double [][][] clt_data1,
										clt_tiles_scene, // double [][][] clt_data2,
										filter,          // double []     lpf,
										1.0,             // double        scale_value, // scale correlation value
										chn_weights,     // double []     col_weights_in, // should have the same dimension as clt_data1 and clt_data2
										fat_zero);       // double        fat_zero)

							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		
		if (late_normalize) {
			ai.set(0);
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						final TileNeibs tn =  new TileNeibs(macroTilesX, macroTilesY);

						double [][] corr_tile_2D = new double [4][tile_length];
						Correlation2d corr2d = new Correlation2d(
								numSens,
								transform_size,             // int transform_size,
								false,                      // boolean monochrome,
								false);                     //   boolean debug)
						// reference tile should not be null, scene = may be
						for (int iMTile = ai.getAndIncrement(); iMTile < macroTiles; iMTile = ai.getAndIncrement()) if (reference_tiles[iMTile] != null){ 
							if (true) { // !combine_empty_only  || (corr_tiles_TD[iMTile] == null)) {
								if (iMTile == dbg_mtile) {
									System.out.println("correlate2DSceneToReference() 2: iMTile = "+iMTile);
								}
								if ((combine_radius > 0) && (!combine_empty_only  || (corr_tiles_TD[iMTile] == null))) { // 
									for (int q = 0; q< 4; q++) {
										Arrays.fill(corr_tile_2D[q], 0.0);
									}
									double disp_tol = tolerance_absolute + tolerance_relative * avg_disparity_ref[iMTile];
									double sw = 0;
									int iMX = iMTile % macroTilesX;
									int iMY = iMTile / macroTilesX;
									for (int dY = -combine_radius; dY <= combine_radius; dY ++) {
										for (int dX = -combine_radius; dX <= combine_radius; dX ++) {
											int indx = tn.getIndex(iMX+dX, iMY + dY);
											if ((indx >= 0) && (corr_tiles_TD[indx] != null)) {
												if ((Math.abs(avg_disparity_scene[iMTile] - avg_disparity_ref[iMTile])) <= disp_tol) {
													double w = rad_weights[dY + combine_radius][dX + combine_radius];
													for (int q = 0; q < 4; q++) {
														for (int i = 0; i < tile_length; i++) {
															corr_tile_2D[q][i] += w * corr_tiles_TD[indx][q][i];
														}
													}
													sw+= w;
												}
											}
										}
									}
									if (sw  <= 0.0) {
										continue; // no non-null tiles around
									}
									double a = 1.0/sw;
									for (int q = 0; q < 4; q++) {
										for (int i = 0; i < tile_length; i++) {
											corr_tile_2D[q][i] *= a;
										}
									}
								} else {
									corr_tile_2D = corr_tiles_TD[iMTile]; // no need to clone, reference OK
								}
								corr2d.normalize_TD(
										corr_tile_2D,         // double [][] td,
										filter,          // double []   lpf, // or null
										fat_zero);       // double      fat_zero);

								corr_tiles[iMTile] = corr2d.convertCorrToPD(
										corr_tile_2D);     // double [][] td);
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		}
		return corr_tiles;
	}
	
	/**
	 * Get width of the macrotiles array
	 * @param reference_QuadClt scene instance
	 * @return width of a macrotile array
	 */
	public int getMacroWidth(final QuadCLT     reference_QuadClt) {
		final TileProcessor tp =         reference_QuadClt.getTileProcessor();
		return   tp.getTilesX()/tp.getTileSize();
	}
	
	/**
	 * Get triplets of {pX, pY, disparity} for each reference macrotile to use with LMA fitting
	 * @param reference_QuadClt reference scene instance
	 * @param reference_tiles reference tiles prepared with prepareReferenceTiles()
	 * @return Array of [macrotile]{pX, pY, disparity}, some macrotiles may be null
	 */
	public static double [][] getMacroPxPyDisp(
			final QuadCLT     reference_QuadClt,
			final double [][][] reference_tiles // prepared with prepareReferenceTiles() + fillTilesNans();
			)
	{
		final int         margin = 0; // 1; // extra margins over 16x16 tiles to accommodate distorted destination tiles
		final TileProcessor tp =         reference_QuadClt.getTileProcessor();
		final int tilesX =               tp.getTilesX();
		final int tilesY =               tp.getTilesY();
		final int transform_size =       tp.getTileSize();
		final int macroTilesX =          tilesX/transform_size;
		final int macroTilesY =          tilesY/transform_size;
		final int macroX0 =              (tilesX - macroTilesX * transform_size)/2; // distribute extra tiles symmetrically ==0 for 324
		final int macroY0 =              (tilesY - macroTilesY * transform_size)/2; // distribute extra tiles symmetrically (242 - 1 tile above and below)
		final double [][] pXpYD = new double [macroTilesX*macroTilesY][];
		final int fullTileSize =         2 * (transform_size + margin);
		final int fullTileLen =          fullTileSize * fullTileSize; 
		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 iMTile = ai.getAndIncrement(); iMTile < pXpYD.length; iMTile = ai.getAndIncrement()) {
						if (reference_tiles[iMTile] != null) {
							
							int mtileY = iMTile / macroTilesX; 
							int mtileX = iMTile % macroTilesX;
							double pY = transform_size * (mtileY * transform_size + macroY0 + transform_size/2);
							double pX = transform_size * (mtileX * transform_size + macroX0 + transform_size/2);
							// find average disparity
							double sw = 0.0, swd = 0.0;
							for (int iTile = 0; iTile < fullTileLen; iTile++) {
								double disparity = reference_tiles[iMTile][QuadCLT.DSRBG_DISPARITY][iTile];
								double strength =  reference_tiles[iMTile][QuadCLT.DSRBG_STRENGTH][iTile];
								if (!Double.isNaN(disparity) && (strength> 0.0)) {
									sw  += strength;
									swd += strength * disparity;
								}
							}
							if (sw > 0.0) {
								pXpYD[iMTile] = new double[] {pX, pY, swd/sw};
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return pXpYD;
	}
	/**
	 * Prepare scene tiles for correlation with the reference ones. Tiles include 5 layers: disparity,
	 * strength and 3 average color components (red, blue and green). 
	 * @param scene_xyz scene X (right),Y (up), Z (negative away form camera) in the reference camera coordinates
	 *        or null to use scene instance coordinates.
	 * @param scene_atr Scene azimuth, tilt and roll (or null to use scene instance).
	 * @param scene_QuadClt Scene QuadCLT instance.
	 * @param reference_QuadClt Reference QuadCLT instance.
	 * @param reference_tiles Reference tiles prepared for correlation.
	 * @param flowXY0 Initial offset of scene tiles (in image pixels) in x (right) and y (down) directions or null (to use all zeros).
	 * @param flowXY_frac Per macrotile residual [-0.5, 0.5) offsets that will be calculated. Should be initialized to
	 *        new double [number_of_macrotiles][] by the caller.
	 * @param tolerance_absolute Filter tiles by having disparity close to the average disparity of the corresponding reference
	 *        ones. tolerance_absolute is the absolute part of the disparity difference. 
	 * @param tolerance_relative Additional component of the disparity tolerance proportional to the reference macrotile disparity.
	 * @param occupancy Skip scene macrotiles having less remaining tiles fraction of all tiles.
	 * @param num_passes Number of Laplacian passes to fill filtered by disparity tiles. 
	 * @param max_change Break the loop of Laplacian passes if the maximal absolute value of the last pass changes falls below
	 *        this threshold.
	 * @param debug_level Debug level.
	 * @return Scene macrotiles - double array [number_of_macrotiles][number_of_channels][numer_of_tiles_per_macrotile], typically
	 *         [][5][256]
	 */
	public static double [][][] prepareSceneTiles(// to match to reference
			// null for {scene,reference}{xyz,atr} uses instances globals 
			final double []   scene_xyz,     // camera center in world coordinates
			final double []   scene_atr,     // camera orientation relative to world frame
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt,
			final double [][][] reference_tiles, // prepared with prepareReferenceTiles() + fillTilesNans();
			final double [][] flowXY, // per macro tile {mismatch in image pixels in X and Y directions
			final double [][] flowXY_frac, // should be initialized as [number of macro tiles][] - returns fractional tile shifts [-0.5, 0.5)
			final double      tolerance_absolute, // absolute disparity half-range in each tile
			final double      tolerance_relative, // relative disparity half-range in each tile
			final double      occupancy,          // fraction of remaining  tiles (<1.0)
			final int         num_passes,
			final double      max_change,
			final int         debug_level)
	{
		final double   diagonal_weight = 0.5 * Math.sqrt(2.0); // relative to ortho
		final int         margin = 0; // 1; // extra margins over 16x16 tiles to accommodate distorted destination tiles
		final TileProcessor tp =         reference_QuadClt.getTileProcessor();
		final double [][] dsrbg_scene =  scene_QuadClt.getDSRBG();
		final int tilesX =               tp.getTilesX();
		final int tilesY =               tp.getTilesY();
		final int transform_size =       tp.getTileSize();
		final int macroTilesX =          tilesX/transform_size;
		final int macroTilesY =          tilesY/transform_size;
		final int macroX0 =              (tilesX - macroTilesX * transform_size)/2; // distribute extra tiles symmetrically ==0 for 324
		final int macroY0 =              (tilesY - macroTilesY * transform_size)/2; // distribute extra tiles symmetrically (242 - 1 tile above and below)
		final double [][][] scene_tiles = new double [macroTilesX*macroTilesY][][];
		final int fullTileSize =         2 * (transform_size + margin);
		final int fullTileLen =          fullTileSize * fullTileSize; 
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final double []  hist_weights = new double [fullTileSize];
		for (int i = 0; i < transform_size; i++) {
			hist_weights[margin + transform_size/2+ i] = Math.sin(Math.PI * (i +0.5) / transform_size);
		}
		final ErsCorrection ers_reference = reference_QuadClt.getErsCorrection();
		final ErsCorrection ers_scene =     scene_QuadClt.getErsCorrection();
		ers_reference.setupERS(); // just in case - setUP using instance paRAMETERS
		ers_scene.setupERS();     // just in case - setUP using instance paRAMETERS
		final int hist_len = 8;
		double wdiag = 0.25 *diagonal_weight / (diagonal_weight + 1.0);
		double wortho = 0.25 / (diagonal_weight + 1.0);
		final double [] neibw = {wortho, wdiag, wortho, wdiag, wortho, wdiag, wortho, wdiag}; 
		final int dbg_mtile =  (debug_level > 0)? 54: -1; // 54 : -1; // 453; // 500;//250;
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					//					double [][] pXpYD = new double [fullTileLen][];
					double [][] tXtYD = new double [fullTileLen][]; // measured in tiles, not pixels (disparity - still pixels)
					for (int iMTile = ai.getAndIncrement(); iMTile < reference_tiles.length; iMTile = ai.getAndIncrement())
						if ((reference_tiles[iMTile] != null) && (flowXY[iMTile] != null)){
							if (iMTile == dbg_mtile) {
								System.out.println("prepareSceneTiles(): iMTile = "+iMTile);
							}
							int mtileY = iMTile / macroTilesX; 
							int mtileX = iMTile % macroTilesX;
							int tY0 = mtileY * transform_size + macroY0 -transform_size/2 - margin;
							int tX0 = mtileX * transform_size + macroX0 -transform_size/2 - margin;
							//						Arrays.fill(pXpYD, null);
							Arrays.fill(tXtYD, null);
							for (int iY = 0; iY < fullTileSize; iY++) {
								int tileY = tY0 + iY;
								if ((tileY >= 0) && (tileY < tilesY)) {
									for (int iX = 0; iX < fullTileSize; iX++) {
										int tileX = tX0 + iX;
										if ((tileX >= 0) && (tileX < tilesX)) {
											//										int nTile = tileX + tileY * tilesX;
											int iTile = iX + iY * fullTileSize;

											double disparity = reference_tiles[iMTile][QuadCLT.DSRBG_DISPARITY][iTile];
											if (disparity < 0) {
												disparity = 0.0; // is it needed or should it work with negative too?
											}
											double centerX = tileX * transform_size + transform_size/2; //  - shiftX;
											double centerY = tileY * transform_size + transform_size/2; //  - shiftY;
											if (!Double.isNaN(disparity)) {
												double [] xyd = ers_reference.getImageCoordinatesERS(
														scene_QuadClt, // QuadCLT cameraQuadCLT, // camera station that got image to be to be matched 
														centerX,        // double px,                // pixel coordinate X in this camera view
														centerY,        //double py,                // pixel coordinate Y in this camera view
														disparity,      // double disparity,         // this view disparity 
														true,           // boolean distortedView,    // This camera view is distorted (diff.rect), false - rectilinear
														ZERO3,          // double [] reference_xyz,  // this view position in world coordinates (typically ZERO3)
														ZERO3,          // double [] reference_atr,  // this view orientation relative to world frame  (typically ZERO3)
														true,           // boolean distortedCamera,  // camera view is distorted (false - rectilinear)
														scene_xyz,     // double [] camera_xyz,     // camera center in world coordinates
														scene_atr,     // double [] camera_atr,     // camera orientation relative to world frame
														LINE_ERR);       // double    LINE_ERR)       // threshold error in scan lines (1.0)
												// xyd[0], xyd[1] are here offset by transform_size/2 (to the center of the tile)
												if (xyd != null) {
													tXtYD[iTile] = new double [] {
															((xyd[0] + flowXY[iMTile][0])/transform_size) - 0.5, // moving 0.5 tiles back
															((xyd[1] + flowXY[iMTile][1])/transform_size) - 0.5, // moving 0.5 tiles back
															xyd[2]};
												} else {
													tXtYD[iTile] = null;
												}
											} else {
												tXtYD[iTile] = null;
											}
										}
									}
								}
							}
							if ((debug_level>1) && (iMTile == dbg_mtile)) {
								String [] dbg_titles= {"tX","tY","disp"};
								String dbg_title= "tXtYD-MX"+mtileX+"_MY"+mtileY;
								double [][] dbg_img = new double [3][fullTileLen];
								for (int nt =0; nt < fullTileLen; nt++) {
									if(tXtYD[nt] != null) {
										for (int i=0; i < dbg_img.length; i++) dbg_img[i][nt] = tXtYD[nt][i];
									} else {
										for (int i=0; i < dbg_img.length; i++) dbg_img[i][nt] = Double.NaN;
									}
								}
								ShowDoubleFloatArrays.showArrays(
										dbg_img,
										fullTileSize,
										fullTileSize,
										true,
										dbg_title,
										dbg_titles);
							}

							// Find best fractional pixel offset
							double [] hist_fx = new double [hist_len];
							double [] hist_fy = new double [hist_len];
							for (int iY = 0; iY < fullTileSize; iY++) if (hist_weights[iY] > 0.0){
								for (int iX = 0; iX < fullTileSize; iX++) if (hist_weights[iX] > 0.0){
									int iTile = iX + iY * fullTileSize;
									if (tXtYD[iTile] != null) {
										int hidx = (int) Math.floor((tXtYD[iTile][0] - Math.floor(tXtYD[iTile][0])) * hist_len);
										if (hidx < 0) hidx = 0; 
										else if (hidx >= hist_len) hidx = hist_len - 1;
										hist_fx[hidx] += hist_weights[iX];
										int hidy = (int) Math.floor((tXtYD[iTile][1] - Math.floor(tXtYD[iTile][1])) * hist_len);
										if (hidy < 0) hidy = 0; 
										else if (hidy >= hist_len) hidy = hist_len - 1;
										hist_fy[hidy] += hist_weights[iY];
									}
								}
							}
							int hist_fx_mx = 0;
							int hist_fy_mx = 0;
							for (int i = 1; i < hist_len; i++) {
								if (hist_fx[i] > hist_fx[hist_fx_mx]) hist_fx_mx = i;
								if (hist_fy[i] > hist_fy[hist_fy_mx]) hist_fy_mx = i;
							}
							double offsX = (0.5 + hist_fx_mx) / hist_len;
							double offsY = (0.5 + hist_fy_mx) / hist_len;
							double swx = 0.0, swy = 0.0, sx =  0.0, sy = 0.0;
							for (int iY = 0; iY < fullTileSize; iY++) {
								for (int iX = 0; iX < fullTileSize; iX++) {
									int iTile = iX + iY * fullTileSize;
									if (tXtYD[iTile] != null) {
										double wx = hist_weights[iX];
										double wy = hist_weights[iX];
										double dx = tXtYD[iTile][0] - offsX;
										double dy = tXtYD[iTile][1] - offsY;
										dx = dx - Math.round(dx);
										dy = dy - Math.round(dy);
										swx += wx;
										swy += wy;
										sx += wx * dx;
										sy += wy * dy;
									}
								}
							}
							offsX += sx/swx;
							offsY += sy/swy;
							// use offsX, offsY as fractional shift and for data interpolation
							if (offsX >= .5) offsX -= 1.0;
							if (offsY >= .5) offsY -= 1.0;
							//							flowXY_frac[iMTile] = new double [] {offsX, offsY};
							flowXY_frac[iMTile] = new double [] {-offsX, -offsY};
							double min_tX = Double.NaN, max_tX = Double.NaN, min_tY = Double.NaN, max_tY = Double.NaN;
							for (int iY = 0; iY < fullTileSize; iY++) {
								for (int iX = 0; iX < fullTileSize; iX++) {
									int iTile = iX + iY * fullTileSize;
									if (tXtYD[iTile] != null) {
										tXtYD[iTile][0]-=offsX;
										tXtYD[iTile][1]-=offsY;
										if (Double.isNaN(min_tX)) {
											min_tX =tXtYD[iTile][0]; 
											min_tY =tXtYD[iTile][1];
											max_tX = min_tX;
											max_tY = min_tY;
										}
										if (min_tX > tXtYD[iTile][0]) min_tX = tXtYD[iTile][0];
										if (min_tY > tXtYD[iTile][1]) min_tY = tXtYD[iTile][1];
										if (max_tX < tXtYD[iTile][0]) max_tX = tXtYD[iTile][0];
										if (max_tY < tXtYD[iTile][1]) max_tY = tXtYD[iTile][1];
									}
								}
							}
							int imin_tX = (int) Math.floor(min_tX);
							int imin_tY = (int) Math.floor(min_tY);
							int imax_tX = (int) Math.ceil (max_tX);
							int imax_tY = (int) Math.ceil (max_tY);
							// See if at least some of fits into the frame
							if ((imin_tX >= tilesX) || (imin_tY >= tilesY) || (imax_tX < 0)  || (imax_tX < 0)) {
								continue; // no overlap at all
							}
							int iwidth =  imax_tX - imin_tX + 1;
							int iheight = imax_tY - imin_tY + 1;
							////							if ((iwidth <= 0) || (iheight <= 0)) {
							if ((iwidth <= 1) || (iheight <= 1)) {
								System.out.println ("prepareSceneTiles(): iwidth ="+iwidth+", iheight ="+iheight+", min_tX="+min_tX+", imin_tY="+imin_tY+", max_tX="+max_tX+", imax_tY="+imax_tY);
								continue;
							}
							double [][] scene_slices = new double [dsrbg_scene.length][iwidth*iheight]; //OOM here
							for (int iY = 0; iY < iheight; iY++) {
								int tY = imin_tY + iY;
								if ((tY >= 0) && (tY < tilesY)) {
									for (int iX = 0; iX < iwidth; iX++) {
										int tX = imin_tX + iX;
										if ((tX >= 0) && (tX < tilesX)) {
											int iTile = iX + iY * iwidth;
											int tile = tX + tilesX * tY;
											if (dsrbg_scene[QuadCLT.DSRBG_STRENGTH][tile] > 0.0) {
												double d = dsrbg_scene[QuadCLT.DSRBG_DISPARITY][tile];
												scene_slices[QuadCLT.DSRBG_DISPARITY][iTile] = d;
												if (!Double.isNaN(d)) {
													scene_slices[QuadCLT.DSRBG_STRENGTH][iTile] = dsrbg_scene[QuadCLT.DSRBG_STRENGTH][tile];
												}
											}
										}
									}
								}
							}
							if ((debug_level>1) && (iMTile == dbg_mtile)) {
								String [] dbg_titles= scene_QuadClt.getDSRGGTitles(); //{"d","s","r","b","g"};
								String dbg_title= "before_disparity-MX"+mtileX+"_MY"+mtileY;
								ShowDoubleFloatArrays.showArrays(
										scene_slices,
										iwidth,
										iheight,
										true,
										dbg_title,
										dbg_titles);
							}
							// filter by disparity - 1 tile around rounded
							final TileNeibs tn =  new TileNeibs(iwidth, iheight);
							for (int iY = 0; iY < fullTileSize; iY++) {
								for (int iX = 0; iX < fullTileSize; iX++) {
									int iTile = iX + iY * fullTileSize;
									if (tXtYD[iTile] != null) {
										double disp_tolerance = tolerance_absolute + tXtYD[iTile][2] * tolerance_relative;
										double disp_min = tXtYD[iTile][2] - disp_tolerance;
										double disp_max = tXtYD[iTile][2] + disp_tolerance;
										int nt = tn.getIndex(
												(int) Math.round(tXtYD[iTile][0]) - imin_tX,
												(int) Math.round(tXtYD[iTile][1]) - imin_tY);
										if (nt >= 0) { // should always be
											for (int dir = 0; dir <9; dir++) {
												int nt1 = tn.getNeibIndex(nt, dir);
												if ((nt1 >= 0) && (scene_slices[QuadCLT.DSRBG_STRENGTH][nt1] > 0.0)) {
													if (    (scene_slices[QuadCLT.DSRBG_DISPARITY][nt1] < disp_min) ||
															(scene_slices[QuadCLT.DSRBG_DISPARITY][nt1] > disp_max)) {
														scene_slices[QuadCLT.DSRBG_STRENGTH][nt1] = 0.0; // disable tile
													}
												}
											}
										}
									}
								}
							}
							if ((debug_level>1) && (iMTile == dbg_mtile)) {
								String [] dbg_titles= scene_QuadClt.getDSRGGTitles(); //{"d","s","r","b","g"};
								String dbg_title= "after_disparity-MX"+mtileX+"_MY"+mtileY;
								ShowDoubleFloatArrays.showArrays(
										scene_slices,
										iwidth,
										iheight,
										true,
										dbg_title,
										dbg_titles);
							}

							// copy rest of the data (all but strength) where strength > 0
							for (int iY = 0; iY < iheight; iY++) {
								int tY = imin_tY + iY;
								if ((tY >= 0) && (tY < tilesY)) {
									for (int iX = 0; iX < iwidth; iX++) {
										int tX = imin_tX + iX;
										if ((tX >= 0) && (tX < tilesX)) {
											int iTile = iX + iY * iwidth;
											int tile = tX + tilesX * tY;
											if (scene_slices[QuadCLT.DSRBG_STRENGTH][iTile] > 0.0) {
												for (int i = 0; i < scene_slices.length; i++) if (i != QuadCLT.DSRBG_STRENGTH) {
													scene_slices[i][iTile] = dsrbg_scene[i][tile];
												}
											}
										}
									}
								}
							}
							if ((debug_level>1) && (iMTile == dbg_mtile)) {
								String [] dbg_titles=scene_QuadClt.getDSRGGTitles(); //{"d","s","r","b","g"};
								String dbg_title= "scene1-MX"+mtileX+"_MY"+mtileY;
								ShowDoubleFloatArrays.showArrays(
										scene_slices,
										iwidth,
										iheight,
										true,
										dbg_title,
										dbg_titles);
							}

							// set NAN everywhere where strength is 0 (including border tiles
							int num_dead = 0;
							for (int iTile = 0; iTile < scene_slices[0].length; iTile++) {
								if (scene_slices[QuadCLT.DSRBG_STRENGTH][iTile] <=0.0) {
									for (int i = 0; i < scene_slices.length; i++) {
										scene_slices[i][iTile] = Double.NaN;
									}
									num_dead++;
								}
							}
							if ((debug_level>1) && (iMTile == dbg_mtile)) {
								System.out.println("scene2-MX"+mtileX+"_MY"+mtileY+" num_dead="+num_dead);
								String [] dbg_titles= scene_QuadClt.getDSRGGTitles(); //{"d","s","r","b","g"};
								String dbg_title= "scene2-MX"+mtileX+"_MY"+mtileY;
								ShowDoubleFloatArrays.showArrays(
										scene_slices,
										iwidth,
										iheight,
										true,
										dbg_title,
										dbg_titles);
							}


							//center_occupancy -> all occupancy						
							double fract_active = 1.0*(scene_slices[0].length - num_dead)/scene_slices[0].length; // all tiles, not center
							if (fract_active < occupancy) {
								flowXY_frac[iMTile] = null;
								continue;
							}
							// Here need to fill NaNs, then

							tilesFillNaN(
									neibw,        // final double []   neibw,
									scene_slices, // final double [][] slices,
									num_passes,   // final int         num_passes,
									max_change,   // final double      max_change,
									iwidth);      // final int         width // got zero!

							if ((debug_level>1) && (iMTile == dbg_mtile)) {
								String [] dbg_titles= scene_QuadClt.getDSRGGTitles(); //{"d","s","r","b","g"};
								String dbg_title= "scene3NaN-MX"+mtileX+"_MY"+mtileY;
								ShowDoubleFloatArrays.showArrays(
										scene_slices,
										iwidth,
										iheight,
										true,
										dbg_title,
										dbg_titles);
							}

							// bi-linear interpolate						
							double [][] scene_mapped = new double [dsrbg_scene.length][fullTileLen];
							boolean need_nan_filter = false;;
							for (int iY = 0; iY < fullTileSize; iY++) {
								for (int iX = 0; iX < fullTileSize; iX++) {
									int iTile = iX + iY * fullTileSize;
									if (tXtYD[iTile] != null) {
										int itX = (int) Math.floor(tXtYD[iTile][0]);
										int itY = (int) Math.floor(tXtYD[iTile][1]);
										double kX = tXtYD[iTile][0]-itX;
										double kY = tXtYD[iTile][1]-itY;
										itX -= imin_tX; // relative to scene_slices = new double [dsrbg_scene.length][iwidth*iheight];
										itY -= imin_tY; // relative to scene_slices = new double [dsrbg_scene.length][iwidth*iheight];
										int indx = itX + itY * iwidth; 
										for (int i = 0; i < scene_mapped.length; i++) {
											scene_mapped[i][iTile] = 
													(1.0 - kY) * ((1.0-kX) * scene_slices[i][indx] +          kX * scene_slices[i][indx +          1])+
													(      kY) * ((1.0-kX) * scene_slices[i][indx + iwidth] + kX * scene_slices[i][indx + iwidth + 1]);
										}									
									} else {
										for (int i = 0; i < scene_mapped.length; i++) {
											scene_mapped[i][iTile] = Double.NaN; // will need to filter?
										}
										need_nan_filter=true;
									}
								}
							}
							if ((debug_level>1) && (iMTile == dbg_mtile)) {
								String [] dbg_titles= scene_QuadClt.getDSRGGTitles(); //{"d","s","r","b","g"};
								String dbg_title= "mapped-MX"+mtileX+"_MY"+mtileY;
								ShowDoubleFloatArrays.showArrays(
										scene_mapped,
										fullTileSize,
										fullTileSize,
										true,
										dbg_title,
										dbg_titles);
							}

							if (need_nan_filter) {
								tilesFillNaN(
										neibw,         // final double []   neibw,
										scene_mapped,  // final double [][] slices,
										num_passes,    // final int         num_passes,
										max_change,    // final double      max_change,
										fullTileSize); // final int         width
								if ((debug_level>1) && (iMTile == dbg_mtile)) {
									String [] dbg_titles= scene_QuadClt.getDSRGGTitles(); // {"d","s","r","b","g"};
									String dbg_title= "mappedNaN-MX"+mtileX+"_MY"+mtileY;
									ShowDoubleFloatArrays.showArrays(
											scene_mapped,
											fullTileSize,
											fullTileSize,
											true,
											dbg_title,
											dbg_titles);
								}

							}
							scene_tiles[iMTile] = scene_mapped;
						}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);

		if (debug_level > 0) {


			// show debug image
			String title =  reference_QuadClt.getImageName() + "-" + scene_QuadClt.getImageName()+"ref-scene";
			/*
			showMacroTiles(
					title,        // String title,
					scene_tiles, // double [][][] source_tiles,
					scene_QuadClt,        // final QuadCLT qthis,
					margin);      // final int     margin); // extra margins over 16x16 tiles to accommodate distorted destination tiles
			 */
			showCompareMacroTiles(
					title,        // String title,
					new double [][][][] {reference_tiles, scene_tiles}, // double [][][][] source_tiles_sets,
					scene_QuadClt,        // final QuadCLT qthis,
					margin);      // final int     margin); // extra margins over 16x16 tiles to accommodate distorted destination tiles

			String [] dbg_titles= {"dX","dY"};
			String dbg_title= "flowXY_frac"; // TODO: Visualize RMS of individual tiles fitting
			double [][] dbg_img = new double [2][macroTilesX*macroTilesY];
			for (int nt =0; nt < flowXY_frac.length; nt++) {
				if(flowXY_frac[nt] != null) {
					for (int i=0; i < dbg_img.length; i++) dbg_img[i][nt] = flowXY_frac[nt][i];
				} else {
					for (int i=0; i < dbg_img.length; i++) dbg_img[i][nt] = Double.NaN;
				}
			}
			if (debug_level > 1+0) {
				ShowDoubleFloatArrays.showArrays(
						dbg_img,
						macroTilesX,
						macroTilesY,
						true,
						dbg_title,
						dbg_titles);
			}


		}
		//		System.out.println("fillTilesNans() DONE.");
		return scene_tiles;

	}

	/**
	 * Prepare reference tiles for correlation with the scene ones. Tiles include 5 layers: disparity,
	 * strength and 3 average color components (red, blue and green). 
	 * @param qthis Reference scene QuadCLT instance.
	 * @param tolerance_absolute Filter reference macrotiles by same disparity (within a disparity range) consisting of the sum 
	 *        of absolute disparity (tolerance_absolute) and a proportional to the average disparity (tolerance_relative).  
	 * @param tolerance_relative Relative to the average disparity part of the disparity filtering.
	 * @param center_occupancy Fraction of non-null tiles in the center 8x8 area of the reference macrotiles after disparity
	 *        filtering (see tolerance_absolute,  tolerance_relative). Below this threshold - skip that macrotile.
	 * @param debug_level Debug level.
	 * @return Reference macrotiles - double array [number_of_macrotiles][number_of_channels][numer_of_tiles_per_macrotile], typically
	 *         [][5][256]
	 */
	public double [][][] prepareReferenceTiles(
			final QuadCLT     qthis,
			final double      tolerance_absolute, // absolute disparity half-range in each tile
			final double      tolerance_relative, // relative disparity half-range in each tile
			final double      center_occupancy,   // fraction of remaining  tiles in the center 8x8 area (<1.0)
			final int         debug_level)
	{
		final int margin =               0; // 1; // extra margins over 16x16 tiles to accommodate distorted destination tiles
		final TileProcessor tp =         qthis.getTileProcessor();
		final double [][] dsrbg =        qthis.getDSRBG();
		final int tilesX =               tp.getTilesX();
		final int tilesY =               tp.getTilesY();
		final int transform_size =       tp.getTileSize();
		final int macroTilesX =          tilesX/transform_size;
		final int macroTilesY =          tilesY/transform_size;
		final int macroX0 =              (tilesX - macroTilesX * transform_size)/2; // distribute extra tiles symmetrically ==0 for 324
		final int macroY0 =              (tilesY - macroTilesY * transform_size)/2; // distribute extra tiles symmetrically (242 - 1 tile above and below)
		final double [][][] source_tiles = new double [macroTilesX*macroTilesY][][];
		final int fullTileSize =         2 * (transform_size + margin);
		final int fullTileLen =          fullTileSize * fullTileSize; 
		final int min_remain_center = (int) Math.round(center_occupancy * transform_size * transform_size);
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		// indices of the center 8x8 of the full 20x20 tile (assuming margin = 4) 
		final Integer [] order_indices = new Integer [transform_size*transform_size];
		for (int i = 0; i <transform_size; i++) {
			int i1 = i + margin + transform_size/2;
			for (int j = 0; j <transform_size; j++) {
				int j1 = j + margin + transform_size/2;
				order_indices[i * transform_size + j] = i1 * fullTileSize + j1;
			}
		}
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					final double [] disparity = new double [fullTileLen];
					final double [] strength =  new double [fullTileLen];
					Integer [] local_indices = new Integer [transform_size*transform_size];
					for (int iMTile = ai.getAndIncrement(); iMTile < source_tiles.length; iMTile = ai.getAndIncrement()) {
						int mtileY = iMTile / macroTilesX; 
						int mtileX = iMTile % macroTilesX;
						int tY0 = mtileY * transform_size + macroY0 -transform_size/2 - margin;
						int tX0 = mtileX * transform_size + macroX0 -transform_size/2 - margin;
						Arrays.fill(strength,  0.0);
						Arrays.fill(disparity, 0.0); // Double.NaN);
						System.arraycopy(order_indices,0,local_indices,0,local_indices.length);
						
						for (int iY = 0; iY < fullTileSize; iY++) {
							int tileY = tY0 + iY;
							if ((tileY >= 0) && (tileY < tilesY)) {
								for (int iX = 0; iX < fullTileSize; iX++) {
									int tileX = tX0 + iX;
									if ((tileX >= 0) && (tileX < tilesX)) {
										int nTile = tileX + tileY * tilesX;
										int iTile = iX + iY * fullTileSize;
										double d = dsrbg[QuadCLT.DSRBG_DISPARITY][nTile];
										double s = dsrbg[QuadCLT.DSRBG_STRENGTH][nTile];
										if (!Double.isNaN(d) && (s > 0.0)){
											disparity[iTile] = d;
											strength[iTile]  = s;
										}
									}
								}
							}
						}
						double sw =   0.0;
						double swd =  0.0;
						int num_remain = 0;
						for (int i = 0; i < local_indices.length; i++) {
							double d = disparity[local_indices[i]];
							double s = strength[local_indices[i]];
							sw += s;
							swd += s*d;
							num_remain++;
						}
						if (num_remain < min_remain_center) {
							continue; // already too few tiles
						}
						Arrays.sort(local_indices, new Comparator<Integer>() {
						    @Override
						    public int compare(Integer lhs, Integer rhs) {
						        // -1 - less than, 1 - greater than, 0 - equal, not inverted for ascending disparity
						        //										return lhs.disparity > rhs.disparity ? -1 : (lhs.disparity < rhs.disparity ) ? 1 : 0;
//						        return disparity[lhs] < disparity[rhs] ? -1 : (disparity[lhs] > disparity[rhs] ) ? 1 : 0;
						    	// modifying to make  NaN greater than all non-NaN and equal to each other
						        return (!(disparity[lhs] >= disparity[rhs])) ? -1 : (!(disparity[lhs] <= disparity[rhs] )) ? 1 : 0;
						    }
						});
						int indx_min = 0;
						int indx_max = num_remain - 1;

						double d_low = Double.NaN;
						double d_high = Double.NaN; 

						while (true) {
							double disp_avg = swd / sw;
							double d_tol = tolerance_absolute + Math.max(disp_avg * tolerance_relative, 0.0);
							d_low =  disp_avg - d_tol;
							d_high = disp_avg + d_tol;
							// see if both min and max are within tolerance
							double d_min = disp_avg - disparity[local_indices[indx_min]];
							double d_max = disparity[local_indices[indx_max]] - disp_avg; 
							
							if ((d_min <= d_tol) && (d_max <= d_tol)) {
								break; // already OK
							}
							num_remain --;
							if (num_remain < min_remain_center) {
								break;
							}
							int indx_gone = -1;
							if (d_min > d_max) {
								indx_gone = indx_min;
								indx_min++;
							} else {
								indx_gone = indx_max;
								indx_max--;
							}
							double d = disparity[local_indices[indx_gone]];
							double s = strength[local_indices[indx_gone]];
							sw  -= s;
							swd -= d * s;
						}
						if (num_remain < min_remain_center) {
							continue; // too few remains in this tile
						}
						// now put all tiles in fullTileSize * fullTileSize that fit in disparity tolerance and positive strength
						source_tiles[iMTile] = new double[dsrbg.length][fullTileLen];
						for (int l = 0; l < dsrbg.length; l++) {
							Arrays.fill(source_tiles[iMTile][l], Double.NaN);
						}
						for (int i = 0; i < fullTileLen; i++) {
							if (!Double.isNaN(disparity[i]) && (strength[i] > 0.0) && (disparity[i] >= d_low) && (disparity[i] <= d_high)) {
								int tileY = tY0 + i / fullTileSize;
								int tileX = tX0 + i % fullTileSize;
								if ((tileY >= 0) && (tileY < tilesY) && (tileX >= 0) && (tileX < tilesX)) {
									int tile = tileY * tilesX + tileX; 
									for (int l = 0; l < dsrbg.length; l++) {
										source_tiles[iMTile][l][i] = dsrbg[l][tile];
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		if (debug_level > 0) {
			// show debug image
			String title = qthis.getImageName()+"-F"+center_occupancy+"-A"+tolerance_absolute+"-R"+tolerance_relative;
			showMacroTiles(
					title,        // String title,
					source_tiles, // double [][][] source_tiles,
					qthis,        // final QuadCLT qthis,
					margin);      // final int     margin); // extra margins over 16x16 tiles to accommodate distorted destination tiles
			
		}

		return source_tiles;
	}
	
	/**
	 * Show macrotiles as an image stack.
	 * @param title Image title to use.
	 * @param macro_tiles macrotiles array as generated by prepareSceneTiles() or prepareReferenceTiles().
	 * @param qthis Scene instance to extract dimensions
	 * @param margin Extra margin around the tiles (not used currently, always 0)
	 */
	public static void showMacroTiles(
			String title,
			double [][][] macro_tiles,
			final QuadCLT qthis,
			final int     margin) // extra margins over 16x16 tiles to accommodate distorted destination tiles
	{
		final TileProcessor tp =         qthis.getTileProcessor();
		final double [][] dsrbg =        qthis.getDSRBG();
		final int tilesX =               tp.getTilesX();
		final int tilesY =               tp.getTilesY();
		final int transform_size =       tp.getTileSize();
		final int macroTilesX =          tilesX/transform_size;
		final int macroTilesY =          tilesY/transform_size;
		final int fullTileSize =         2 * (transform_size + margin);
		
		// show debug image
		final int dbg_with =   macroTilesX * (fullTileSize +1) - 1;
		final int dbg_height = macroTilesY * (fullTileSize +1) - 1;
		final double [][] dbg_img = new double [dsrbg.length][dbg_with * dbg_height];
		for (int l = 0; l < dbg_img.length; l++) {
			Arrays.fill(dbg_img[l],  Double.NaN);
		}
		for (int mtile = 0; mtile < macro_tiles.length; mtile++) if (macro_tiles[mtile] != null){
			int mTileY = mtile / macroTilesX;
			int mTileX = mtile % macroTilesX;
			for (int iY = 0; iY < fullTileSize; iY++) {
				int tileY = (fullTileSize +1) * mTileY + iY;
				for (int iX = 0; iX < fullTileSize; iX++) {
					int tileX = (fullTileSize +1) * mTileX + iX;
					for (int l = 0; l < dbg_img.length; l++) {
						dbg_img[l][tileY * dbg_with + tileX] = macro_tiles[mtile][l][iY * fullTileSize + iX];
					}							
				}
			}
		}
		String [] dsrbg_titles = qthis.getDSRGGTitles(); //{"d", "s", "r", "b", "g"};
		ShowDoubleFloatArrays.showArrays(
				dbg_img,
				dbg_with,
				dbg_height,
				true,
				title,
				dsrbg_titles);
		
	}

	
	
	/**
	 * Calculate and display comparison stack of reference and scene images 
	 * @param suffix Add this text to the end of image name
	 * blur_reference Process and blur the reference image same as the scene one 
	 * @param camera_xyz0 scene camera offset in world coordinates
	 * @param camera_atr0 scene camera orientation  in world coordinates
	 * @param reference_QuadCLT reference scene instance
	 * @param scene_QuadCLT scene instance
	 * @param iscale upsample for interpolation
	 */
	public static void compareRefSceneTiles(
			String suffix,
			boolean blur_reference,
			double [] camera_xyz0,
			double [] camera_atr0,
			QuadCLT reference_QuadCLT,
			QuadCLT scene_QuadCLT,
			int iscale) // 8
	{
		String title =  reference_QuadCLT.getImageName()+"-"+scene_QuadCLT.image_name+suffix;
		double [][] dsrbg = transformCameraVew( // shifts previous image correctly (right)
				title,                   // final String    title,
				null, // final double [][] dsrbg_camera_in,
				camera_xyz0, // double [] camera_xyz, // camera center in world coordinates
				camera_atr0, //double [] camera_atr, // camera orientation relative to world frame
				scene_QuadCLT,       // QuadCLT   camera_QuadClt,
				reference_QuadCLT,       // reference
				iscale);
		double [][] dsrbg_ref;
		if (blur_reference) {
			dsrbg_ref = transformCameraVew( // shifts previous image correctly (right)
					title+"-reference",     // final String    title,
					null, // final double [][] dsrbg_camera_in,
					ZERO3, // camera_xyz0,  // double [] camera_xyz, // camera center in world coordinates
					ZERO3, // camera_atr0,  // double [] camera_atr, // camera orientation relative to world frame
					reference_QuadCLT,      // scene_QuadCLT,       // QuadCLT   camera_QuadClt,
					reference_QuadCLT,      // reference_QuadCLT,       // reference
					iscale);
		} else {
			dsrbg_ref= reference_QuadCLT.getDSRBG();
		}
		double [][][] pair = {dsrbg_ref,  dsrbg};
		TileProcessor tp = reference_QuadCLT.getTileProcessor();
        int tilesX = tp.getTilesX();
		int tilesY = tp.getTilesY();
		String [] dsrbg_titles = reference_QuadCLT.getDSRGGTitles(); // {"d", "s", "r", "b", "g"};

		// combine this scene with warped previous one
		String [] rtitles = new String[2* dsrbg_titles.length];
		double [][] dbg_rslt = new double [rtitles.length][];
		for (int i = 0; i < dsrbg_titles.length; i++) {
			rtitles[2*i] =    dsrbg_titles[i]+"0";
			rtitles[2*i+1] =  dsrbg_titles[i];
			dbg_rslt[2*i] =   pair[0][i];
			dbg_rslt[2*i+1] = pair[1][i];
		}
		ShowDoubleFloatArrays.showArrays(
				dbg_rslt,
				tilesX,
				tilesY,
				true,
				title,
				rtitles);
		
	}
	
	public static void compareRefSceneTiles(
			String suffix,
			boolean blur_reference,
			double [][][] scene_xyzatr, // includeS reference (last)
			double [][][] scene_ers_dt, // includeS reference (last)
			QuadCLT [] scenes,
			int iscale) // 8
	{
		int nscenes = scenes.length;
		int indx_ref = nscenes - 1;
		String title =  "previous_frames_matching"+suffix;
		double [][][] dsrbg = new double [nscenes][][];
		String [] time_stamps = new String[nscenes];
		// [0] - last scene before the reference one
		for (int i = 0; i < nscenes; i++) {
			int indx = dsrbg.length - i - 1;
			time_stamps[i] = scenes[indx].getImageName();
			System.out.println("\n"+i+ ": "+ time_stamps[i]+" compareRefSceneTiles()");
			if ((i == 0) && !blur_reference) {
				dsrbg[0]= scenes[indx_ref].getDSRBG();
			} else {
				ErsCorrection ers_scene = scenes[indx].getErsCorrection();
				double [] ers_scene_original_xyz_dt = ers_scene.getErsXYZ_dt();
				double [] ers_scene_original_atr_dt = ers_scene.getErsATR_dt();
				ers_scene.setErsDt(
						scene_ers_dt[indx][0], // double []    ers_xyz_dt,
						scene_ers_dt[indx][1]); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
				ers_scene.setupERS();
				dsrbg[i] = transformCameraVew(   // shifts previous image correctly (right) null pointer
						title,                   // final String    title,
						null, // final double [][] dsrbg_camera_in,
						scene_xyzatr[indx][0],   // double [] camera_xyz, // camera center in world coordinates
						scene_xyzatr[indx][1],   //double [] camera_atr, // camera orientation relative to world frame
						scenes[indx],            // QuadCLT   camera_QuadClt,
						scenes[indx_ref],        // reference
						iscale);
				ers_scene.setErsDt(
						ers_scene_original_xyz_dt, // double []    ers_xyz_dt,
						ers_scene_original_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
				ers_scene.setupERS();
			}
		}
		
		TileProcessor tp = scenes[indx_ref].getTileProcessor();
        int tilesX = tp.getTilesX();
		int tilesY = tp.getTilesY();
		String [] dsrbg_titles =  scenes[indx_ref].getDSRGGTitles(); // {"d", "s", "r", "b", "g"};{"d", "s", "r", "b", "g"};
		int nslices = dsrbg_titles.length;

		// combine this scene with warped previous one
		String [] rtitles = new String[nscenes * nslices];
		double [][] dbg_rslt = new double [rtitles.length][];
		for (int nslice = 0; nslice < nslices; nslice++) {
			for (int nscene = 0; nscene < nscenes; nscene++) {
				rtitles[nscenes * nslice + nscene] =    dsrbg_titles[nslice]+"-"+time_stamps[nscene];
				if (dsrbg[nscene] !=  null) {
					dbg_rslt[nscenes * nslice + nscene] =   dsrbg[nscene][nslice];
				}
			}
		}
		ShowDoubleFloatArrays.showArrays(
				dbg_rslt,
				tilesX,
				tilesY,
				true,
				title,
				rtitles);

	}
	
	
	
	/**
	 * Show macrotiles in comparison, typically reference to scene ones
	 * @param title Image stack title. 
	 * @param source_tiles_sets typically {reference_tiles, scene tiles} pair, generated by prepareReferenceTiles()
	 *        and prepareSceneTiles(), respectively.
	 * @param qthis Scene instance to extract dimensions
	 * @param margin Extra margin around the tiles (not used currently, always 0)
	 */
	public static void showCompareMacroTiles(
			String title,
			double [][][][] source_tiles_sets,
			final QuadCLT qthis,
			final int     margin) // extra margins over 16x16 tiles to accommodate distorted destination tiles
	{
		final TileProcessor tp =         qthis.getTileProcessor();
		final double [][] dsrbg =        qthis.getDSRBG();
		final int tilesX =               tp.getTilesX();
		final int tilesY =               tp.getTilesY();
		final int transform_size =       tp.getTileSize();
		final int macroTilesX =          tilesX/transform_size;
		final int macroTilesY =          tilesY/transform_size;
		final int fullTileSize =         2 * (transform_size + margin);
		
		// show debug image
		final int dbg_with =   macroTilesX * (fullTileSize +1) - 1;
		final int dbg_height = macroTilesY * (fullTileSize +1) - 1;
		final double [][] dbg_img = new double [dsrbg.length * source_tiles_sets.length][dbg_with * dbg_height];
		for (int l = 0; l < dbg_img.length; l++) {
			Arrays.fill(dbg_img[l],  Double.NaN);
		}
		String [] titles = qthis.getDSRGGTitles(); //{"d", "s", "r", "b", "g"};
		String [] dsrbg_titles = new String [titles.length * source_tiles_sets.length ]; 
		
		for (int iset = 0; iset < source_tiles_sets.length; iset ++) {
			for (int l = 0; l < titles.length; l++) {
				dsrbg_titles[l * source_tiles_sets.length + iset] = titles[l]+"-"+iset;
			}
			double [][][] source_tiles = source_tiles_sets[iset];
			for (int mtile = 0; mtile < source_tiles.length; mtile++) if (source_tiles[mtile] != null){
				int mTileY = mtile / macroTilesX;
				int mTileX = mtile % macroTilesX;
				for (int iY = 0; iY < fullTileSize; iY++) {
					int tileY = (fullTileSize +1) * mTileY + iY;
					for (int iX = 0; iX < fullTileSize; iX++) {
						int tileX = (fullTileSize +1) * mTileX + iX;
						for (int l = 0; l < titles.length; l++) {
							dbg_img[l*source_tiles_sets.length + iset][tileY * dbg_with + tileX] = source_tiles[mtile][l][iY * fullTileSize + iX];
						}							
					}
				}
			}
		}
//		String [] dsrbg_titles = {"d", "s", "r", "b", "g"};
		ShowDoubleFloatArrays.showArrays(
				dbg_img,
				dbg_with,
				dbg_height,
				true,
				title,
				dsrbg_titles);
		
	}
	
	/**
	 * Show multiple sets of 2D correlation tiles as image stack
	 * @param title Image stack title. 
	 * @param corr_tiles An array of several 2D correlation tiles sets, each containing per-macrotile
	 *        2D correlation tiles (or null), typically 255-long.
	 * @param tilesX Number of macrotiles in a row (typically 40 for 5MPix images).
	 * @param tile_width Correlation tile width (typically 15)
	 * @param tile_height  Correlation tile height (typically 15)
	 */
	public static void showCorrTiles(
			String title,
			double [][][] corr_tiles,
			int           tilesX,
			int           tile_width,
			int           tile_height)
	{
		// show debug image
		final int dbg_with =   tilesX * (tile_width +1) - 1;
		final int dbg_height = (corr_tiles[0].length / tilesX) * (tile_height +1) - 1;
		final double [][] dbg_img = new double [corr_tiles.length][dbg_with * dbg_height];
		for (int l = 0; l < dbg_img.length; l++) {
			Arrays.fill(dbg_img[l],  Double.NaN);
		}
		for (int slice = 0; slice < corr_tiles.length; slice++) {
			for (int mtile = 0; mtile < corr_tiles[slice].length; mtile++) if (corr_tiles[slice][mtile] != null){
				int mTileY = mtile / tilesX;
				int mTileX = mtile % tilesX;
				for (int iY = 0; iY < tile_height; iY++) {
					int tileY = (tile_height +1) * mTileY + iY;
					for (int iX = 0; iX < tile_width; iX++) {
						int tileX = (tile_width +1) * mTileX + iX;
						dbg_img[slice][tileY * dbg_with + tileX] = corr_tiles[slice][mtile][iY * tile_width + iX];
					}
				}
			}
		}
		ShowDoubleFloatArrays.showArrays(
				dbg_img,
				dbg_with,
				dbg_height,
				true,
				title); //	dsrbg_titles);
	}
	
	public static double [][] getSceneDisparityStrength(
			final CLTParameters       clt_parameters,
			final boolean     to_ref_disparity, // false - return scene disparity, true - convert disparity back to the reference scene
			final double []   disparity_ref,   // invalid tiles - NaN in disparity
			final double []   disparity_scene, // invalid tiles - NaN in disparity (just for masking out invalid scene tiles)
			final double []   strength_scene,  // to calculate interpolated strength
			final double []   scene_xyz, // camera center in world coordinates
			final double []   scene_atr, // camera orientation relative to world frame
			final double []   scene_ers_xyz_dt, // camera ERS linear
			final double []   scene_ers_atr_dt, // camera ERS linear
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt,
			final int         margin,
			final double      tolerance_ref_absolute,
			final double      tolerance_ref_relative,
			final double      tolerance_scene_absolute, // of 4 bi-linear interpolation corners
			final double      tolerance_scene_relative)  // of 4 bi-linear interpolation corners
	{
		TileProcessor tp = reference_QuadClt.getTileProcessor();
		final int tilesX = tp.getTilesX();
		final int tilesY = tp.getTilesY();
		final int transform_size = tp.getTileSize();
		final int tiles = tilesX*tilesY;
//		final double scene_disparity_cor = clt_parameters.imp.disparity_corr; // 04/07/2023 // 0.0;
		final double scene_disparity_cor = clt_parameters.imp.disparity_corr+ reference_QuadClt.getDispInfinityRef() ; // 12/11/2025 - added ref_scene.getDispInfinityRef()
		final boolean [] valid_tiles = new boolean[tiles];
		scene_QuadClt.getErsCorrection().setErsDt(
				scene_ers_xyz_dt, // double []    ers_xyz_dt,
				scene_ers_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
		//setupERS() will be inside transformToScenePxPyD()
		// OK to use the same reference_QuadClt for both reference_QuadClt and scene_QuadClt
		double [][] scene_pXpYD = transformToScenePxPyD( // will be null for disparity == NaN
				null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
				disparity_ref,      // 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_QuadClt,      // final QuadCLT     scene_QuadClt,
				reference_QuadClt); // final QuadCLT     reference_QuadClt)
		
		TpTask[]  tp_tasks =  GpuQuad.setInterTasks( // just to calculate valid_tiles
				scene_QuadClt.getNumSensors(),
				scene_QuadClt.getGeometryCorrection().getSensorWH()[0],
				!scene_QuadClt.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_QuadClt.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
		//FIXME:  not clear here tp_tasks was supposed to go? no
		/*
		scene_QuadClt.getGPU().setInterTasks(
				scene_pXpYD, // final double [][]         pXpYD, // per-tile array of pX,pY,disparity triplets (or nulls)
       			scene_QuadClt.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,            
    			threadsMax); // final int                 threadsMax)  // maximal number of threads to launch
		*/
		
		final double [][] disparity_strength = new double [2][tiles];
		Arrays.fill(disparity_strength[0], Double.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() {
					double [] corners =   new double [4];
					double [] strengths = new double [4];
					for (int nTile = ai.getAndIncrement(); nTile < tiles; nTile = ai.getAndIncrement()) if (valid_tiles[nTile]){
						double pX =            scene_pXpYD[nTile][0];
						double pY =            scene_pXpYD[nTile][1];
						double tX = (pX - transform_size/2)/transform_size;
						double tY = (pY - transform_size/2)/transform_size;
						double disp_from_ref = scene_pXpYD[nTile][2];
						int itX0 = (int) Math.floor(tX); 
						int itY0 = (int) Math.floor(tY);
						double kX = tX - itX0;
						double kY = tY - itY0;
						double [] corner_weights = {(1.0-kX)*(1.0-kY), kX*(1.0-kY),(1.0-kX)*kY, kX*kY};
						int itX1 = itX0 + 1;
						if (itX1 >= tilesX) {
							itX1 = tilesX - 1;
						}
						int itY1 = itY0 + 1;
						if (itY1 >= tilesY) {
							itY1 = tilesY - 1;
						}
						corners[0] =  disparity_scene[itX0 + itY0 * tilesX];
						corners[1] =  disparity_scene[itX1 + itY0 * tilesX];
						corners[2] =  disparity_scene[itX0 + itY1 * tilesX];
						corners[3] =  disparity_scene[itX1 + itY1 * tilesX];
						strengths[0] = strength_scene[itX0 + itY0 * tilesX];
						strengths[1] = strength_scene[itX1 + itY0 * tilesX];
						strengths[2] = strength_scene[itX0 + itY1 * tilesX];
						strengths[3] = strength_scene[itX1 + itY1 * tilesX];

						double disp_tol_ref = tolerance_ref_absolute + disp_from_ref * tolerance_ref_relative;
						double disp_min_ref =  disp_from_ref - disp_tol_ref; 
						double disp_max_ref =  disp_from_ref + disp_tol_ref;
						int idisp_min = 0, idisp_max = 0;
						int num_corners = 0;
						for (int i = 0; i < 4; i++) {
							if (!Double.isNaN(corners[i])) {
								if ((corners[i] < disp_min_ref) || (corners[i] > disp_max_ref)){
									corners[i] = Double.NaN;
									continue;
								}
								if (!(corners[i] >= corners[idisp_min])) {
									idisp_min = i; 
								}
								if (!(corners[i] <= corners[idisp_max])) {
									idisp_max = i; 
								}
								num_corners++;
							}
						}
						if (num_corners > 0) {
							double disp_half = 0.5* (corners[idisp_min] + corners[idisp_max]);
							double disp_range = 2 * (tolerance_scene_absolute + tolerance_ref_relative * disp_half);
							while ((num_corners > 0) && ((corners[idisp_max] - corners[idisp_min]) > disp_range )) {
								num_corners--;
								if (num_corners > 0) {
									if (disp_half > disp_from_ref) { // remove max
										corners[idisp_max] = Double.NaN;
										// find new max
										idisp_max = idisp_min;
										for (int i = 0; i < 4; i++) {
											if (!(corners[i] <= corners[idisp_max])) {
												idisp_max = i; 
											}
										}
									} else {
										corners[idisp_min] = Double.NaN;
										// find new min
										idisp_min = idisp_max;
										for (int i = 0; i < 4; i++) {
											if (!(corners[i] >= corners[idisp_min])) {
												idisp_min = i; 
											}
										}
									}
								}
							}
							if (num_corners > 0) {
								double sw=0.0, swd = 0.0, sws = 0.0;
								for (int i = 0; i < 4; i++) {
									if (!Double.isNaN(corners[i])) {
										sw += corner_weights[i];
										swd += corner_weights[i] * corners[i];
										sws += corner_weights[i] * strengths[i];
									}
								}
								disparity_strength[0][nTile] = swd / sw; 
								disparity_strength[1][nTile] = sws / sw; 
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		if (to_ref_disparity) {
			ai.set(0);
//double [][] scene_pXpYD
			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 (scene_pXpYD != null) {
							if (Double.isNaN(disparity_strength[0][nTile])) {
								scene_pXpYD[nTile] = null;
							} else { // replace scene disparity (strength will stay the same
								scene_pXpYD[nTile][2] = disparity_strength[0][nTile];
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
			double [][] toref_pXpYD = transformFromScenePxPyD( // does not look at identity scene_xyz, scene_atr 
					scene_pXpYD, // final double [][] pXpYD_scene, // tiles correspond to reference, pX,pY,D - for scene
					scene_xyz,   // final double []   scene_xyz,   // camera center in world coordinates
					scene_atr,   // final double []   scene_atr,   // camera orientation relative to world frame
					scene_QuadClt, // final QuadCLT     scene_QuadClt,
					reference_QuadClt); // final QuadCLT     reference_QuadClt)
			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 (toref_pXpYD[nTile] != null) {
								disparity_strength[0][nTile] = toref_pXpYD[nTile][2];
							} else {
								disparity_strength[0][nTile] = Double.NaN;
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		}
		return disparity_strength;
	}

	/**
	 * Transform disparity to world XYZ to test distance in meters
	 * @param disparity_ref [tilesX * tilesY] per-tile disparity
	 * @param quadClt       scene
	 * @param threadsMax    
	 * @return per tile array (with possible nulls) of X,Y,Z triplets in meters (left, up, negative distance)
	 */
	public static double [][] transformToWorldXYZ(
			final double []   disparity_ref, // invalid tiles - NaN in disparity
			final QuadCLT     quadClt, // now - may be null - for testing if scene is rotated ref
			int               threadsMax){
		return transformToWorldXYZ(
				disparity_ref, // invalid tiles - NaN in disparity
				quadClt, // now - may be null - for testing if scene is rotated ref
		        ZERO3,
		        ZERO3,
				threadsMax);
	}
	
	public static double [][] transformToWorldXYZ(
			final double []   disparity_ref, // invalid tiles - NaN in disparity
			final QuadCLT     quadClt, // now - may be null - for testing if scene is rotated ref
	        double []         reference_xyz,  // this view position in world coordinates (typically ZERO3)
	        double []         reference_atr,  // this view orientation relative to world frame  (typically ZERO3)
			int               threadsMax)
	{
		TileProcessor tp = quadClt.getTileProcessor();
		final int tilesX = tp.getTilesX();
		final int tilesY = tp.getTilesY();
		final int tiles = tilesX*tilesY;
		final int transform_size = tp.getTileSize();
		final double [][] world_xyz=           new double [tiles][];
		final ErsCorrection ersCorrection = quadClt.getErsCorrection();
		ersCorrection.setupERS();
		final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < tiles; nTile = ai.getAndIncrement()) if (!Double.isNaN(disparity_ref[nTile])) {
						double disparity = disparity_ref[nTile];
						int tileY = nTile / tilesX;  
						int tileX = nTile % tilesX;
						double centerX = tileX * transform_size + transform_size/2; //  - shiftX;
						double centerY = tileY * transform_size + transform_size/2; //  - shiftY;
						world_xyz[nTile] = ersCorrection.getWorldCoordinatesERS( // ersCorrection - reference
								centerX,        // double px,                // pixel coordinate X in the reference view
								centerY,        // double py,                // pixel coordinate Y in the reference view
								disparity,      // double disparity,         // reference disparity 
								true,           // boolean distortedView,    // This camera view is distorted (diff.rect), false - rectilinear
								reference_xyz,  // double [] reference_xyz,  // this view position in world coordinates (typically ZERO3)
								reference_atr); // double [] reference_atr,  // this view orientation relative to world frame  (typically ZERO3)
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return world_xyz;
	}

	public static double [][] transformToScenePxPyD(
			final Rectangle   full_woi_in,      // show larger than sensor WOI (or null) IN TILES
			final double []   disparity_ref,    // invalid tiles - NaN in disparity
			final double [][] scene_xyzatr,     // camera center in world coordinates, camera orientation relative to world frame
			final QuadCLT     scene_QuadClt) {
		return transformToScenePxPyD(
				full_woi_in,      // final Rectangle   full_woi_in,      // show larger than sensor WOI (or null) IN TILES
				disparity_ref,    // final double []   disparity_ref, // invalid tiles - NaN in disparity
				scene_xyzatr[0],  // final double []   scene_xyz, // camera center in world coordinates
				scene_xyzatr[1],  // final double []   scene_atr, // camera orientation relative to world frame
				scene_QuadClt,    // final QuadCLT     scene_QuadClt,
				null);            // final QuadCLT     reference_QuadClt)
	}
	
	
	public static double [][] transformToScenePxPyD(
			final Rectangle   full_woi_in,      // show larger than sensor WOI (or null) IN TILES
			final double []   disparity_ref, // invalid tiles - NaN in disparity
			final double []   scene_xyz, // camera center in world coordinates
			final double []   scene_atr, // camera orientation relative to world frame
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt) {
		return transformToScenePxPyD(
				full_woi_in,   // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
				disparity_ref, // invalid tiles - NaN in disparity
				scene_xyz,     // camera center in world coordinates
				scene_atr,     // camera orientation relative to world frame
				scene_QuadClt,
				reference_QuadClt,
				THREADS_MAX);
	}
	
	/**
	 * Calculate pX, pY, Disparity triplets for the rotated scene to match uniform grid of a virtual camera
	 * Supports reference window larger that the physical sensor to show more of the other frames with partial
	 * overlap.
	 * @param full_woi_in null or a larger reference window {width, height, left, top}
	 * @param disparity_ref_in disparity value - either full_woi size or a reference frame only (rest will be 0)  
	 * @param scene_xyz scene linear offset (in meters)
	 * @param scene_atr scene azimuth, tilt, roll offset
	 * @param scene_QuadClt to find TileProcessor for tilesX, tilesY, tileSize and ersSceneCorrection
	 * @param reference_QuadClt
	 * @param threadsMax
	 * @return pX, pY, Disparity of the other scene. pX, pY are measured from the sensor top left corner
	 */
	public static double [][] transformToScenePxPyD(
			final Rectangle   full_woi_in,      // show larger than sensor WOI (or null) IN TILES
			final double []   disparity_ref_in, // invalid tiles - NaN in disparity - should be ok, will return nulls for those tiles
			final double []   scene_xyz,        // camera center in world coordinates
			final double []   scene_atr,        // camera orientation relative to world frame
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt, // now - may be null - for testing if scene is rotated ref
			int               threadsMax)
	{
		boolean debug_ers = false; // true; // false; // true; // true; //11.01.2022
		boolean ignore_ers = false; // false;
///		TileProcessor tp = scene_QuadClt.getTileProcessor();
		final int tilesX = (full_woi_in==null) ? scene_QuadClt.getTilesX() : full_woi_in.width; // full width,including extra
		final int tilesY = (full_woi_in==null) ? scene_QuadClt.getTilesY() : full_woi_in.height;
		final int offsetX_ref = (full_woi_in==null) ? 0 : full_woi_in.x;
		final int offsetY_ref = (full_woi_in==null) ? 0 : full_woi_in.y;
		int ref_w = scene_QuadClt.getTilesX();
		int ref_h = scene_QuadClt.getTilesY();
		double [] dref = disparity_ref_in;
		if (full_woi_in!=null) {
			if ((ref_w + offsetX_ref) > tilesX) ref_w =  tilesX - offsetX_ref;
			if ((ref_h + offsetY_ref) > tilesY) ref_h =  tilesY - offsetY_ref;
			if (disparity_ref_in.length < (full_woi_in.width * full_woi_in.height)) {
				dref= new double[full_woi_in.width * full_woi_in.height];
				for (int i = 0; i < ref_h; i++) {
					System.arraycopy(
							disparity_ref_in,
							i * scene_QuadClt.getTilesX(), // not truncated
							dref,
							(i + offsetY_ref) * full_woi_in.width + offsetX_ref,
							ref_w); // may be truncated
				}
			}
		}
		final boolean ref_is_identity = 
				(scene_xyz[0]==0.0) && (scene_xyz[1]==0.0) && (scene_xyz[2]==0.0) && // java.lang.NullPointerException
				(scene_atr[0]==0.0) && (scene_atr[1]==0.0) && (scene_atr[2]==0.0);
		final double []   disparity_ref = dref;
		final int tiles = tilesX*tilesY;
		final int transform_size = scene_QuadClt.getTileSize();
		final double [][] pXpYD=               new double [tiles][];
		final ErsCorrection ersSceneCorrection =     scene_QuadClt.getErsCorrection();
		final ErsCorrection ersReferenceCorrection = (reference_QuadClt!=null)? reference_QuadClt.getErsCorrection(): ersSceneCorrection;
		if (reference_QuadClt!=null) {
			if (ignore_ers) {
				ersReferenceCorrection.setErsDt(
						ZERO3, // double []    ers_xyz_dt,
						ZERO3); // double []    ers_atr_dt);
			}
			ersReferenceCorrection.setupERS(); // just in case - setUP using instance parameters
		}
		if (ignore_ers) {
			ersSceneCorrection.setErsDt(
					ZERO3, // double []    ers_xyz_dt,
					ZERO3); // double []    ers_atr_dt);
		}
		ersSceneCorrection.setupERS();
		if (debug_ers) {
			boolean same_scene = reference_QuadClt == scene_QuadClt;
			if (reference_QuadClt != null) {
				System.out.println("reference: "+ reference_QuadClt.getImageName());
				ersReferenceCorrection.printVectors(null,null);
				
			}
			System.out.println("scene: "+ scene_QuadClt.getImageName()+(same_scene? " same scene as ref":""));
			ersSceneCorrection.printVectors(scene_xyz, scene_atr);
		}
		
		final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < tiles; nTile = ai.getAndIncrement()) if (!Double.isNaN(disparity_ref[nTile])) {// null !!!
						double disparity = disparity_ref[nTile];
						int tileY = nTile / tilesX;  
						int tileX = nTile % tilesX;
						double centerX = (tileX + 0.5 - offsetX_ref) * transform_size; //  - shiftX;
						double centerY = (tileY + 0.5 - offsetY_ref) * transform_size; //  - shiftY;
						if (disparity < 0) {
							disparity = 1.0* disparity; // 0.0;
						}
						if ((scene_QuadClt == reference_QuadClt) && (ref_is_identity)) {
							pXpYD[nTile] = new double [] {centerX, centerY, disparity};
						} else {
							pXpYD[nTile] = ersReferenceCorrection.getImageCoordinatesERS( // ersCorrection - reference
									scene_QuadClt,  // QuadCLT cameraQuadCLT, // camera station that got image to be to be matched 
									centerX,        // double px,                // pixel coordinate X in the reference view
									centerY,        // double py,                // pixel coordinate Y in the reference view
									disparity,      // double disparity,         // reference disparity 
									true,           // boolean distortedView,    // This camera view is distorted (diff.rect), false - rectilinear
									ZERO3,          // double [] reference_xyz,  // this view position in world coordinates (typically ZERO3)
									ZERO3,          // double [] reference_atr,  // this view orientation relative to world frame  (typically ZERO3)
									true,           // boolean distortedCamera,  // camera view is distorted (false - rectilinear)
									scene_xyz,      // double [] camera_xyz,     // camera center in world coordinates
									scene_atr,      // double [] camera_atr,     // camera orientation relative to world frame
									LINE_ERR);      // double    line_err)       // threshold error in scan lines (1.0)
							if (pXpYD[nTile] != null) {
								if (    (pXpYD[nTile][0] < 0.0) ||
										(pXpYD[nTile][1] < 0.0) ||
										(pXpYD[nTile][0] > ersSceneCorrection.getSensorWH()[0]) ||
										(pXpYD[nTile][1] > ersSceneCorrection.getSensorWH()[1])) {
									pXpYD[nTile] = null;
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return pXpYD;
	}
	
	/**
	 * Calculate pX, pY, Disparity triplets for the rotated scene to match non-uniform grid of a reference camera
	 * May be used in iterative reversal - uniform grid on the offset camera (real or virtual) to non-uniform grid
	 * on reference one
	 * @param reference_pXpYD_in pXpYD triplets for the reference camera (uniform or non-uniform)  
	 * @param scene_xyz scene linear offset from the reference scene in reference scene coordinates (in meters)
	 * @param scene_atr scene azimuth, tilt, roll offset relative to the reference scene
	 * @param reference_QuadClt reference scene instance
	 * @return pX, pY, Disparity of the other scene. pX, pY are measured from the sensor top left corner
	 */
	
	public static double [][] transformToScenePxPyD(
			final double [][] reference_pXpYD,   // invalid tiles - NaN in disparity. Should be no nulls, no NaN disparity
			final double []   scene_xyz,         // camera center in world (reference) coordinates
			final double []   scene_atr,         // camera orientation relative to world (reference) frame
			final QuadCLT     reference_QuadClt,
			final QuadCLT     scene_QuadClt)     
	{
		boolean debug_ers = false; // true; // false; // true; // true; //11.01.2022
		boolean ignore_ers = false; // false;
		TileProcessor tp = reference_QuadClt.getTileProcessor();
		final int tilesX = tp.getTilesX();
		final int tilesY = tp.getTilesY();
		final int tiles = tilesX*tilesY;
		final double [][] pXpYD=               new double [tiles][];
		final ErsCorrection ersReferenceCorrection = reference_QuadClt.getErsCorrection();
		if (ignore_ers) {
			ersReferenceCorrection.setErsDt(
					ZERO3, // double []    ers_xyz_dt,
					ZERO3); // double []    ers_atr_dt);
		}
		ersReferenceCorrection.setupERS(); // just in case - setUP using instance parameters
		if (debug_ers) {
				System.out.println("reference: "+ reference_QuadClt.getImageName());
				ersReferenceCorrection.printVectors(null,null);
		}
		final int scene_width =  ersReferenceCorrection.getSensorWH()[0];
		final int scene_height = ersReferenceCorrection.getSensorWH()[1];
		
		final Thread[] threads = ImageDtt.newThreadArray(ImageDtt.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 < tiles; nTile = ai.getAndIncrement()) {
						if ((reference_pXpYD[nTile] != null) && !Double.isNaN(reference_pXpYD[nTile][2])) {// null !!!
							double disparity = reference_pXpYD[nTile][2];
							double centerX = reference_pXpYD[nTile][0]; // (tileX + 0.5 - offsetX_ref) * transform_size; //  - shiftX;
							double centerY = reference_pXpYD[nTile][1]; // (tileY + 0.5 - offsetY_ref) * transform_size; //  - shiftY;
							if (disparity < 0) {
								disparity = 1.0* disparity; // 0.0;
							}
							pXpYD[nTile] = ersReferenceCorrection.getImageCoordinatesERS( // ersCorrection - reference
									// important to have scene, not reference here!
									scene_QuadClt, // scene_QuadClt,  // QuadCLT cameraQuadCLT, // camera station that got image to be to be matched 
									centerX,        // double px,                // pixel coordinate X in the reference view
									centerY,        // double py,                // pixel coordinate Y in the reference view
									disparity,      // double disparity,         // reference disparity 
									true,           // boolean distortedView,    // This camera view is distorted (diff.rect), false - rectilinear
									ZERO3,          // double [] reference_xyz,  // this view position in world coordinates (typically ZERO3)
									ZERO3,          // double [] reference_atr,  // this view orientation relative to world frame  (typically ZERO3)
									true,           // boolean distortedCamera,  // camera view is distorted (false - rectilinear)
									scene_xyz,      // double [] camera_xyz,     // camera center in world coordinates
									scene_atr,      // double [] camera_atr,     // camera orientation relative to world frame
									LINE_ERR);      // double    line_err)       // threshold error in scan lines (1.0)
							if (pXpYD[nTile] != null) {
								if (    (pXpYD[nTile][0] < 0.0) ||
										(pXpYD[nTile][1] < 0.0) ||
										(pXpYD[nTile][0] > scene_width) ||
										(pXpYD[nTile][1] > scene_height)) {
									pXpYD[nTile] = null;
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return pXpYD;
	}

	//TODO: refine inter-scene pose to accommodate refined disparity map
	/**
	 * Removing BG tiles that are not visible because of the FG ones
	 * @param tp TileProcessor instance to get image dimensions
	 * @param pXpYD Array of pX, pY, Disparity triplets for the current camera calculated from the reference 3D model
	 * @param max_overlap maximal area overlap (for the full 16x16 image tiles) that allows the BG tile to be kept
	 * @param pXpYD_cam optional array of this camera disparity map to "cast shadows" from the objects that are not visible
	 * in the reference (accurate) 3D model TODO: pre-filter to remove those that should be visible in pXpYD? At least remove
	 * low-confidence triplets.
	 * @param min_adisp_cam minimal absolute disparity difference for pXpYD_cam to consider
	 * @param min_rdisp_cam minimal relative disparity difference for pXpYD_cam to consider
	 * @param debug_level debug level
	 * @return copy of pXpYD with occluded elements nulled
	 */
	
	public static double [][] filterBG (
			final TileProcessor tp,
			final double [][] pXpYD,
			final double max_overlap,
//			final double [][] pXpYD_cam,
			final double []   disparity_cam,
//			final double min_str_cam,
			final double min_adisp_cam,
			final double min_rdisp_cam,
			final int    dbg_tileX,
			final int    dbg_tileY,
			final int    debug_level
			){
		final int tilesX = tp.getTilesX();
		final int tilesY = tp.getTilesY();
		final int dbg_nTile = -(dbg_tileY * tilesX + dbg_tileX);
		final int tileSize = tp.getTileSize();
//		final double tileSize2 = tileSize * 2;
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final int tiles = tilesX*tilesY;
		final TileNeibs tn =  new TileNeibs(tilesX, tilesY);
		ArrayList<List<Integer>> fg_bg_list = new ArrayList<List<Integer>> (tiles);
		for (int i = 0; i < tiles; i++) {
			fg_bg_list.add(Collections.synchronizedList(new ArrayList<Integer>()));
		}
		final int offs_range = 1; // (max_overlap < 0.5) ? 2 : 1; 
		final double[][] pXpYD_filtered = pXpYD.clone();
		final AtomicInteger ai_num_tiles = new AtomicInteger(0);
		final AtomicInteger ai_num_removed = new AtomicInteger(0);
		final int overlap_radius = 4; // 9x9
		final int overlap_diameter = 2 * overlap_radius + 1; // 9
		final int overlap_size =  overlap_diameter * overlap_diameter; // 81
		final double scale_dist = 2.00 * overlap_radius / tileSize; // 1.0 for overlap_radius==4

		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int indx = ai.getAndIncrement(); indx < pXpYD.length; indx = ai.getAndIncrement()) if (pXpYD[indx] != null) {
						int tx = (int)Math.round(pXpYD[indx][0]/tileSize);
						int ty = (int)Math.round(pXpYD[indx][1]/tileSize);
						if ((debug_level > 0) && (indx == dbg_nTile)) { //(tx == dbg_tileX) && (ty == dbg_tileY)) {
							System.out.println("filterBG(): tx = "+tx+", ty="+ty+", indx="+indx);
							System.out.print("");
						}
						if ((tx >=0) && (ty >=0) && (tx < tilesX) && (ty < tilesY)) {
							int nTile = ty * tilesX + tx;
							synchronized(fg_bg_list.get(nTile)) {
								fg_bg_list.get(nTile).add(indx);
							}
							ai_num_tiles.getAndIncrement();
						} else {
							pXpYD_filtered[indx] = null;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		// filter by the reference model
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					boolean [] overlap_staging = new boolean [overlap_size];
					for (int nTile = ai.getAndIncrement(); nTile < tiles; nTile = ai.getAndIncrement()) if (fg_bg_list.get(nTile).size() > 0) {
						for (int tindx_bg: fg_bg_list.get(nTile)) {
							double [] txyd_bg = pXpYD[tindx_bg];
// 		final int offs_range = (max_overlap < 0.5) ? 2 : 1;
							if ((debug_level > -1) && (tindx_bg == dbg_nTile)) {
								System.out.println("filterBG(): tindx_bg="+tindx_bg+", nTile = "+nTile+", txyd_bg[0]="+txyd_bg[0]+", txyd_bg[1]="+txyd_bg[1]+", txyd_bg[2]="+txyd_bg[2]);
								System.out.print("");
							}
							Arrays.fill(overlap_staging, false);
							boolean some_overlap = false;
							for (int dty = -offs_range; dty <= offs_range; dty++) {
								for (int dtx = -offs_range; dtx <= offs_range; dtx++) {
									int nTile_fg = tn.getNeibIndex(nTile, dtx, dty);
									if (nTile_fg >= 0) 	for (int tindx_fg: fg_bg_list.get(nTile_fg)) if (tindx_fg != tindx_bg){
										double [] txyd_fg = pXpYD[tindx_fg];
										// check if FG is closer than BG (here does not have to be significantly closer
										if (txyd_fg[2] > txyd_bg[2]) {
											// see if there is any overlap
											double x_fg_bg = txyd_fg[0] - txyd_bg[0];
											double y_fg_bg = txyd_fg[1] - txyd_bg[1];
											if ((Math.abs(x_fg_bg) < tileSize) && (Math.abs(y_fg_bg) < tileSize)) {
												applyOverlap(
														overlap_staging,          // boolean [] staging,
														overlap_diameter,         // int        overlap_diameter,
														scale_dist,               // double     scale_dist,
														x_fg_bg,  // double     dx,
														y_fg_bg); // double     dy
												some_overlap = true;
											}
										}
									}
								}
							}
							// apply camera disparity map
							if (disparity_cam != null) {
								double ddisp = min_adisp_cam +  txyd_bg[2] * min_rdisp_cam;
								int tx = (int) Math.round(txyd_bg[0]/tileSize);
								int ty = (int) Math.round(txyd_bg[1]/tileSize);
								// Limit to 0.. max?
								int nTile_fg_center = ty * tilesX + tx;
								for (int dty = -offs_range; dty <= offs_range; dty++) {
									for (int dtx = -offs_range; dtx <= offs_range; dtx++) {
										int nTile_fg = tn.getNeibIndex(nTile_fg_center, dtx, dty);
										if ((nTile_fg >= 0) && (disparity_cam[nTile_fg] - txyd_bg[2] > ddisp)){
											double x_fg_bg = (tx + dtx + 0.5) * tileSize - txyd_bg[0];
											double y_fg_bg = (ty + dty + 0.5) * tileSize - txyd_bg[1];
											if ((Math.abs(x_fg_bg) < tileSize) && (Math.abs(y_fg_bg) < tileSize)) {
												applyOverlap(
														overlap_staging,          // boolean [] staging,
														overlap_diameter,         // int        overlap_diameter,
														scale_dist,               // double     scale_dist,
														x_fg_bg,  // double     dx,
														y_fg_bg); // double     dy
												some_overlap = true;
											}
										}
									}
								}
							}
							if (some_overlap) { // count actual overlap
								int nuv_overlap = 0;
								for (boolean staging_point: overlap_staging) {
									if (staging_point) {
										nuv_overlap++;
									}
								}
								double frac_overlap = 1.0 * nuv_overlap / overlap_staging.length;
								if (frac_overlap> max_overlap) {
									ai_num_removed.getAndIncrement();
									pXpYD_filtered[tindx_bg] = null; // OK that it still remains in the lists
								}
							}


							/*
											double overlapX = Math.max(tileSize2 - Math.abs(txyd_fg[0] - txyd_bg[0]), 0)/tileSize2;
											double overlapY = Math.max(tileSize2 - Math.abs(txyd_fg[1] - txyd_bg[1]), 0)/tileSize2;
											if ((overlapX * overlapY) > max_overlap) { // remove BG tile
												pXpYD_filtered[tindx_bg] = null; // OK that it still remains in the lists
												ai_num_removed_ref.getAndIncrement();
												if ((debug_level > -1) && (nTile==dbg_nTile)) {
													System.out.println("+++++++++++++++ filterBG(): nTile = "+nTile+
															", txyd_bg[0]="+txyd_bg[0]+", txyd_bg[1]="+txyd_bg[1]+", txyd_bg[2]="+txyd_bg[2]+
															", txyd_fg[0]="+txyd_fg[0]+", txyd_fg[1]="+txyd_fg[1]+", txyd_fg[2]="+txyd_fg[2]);
													System.out.print("");
												}
											}
							 */

						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		if (debug_level > -1){
			System.out.println("filterBG(): num_all_tiles = "+ai_num_tiles.get()+
					", num_removed="+ ai_num_removed.get()+
					", remaining tiles="+(ai_num_tiles.get() - ai_num_removed.get()));
			System.out.print("");
		}

		return pXpYD_filtered;
	}

	
	private static void applyOverlap(
			boolean [] staging,
			int        overlap_diameter,
			double     scale_dist,
			double     dx,
			double     dy
			) { 
		int ix0 = (int) Math.round(dx * scale_dist);
		int ix1 = ix0 + overlap_diameter;
		int iy0 = (int) Math.round(dy * scale_dist);
		int iy1 = iy0 + overlap_diameter;
		if (ix0 < 0) ix0 = 0;
		if (ix1 > overlap_diameter) ix1 = overlap_diameter;
		if (iy0 < 0) iy0 = 0;
		if (iy1 > overlap_diameter) iy1 = overlap_diameter;
		for (int iy = iy0; iy < iy1; iy++) {
			int line_start = iy * overlap_diameter;
			Arrays.fill(staging,  line_start + ix0, line_start + ix1, true);
		}
	}

	public static double [][] transformFromScenePxPyD(
			final double [][] pXpYD_scene, // tiles correspond to reference, pX,pY,D - for scene
			final double []   scene_xyz,   // camera center in world coordinates
			final double []   scene_atr,   // camera orientation relative to world frame
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt)
	{
		TileProcessor tp = reference_QuadClt.getTileProcessor();
		final int tilesX = tp.getTilesX();
		final int tilesY = tp.getTilesY();
		final int tiles = tilesX*tilesY;
		final double [][] pXpYD=               new double [tiles][];
		final ErsCorrection ersReferenceCorrection = reference_QuadClt.getErsCorrection();
		final ErsCorrection ersSceneCorrection =     scene_QuadClt.getErsCorrection();
		ersReferenceCorrection.setupERS(); // just in case - setUP using instance paRAMETERS
		ersSceneCorrection.setupERS();
		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 < tiles; nTile = ai.getAndIncrement()) if (pXpYD_scene[nTile] != null) {
						double centerX = pXpYD_scene[nTile][0]; //  - shiftX;
						double centerY = pXpYD_scene[nTile][1]; //  - shiftX;
						double disparity = pXpYD_scene[nTile][2];
						if (disparity < 0) {
							disparity = 0.0;
						}
						if (scene_QuadClt == reference_QuadClt) {
							pXpYD[nTile] = new double [] {centerX, centerY, disparity};
						} else {
							pXpYD[nTile] = ersSceneCorrection.getImageCoordinatesERS( // ersCorrection - reference
									reference_QuadClt, // QuadCLT cameraQuadCLT, // camera station that got image to be to be matched 
									centerX,           // double px,                // pixel coordinate X in the reference view
									centerY,           // double py,                // pixel coordinate Y in the reference view
									disparity,         // double disparity,         // reference disparity 
									true,              // boolean distortedView,    // This camera view is distorted (diff.rect), false - rectilinear
									scene_xyz,         // double [] reference_xyz,  // this view position in world coordinates (typically ZERO3)
									scene_atr,         // double [] reference_atr,  // this view orientation relative to world frame  (typically ZERO3)
									true,              // boolean distortedCamera,  // camera view is distorted (false - rectilinear)
									ZERO3,             // double [] camera_xyz,     // camera center in world coordinates
									ZERO3,             // double [] camera_atr,     // camera orientation relative to world frame
									LINE_ERR);         // double    line_err)       // threshold error in scan lines (1.0)
							if (pXpYD[nTile] != null) {
								if (    (pXpYD[nTile][0] < 0.0) ||
										(pXpYD[nTile][1] < 0.0) ||
										(pXpYD[nTile][0] > ersReferenceCorrection.getSensorWH()[0]) ||
										(pXpYD[nTile][1] > ersReferenceCorrection.getSensorWH()[1])) {
									pXpYD[nTile] = null;
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return pXpYD; // Only disparity matters, pX, py - just for testing
	}
	
	
	
	/**
	 * Transform scene view to visually match with a reference scene. It is not accurate as it uses resampling and
	 * related low pass filtering.
	 * @param title image title to print
	 * @param dsrbg_camera_in - null (old compatibility) or [variable_length][tiles] array of disparity, strength, ... for the camera tiles 
	 * @param scene_xyz Scene X (right),Y (up), Z (negative away form camera) in the reference camera coordinates
	 *        or null to use scene instance coordinates.
	 * @param scene_atr Scene azimuth, tilt and roll (or null to use scene instance).
	 * @param scene_QuadClt Scene QuadCLT instance.
	 * @param reference_QuadClt Reference QuadCLT instance.
	 * @param iscale interpolation scale (use finer grid), typically 8
	 * @return Per-tile array of resampled {disparity,strength,red,blue,green} values (or nulls).
	 */
	public static double [][] transformCameraVew(
			final String    title,
			final double [][] dsrbg_camera_in,
			final double [] scene_xyz, // camera center in world coordinates (in the reference camera coordinates)
			final double [] scene_atr, // camera orientation relative to world frame (in the reference camera coordinates)
			final QuadCLT   scene_QuadClt,
			final QuadCLT   reference_QuadClt,
			final int       iscale)
	{
		boolean debug = (title != null) && (title.length() > 0);
		final double line_error = 0.5;
		TileProcessor tp = reference_QuadClt.getTileProcessor();
		final int tilesX = tp.getTilesX();
		final int tilesY = tp.getTilesY();
		final int tiles = tilesX*tilesY;
		final int transform_size = tp.getTileSize();
///		final int rel_num_passes = 10;
///		final int num_passes =    transform_size; // * 2;

		final int stilesX = iscale*tilesX; 
		final int stilesY = iscale*tilesY;
		final int stiles = stilesX*stilesY;
		final double sigma = 0.5 * iscale; // was 0.5
		final double scale =  1.0 * iscale/transform_size;
		final double [][] dsrbg_camera = (dsrbg_camera_in == null) ? scene_QuadClt.getDSRBG() : dsrbg_camera_in;
		if (dsrbg_camera == null) {
			return null;
		}
		final double [][] ds =        new double [dsrbg_camera.length][stiles]; // null pointer
		for (int i = 0; i <ds.length; i++) {
			for (int j = 0; j <ds[i].length; j++) {
				ds[i][j] = Double.NaN;
			}
		}
		
		final ErsCorrection ersReferenceCorrection = reference_QuadClt.getErsCorrection();
		final ErsCorrection ersSceneCorrection =     scene_QuadClt.getErsCorrection();
		ersReferenceCorrection.setupERS(); // just in case - setUP using instance paRAMETERS
		ersSceneCorrection.setupERS();
		if (debug) {
			System.out.println("\ntransformCameraVew(): transformCameraVew(): >> "+title +" <<");
			System.out.println("transformCameraVew(): Reference scene ("+reference_QuadClt.getImageName()+"):");
			ersReferenceCorrection.printVectors(null, null);
			System.out.println("transformCameraVew(): Target scene ("+scene_QuadClt.getImageName()+"):");
			ersSceneCorrection.printVectors    (scene_xyz, scene_atr);
		}
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		DoubleAccumulator [] azbuffer = new DoubleAccumulator[tiles];
		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()) {
						azbuffer[nTile] = new DoubleAccumulator (Double::max, Double.NEGATIVE_INFINITY);
					}
				}
			};
		}		      
		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 ((dsrbg_camera[QuadCLT.DSRBG_STRENGTH] == null) || (dsrbg_camera[QuadCLT.DSRBG_STRENGTH][nTile] > 0.0)) { // null pointer
						double disparity = dsrbg_camera[QuadCLT.DSRBG_DISPARITY][nTile];
						if (!Double.isNaN(disparity)) {
							int tileY = nTile / tilesX;  
							int tileX = nTile % tilesX;
							double centerX = tileX * transform_size + transform_size/2; //  - shiftX;
							double centerY = tileY * transform_size + transform_size/2; //  - shiftY;
							if (disparity < 0) {
								disparity = 0.0;
							}
							double [] pXpYD = ersSceneCorrection.getImageCoordinatesERS( // ersCorrection - reference
									reference_QuadClt, // QuadCLT cameraQuadCLT, // camera station that got image to be to be matched 
									centerX,        // double px,                // pixel coordinate X in the reference view
									centerY,        // double py,                // pixel coordinate Y in the reference view
									disparity,      // double disparity,         // reference disparity 
									true,           // boolean distortedView,    // This camera view is distorted (diff.rect), false - rectilinear
									scene_xyz,      // double [] reference_xyz,  // this view position in world coordinates (typically ZERO3)
									scene_atr,      // double [] reference_atr,  // this view orientation relative to world frame  (typically ZERO3)
									true,           // boolean distortedCamera,  // camera view is distorted (false - rectilinear)
									ZERO3,          // double [] camera_xyz,     // camera center in world coordinates
									ZERO3,          // double [] camera_atr,     // camera orientation relative to world frame
									line_error); // LINE_ERR);      // double    line_err)       // threshold error in scan lines (1.0)
							if (pXpYD != null) {
								int px = (int) Math.round(pXpYD[0]/transform_size);
								int py = (int) Math.round(pXpYD[1]/transform_size);
								int spx = (int) Math.round(pXpYD[0]*scale);
								int spy = (int) Math.round(pXpYD[1]*scale);
								if ((px >= 0) && (py >= 0) && (px < tilesX) & (py < tilesY)) {
									double d = pXpYD[2];
									azbuffer[nTile].accumulate(pXpYD[2]);
									//Z-buffer
									if (!(d < azbuffer[nTile].get())) {
										if ((spx >= 0) && (spy >= 0) && (spx < stilesX) & (spy < stilesY)) {
											int sTile = spx + spy* stilesX;
											ds[QuadCLT.DSRBG_DISPARITY][sTile] = d; // pXpYD[2]; //reduce*
											for (int i = QuadCLT.DSRBG_STRENGTH; i < dsrbg_camera.length; i++) {
												ds[i][sTile] = (dsrbg_camera[i] != null) ?dsrbg_camera[i][nTile]:0.001; // reduce * 
											}
										}								
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		if (debug) {
			ShowDoubleFloatArrays.showArrays(
				ds,
				stilesX,
				stilesY,
				true,
				"ds-0",
				new String[] {"D","S"});
		}
		
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int i = ai.getAndIncrement(); i < ds.length; i = ai.getAndIncrement()) {
						ds[i] = (new DoubleGaussianBlur()).blurWithNaN(
								ds[i], // double[] pixels,
								null,  // double [] in_weight, // or null
								stilesX, // int width,
								stilesY, // int height,
								sigma, // double sigmaX,
								sigma, // double sigmaY,
								0.01); // double accuracy);
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		if (debug) {
			ShowDoubleFloatArrays.showArrays(
					ds,
					stilesX,
					stilesY,
					true,
					"ds-1",
					new String[] {"D","S"});
		}
		
		final double [][] dsrbg_out = new double [dsrbg_camera.length][tiles];
		final int [][] num_non_nan = new int [dsrbg_out.length] [tiles];
		
		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 (dsrbg_camera[QuadCLT.DSRBG_STRENGTH][nTile] > 0.0) {
						int tileY = nTile / tilesX;  
						int tileX = nTile % tilesX;
						int tile =  tileX +  tileY *  tilesX;
						int stileY0 = tileY * iscale;
						int stileY1 = stileY0 + iscale; 
						int stileX0 = tileX * iscale;
						int stileX1 = stileX0 + iscale; 
						for (int stileY = stileY0; stileY < stileY1; stileY++) {
							for (int stileX = stileX0; stileX < stileX1; stileX++) {
								int stile = stileX + stileY * stilesX;
								for (int i = 0; i < dsrbg_out.length; i++) {
									double d = ds[i][stile];
									if (!Double.isNaN(d)) {
										num_non_nan[i][tile] ++;
										dsrbg_out[i][tile] += d;
									}	
								}
							}							
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		if (debug) {
			ShowDoubleFloatArrays.showArrays(
					dsrbg_out,
					tilesX,
					tilesY,
					true,
					"dsrbg_out-0");
		}
		
		
		for (int i = 0; i < dsrbg_out.length; i++) {
			for (int j = 0; j < tiles; j++) {
				if (num_non_nan[i][j] == 0) {
					dsrbg_out[i][j] = Double.NaN;
				} else {
					dsrbg_out[i][j]/=num_non_nan[i][j];
				}
			}
		}
		if (debug) {
			ShowDoubleFloatArrays.showArrays(
					dsrbg_out,
					tilesX,
					tilesY,
					true,
					"dsrbg_out-1");
		}
		return dsrbg_out;
	}

	@Deprecated
	public void adjustPairsDualPass(
			CLTParameters  clt_parameters,			
			double k_prev, 
			QuadCLT [] scenes, // ordered by increasing timestamps
			int debug_level
			)
	{
		double scale_two_omegas = 2.00; // ers angular velocities contain double omegas
		
		// Maintain pose differences and velocities, they are saved after pass2
		double [][][] scenes_xyzatr = new double [scenes.length][][]; // previous scene relative to the next one  
		double [][][] ers_xyzatr =    new double [scenes.length][][]; // previous scene relative to the next one  
		
		// for scanning around
		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}};
		
		boolean pattern_mode = clt_parameters.ofp.pattern_mode;  //  true; // not yet used
		double max_rms_maybe = clt_parameters.ofp.max_rms_maybe; //  7.0;
		double max_rms_sure =  clt_parameters.ofp.max_rms_sure;  //  2.1;
		int pix_step =         clt_parameters.ofp.pix_step;      // 20;
		int search_rad =       clt_parameters.ofp.search_rad;    //  2; // 0;
		boolean next_predict = true; // Do not use macrotiles for 3-rd and next scenes - assume linear motion 
		boolean use_second_pass = false; // Probably not needed - instead just set velocities from neighbors
		
		// pass I -  adjust pairs using angular and linear velocities from intrascene ERS.
		for (int i = 1; i < scenes.length; i++) {
			QuadCLT reference_QuadClt = scenes[i];
			QuadCLT scene_QuadClt = scenes[i - 1];
			double [][] pose = new double [2][3];
			if (!clt_parameters.ofp.ignore_ers) { // ignore pose (all 0 when ERS is not available)
				pose = 	getPoseFromErs(  
						k_prev,
						reference_QuadClt,
						scene_QuadClt,
						debug_level);
			}
			reference_QuadClt.getErsCorrection().setupERSfromExtrinsics();
			scene_QuadClt.getErsCorrection().setupERSfromExtrinsics();
			double [] rms2 = new double[2];
			if ((i <= 1) || !next_predict) {
				// for scanning around
				double angle_per_step = reference_QuadClt.getGeometryCorrection().getCorrVector().getTiltAzPerPixel() * pix_step;
				double [] rmses = new double [(2*search_rad+1)*(2*search_rad+1)];
				Arrays.fill(rmses, max_rms_maybe+ 1.0); // undefined - set larger than worst
				double [][] atrs = new double [rmses.length][];
				int rad = 0, dir=0, n=0;
				int ntry = 0;
				int num_good=0;
				try_around:
					for (rad = 0; 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 (debug_level > -2) {
									System.out.println("interPairsLMA(): trying adjustPairsLMA() with initial offset azimuth: "+
											atr[0]+", tilt ="+atr[1]);
								}
								//						double [][] new_pose =
								scenes_xyzatr[i] = adjustPairsLMA(
										clt_parameters,     // CLTParameters  clt_parameters,			
										reference_QuadClt, // QuadCLT reference_QuadCLT,
										scene_QuadClt, // QuadCLT scene_QuadCLT,
										pose[0], // xyz
										atr, // atr
										clt_parameters.ilp.ilma_lma_select,             // final boolean[]   param_select,
										clt_parameters.ilp.ilma_regularization_weights, //  final double []   param_regweights,
										rms2, // 			double []      rms, // null or double [2]
										null, // double [][]    dbg_img,
										max_rms_maybe, // double         max_rms,
										clt_parameters.ofp.debug_level_optical); // int debug_level)
								if ((scenes_xyzatr[i] != null) & !Double.isNaN(rms2[0])) {
									atrs[ntry] = scenes_xyzatr[i][1].clone();
									rmses[ntry] =  rms2[0];
									num_good++;
									if (rms2[0] <= max_rms_sure) {
										break try_around;
									}
								}
								ntry++;
							}
						}
					}
				if (ntry >= rmses.length) { // otherwise all is done already, last atr is set to scene
					if (debug_level>-3) {
						System.out.println("interPairsLMA(): left "+ntry+" candidates");
					}
					// rerun with the best atr
					int best_indx = 0;
					for (int j = 1; j < rmses.length; j++) {
						if (rmses[j] < rmses[best_indx]) {
							best_indx = j;
						}
					}
					if (debug_level > -2) {
						System.out.println("---- interPairsLMA(): re-trying adjustPairsLMA() with initial offset azimuth: "+
								atrs[best_indx][0]+", tilt ="+atrs[best_indx][1]+" best_indx="+best_indx); /// null pointer
					}

					scenes_xyzatr[i] = adjustPairsLMA(
							clt_parameters, // CLTParameters  clt_parameters,
							// FIXME: *********** update getPoseFromErs to use QUADCLTCPU ! **********				
							reference_QuadClt, // QuadCLT reference_QuadCLT,
							scene_QuadClt, // QuadCLT scene_QuadCLT,
							pose[0], // xyz
							atrs[best_indx], // pose[1], // atr
							clt_parameters.ilp.ilma_lma_select,             // final boolean[]   param_select,
							clt_parameters.ilp.ilma_regularization_weights, //  final double []   param_regweights,
							rms2, // 			double []      rms, // null or double [2]
							null, // double [][]    dbg_img,
							max_rms_maybe, // double         max_rms,
							clt_parameters.ofp.debug_level_optical); // 1); // -1); // int debug_level);
					if (debug_level > -1) {
						System.out.println("Pass 1 MACRO scene "+i+" (of "+ scenes.length+") "+
								reference_QuadClt.getImageName() + "/" + scene_QuadClt.getImageName()+(" Done.\n"));
					}
					if (num_good == 0) {
						System.out.println("****** Error ! Could not find any good match in a pair of consecutive scenes ! *****");
					}
				}

			} else {
				double dt_prev = scenes[i - 1].getTimeStamp() - scenes[i - 2].getTimeStamp();
				double dt_this = scenes[i - 0].getTimeStamp() - scenes[i - 1].getTimeStamp();
				double tscale = dt_this / dt_prev;
				scenes_xyzatr[i] = new double [2][3];
				for (int t = 0; t < scenes_xyzatr[i].length; t++) {
					for (int j = 0; j < scenes_xyzatr[i][t].length; j++) {
						scenes_xyzatr[i][t][j] = tscale * scenes_xyzatr[i-1][t][j];
					}
				}
			}

			scenes_xyzatr[i] = Interscene.adjustPairsLMAInterscene(
					clt_parameters,                                 // CLTParameters  clt_parameters,
				    clt_parameters.imp.use_lma_dsi,
					false,               //	boolean        fpn_disable,   // disable fpn filter if images are known to be too close
					false,               // boolean        disable_ers,
					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 reference_QuadCLT,
					null, // double []        ref_disparity, // null or alternative reference disparity
					null, // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
					scene_QuadClt,                                  // QuadCLT scene_QuadCLT,
					scenes_xyzatr[i][0],                            // xyz
					scenes_xyzatr[i][1],                            // atr
					null, //					double []      scene_xyz_pull, // if both are not null, specify target values to pull to 
					null, //					double []      scene_atr_pull, // 
					clt_parameters.ilp.ilma_lma_select,             // final boolean[]   param_select,
					clt_parameters.ilp.ilma_regularization_weights, // final double []   param_regweights,
					rms2,                                           // double []      rms, // null or double [2]
					clt_parameters.imp.max_rms,                     // double         max_rms,
					clt_parameters.imp.debug_level);                // 1); // -1); // int debug_level);
			if (debug_level > -1) {
				System.out.println("Pass 1 scene "+i+" (of "+ scenes.length+") "+
						reference_QuadClt.getImageName() + "/" + scene_QuadClt.getImageName()+(" Done.\n"));
			}
		} // for (int i = 1; i < scenes.length; i++) {
		
		// TODO: Add setting velocities w/o second pass
		
		// updating ERS from delta pose
		for (int i = 0; i < scenes.length; i++) {
			int i_prev = i - ((i > 0) ? 1 : 0);
			int i_next = i + ((i < (scenes.length - 1)) ? 1 : 0);
			double dt =  scenes[i_next].getTimeStamp() - scenes[i_prev].getTimeStamp();
			ers_xyzatr[i] = new double[2][3];
			// both prev and next are differential, so they are added
			
			if (i>0) {
				for (int j = 0; j < 3; j++) {
					ers_xyzatr[i][0][j] += scenes_xyzatr[i][0][j];
					ers_xyzatr[i][1][j] += scenes_xyzatr[i][1][j];
				}
			}
			if (i < (scenes.length - 1)) {
				for (int j = 0; j < 3; j++) {
					ers_xyzatr[i][0][j] += scenes_xyzatr[i + 1][0][j]; // null
					ers_xyzatr[i][1][j] += scenes_xyzatr[i + 1][1][j];
				}
			}
			for (int j = 0; j < 3; j++) {
				ers_xyzatr[i][0][j] *= 1.0 / dt;
				ers_xyzatr[i][1][j] *= scale_two_omegas / dt;
			}
			ers_xyzatr[i][1][0] = -ers_xyzatr[i][1][0]; /// TESTING!
			ers_xyzatr[i][1][2] = -ers_xyzatr[i][1][2]; /// TESTING!
		}
		
		boolean show_results = clt_parameters.ofp.show_result_images; //  true;
		if (show_results) {
			int dbg_w = ers_xyzatr.length;
			int dbg_h = 6;
			double [] dbg_img = new double [dbg_w * dbg_h];
			for (int dh = 0; dh  < dbg_h; dh++) {
				for (int dw = 0; dw  < dbg_w; dw++) {
					dbg_img[dh*dbg_w + dw] = ers_xyzatr[dw][dh / 3][dh % 3];
				}
			}
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					dbg_w,
					dbg_h,
					"ers_xyzatr_fixed"); //	dsrbg_titles);
		}
		if (clt_parameters.ofp.lpf_pairs > 0.0) {
			ers_xyzatr = LPFVelocities(
					ers_xyzatr,                    // double [][][]  ers_xyzatr_in,
					clt_parameters.ofp.lpf_pairs); // double         half_run_range
			if (show_results) {
				int dbg_w = ers_xyzatr.length;
				int dbg_h = 6;
				double [] dbg_img = new double [dbg_w * dbg_h];
				for (int dh = 0; dh  < dbg_h; dh++) {
					for (int dw = 0; dw  < dbg_w; dw++) {
						if (ers_xyzatr[dw] == null) {
							System.out.println("adjustPairsDualPass(): ers_xyzatr["+dw+"] == null");
							continue;
						}
						if (ers_xyzatr[dw][dh/3] == null) {
							System.out.println("adjustPairsDualPass(): ers_xyzatr["+dw+"]["+(dh/3)+"] == null");
							continue;
						}
						dbg_img[dh*dbg_w + dw] = ers_xyzatr[dw][dh / 3][dh % 3];
					}
				}
				ShowDoubleFloatArrays.showArrays(
						dbg_img,
						dbg_w,
						dbg_h,
						"ers_xyzatr_lpf"+clt_parameters.ofp.lpf_pairs); //	dsrbg_titles);
			}
		}
	//{camera_xyz0, camera_atr0}	
		if (!use_second_pass) {
			for (int i = 1; i < scenes.length; i++) {
				QuadCLT reference_QuadClt = scenes[i];
				QuadCLT scene_QuadClt = scenes[i - 1];
				ErsCorrection ers_reference = reference_QuadClt.getErsCorrection();
//				ErsCorrection ers_scene =     scene_QuadClt.getErsCorrection();
				ers_reference.addScene(scene_QuadClt.getImageName(),
						scenes_xyzatr[i][0],
						scenes_xyzatr[i][1],
						ers_xyzatr[i][0],		
						ers_xyzatr[i][1]		
						);
			}		
		} else {
			// pass II - set scene velocities from offsets to 1 before and one after, freeze ERS parameters, and
			// adjust other ones.
			boolean[]   param_select2 =     clt_parameters.ilp.ilma_lma_select.clone();             // final boolean[]   param_select,
			double []   param_regweights2 = clt_parameters.ilp.ilma_regularization_weights; //  final double []   param_regweights,
			// freeze reference ERS, free scene ERS
			for (int j = 0; j <3; j++) {
				param_select2[ErsCorrection.DP_DVX  + j] = false;
				param_select2[ErsCorrection.DP_DVAZ + j] = false;
				//			param_select2[ErsCorrection.DP_DSVX  + j] = false; // disabling, may check with high rot speed
				//			param_select2[ErsCorrection.DP_DSVAZ + j] = true;  // so far ers correction noise is too high to compare
				param_regweights2[ErsCorrection.DP_DSVX +  j] = 0.0;
				param_regweights2[ErsCorrection.DP_DSVAZ + j] = 0.0;
			}
			double [][][] scenes_xyzatr1 = new double [scenes.length][][]; // previous scene relative to the next one  

			for (int i = 1; i < scenes.length; i++) {
				QuadCLT reference_QuadClt = scenes[i];
				QuadCLT scene_QuadClt = scenes[i - 1];
				double [][] pose = 	scenes_xyzatr[i];
				ErsCorrection ers_reference = reference_QuadClt.getErsCorrection();
				ErsCorrection ers_scene =     scene_QuadClt.getErsCorrection();
				ers_reference.ers_wxyz_center_dt = ers_xyzatr[i][0].clone();
				ers_reference.ers_watr_center_dt = ers_xyzatr[i][1].clone();
				int i_prev = i - ((i > 0) ? 1 : 0);
				ers_reference.setupERS(); // just in case - setUP using instance paRAMETERS
				ers_scene.ers_wxyz_center_dt = ers_xyzatr[i_prev][0].clone();
				ers_scene.ers_watr_center_dt = ers_xyzatr[i_prev][1].clone();
				ers_scene.setupERS();     // just in case - setUP using instance paRAMETERS
				scenes_xyzatr1[i] = adjustPairsLMA(
						clt_parameters,     // CLTParameters  clt_parameters,			
						reference_QuadClt,  // QuadCLT reference_QuadCLT,
						scene_QuadClt, // QuadCLT scene_QuadCLT,
						pose[0], // xyz
						pose[1], // atr
						param_select2,             // final boolean[]   param_select,
						param_regweights2, //  final double []   param_regweights,
						null, // 			double []      rms, // null or double [2]
						null, // double [][]    dbg_img,
						0.0, // double         max_rms,
						debug_level); // int debug_level)
				ers_reference.addScene(scene_QuadClt.getImageName(),
						scenes_xyzatr1[i][0],
						scenes_xyzatr1[i][1],
						ers_scene.getErsXYZ_dt(),		
						ers_scene.getErsATR_dt()		
						);
				if (debug_level > -1) {
					System.out.println("Pass 2 scene "+i+" (of "+ scenes.length+") "+
							reference_QuadClt.getImageName() + "/" + scene_QuadClt.getImageName()+" Done.");
				}
			}

			if (show_results) {
				double [][][] ers_xyzatr_dt = new double [scenes.length][][];
				for (int i = 0; i < scenes.length; i++) {
					ErsCorrection ers_scene =     scenes[i].getErsCorrection();
					ers_xyzatr_dt[i] = new double[][] {
						ers_scene.ers_wxyz_center_dt,
						ers_scene.ers_watr_center_dt};
				}
				int dbg_w = ers_xyzatr_dt.length;
				int dbg_h = 6;
				double [] dbg_img = new double [dbg_w * dbg_h];
				for (int dh = 0; dh  < dbg_h; dh++) {
					for (int dw = 0; dw  < dbg_w; dw++) {
						dbg_img[dh*dbg_w + dw] = ers_xyzatr_dt[dw][dh / 3][dh % 3];
					}
				}
				ShowDoubleFloatArrays.showArrays(
						dbg_img,
						dbg_w,
						dbg_h,
						"ers_xyzatr_adjusted"); //	dsrbg_titles);
			}


			//TODO: Add update ers with lpf here?
			//		boolean show_results = true;
			if (show_results) {
				int dbg_w = scenes_xyzatr1.length;
				int dbg_h = 6;
				double [] dbg_img = new double [dbg_w * dbg_h];
				Arrays.fill(dbg_img, Double.NaN);
				for (int dh = 0; dh  < dbg_h; dh++) {
					for (int dw = 0; dw  < dbg_w; dw++) {
						if (scenes_xyzatr1[dw] != null) {
							dbg_img[dh*dbg_w + dw] = scenes_xyzatr1[dw][dh / 3][dh % 3];
						}
					}
				}
				ShowDoubleFloatArrays.showArrays(
						dbg_img,
						dbg_w,
						dbg_h,
						"scenes_xyzatr1"); //	dsrbg_titles);
			}
		}


		
		// Update ERS velocities with running average?
		
		for (int i = 1; i < scenes.length; i++) {
			QuadCLT reference_QuadClt = scenes[i];
			reference_QuadClt.saveInterProperties( // save properties for interscene processing (extrinsics, ers, ...)
		            null, // String path,             // full name with extension or w/o path to use x3d directory
		            debug_level+1);
		}		
	}

	public static void photoEach(
			CLTParameters                                 clt_parameters,
			ColorProcParameters                           colorProcParameters,
			QuadCLT                                       quadCLT_main, // tiles should be set
			QuadCLT                                       quadCLT_ref, // tiles should be set
			final double [][]                             dsi,
			final int                                     photo_num_full, // 
			final boolean                                 batch_mode,
			final int                                     threadsMax,  // int               threadsMax,
			final boolean                                 updateStatus,
			final int                                     debugLevel) {
//		boolean [] blue_sky = null; //  quadCLT_ref.getBlueSky();
//		double [] d_blue_sky = dsi[TwoQuadCLT.DSI_BLUE_SKY_AUX];
		if (debugLevel > -3) {
			System.out.println("**** Running photometric equalization for "+quadCLT_ref.getImageName()+
					", current was from scene "+quadCLT_ref.getPhotometricScene()+" ****");
		}
    	int      photo_num_refines =         clt_parameters.photo_num_refines; //  3;    // Calibrate, remove outliers, recalibrate, ... 
    	int      photo_min_good =            clt_parameters.photo_min_good;    //  1000;    // Minimal number of good pixels for photometric calibration 
    	double   photo_min_strength =        clt_parameters.photo_min_strength; // 0.0;  // maybe add to filter out weak tiles
    	double   photo_max_diff =            clt_parameters.photo_max_diff; //    40.0;  // To filter mismatches. Normal (adjusted) have RMSE ~9
    	int      photo_order =               clt_parameters.photo_order;
    	double   photo_std_1 =               clt_parameters.photo_std_1; //  50.0;   // Minimal standard deviation of the filtered values for poly order 1
    	double   photo_std_2 =               clt_parameters.photo_std_2; // 200.0;   // Minimal standard deviation of the filtered values for poly order 2
    	int      photo_offs_set =            clt_parameters.photo_offs_set; //  0;    // 0 - keep weighted offset average, 1 - balance result image, 2 - set weighted average to specific value
    	double   photo_offs =                clt_parameters.photo_offs;     // 21946; // weighted average offset target value, if photo_offs_set (and not photo_offs_balance)
	
		// preparing same format as after combo, filling in only needed data
		double [][] ds_photo = new double[TwoQuadCLT.DSI_LENGTH][];
		ds_photo[OpticalFlow.COMBO_DSN_INDX_DISP] =        dsi[TwoQuadCLT.DSI_DISPARITY_AUX_LMA];
		ds_photo[OpticalFlow.COMBO_DSN_INDX_DISP_BG_ALL] = dsi[TwoQuadCLT.DSI_DISPARITY_AUX_LMA];
		ds_photo[OpticalFlow.COMBO_DSN_INDX_STRENGTH] =    dsi[TwoQuadCLT.DSI_STRENGTH_AUX];
		ds_photo[OpticalFlow.COMBO_DSN_INDX_BLUE_SKY] =    dsi[TwoQuadCLT.DSI_BLUE_SKY_AUX];
			
		boolean photo_each_debug = !batch_mode && clt_parameters.photo_each_debug; // false; // true; // false;
		boolean photo_each_debug2 = !batch_mode && clt_parameters.photo_each_debug; // false; // true; // false;
		
		double [] offsets_bkp=  quadCLT_ref.getLwirOffsets();
		double [] scales_bkp=   quadCLT_ref.getLwirScales();
		double [] scales2_bkp = quadCLT_ref.getLwirScales2();
		String    photometric_scene_backup = quadCLT_ref.getPhotometricScene();
		
		for (int nrecalib = 0; nrecalib < photo_num_full; nrecalib++) { // maybe need to correct just offsets?
			int poly_order = photo_order;
			if ((poly_order > 1) && (nrecalib == 0)) {
				poly_order = 1;
			}
			boolean ok = QuadCLT.calibratePhotometric2(
					clt_parameters,           // CLTParameters     clt_parameters,
					quadCLT_ref,      // final QuadCLT     ref_scene, will set photometric calibration to this scene
					photo_min_strength,       // final double      min_strength,
					photo_max_diff,           // final double      max_diff,    // 30.0
					poly_order,               // final int         photo_order, // 0 - offset only, 1 - linear, 2 - quadratic
					photo_std_1,    // final double photo_std_1,   //  50.0;   // Minimal standard deviation of the filtered values for poly order 1
					photo_std_2,    // final double photo_std_2,   // 200.0;   // Minimal standard deviation of the filtered values for poly order 2
					photo_offs_set, // final int    photo_offs_set,// 0;     // 0 - keep weighted offset average, 1 - balance result image, 2 - set weighted average to specific value
					photo_offs,     // final double photo_offs,    // 21946; // weighted average offset target value, if photo_offs_set (and not photo_offs_balance)
					photo_num_refines,        // final int         num_refines, // 2
				    photo_min_good, // final int         min_good,     // minimal number of "good" pixels
					ds_photo,       // combo_dsn_final_filtered, // final double [][] combo_dsn_final,     // double [][]    combo_dsn_final, // dls,
					threadsMax,               // int               threadsMax,
					photo_each_debug);        //final boolean     debug)
			if (!ok) {
				System.out.println("************** Failed calibratePhotometric2, restoring original");
//				quadCLT_ref.setLwirOffsets(quadCLT_main.getLwirOffsets());
//				quadCLT_ref.setLwirScales (quadCLT_main.getLwirScales ());
//				quadCLT_ref.setLwirScales2(quadCLT_main.getLwirScales2());
//				quadCLT_ref.setPhotometricScene(quadCLT_main.getPhotometricScene());
				// Retry linear only
				ok = QuadCLT.calibratePhotometric2(
						clt_parameters,           // CLTParameters     clt_parameters,
						quadCLT_ref,      // final QuadCLT     ref_scene, will set photometric calibration to this scene
						photo_min_strength,       // final double      min_strength,
						photo_max_diff,           // final double      max_diff,    // 30.0
						1, // poly_order,               // final int         photo_order, // 0 - offset only, 1 - linear, 2 - quadratic
						photo_std_1,    // final double photo_std_1,   //  50.0;   // Minimal standard deviation of the filtered values for poly order 1
						photo_std_2,    // final double photo_std_2,   // 200.0;   // Minimal standard deviation of the filtered values for poly order 2
						photo_offs_set, // final int    photo_offs_set,// 0;     // 0 - keep weighted offset average, 1 - balance result image, 2 - set weighted average to specific value
						photo_offs,     // final double photo_offs,    // 21946; // weighted average offset target value, if photo_offs_set (and not photo_offs_balance)
						photo_num_refines,        // final int         num_refines, // 2
					    photo_min_good, // final int         min_good,     // minimal number of "good" pixels
						ds_photo,                 // combo_dsn_final_filtered, // final double [][] combo_dsn_final,     // double [][]    combo_dsn_final, // dls,			
						threadsMax,               // int               threadsMax,
						photo_each_debug);        //final boolean     debug)
				if (!ok) {
					System.out.println("Failed even linear photometric on pass "+nrecalib+", abandoning calibration");
					quadCLT_ref.setLwirOffsets(offsets_bkp);
					quadCLT_ref.setLwirScales (scales_bkp);
					quadCLT_ref.setLwirScales2(scales2_bkp);
					quadCLT_ref.setPhotometricScene(photometric_scene_backup);
					break;
				}
				
			}
			// copy offsets to the current to be saved with other properties. Is it correct/needed?
			quadCLT_ref.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);
			quadCLT_ref.setDSI(dsi); // try to avoid saving, will complain on restoring, but keep

			// Re-read reference and other scenes using new offsets	
			quadCLT_ref.setQuadClt(); // should work even when the data is new for the same scene
			quadCLT_ref.restoreFromModel( // resets dsi to null
					clt_parameters,
					colorProcParameters,
					null,                 // double []    noise_sigma_level,
					-1,                   // noise_variant, // <0 - no-variants, compatible with old code
					null,                 // final QuadCLTCPU     ref_scene, // may be null if scale_fpn <= 0
					threadsMax,
					debugLevel);

			// Re-measure and update (is it needed?)
			CLTPass3d scan = new CLTPass3d(quadCLT_ref.tp);
			scan.setTileOpDisparity(dsi[TwoQuadCLT.DSI_DISPARITY_AUX]);
			quadCLT_ref.setQuadClt(); // just in case ?
			quadCLT_ref.CLTMeas( // perform single pass according to prepared tiles operations and disparity
					clt_parameters, // EyesisCorrectionParameters.CLTParameters           clt_parameters,
					scan,           // final CLTPass3d   scan,
					false,          // final boolean     save_textures,
					false,          // final boolean       need_diffs,     // calculate diffs even if textures are not needed 
					0,              // final int         clust_radius,
					true,           // final boolean     save_corr,
					true, // false,          // final boolean     run_lma, // =    true;
					0.0,            // final double        max_chn_diff, // filter correlation results by maximum difference between channels
					-1.0,           // final double        mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
					threadsMax,     // final int         threadsMax,  // maximal number of threads to launch
					updateStatus,   // final boolean     updateStatus,
					debugLevel-2);  // final int         debugLevel);
			double [][] aux_new_scan = TileProcessor.getDSLMA(
					scan,
					false); // boolean force_final);
			dsi[TwoQuadCLT.DSI_DISPARITY_AUX] =     aux_new_scan[0]; // compare diffs
			dsi[TwoQuadCLT.DSI_STRENGTH_AUX] =      aux_new_scan[1];
			dsi[TwoQuadCLT.DSI_DISPARITY_AUX_LMA] = aux_new_scan[2];
			// Re-measure and update from BG spread and average in dsi
			CLTPass3d bgscan = new CLTPass3d(quadCLT_ref.tp);
			bgscan.setTileOpDisparity(null);
			quadCLT_ref.setQuadClt(); // just in case ?
			quadCLT_ref.CLTMeas( // perform single pass according to prepared tiles operations and disparity
					clt_parameters, // EyesisCorrectionParameters.CLTParameters           clt_parameters,
					bgscan,           // final CLTPass3d   scan,
					true, // false,          // final boolean     save_textures,
					true, // false,          // final boolean       need_diffs,     // calculate diffs even if textures are not needed 
					0,              // final int         clust_radius,
					true,           // final boolean     save_corr, IS IT NEEDED?
					false,          // final boolean     run_lma, // =    true;
					0.0,            // final double        max_chn_diff, // filter correlation results by maximum difference between channels
					-1.0,           // final double        mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
					threadsMax,     // final int         threadsMax,  // maximal number of threads to launch
					updateStatus,   // final boolean     updateStatus,
					debugLevel-2);  // final int         debugLevel);
			
			if (photo_each_debug2) {
				quadCLT_ref.tp.showScan(bgscan, quadCLT_ref.getImageName()+"-bgscan-"+nrecalib); // nrecalib
				quadCLT_ref.tp.showScan(scan,   quadCLT_ref.getImageName()+"-scan-"+nrecalib);
			}
			dsi[TwoQuadCLT.DSI_SPREAD_AUX] = bgscan.getSecondMax(); //    //aux_bg_scan[3];
			dsi[TwoQuadCLT.DSI_AVGVAL_AUX] = bgscan.getAvgVal(); //aux_bg_scan[4];
			
			quadCLT_ref.setDSI(dsi); // Restore known dsi
//			quadCLT_ref.setBlueSky(null); //ref_blue_sky); it is null here
			quadCLT_ref.setDSRBG(
					clt_parameters, // CLTParameters  clt_parameters,
					threadsMax,     // int            threadsMax,  // maximal number of threads to launch
					updateStatus,   // boolean        updateStatus,
					debugLevel);    // int            debugLevel)
		}
		quadCLT_ref.setPhotometricUpdated(true);
		return;
	}
	
	
	public static void buildRefDSI(
			CLTParameters                                 clt_parameters,
			boolean                                       fast,
			boolean                                       skip_photo,
			ColorProcParameters                           colorProcParameters,
			EyesisCorrectionParameters.RGBParameters      rgbParameters,
    		boolean                                       batch_mode,
    		String                                        set_name,
			QuadCLT                                       quadCLT_main, // tiles should be set
			QuadCLT                                       quadCLT_ref, // tiles should be set
			final int                                     threadsMax,  // maximal number of threads to launch
			final boolean                                 updateStatus,
			final int                                     debugLevel) {
		final int debugLevelInner=   clt_parameters.batch_run? -2: debugLevel; // copied from TQ
		if (debugLevelInner > -3) {
			System.out.println("buildRefDSI(): clt_parameters.batch_run="+clt_parameters.batch_run+", debugLevel="+debugLevel+", debugLevelInner="+debugLevelInner+", fast="+fast);
		}
    	boolean  photo_each =        clt_parameters.photo_each; //        true;  // perform photogrammetric calibration to equalize pixel values
    	boolean sky_extract =        clt_parameters.imp.sky_extract;
 		double sky_highest_min =     clt_parameters.imp.sky_highest_min;
 		double cold_frac =           clt_parameters.imp.cold_frac;
 		double hot_frac =            clt_parameters.imp.hot_frac;
 		double cold_scale =          clt_parameters.imp.cold_scale;
 		double sky_seed =            clt_parameters.imp.sky_seed;
 		double lma_seed =            clt_parameters.imp.lma_seed;
		double seed_temp =           clt_parameters.imp.seed_temp;
 		int    sky_shrink =          clt_parameters.imp.sky_shrink;
 		int    sky_bottleneck =      clt_parameters.imp.sky_bottleneck;
 		int    sky_reexpand_extra =  clt_parameters.imp.sky_reexpand_extra;
 		int    seed_rows =           clt_parameters.imp.seed_rows;
 		double max_disparity =       clt_parameters.imp.max_disparity;
 		double max_disparity_strength=clt_parameters.imp.max_disparity_strength;
 		double sky_lim =             clt_parameters.imp.sky_lim;
		double lim_temp =            clt_parameters.imp.lim_temp;
 		int    sky_expand_extra =    clt_parameters.imp.sky_expand_extra;
 		double min_strength =        clt_parameters.imp.min_strength;
 		int    lowest_sky_row =      clt_parameters.imp.lowest_sky_row;
 		double sky_bottom_override = clt_parameters.imp.sky_bottom_override;
 		int    sky_override_shrink = clt_parameters.imp.sky_override_shrink;
		double disp_boost_min =      clt_parameters.imp.disp_boost_min; //  0.5;
		double disp_boost_diff  =    clt_parameters.imp.disp_boost_diff; // 0.35;
		int    disp_boost_neibs  =   clt_parameters.imp.disp_boost_neibs; // 2;
		double disp_boost_amount  =  clt_parameters.imp.disp_boost_amount; // 2.0;
		
		boolean clouds_en =          clt_parameters.imp.clouds_en;     	  // true; // enable clouds in the sky detection / processing
		double  clouds_fom =         clt_parameters.imp.clouds_fom;       // 30.0; // maximal FOM for clouds (must be <=)         
		double  clouds_spread =      clt_parameters.imp.clouds_spread;    // 60.0; // maximal spread for clouds (must be <=)         
		double  clouds_disparity =   clt_parameters.imp.clouds_disparity; //  0.1; // maximal disparity for strong clouds         
		double  clouds_weak =        clt_parameters.imp.clouds_weak;      //  0.18;// maximal strength for near definite clouds         		
		double  clouds_strength =    clt_parameters.imp.clouds_strength;  //  0.25;// minimal strength for far strong clouds (definitely cloud)         		
		double  clouds_not_strength= clt_parameters.imp.clouds_not_strength;//0.4;  // maximal strength for near maybe clouds (if it has strong cloud neighbor)         		
		boolean clouds_strong =      clt_parameters.imp.clouds_strong;    // true;  // allow weak cloud if it has strong (1.5x) cloud neib
		
		// process smooth walls mistaken for sky (disable for natural environments)
		boolean  wall_en =           clt_parameters.imp.wall_en;          // true;  // enable smooth walls detection/processing
		boolean  wall_dflt =         clt_parameters.imp.wall_dflt;        // true;  // default (undetected) is wall (false - sky)
		double   wall_str =          clt_parameters.imp.wall_str;         // 0.1;   // minimal strength of the far object (small - just non-NaN disparity)         
		double   wall_far =          clt_parameters.imp.wall_far;         // 0.2;   // maximal disparity to consider cluster to be sky         
		double   wall_near =         clt_parameters.imp.wall_near;        // 1.0;   // minimal disparity to consider cluster to be wall         

		// processing far treeline that may be confused with clouds. Only behind far objects such as far horizontal surface.
	    boolean  treeline_en =       clt_parameters.imp.treeline_en;   	  //  true;  // look not only under, but diagonal too.
	    boolean  treeline_wide =     clt_parameters.imp.treeline_wide;    //  true;  // enable treeline processing
	    int      treeline_height =   clt_parameters.imp.treeline_height;  //  5;    // maximal height of the treeline (tiles)
	    int      treeline_width =    clt_parameters.imp.treeline_width;   //  3;    // minimal horizontal width of the treeline (tiles)
	    boolean  treeline_lim_high = clt_parameters.imp.treeline_lim_high;//  false; // limit too high treeline (false - delete completely)
	    double   treeline_str =      clt_parameters.imp.treeline_str;     //  0.8;  // treeline minimal strength 
	    double   treeline_far =      clt_parameters.imp.treeline_far;     //  0.04; // treeline min disparity (pix)
	    double   treeline_near =     clt_parameters.imp.treeline_near;    //  0.4;  // treeline max disparity (pix)
	    double   treeline_fg_str =   clt_parameters.imp.treeline_fg_str;  //  0.8;  // pre-treeline FG objects (such as flat ground) minimal strength 
	    double   treeline_fg_far =   clt_parameters.imp.treeline_fg_far;  //  0.2;  // pre-treeline FG objects  min disparity (pix)
	    double   treeline_fg_near =  clt_parameters.imp.treeline_fg_near; //  0.5;  // pre-treeline FG objects  max disparity (pix)
		
		// suspecting indoors (disabling sky)
		boolean indoors_en =         clt_parameters.imp.indoors_en;       // true; // allow weak cloud if it has strong (1.5x) cloud neib
		double  indoors_str =        clt_parameters.imp.indoors_str;      //  0.5; // minimal strength of the far object         
		double  indoors_disp =       clt_parameters.imp.indoors_disp;     //  0.8; // maximal minimal outdoor strong disparity         
		int     indoors_min_out  =   clt_parameters.imp.indoors_min_out; //  10;   // minimal strong far tiles to deny indoors

		QuadCLT   dbg_scene = clt_parameters.imp.save_debug_images? quadCLT_ref: null; // use to save debug images if not null

		boolean [] ref_blue_sky = null; // turn off "lma" in the ML output
		
		int     gr_max_clust_radius = clt_parameters.gr_max_clust_radius;			  
		double  disp_scan_start = clt_parameters.disp_scan_start;
		double  disp_scan_step =  clt_parameters.disp_scan_step;
		int  disp_scan_count =    clt_parameters.disp_scan_count;
		boolean no_bg_generate = !clt_parameters.generate_bg;
		boolean generate_keyframes = clt_parameters.imp.generate_keyframes;
		
		boolean no_lma =         false;			  
		if (fast) {
			no_lma =         true;
			no_bg_generate = true;
			photo_each =     false;
			sky_extract =    false;
		}
		if (debugLevel > -3) {
			System.out.println("buildRefDSI(): Running preExpandCLTQuad3d() for scene "+quadCLT_ref.getImageName());
			System.out.println("buildRefDSI(): quadCLT_ref.hasNewImageData() -> "+quadCLT_ref.hasNewImageData());
		}
		// next probably not needed (was troubleshooting)
		quadCLT_ref.saveQuadClt(); // to re-load new set of Bayer images to the GPU (do nothing for CPU). Spend several days to find this bug!

		
		if (debugLevel > -3) {
			//quadCLT_main
			System.out.println("buildRefDSI(): quadCLT_ref.tp.clt_3d_passes.size()="+quadCLT_ref.tp.clt_3d_passes.size());
//			System.out.println("buildRefDSI(): suspecting processing *-DSI_MAIN.tiff without loading new images to the GPU, running quadCLT_ref.saveQuadClt(). 12/07/2025");
			boolean show_sources = debugLevel > 1000;
			if (show_sources) {
				quadCLT_ref.showImageData();
			}
			
			
		}
		
		
		quadCLT_ref.preExpandCLTQuad3d( // returns ImagePlus, but it already should be saved/shown
				clt_parameters,
				  // adding these parameters for more flexibility in accuracy/speed
				gr_max_clust_radius, // int      gr_max_clust_radius,			  
				disp_scan_start,     // double  disp_scan_start,
				disp_scan_step,      // double  disp_scan_step,
				disp_scan_count,     // int  disp_scan_count,
				no_bg_generate,      // boolean no_bg_generate,
				no_lma,              // boolean no_lma,			  
				colorProcParameters,
				rgbParameters,
				threadsMax,  // maximal number of threads to launch
				updateStatus,
				debugLevelInner);
		double [][] aux_last_scan = TileProcessor.getDSLMA(
				quadCLT_ref.tp.clt_3d_passes.get( quadCLT_ref.tp.clt_3d_passes.size() -1),
				false); // boolean force_final);
		double [][] dsi = new double [TwoQuadCLT.DSI_SLICES.length][];
		dsi[TwoQuadCLT.DSI_DISPARITY_AUX] =     aux_last_scan[0];
		dsi[TwoQuadCLT.DSI_STRENGTH_AUX] =      aux_last_scan[1];
		dsi[TwoQuadCLT.DSI_DISPARITY_AUX_LMA] = aux_last_scan[2];
		dsi[TwoQuadCLT.DSI_SPREAD_AUX] =        aux_last_scan[3];
		dsi[TwoQuadCLT.DSI_AVGVAL_AUX] =        aux_last_scan[4];

		if (quadCLT_main.correctionsParameters.clt_batch_dsi_cm_strength) {
			CLTPass3d scan = new CLTPass3d(quadCLT_ref.tp);
			scan.setTileOpDisparity(aux_last_scan[0]); // measure w/o LMA, use just strength
			quadCLT_ref.CLTMeas( // perform single pass according to prepared tiles operations and disparity
					clt_parameters, // EyesisCorrectionParameters.CLTParameters           clt_parameters,
					scan,           // final CLTPass3d   scan,
					false,          // final boolean     save_textures,
					false,          // final boolean       need_diffs,     // calculate diffs even if textures are not needed 
					0,              // final int         clust_radius,
					true,           // final boolean     save_corr,
					false,          // final boolean     run_lma, // =    true;
					0.0,            // final double        max_chn_diff, // filter correlation results by maximum difference between channels
					-1.0,           // final double        mismatch_override, // keep tile with large mismatch if there is LMA with really strong correlation
					threadsMax,     // final int         threadsMax,  // maximal number of threads to launch
					updateStatus,   // final boolean     updateStatus,
					debugLevel-2);  // final int         debugLevel);
			dsi[TwoQuadCLT.DSI_STRENGTH_AUX] =      scan.getStrength();
			if (debugLevel > 1) {
				quadCLT_ref.tp.showScan(
						scan, // CLTPass3d   scan,
						"test-strength");
			}
		} else {
			dsi[TwoQuadCLT.DSI_STRENGTH_AUX] =      aux_last_scan[1];
		}
		boolean ran_photo_each = false;
		quadCLT_ref.tp.resetCLTPasses();
		if (generate_keyframes) {
			int tile_size = quadCLT_ref.getTileSize();
			int [] wh = {quadCLT_ref.getTilesX()*tile_size,quadCLT_ref.getTilesY()*tile_size};
			double [][] keyframe16 = quadCLT_ref.renderDoubleFromTDMono (
					-1, // all sensors // final int         sensor_mask, 
					wh,              // null, // int []  wh,
	                false);          // boolean use_reference
			String [] titles16 = new String [keyframe16.length];
			for (int i = 0; i <titles16.length; i++) {
				titles16[i] = "port_"+i;
			}
			quadCLT_ref.saveDoubleArrayInModelDirectory(
					"-KEYFRAME-SENSORS", // String      suffix,
					titles16,            // String []   labels, // or null
					keyframe16,          // double [][] data,
					wh[0],               // int         width,
					wh[1]);              // int         height)
		}
		
		
		// perform photometric here, after first DSI
		if (!skip_photo && (photo_each && (!quadCLT_ref.isPhotometricThis() || !batch_mode))) {
//			if (debugLevel > -3) {
//				System.out.println("**** Running photometric equalization for "+quadCLT_ref.getImageName()+
//						", current was from scene "+quadCLT_ref.getPhotometricScene()+" ****");
//			}
			photoEach(
					clt_parameters,      // CLTParameters                                 clt_parameters,
					colorProcParameters, // ColorProcParameters                           colorProcParameters,
					quadCLT_main,        // QuadCLT                                       quadCLT_main, // tiles should be set
					quadCLT_ref,         // QuadCLT                                       quadCLT_ref, // tiles should be set
					dsi,                 // final double [][]                             dsi,
			    	clt_parameters.photo_num_full, // int      photo_num_full =              
					batch_mode,          // final boolean                                 batch_mode,
					threadsMax,          // final int                                     threadsMax,  // int               threadsMax,
					updateStatus,        // final boolean                                 updateStatus,
					debugLevel);         // final int                                     debugLevel)			
			ran_photo_each = true; // will need to re-run after blue sky detection
			
		} else {
			System.out.println("(Re)using photometric calibration from this sequence reference "+quadCLT_ref.getPhotometricScene());
			quadCLT_ref.setQuadClt(); // just in case ?
		}
		
		if (sky_extract) {
			quadCLT_ref.setBlueSky  ( // initial BS from single scene
					max_disparity,
					max_disparity_strength,
					sky_seed,           // double sky_seed, //  =       7.0;  // start with product of strength by diff_second below this
					lma_seed,
					seed_temp,     //double seed_temp, //         0.5;  // seed colder that this point between min and max temp						
					sky_lim,            // double sky_lim, //   =      15.0; // then expand to product of strength by diff_second below this
					lim_temp, //						double lim_temp, //          0.5;  // sky colder that this point between min and max temp						
					sky_shrink,         // int    sky_shrink, //  =       4;
					sky_expand_extra,   // int    sky_expand_extra, //  = 100; // 1?
					sky_bottleneck,     //int    sky_bottleneck, // 
					sky_reexpand_extra,   //int    sky_reexpand_extra,  // 9; re-expand after bottleneck in addition to how it was shrank
					cold_scale, // =       0.2;  // <=1.0. 1.0 - disables temperature dependence
					cold_frac, // =        0.005; // this and lower will scale fom by  cold_scale
					hot_frac, // =         0.9;    // this and above will scale fom by 1.0
					min_strength, // =     0.08;
					seed_rows, // =        5; // sky should appear in this top rows 
					lowest_sky_row,        //  =   50;// appears that low - invalid, remove completely
					sky_bottom_override,   // double sky_temp_override,     // really cold average seed - ignore lowest_sky_row filter
					sky_override_shrink,   // int    shrink_for_temp,       // shrink before finding hottest sky
					sky_highest_min,       //  =   100; // lowest absolute value should not be higher (requires photometric)
					clouds_en,             // enable clouds in the sky detection / processing
					clouds_fom,            // maximal FOM for clouds (must be <=)         
					clouds_spread,         // maximal spread for clouds (must be <=)         
					clouds_disparity,      // maximal disparity for strong clouds         
					clouds_weak,           // maximal strength for near definite clouds         		
					clouds_strength,       // minimal strength for far strong clouds (definitely cloud)         		
					clouds_not_strength,   // maximal strength for near maybe clouds (if it has strong cloud neighbor)         		
					clouds_strong,         // true; // allow weak cloud if it has strong (1.5x) cloud neib

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

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

					indoors_en,            // true; // allow weak cloud if it has strong (1.5x) cloud neib
					indoors_str,           //  0.5; // minimal strength of the far object         
					indoors_disp,          //  0.8; // maximal minimal outdoor strong disparity         
					indoors_min_out,       //  10;   // minimal strong far tiles to deny indoors
					disp_boost_min,        // double disp_boost_min,    //  = 0.5;
					disp_boost_diff,       //double disp_boost_diff,   //  = 0.35;
					disp_boost_neibs,   //int    disp_boost_neibs,  //  = 2;
					disp_boost_amount,  //double disp_boost_amount, //  = 2.0;
					dsi[TwoQuadCLT.DSI_STRENGTH_AUX], // double [] strength,
					dsi[TwoQuadCLT.DSI_SPREAD_AUX], // double [] spread,
					dsi[TwoQuadCLT.DSI_DISPARITY_AUX_LMA], // double [] spread,
					dsi[TwoQuadCLT.DSI_AVGVAL_AUX],//	double [] avg_val,
					dbg_scene,          // QuadCLT   dbg_scene,    // use to save debug images if not null
					batch_mode? -1: 1); /// debugLevel);        // int debugLevel)
		} else {
			quadCLT_ref.setBlueSky (new double [quadCLT_ref.tp.getTilesX()*quadCLT_ref.tp.getTilesY()]);
		}
		if (ran_photo_each) {
//			quadCLT_ref.setBlueSky(null); // Reset blue sky - is it needed?
			// see if blue sky was detected - rerun photoEach
//			boolean [] blue_sky = quadCLT_ref.getBlueSky();
			double []  blue_sky = quadCLT_ref.getDoubleBlueSky();
			boolean has_blue_sky = false;
			for (int i = 0; i < blue_sky.length; i++) if (blue_sky[i] > 0) {
				has_blue_sky = true;
				break;
			}
			if (has_blue_sky) {
//				dsi[TwoQuadCLT.DSI_BLUE_SKY_AUX] =      new double [blue_sky.length];
//				for (int i = 0; i < blue_sky.length; i++) {
//					dsi[TwoQuadCLT.DSI_BLUE_SKY_AUX][i] = blue_sky[i]? 1.0 : 0.0;
//				}
				if (debugLevel > -3) {
					System.out.println("Detected non-empty Blue Sky after initial DSI in "+quadCLT_ref.getImageName()+
							", re-running photoEach()");
				}
				photoEach(
						clt_parameters,      // CLTParameters                                 clt_parameters,
						colorProcParameters, // ColorProcParameters                           colorProcParameters,
						quadCLT_main,        // QuadCLT                                       quadCLT_main, // tiles should be set
						quadCLT_ref,         // QuadCLT                                       quadCLT_ref, // tiles should be set
						dsi,                 // final double [][]                             dsi,
						// just once?
				    	clt_parameters.photo_num_full, // int      photo_num_full =              
						batch_mode,          // final boolean                                 batch_mode,
						threadsMax,          // final int                                     threadsMax,  // int               threadsMax,
						updateStatus,        // final boolean                                 updateStatus,
						debugLevel);         // final int                                     debugLevel)			

			} else if (sky_extract){
				System.out.println("**** FAILED to detect non-empty Blue Sky after initial DSI in "+quadCLT_ref.getImageName()+
						", if there is actual sky in the field of view, some \"sky\" parameters need to be adjusted.");
				
			}
		}
		
		quadCLT_ref.saveDSIAll (
				"-DSI_MAIN", // String suffix, // "-DSI_MAIN"
				dsi);
		quadCLT_ref.set_orient(0); // reset orientations
		quadCLT_ref.set_accum(0);  // reset accumulations ("build_interscene") number
		quadCLT_ref.saveInterProperties( // save properties for interscene processing (extrinsics, ers, ...)
				null, // String path,             // full name with extension or w/o path to use x3d directory
				//							 	null, // Properties properties,   // if null - will only save extrinsics)
				debugLevel);
		return;
	}
	
	public static void reuseRefDSI(
			CLTParameters       clt_parameters,
			ColorProcParameters colorProcParameters,
			QuadCLT             quadCLT_main, // tiles should be set
			QuadCLT             quadCLT_ref, // tiles should be set
			final boolean       batch_mode,
			final int           threadsMax,  // int               threadsMax,
			final boolean       updateStatus,
			final int           debugLevel) {
		boolean  photo_each =        clt_parameters.photo_each; //        true;  // perform photogrammetric calibration to equalize pixel values
		boolean  photo_to_main=      clt_parameters.photo_to_main; // maybe it will not be needed, it will apply this calibration to the next scene sequence
		boolean sky_recalc =         clt_parameters.imp.sky_recalc; // force blue sky recalculation even if it exists
		double sky_highest_min =     clt_parameters.imp.sky_highest_min;
		double cold_frac =           clt_parameters.imp.cold_frac;
		double hot_frac =            clt_parameters.imp.hot_frac;
		double cold_scale =          clt_parameters.imp.cold_scale;

		double sky_seed =            clt_parameters.imp.sky_seed;
		double lma_seed =            clt_parameters.imp.lma_seed;
		double seed_temp =           clt_parameters.imp.seed_temp;
		int    sky_shrink =          clt_parameters.imp.sky_shrink;
		int    sky_bottleneck =      clt_parameters.imp.sky_bottleneck;
 		int    sky_reexpand_extra =  clt_parameters.imp.sky_reexpand_extra;
		int    seed_rows =           clt_parameters.imp.seed_rows;
 		double max_disparity =       clt_parameters.imp.max_disparity;
 		double max_disparity_strength=clt_parameters.imp.max_disparity_strength;
		double sky_lim =             clt_parameters.imp.sky_lim;
		double lim_temp =            clt_parameters.imp.lim_temp;
		int    sky_expand_extra =    clt_parameters.imp.sky_expand_extra;
		double min_strength =        clt_parameters.imp.min_strength;
		int    lowest_sky_row =      clt_parameters.imp.lowest_sky_row;
		double sky_bottom_override = clt_parameters.imp.sky_bottom_override;
		int    sky_override_shrink = clt_parameters.imp.sky_override_shrink;
		double disp_boost_min =      clt_parameters.imp.disp_boost_min; //  0.5;
		double disp_boost_diff  =    clt_parameters.imp.disp_boost_diff; // 0.35;
		int    disp_boost_neibs  =   clt_parameters.imp.disp_boost_neibs; // 2;
		double disp_boost_amount  =  clt_parameters.imp.disp_boost_amount; // 2.0;
		double scale_combo_strength =clt_parameters.imp.scale_combo_strength; //0.4; // reduce strength when it comes from combo, not DSI-MAIN
		
		boolean clouds_en =          clt_parameters.imp.clouds_en;     	  //   true; // enable clouds in the sky detection / processing
		double  clouds_fom =         clt_parameters.imp.clouds_fom;       //   30.0; // maximal FOM for clouds (must be <=)         
		double  clouds_spread =      clt_parameters.imp.clouds_spread;    //   60.0; // maximal spread for clouds (must be <=)         
		double  clouds_disparity =   clt_parameters.imp.clouds_disparity; //    0.1; // maximal disparity for strong clouds         
		double  clouds_weak =        clt_parameters.imp.clouds_weak;      //  0.18;// maximal strength for near definite clouds         		
		double  clouds_strength =    clt_parameters.imp.clouds_strength;  //  0.25;// minimal strength for far strong clouds (definitely cloud)         		
		double  clouds_not_strength= clt_parameters.imp.clouds_not_strength;//0.4;  // maximal strength for near maybe clouds (if it has strong cloud neighbor)         		
		boolean clouds_strong =      clt_parameters.imp.clouds_strong;    // true; // allow weak cloud if it has strong (1.5x) cloud neib

		// process smooth walls mistaken for sky (disable for natural environments)
		boolean  wall_en =           clt_parameters.imp.wall_en;          // true;  // enable smooth walls detection/processing
		boolean  wall_dflt =         clt_parameters.imp.wall_dflt;        // true;  // default (undetected) is wall (false - sky)
		double   wall_str =          clt_parameters.imp.wall_str;         // 0.1;   // minimal strength of the far object (small - just non-NaN disparity)         
		double   wall_far =          clt_parameters.imp.wall_far;         // 0.2;   // maximal disparity to consider cluster to be sky         
		double   wall_near =         clt_parameters.imp.wall_near;        // 1.0;   // minimal disparity to consider cluster to be wall         

		// processing far treeline that may be confused with clouds. Only behind far objects such as far horizontal surface.
	    boolean  treeline_en =       clt_parameters.imp.treeline_en;   	  //  true;  // enable treeline processing
	    boolean  treeline_wide =     clt_parameters.imp.treeline_wide;    //  true;  // enable treeline processing
	    int      treeline_height =   clt_parameters.imp.treeline_height;  //  5;    // maximal height of the treeline (tiles)
	    int      treeline_width =    clt_parameters.imp.treeline_width;   //  3;    // minimal horizontal width of the treeline (tiles)
	    boolean  treeline_lim_high = clt_parameters.imp.treeline_lim_high;//  false; // limit too high treeline (false - delete completely)
	    double   treeline_str =      clt_parameters.imp.treeline_str;     //  0.8;  // treeline minimal strength 
	    double   treeline_far =      clt_parameters.imp.treeline_far;     //  0.04; // treeline min disparity (pix)
	    double   treeline_near =     clt_parameters.imp.treeline_near;    //  0.4;  // treeline max disparity (pix)
	    double   treeline_fg_str =   clt_parameters.imp.treeline_fg_str;  //  0.8;  // pre-treeline FG objects (such as flat ground) minimal strength 
	    double   treeline_fg_far =   clt_parameters.imp.treeline_fg_far;  //  0.2;  // pre-treeline FG objects  min disparity (pix)
	    double   treeline_fg_near =  clt_parameters.imp.treeline_fg_near; //  0.5;  // pre-treeline FG objects  max disparity (pix)
    	boolean sky_extract =        clt_parameters.imp.sky_extract;

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

		QuadCLT   dbg_scene = clt_parameters.imp.save_debug_images? quadCLT_ref: null; // use to save debug images if not null
		// if (build_ref_dsi) {
		// need to read photometric from reference scene -INTERFRAME.corr-xml, and if it exists - set and propagate to main?
		if (photo_each  && !quadCLT_ref.isPhotometricThis()) {
			if (debugLevel > -3) {
				System.out.println("**** Per-sequence photogrammetric calibration is required, but it does not exist ***");
			}
			if (photo_to_main) {
				quadCLT_main.setLwirOffsets(quadCLT_ref.getLwirOffsets());
				quadCLT_main.setLwirScales (quadCLT_ref.getLwirScales ());
				quadCLT_main.setLwirScales2(quadCLT_ref.getLwirScales2());
				quadCLT_main.setPhotometricScene(quadCLT_ref.getPhotometricScene());
				if (debugLevel > -3) {
					System.out.println("Propagating per-sequence photogrammetric calibration to main instance, will apply to next sequence and config file");
				}
			}
		} else {
			System.out.println("Without building reference DSI: (re)using photometric calibration from this sequence reference "+quadCLT_ref.getPhotometricScene());
		}
		// read DSI_MAIN
		double [][] dsi = quadCLT_ref.readDsiMain();
		if (dsi != null) {
			quadCLT_ref.setDSI(dsi); // was not here! (11/26/2022)
		} else {
			quadCLT_ref.readComboDSI(true);
			dsi = quadCLT_ref.dsi;
		}
		double [][] combo_dsi = null;
		if ((dsi[TwoQuadCLT.DSI_SPREAD_AUX] == null) && (!quadCLT_ref.hasCenterClt())) {
			System.out.println("DSI_MAIN file has old format and does not have spread data, no center/cuas, will recalculate.");
		} else {
			if ((dsi[TwoQuadCLT.DSI_BLUE_SKY_AUX] == null) || sky_recalc) { //
				// Sets quadCLT_ref.dsi and blue sky (if exists)
				combo_dsi = quadCLT_ref.restoreComboDSI(true); // result is full length, missing slices are null
				if (combo_dsi != null) {
					dsi[TwoQuadCLT.DSI_STRENGTH_AUX] =      combo_dsi[COMBO_DSN_INDX_STRENGTH];     // double [] strength,
					dsi[TwoQuadCLT.DSI_DISPARITY_AUX_LMA]=  combo_dsi[COMBO_DSN_INDX_LMA]; //double [] disp_lma,
					dsi[TwoQuadCLT.DSI_BLUE_SKY_AUX] =      combo_dsi[COMBO_DSN_INDX_BLUE_SKY]; // if exist, already set to quadCLT_ref.dsi 
				}
			}
			double [] ref_blue_sky = dsi[TwoQuadCLT.DSI_BLUE_SKY_AUX];
			if ((ref_blue_sky == null) || sky_recalc) {
				if (debugLevel > -3) {
					System.out.println("Blue Sky does not exist or recalculation is forced. Calculating and updating photometrics");
				}
				if (sky_extract) { 
					double [] bs_strength = dsi[TwoQuadCLT.DSI_STRENGTH_AUX].clone();
					// try getting combo dsi
					if ((combo_dsi != null) && (scale_combo_strength != 1.0) && dsi[TwoQuadCLT.DSI_STRENGTH_AUX]!= null) {
						for (int i = 0; i < dsi[TwoQuadCLT.DSI_STRENGTH_AUX].length; i++) {
							bs_strength[i] *= scale_combo_strength;
						}
						//scale_combo_strength
					}
					quadCLT_ref.setBlueSky  (
							max_disparity,
							max_disparity_strength,
							sky_seed,                              // double sky_seed, //  =       7.0;  // start with product of strength by diff_second below this
							lma_seed,                              //          2.0;  // seed - disparity_lma limit
							seed_temp,     //double seed_temp, //         0.5;  // seed colder that this point between min and max temp						
							sky_lim,                               // double sky_lim, //   =      15.0; // then expand to product of strength by diff_second below this
							lim_temp, //						double lim_temp, //          0.5;  // sky colder that this point between min and max temp						
							sky_shrink,                            // int    sky_shrink, //  =       4;
							sky_expand_extra,                      // int    sky_expand_extra, //  = 100; // 1?
							sky_bottleneck,       //int    sky_bottleneck, // 
							sky_reexpand_extra,   //int    sky_reexpand_extra,  // 9; re-expand after bottleneck in addition to how it was shrank
							cold_scale, // =       0.2;  // <=1.0. 1.0 - disables temperature dependence
							cold_frac, // =        0.005; // this and lower will scale fom by  cold_scale
							hot_frac, // =         0.9;    // this and above will scale fom by 1.0
							min_strength, // =     0.08;
							seed_rows, // =        5; // sky should appear in this top rows
							lowest_sky_row,        //  =     50;// appears that low - invalid, remove completely
							sky_bottom_override,   // double sky_temp_override,     // really cold average seed - ignore lowest_sky_row filter
							sky_override_shrink,   // int    shrink_for_temp,       // shrink before finding hottest sky
							sky_highest_min,       //  =   100; // lowest absolute value should not be higher (requires photometric)

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

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

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

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

							disp_boost_min,        // double disp_boost_min,    //  = 0.5;
							disp_boost_diff,       //double disp_boost_diff,   //  = 0.35;
							disp_boost_neibs,      //int    disp_boost_neibs,  //  = 2;
							disp_boost_amount,     //double disp_boost_amount, //  = 2.0;
							bs_strength, // dsi[TwoQuadCLT.DSI_STRENGTH_AUX],      // double [] strength,
							dsi[TwoQuadCLT.DSI_SPREAD_AUX],        // double [] spread,
							dsi[TwoQuadCLT.DSI_DISPARITY_AUX_LMA], //double [] disp_lma,
							dsi[TwoQuadCLT.DSI_AVGVAL_AUX],//	double [] avg_val,
							dbg_scene,          // QuadCLT   dbg_scene,    // use to save debug images if not null
							debugLevel);        // int debugLevel)
					if (debugLevel > -3) {
						System.out.println("Calculated missing Blue Sky in "+quadCLT_ref.getImageName()+
								", re-running photoEach()");
					}
				} else {
					quadCLT_ref.setBlueSky (new double [quadCLT_ref.tp.getTilesX()*quadCLT_ref.tp.getTilesY()]);
					if (debugLevel > -3) {
						System.out.println("Used zeros for the missing Blue Sky in "+quadCLT_ref.getImageName()+
								", not re-running photoEach()");
					}

				}
				// save inter-lma if available
				if (combo_dsi != null) {
					if (debugLevel > -3) {
						System.out.println("Updating Blue Sky for scene "+quadCLT_ref.getImageName());
					}
					String rslt_suffix = QuadCLTCPU.DSI_SUFFIXES[clt_parameters.correlate_lma?
							QuadCLTCPU.INDEX_INTER_LMA:QuadCLTCPU.INDEX_INTER];
					// combo_dsi read from file always has all slices, missing are null 
					combo_dsi[COMBO_DSN_INDX_BLUE_SKY] = quadCLT_ref.dsi[TwoQuadCLT.DSI_BLUE_SKY_AUX];      
					quadCLT_ref.saveDoubleArrayInModelDirectory(
							rslt_suffix,                  // String      suffix,
							OpticalFlow.COMBO_DSN_TITLES, // null,          // String []   labels, // or null
							combo_dsi,                        // dbg_data,         // double [][] data,
							quadCLT_ref.tp.getTilesX(),   // tilesX,                // int         width,
							quadCLT_ref.tp.getTilesY());  // int         height)
				}
				if (sky_extract) { 
					photoEach(
							clt_parameters,      // CLTParameters                                 clt_parameters,
							colorProcParameters, // ColorProcParameters                           colorProcParameters,
							quadCLT_main,        // QuadCLT                                       quadCLT_main, // tiles should be set
							quadCLT_ref,         // QuadCLT                                       quadCLT_ref, // tiles should be set
							dsi,                 // final double [][]                             dsi,
							// just once?
							clt_parameters.photo_num_full, // int      photo_num_full =              
							batch_mode,          // final boolean                                 batch_mode,
							threadsMax,          // final int                                     threadsMax,  // int               threadsMax,
							updateStatus,        // final boolean                                 updateStatus,
							debugLevel);         // final int                                     debugLevel)	
				}
			} else {
				if (debugLevel > -3) {
					System.out.println("Blue Sky is available, reusing it, no need for updating photometrics");
				}
				quadCLT_ref.setBlueSky(ref_blue_sky); // fixing old case where DSI-MAIN did not have bs while combo - did
			}
		}
		return;
	}

	public static void runPhotometric(
			CLTParameters       clt_parameters,
			ColorProcParameters colorProcParameters,
    		String              set_name,
			QuadCLT[]           quadCLTs,
			int                 earliest_scene,
			int                 last_scene,
			int                 ref_index,
			double [][]         combo_dsn_final_filtered,
			int                 threadsMax,  // maximal number of threads to launch
			boolean             updateStatus,
			int                 debugLevel) {
    	int      photo_num_full =            clt_parameters.photo_num_full; //     1;    // Number of full recalibrations with re-processing of the images  
    	int      photo_num_refines =         clt_parameters.photo_num_refines; //  3;    // Calibrate, remove outliers, recalibrate, ... 
    	int      photo_min_good =            clt_parameters.photo_min_good;    //  1000;    // Minimal number of good pixels for photometric calibration 
    	double   photo_min_strength =        clt_parameters.photo_min_strength; // 0.0;  // maybe add to filter out weak tiles
    	double   photo_max_diff =            clt_parameters.photo_max_diff; //    40.0;  // To filter mismatches. Normal (adjusted) have RMSE ~9
    	int      photo_order =               clt_parameters.photo_order;
    	double   photo_std_1 =               clt_parameters.photo_std_1; //  50.0;   // Minimal standard deviation of the filtered values for poly order 1
    	double   photo_std_2 =               clt_parameters.photo_std_2; // 200.0;   // Minimal standard deviation of the filtered values for poly order 2
    	int      photo_offs_set =            clt_parameters.photo_offs_set; //  0;    // 0 - keep weighted offset average, 1 - balance result image, 2 - set weighted average to specific value
    	double   photo_offs =                clt_parameters.photo_offs;     // 21946; // weighted average offset target value, if photo_offs_set (and not photo_offs_balance)
    	boolean  photo_debug =               clt_parameters.photo_debug; //       false; // Generate images and text
//    	boolean [] ref_blue_sky = quadCLTs[ref_index].getBlueSky(); // null; // turn off "lma" in the ML output
    	double [] ref_double_blue_sky = quadCLTs[ref_index].getDoubleBlueSky(); // null; // turn off "lma" in the ML output

		for (int nrecalib = 0; nrecalib < photo_num_full; nrecalib++) {
			int poly_order = photo_order;
			if ((poly_order > 1) && (nrecalib == 0)) {
				poly_order = 1;
			}
			QuadCLT.calibratePhotometric2(
					clt_parameters,           // CLTParameters     clt_parameters,
					quadCLTs[ref_index],      // final QuadCLT     ref_scene, will set photometric calibration to this scene
					photo_min_strength,       // final double      min_strength,
					photo_max_diff,           // final double      max_diff,    // 30.0
					poly_order,               // final int         photo_order, // 0 - offset only, 1 - linear, 2 - quadratic
					photo_std_1,  // final double      photo_std_1,  //  50.0;   // Minimal standard deviation of the filtered values for poly order 1
					photo_std_2,  // final double      photo_std_2,	// 200.0;   // Minimal standard deviation of the filtered values for poly order 2
					photo_offs_set, // final int    photo_offs_set,// 0;     // 0 - keep weighted offset average, 1 - balance result image, 2 - set weighted average to specific value
					photo_offs,     // final double photo_offs,    // 21946; // weighted average offset target value, if photo_offs_set (and not photo_offs_balance)
					photo_num_refines,        // final int         num_refines, // 2
				    photo_min_good, // final int         min_good,     // minimal number of "good" pixels
					combo_dsn_final_filtered, // final double [][] combo_dsn_final,     // double [][]    combo_dsn_final, // dls,
					threadsMax,               // int               threadsMax,
					photo_debug);             //final boolean     debug)
			// copy offsets to the current to be saved with other properties. Is it correct/needed?
			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);
			quadCLTs[ref_index].setBlueSky(ref_double_blue_sky); // ref_blue_sky); Is it needed??????
			quadCLTs[ref_index].setDSRBG(
					clt_parameters, // CLTParameters  clt_parameters,
					threadsMax,     // int            threadsMax,  // maximal number of threads to launch
					updateStatus,   // boolean        updateStatus,
					debugLevel);    // int            debugLevel)
///			ErsCorrection ers_reference = quadCLTs[ref_index].getErsCorrection();
		}
		// propagate to other scenes in this sequence
		for (int scene_index =  last_scene; scene_index >= earliest_scene ; scene_index--) if (scene_index != ref_index){
			//						quadCLTs[scene_index] = (QuadCLT) quadCLT_main.spawnNoModelQuadCLT( // restores image data
			// to include ref scene photometric calibration
			/*
			quadCLTs[scene_index] = quadCLTs[ref_index].spawnNoModelQuadCLT( // restores image data
					set_name,
					clt_parameters,
					colorProcParameters, //
					threadsMax,
					debugLevel-2);
			*/
			// keep original names and whole instances
			quadCLTs[scene_index].restoreNoModel(
					clt_parameters,
					colorProcParameters,
					null,                 // double []    noise_sigma_level,
					-1,                   // noise_variant, // <0 - no-variants, compatible with old code
					null,                 // final QuadCLTCPU     ref_scene, // may be null if scale_fpn <= 0
					threadsMax,
					debugLevel-2);
		}
	}
	
	
	/**
	 * 
	 * @param batch_mode
	 * @param quadCLT_main
	 * @param last_index
	 * @param clt_parameters
	 * @param colorProcParameters
	 * @param channelGainParameters
	 * @param rgbParameters
	 * @param equirectangularParameters
	 * @param properties
	 * @param reset_from_extrinsics
	 * @param videos
	 * @param stereo_widths
	 * @param start_ref_pointers sho
	 * @param threadsMax
	 * @param updateStatus
	 * @param debugLevel
	 * @return
	 * @throws Exception
	 */
    public String buildSeries(
    		boolean                                              batch_mode,
    		int                                                  operation_mode, // 0 - default, 1 - prepare LY (only works for initial orientation)
    		QuadCLT[]                                            index_scenes,
			QuadCLT                                              quadCLT_main, // tiles should be set
			int                                                  last_index, // -1 - last
			CLTParameters                                        clt_parameters,
			ColorProcParameters                                  colorProcParameters,
			CorrectionColorProc.ColorGainsParameters             channelGainParameters,
			EyesisCorrectionParameters.RGBParameters             rgbParameters,
			EyesisCorrectionParameters.EquirectangularParameters equirectangularParameters,
			Properties                                           properties,
			boolean                                              reset_from_extrinsics,
			String [][]                                          videos, // null or String[1][] list of generated avi or webm paths
			int [][]                                             stereo_widths, // null or int[1][] matching videos -
			int []                                               start_ref_pointers, // [0] - earliest valid scene, [1] ref_index 01/21/2026 - added third element - last pointer, avoiding relative -1.
			                                                     // each element is 0 for non-stereo and full width for stereo 
			String []                                            cuas_centers, // [0] - cumulative input, [1] - cumulative output
			// processing UAS logs
			UasLogReader                                         uasLogReader,
			int []                                               build_series_result,
			final int        threadsMax,  // maximal number of threads to launch
			final boolean    updateStatus,
			final int        debugLevel)  throws Exception
    {
		// Read configuration parameters from the index scene and overwrite those in quadCLT_main, to use the latest used
		// when the index scene parameters were used. If ((index_scenes[0] == null), use the QuadCLT[(QuadCLT.length +) last_index] ???
    	// and replace all clt_parameters with those read from the file

    	
    	
    	int earliest_scene = 0;      // increase on failure
    	boolean sky_extract =                clt_parameters.imp.sky_extract; //   11/22/2025 exiting loop for downward footage (no sky) 	
    	boolean center_reference =           clt_parameters.imp.center_reference;
    	boolean reset_photometric =          clt_parameters.imp.reset_photometric;
    	boolean build_ref_dsi =              clt_parameters.imp.force_ref_dsi;
    	boolean force_initial_orientations = clt_parameters.imp.force_orientations ;
    	boolean run_ly =                     clt_parameters.imp.run_ly; // run LY adjust and exit
    	boolean force_ly =                   clt_parameters.imp.force_ly; // calculate field calibration for this series even if it was calibrated earlier
    	boolean continue_ly =                clt_parameters.imp.continue_ly; // re-run series after performing LY correction (and rename version subdirectory)
    	int run_ly_mode=                     clt_parameters.imp.run_ly_mode; //  3;     // +1 - lazy eye, +2 - infinity 
    	boolean run_ly_ims =                 clt_parameters.imp.run_ly_ims; // adjust infinity (if enabled) using horizontal movement from the IMS
    	boolean match_ims_timing =           clt_parameters.imp.match_ims_timing; // // only used when manually matching ims with images (sync lost). Fine-tuning offset by rotations
    	int min_num_interscene =             clt_parameters.imp.min_num_interscene; //  2; // make from parameters, should be >= 1
    	int min_num_orient =                 clt_parameters.imp.min_num_orient;    //  2; // make from parameters, should be >= 1
    	boolean sfm_only =                   clt_parameters.imp.sfm_only; // process only data with SfM
    	boolean export_images =              clt_parameters.imp.export_images;
    	boolean export_dsi_image =           clt_parameters.imp.export_ranges;
    	boolean debug_ranges =               clt_parameters.imp.debug_ranges && !batch_mode;
    	boolean export_ml_files =            clt_parameters.imp.export_ml_files;

    	boolean  photo_en =                  clt_parameters.photo_en; //          false; // perform photogrammetric calibration to equalize pixel values
    	
    	boolean show_dsi_image =             clt_parameters.imp.show_ranges && !batch_mode;
    	boolean show_images =                clt_parameters.imp.show_images && !batch_mode;
//    	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 
    	
    	boolean show_images_bgfg =           clt_parameters.imp.show_images_bgfg && !batch_mode;
    	boolean show_images_mono =           clt_parameters.imp.show_images_mono && !batch_mode;

		double  range_disparity_offset =     clt_parameters.imp.range_disparity_offset ; //   -0.08;
		double  range_min_strength =         clt_parameters.imp.range_min_strength ; // 0.5;
		double  range_max =                  clt_parameters.imp.range_max ; //  5000.0;
		
		boolean [] save_mapped_mono_color =  {clt_parameters.imp.save_mapped_mono, clt_parameters.imp.save_mapped_color};
		boolean [] gen_avi_mono_color =      {clt_parameters.imp.gen_avi_mono, clt_parameters.imp.gen_avi_color};
		boolean [] show_mono_color =        {
				clt_parameters.imp.show_mapped_mono && !batch_mode,
				clt_parameters.imp.show_mapped_color && !batch_mode};
		
		boolean [] gen_seq_mono_color =  {
				save_mapped_mono_color[0]  || gen_avi_mono_color[0] || show_mono_color[0],
				save_mapped_mono_color[1]  || gen_avi_mono_color[1] || show_mono_color[1]} ;
		// skip completely if no color or mono, tiff or video
		boolean adjust_imu_orient =          clt_parameters.imp.adjust_imu_orient; // calculate camera orientation correction from predicted by IMS
		boolean calc_quat_corr =             clt_parameters.imp.calc_quat_corr; // calculate camera orientation correction from predicted by IMS
		// apply_quat_corr used inside getGroundIns() - used when generating output
//		boolean apply_quat_corr =            clt_parameters.imp.apply_quat_corr; // apply camera orientation correction from predicted by IMS
		boolean use_ims_rotation =           clt_parameters.imp.use_quat_corr;   // use internally (probably deprecated - not)
		boolean inertial_only =              clt_parameters.imp.inertial_only;   // use internally
		
		boolean generate_egomotion =         clt_parameters.imp.generate_egomotion; // generate egomotion table (image-based and ims)
		boolean generate_mapped =            clt_parameters.imp.generate_mapped &&
				(gen_seq_mono_color[0] || gen_seq_mono_color[1]);   // generate sequences - Tiff and/or video
		boolean export3d =                   clt_parameters.imp.export3d; //  true;
		boolean export3dterrain =            clt_parameters.imp.export3dterrain; //  true;
		boolean export_vegetation=           clt_parameters.imp.export_vegetation;

		boolean export_CT =                  clt_parameters.imp.export_CT && !clt_parameters.imp.lock_position; //  false;
		double  ct_min =                     clt_parameters.imp.ct_min ;
		double  ct_max =                     clt_parameters.imp.ct_max ;
		double  ct_step =                    clt_parameters.imp.ct_step ;
		int     ct_expand =                  clt_parameters.imp.ct_expand ;
		
		boolean [] annotate_mono_color = {clt_parameters.imp.annotate_mono,clt_parameters.imp.annotate_color}; 
		boolean annotate_transparent_mono = clt_parameters.imp.annotate_transparent_mono;
		
		
		
		
		if ((index_scenes[0] != null) && (LogTee.getSceneLog()==null)) {
			index_scenes[0].startLogging();
		}
		
		
		boolean [] generate_modes3d = {
				clt_parameters.imp.generate_raw,
				clt_parameters.imp.generate_inf,
				clt_parameters.imp.generate_fg,
				clt_parameters.imp.generate_bg};
		boolean generate_stereo =            clt_parameters.imp.generate_stereo;
		
		double [][] stereo_views =       clt_parameters.imp.stereo_views; // {0.0, 200.0, 500.0, 1000.0}; 
		boolean [] generate_stereo_var = clt_parameters.imp.generate_stereo_var;
		
		boolean stereo_merge =       clt_parameters.imp.stereo_merge;
		boolean anaglyth_en =        clt_parameters.imp.anaglyth_en;
		final Color anaglyph_left =  clt_parameters.imp.anaglyph_left;
		final Color anaglyph_right = clt_parameters.imp.anaglyph_right;
		
		int     stereo_gap =         clt_parameters.imp.stereo_gap;
		
		int     extra_hor_tile =     clt_parameters.imp.extra_hor_tile;
		int     extra_vert_tile =    clt_parameters.imp.extra_vert_tile;
		boolean crop_3d =            clt_parameters.imp.crop_3d;
		int     sensor_mask =        clt_parameters.imp.sensor_mask; // -1 - all
		// video
		double  video_fps =          clt_parameters.imp.video_fps;
		int     mode_avi =           clt_parameters.imp.mode_avi;
		int     avi_JPEG_quality =   clt_parameters.imp.avi_JPEG_quality; // 90;
	    boolean run_ffmpeg =         clt_parameters.imp.run_ffmpeg;
		String  video_ext =          clt_parameters.imp.video_ext;
		String  video_codec =        clt_parameters.imp.video_codec.toLowerCase();
		int     video_crf =          clt_parameters.imp.video_crf;
		double  video_bitrate_m =    clt_parameters.imp.video_bitrate_m;
		
		boolean remove_avi =         clt_parameters.imp.remove_avi;
		boolean um_mono =            clt_parameters.imp.um_mono;
		double  um_sigma =           clt_parameters.imp.um_sigma;
		double  um_weight =          clt_parameters.imp.um_weight;
		boolean um_mono_linear=      clt_parameters.imp.um_mono_linear;
		boolean mono_fixed =         clt_parameters.imp.mono_fixed;
		double  mono_range =         clt_parameters.imp.mono_range;
		
		boolean reuse_video =        clt_parameters.imp.reuse_video &&
				(gen_seq_mono_color[0] || gen_seq_mono_color[1]);   // generate sequences - Tiff and/or video

		
		final Color annotate_color_color = clt_parameters.imp.annotate_color_color;
		final Color annotate_color_mono =  clt_parameters.imp.annotate_color_mono;
		
		boolean  test_ers =          clt_parameters.imp.test_ers && !batch_mode;
		int     test_ers0 =          clt_parameters.imp.test_ers0; // try adjusting a pair of scenes with ERS. Reference scene index
		int     test_ers1 =          clt_parameters.imp.test_ers1; // try adjusting a pair of scenes with ERS. Other scene index
		test_ers &= (test_ers0 >= 0) && (test_ers1 >= 0);
		boolean use_combo_reliable = clt_parameters.imp.use_combo_reliable;
		boolean ref_need_lma =       clt_parameters.imp.ref_need_lma;
		boolean ref_need_lma_combo = clt_parameters.imp.ref_need_lma_combo;
		double  min_ref_str =        clt_parameters.imp.min_ref_str;
		double  min_ref_str_lma =    clt_parameters.imp.min_ref_str_lma;

		boolean sfm_filter =         clt_parameters.imp.sfm_filter & !clt_parameters.imp.lock_position;  //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_minabs  =        clt_parameters.imp.sfm_minabs; //5.0;  // SFM gain threshold to use min_ref_str_sfm as minimal reliable strength
		double  sfm_fracall =        clt_parameters.imp.sfm_fracall; // 0.3;    // minimal relative area of the SfM-enabled tiles (do not apply filter if less)	
		double  min_ref_str_sfm =    clt_parameters.imp.min_ref_str_sfm; // 0.1;  // reliable strength when sfm gain is available
		
		double min_ref_frac=         clt_parameters.imp.min_ref_frac;
		
		boolean cuas_subtract_fpn =  clt_parameters.imp.cuas_subtract_fpn;
		
		boolean cuas_subtract_rowcol=clt_parameters.imp.cuas_subtract_rowcol;// Subtract row/column noise
		
		boolean cuas_debug =         clt_parameters.imp.cuas_debug;  // save debug images (and show them if not in batch mode)
		boolean cuas_step_debug =    clt_parameters.imp.cuas_step_debug;
		boolean cuas_reset_first=    clt_parameters.imp.cuas_reset_first && (index_scenes[0] == null); // first_in_series;
		
		boolean combine_clt =        clt_parameters.imp.cuas_rotation; // (cuas_atr[0] != 0) || (cuas_atr[1] != 0) || (cuas_atr[2] != 0);
		boolean update_existing =    clt_parameters.imp.cuas_update_existing; // re-create center_CLT if it exists (FIXME: accumulates errors - need fixing)
		
		double [] ref_blue_sky = null; // turn off "lma" in the ML output  
		if (reuse_video) { // disable all other options
			generate_mapped = false;
			export_images = false;
			debug_ranges = false;
			export_dsi_image = false;
			show_dsi_image = false;
			export_ml_files = false;
			test_ers = false;
		}
		
		ArrayList<String> video_list = new ArrayList<String>();
		ArrayList<Integer> stereo_widths_list = new ArrayList<Integer>();
		if ((quadCLT_main != null) && (quadCLT_main.getGPU() != null)) {
			quadCLT_main.getGPU().resetGeometryCorrection();
			quadCLT_main.gpuResetCorrVector(); // .getGPU().resetGeometryCorrectionVector();
		}
		// final boolean    batch_mode = clt_parameters.batch_run;
		this.startTime=System.nanoTime();
		String [] sourceFiles0=quadCLT_main.correctionsParameters.getSourcePaths();
		SetChannels [] set_channels_main = quadCLT_main.setChannels(debugLevel);
		if ((set_channels_main == null) || (set_channels_main.length==0)) {
			System.out.println("buildSeriesTQ(): No files to process (of "+sourceFiles0.length+")");
			return null;
		}
		// set_channels will include all 99 scenes even as quadCLTs.length matches ref_index
		SetChannels [] set_channels=quadCLT_main.setChannels(debugLevel); 
		if (last_index < 0) {
			last_index += set_channels.length; 
		}
		if (start_ref_pointers != null) {
			start_ref_pointers[0] = 0;
			start_ref_pointers[1] = last_index;
			start_ref_pointers[2] = last_index; // this will not be modified anymore - just to calculate smaller overlap by the caller
		}
		String [] scene_names = new String[ set_channels_main.length];
		for (int i = 0; i < scene_names.length; i++) {
			scene_names[i] = set_channels_main[i].name();
		}
		Arrays.sort(scene_names);
		QuadCLT [] quadCLTs = new QuadCLT [last_index+1]; //  [set_channels.length];
		
		//start_index
		double [][][] scenes_xyzatr =      new double [quadCLTs.length][][]; // previous scene relative to the next one
		scenes_xyzatr[last_index] = new double[2][3]; // all zeros
		boolean ims_use =      center_reference || clt_parameters.imp.ims_use; // center works with IMS only
		boolean use_cuas = clt_parameters.imp.cuas_rotation && ims_use; // lock_position & ims_use; // needs
    	int     min_num_scenes =    use_cuas ? ((int) clt_parameters.imp.cuas_min_series): clt_parameters.imp.min_num_scenes; // abandon series if there are less than this number of scenes in it
		if ((last_index - earliest_scene + 1) < min_num_scenes) {
			System.out.println("buildSeries(): scene series is too short ("+(last_index+1)+" scenes, minimal number is "+min_num_scenes+". Skipping this series");
			if (start_ref_pointers != null) {
				start_ref_pointers[0] = 0; // earliest_scene;
			}
			if (cuas_centers != null) {
				cuas_centers[1] = cuas_centers[0];
			}
			return null;
		}
		
		
    	
		
		QuadCLT center_CLT = null; // used for CUAS - rotation center
		// See if build_ref_dsi is needed
		if (!build_ref_dsi) {
			// Added 03.25.26 - for cuas ended up w/o jp4 copy, when processing the second sequence
			TwoQuadCLT.copyJP4src( // actually there is no sense to process multiple image sets. Combine with other
					// processing?
					set_channels[last_index].set_name, // String set_name
					quadCLT_main, // QuadCLT quadCLT_main,
					null, // QuadCLT quadCLT_aux,
					null, // QuadCLT quadCLT_this,
					clt_parameters, // EyesisCorrectionParameters.DCTParameters dct_parameters,
					true, // boolean                                  skip_existing,
					true, // false, // boolean                                  search_KML,
					debugLevel);
			// try reading full
			quadCLTs[last_index] = (QuadCLT) quadCLT_main.spawnNoModelQuadCLT( // will conditionImageSet copies quat_corr from this
					set_channels[last_index].set_name,
					clt_parameters,
					colorProcParameters, //
					threadsMax,
					debugLevel-2);
			String parent_clt_name = null;
			if (cuas_centers != null) {
				parent_clt_name = cuas_centers[0]; // may be ==""
			}
			if (use_cuas  && (cuas_centers != null)) { // (parent_clt_name != null)) { //  && (parent_clt_name.length() > 0)) {
				System.out.println("cuas mode, verifying that all scenes have files copied - optimize"); // multithreaded?
				int [] scene_range = {0, last_index - 1}; // all but last
				TwoQuadCLT.copyJP4src(
						set_channels,   // final SetChannels []                     set_channels,
						scene_range,    // final int []                             range, // [earlies, latest]
						quadCLT_main,   // final QuadCLT                            quadCLT_main,  // tiles should be set
						null,           // final QuadCLT                            quadCLT_aux,
						clt_parameters, // final CLTParameters                      clt_parameters,
						true, // boolean                                  skip_existing,
						true, // false, // boolean                                  search_KML,
						debugLevel);
				double []   dbg_weights = cuas_debug ? new double [quadCLTs[last_index].getTilesX() * quadCLTs[last_index].getTilesY()] : null;
				String parent_or_null = ((parent_clt_name != null) && (parent_clt_name.length()>0)) ? parent_clt_name : null;  
				boolean         ignore_this =    (parent_or_null != null) ? clt_parameters.imp.cuas_reset_first : false;
				boolean         use_parent_dsi = (parent_or_null != null) ? clt_parameters.imp.cuas_dsi_parent :  false;
				center_CLT = QuadCLT.restoreCenterClt(// null
						clt_parameters,       // CLTParameters                                 clt_parameters,
						ignore_this,          // cuas_reset_first, // boolean         ignore_this,
						use_parent_dsi,       // boolean         use_parent_dsi,
						quadCLTs[last_index], // ref_index]);
						parent_or_null,       // test_path); // String full_path); // if non-null, use it instead of ref_clt
						false,                // boolean         single_first, // true: read single first, cumulative second 
						dbg_weights,          // double []   dbg_weights, // should be null or double [tilesX*tilesY] - will return debug weight ratio (new to cumul)
						debugLevel);          // int             debugLevel);
				// see if it was able to get
				
				if (center_CLT != null) {
					if (cuas_debug) { // show_clt && !clt_parameters.batch_run) {
						ImagePlus imp_weights = ShowDoubleFloatArrays.makeArrays( // all 0-s
								dbg_weights,
								quadCLTs[last_index].getTilesX(),
								quadCLTs[last_index].getTilesY(),
								quadCLTs[last_index].getImageName()+"-restoreCenterClt-weights");
						if (imp_weights != null) {
							String suffix ="-RESTORE_CENTER_CLT-WEIGHTHS";
							center_CLT.saveImagePlusInModelDirectory(
									suffix,          // String      suffix, // null - use title from the imp
									imp_weights); // ImagePlus   imp)
							if (!clt_parameters.batch_run) {
								imp_weights.show();
							}
						}
					}
					
//					double [][] image_center = center_CLT.convertCenterClt(null); // when null, uses center_CLT.center_clt
					double [][] image_center = center_CLT.convertCenterClt(
							null, // when null, uses center_CLT.center_clt
							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;
							
//					System.out.print("buildSeries(): ");
					center_CLT.setImageCenter(image_center);
					boolean show_image_center = cuas_debug && !clt_parameters.batch_run;
					if (show_image_center) { // restored, not new!
						ShowDoubleFloatArrays.showArrays(
								image_center[0],
								center_CLT.getTilesX() * center_CLT.getTileSize(),
								center_CLT.getTilesY() * center_CLT.getTileSize(),
								center_CLT.getImageName()+"-CENTER-AVG");
					}
					
					
					if (cuas_debug) { // show_clt && !clt_parameters.batch_run) {
						ImagePlus imp_center_clt= center_CLT.showCenterClt(
			    				null, // float [][] fclt, // may be null
								clt_parameters,            // CLTParameters clt_parameters,
								!clt_parameters.batch_run); // true);          // 						boolean       show);
						if (imp_center_clt != null) {
							String suffix ="-CLT-RESTORED";
							center_CLT.saveImagePlusInModelDirectory(
									suffix,          // String      suffix, // null - use title from the imp
									imp_center_clt); // ImagePlus   imp)
						}
					}
				}
			}
			
			if ((center_CLT != null) && center_CLT.hasCenterClt()) {
				Properties prop = center_CLT.restoreInterProperties( // restore properties for interscene processing (extrinsics, ers, ...) // get relative poses (98)
						null, // String path,             // full name with extension or null to use x3d directory
						false, // boolean all_properties,//				null, // Properties properties,   // if null - will only save extrinsics)
						debugLevel);
				// TODO:
				// if (prop == null) - failed to restore. Maybe OK, will need minimum (later will provide center_CLT as input)
			} else { // below, in cuas mode quadCLTs[last_index] is created, but no DSI data, it needs build_dsi
				if (center_reference && (quadCLTs[last_index] != null) && quadCLTs[last_index].dsiExists()) { // 07.13.2025: added quadCLTs[last_index].dsiExists() 
					quadCLTs[last_index].restoreInterProperties( // restore properties for interscene processing (extrinsics, ers, ...) // get relative poses (98)
							null, // String path,             // full name with extension or null to use x3d directory
							false, // boolean all_properties,//				null, // Properties properties,   // if null - will only save extrinsics)
							debugLevel);
					int center_index =quadCLTs[last_index].getReferenceIndex(new QuadCLT[] {quadCLTs[last_index]}); // null); self, may return 0
					int [] first_last_index = quadCLTs[last_index].getFirstLastIndex(new QuadCLT[] {quadCLTs[last_index]});				
					if ((center_index == -1) || (center_index == 0)) { // 0 - was self-referencing, TODO: debug, should not be
						// -2 - reference somewhere, not to itself
						// -1 - no reference to a center
						// 0 - reference
						// with overlap it may already have *-DSI_MAIN or even *-INTER-INTRA-LMA.tiff
						//					int [] first_last_index = quadCLTs[last_index].getFirstLastIndex(quadCLTs);
						if (first_last_index == null) {
							build_ref_dsi = true;
						} else { // should have DSI 
							if (debugLevel >-2) {
								System.out.println("no link to center, but this seems to be other's center "+quadCLTs[last_index].getReferenceTimestamp()+
										", forcing initial orientation.");
							}
							force_initial_orientations = true;
						}
					} else { // -2 - reference somewhere, not to itself
						QuadCLT try_ref_scene = (QuadCLT) quadCLT_main.spawnQuadCLT( // will conditionImageSet
								quadCLTs[last_index].getReferenceTimestamp(), // set_channels[last_index].set_name,
								clt_parameters,
								colorProcParameters, //
								threadsMax,
								debugLevel-2);
						if ((try_ref_scene == null) || (try_ref_scene.getDLS() == null)) {
							if (debugLevel >-2) {
								System.out.println("DSI data for scene "+quadCLTs[last_index].getReferenceTimestamp()+
										" does not exist, forcing initial orientation.");
								// consider copyJP4src for the lower half (now it is not needed) 
							}
							force_initial_orientations = true;
						} else {
							try_ref_scene.restoreInterProperties( // restore properties for interscene processing (extrinsics, ers, ...) // get relative poses (98)
									null, // String path,             // full name with extension or null to use x3d directory
									false, // boolean all_properties,//				null, // Properties properties,   // if null - will only save extrinsics)
									debugLevel);
							
							first_last_index = try_ref_scene.getFirstLastIndex(quadCLTs);
//							first_last_index = try_ref_scene.getFirstLastIndex(scene_names); // returns first, last, index
							
							if (first_last_index == null) { // [-1,-1] - OK
								//							if ((first_last_index == null) ||(first_last_index[0] < 0) || (first_last_index[1] < 0)) {
								force_initial_orientations = true;
							}
						}
					}
				} else {
					if ((quadCLTs[last_index] == null) || !quadCLTs[last_index].dsiExists()) {
						if (debugLevel >-2) {
							System.out.println("DSI data for scene "+set_channels[last_index].set_name+" does not exist, forcing its calculation.");
						}
						build_ref_dsi = true;
					}
				}
			} // if ((center_CLT != null) && center_CLT.hasCenterClt()) {} else {
		}
		// disabling for cuas
		if (!use_cuas && !build_ref_dsi && (quadCLTs[last_index] != null)) { // is it needed in CUAS mode?
			quadCLTs[last_index].restoreInterProperties(null, false, debugLevel); //null probably not needed - already 
		}
		// 1. Reference scene DSI
//		while ((quadCLTs[ref_index] == null) || (quadCLTs[ref_index].getBlueSky() == null)) { // null
//		String center_ts = null; // name of the center scene - not yet known
		double [][] combo_dsn_final = null;
		
//		if (reset_photometric && first_in_series && build_ref_dsi) {
		if (reset_photometric && (index_scenes[0] == null)) { // first_in_series) {
			if (build_ref_dsi || ((center_CLT != null) && center_CLT.hasCenterClt())) {
				if (debugLevel > -3) {
					System.out.println("buildSeries(): *** resetting photometric calibration for a new series ***");
				}
				quadCLT_main.resetLwirCalibration();
			// and immediately recalibrate offsets
			quadCLTs[last_index] = (QuadCLT) quadCLT_main.spawnNoModelQuadCLT( // will conditionImageSet
					set_channels[last_index].set_name,
					clt_parameters,
					colorProcParameters, //
					threadsMax,
					debugLevel-2);
			}
		}
		
		//************* move loading center DSI here before building reference DSI. Or after "blue sky 
		QuadCLT source_CLT = (center_CLT != null) ? center_CLT :quadCLTs[last_index];
///		while ((quadCLTs[last_index] == null) || !quadCLTs[last_index].hasBlueSky()) { // null
//		while ((quadCLTs[last_index] == null) || (!source_CLT.hasBlueSky() && !use_cuas)) { // null FIXME 07.08.2025. added && !use_cuas to prevent forever loop
//		while ((quadCLTs[last_index] == null) || !source_CLT.hasBlueSky()) { // null FIXME 07.08.2025. added && !use_cuas to prevent forever loop. Restores - needs once at least, will break
//		while ((quadCLTs[last_index] == null) || !source_CLT.hasBlueSky()) { // null FIXME 07.08.2025. added && !use_cuas to prevent forever loop. Restores - needs once at least, will break
		
		double [] extr_corr = null; // to later set and then test to see if preparation for LY is needed
		
		while (true) { // null FIXME 07.08.2025. added && !use_cuas to prevent forever loop. Restores - needs once at least, will break
			if (build_ref_dsi) {
				TwoQuadCLT.copyJP4src( // actually there is no sense to process multiple image sets. Combine with other
						// processing?
						set_channels[last_index].set_name, // String set_name
						quadCLT_main, // QuadCLT quadCLT_main,
						null, // QuadCLT quadCLT_aux,
						null, // QuadCLT quadCLT_this,
						clt_parameters, // EyesisCorrectionParameters.DCTParameters dct_parameters,
						true, // boolean                                  skip_existing,
						true, // false, // boolean                                  search_KML,
						debugLevel);
				quadCLTs[last_index] = (QuadCLT) quadCLT_main.spawnNoModelQuadCLT( // will conditionImageSet
						set_channels[last_index].set_name,
						clt_parameters,
						colorProcParameters, //
						threadsMax,
						debugLevel-2);
				/* included in spawnNoModelQuadCLT->restoreNoModel
				quadCLTs[last_index].setupIMS(
						clt_parameters, // CLTParameters clt_parameters,
						false,          // skip_existing,  // boolean skip_existing,
						debugLevel);    // int     debugLevel);
*/						
				quadCLTs[last_index].saveQuadClt(); // to re-load new set of Bayer images to the GPU (do nothing for CPU) and Geometry
				boolean fast = center_reference; // false;
				if (center_reference) { // invalidate pointer to recalculate
					quadCLTs[last_index].setRefPointer((String) null); 
				}
				// See if LY data is available, use if possible
				if (debugLevel > -3) {
					System.out.println("Testing if LY data is available for scene "+(quadCLTs[last_index].getImageName()));
				}				
				extr_corr = quadCLTs[last_index].restoreFieldCalibration( // restore properties for interscene processing (extrinsics, ers, ...)
						true, // false,        // boolean apply,     // Apply to the current instance
						debugLevel); // int     debugLevel)
				if (extr_corr != null) { // exists
					// set reference to scene properties and quadCLT_main
					CorrVector corr_vector = quadCLTs[last_index].getGeometryCorrection().getCorrVector(); // set in restoreFieldCalibration(true,...)
					quadCLT_main.getGeometryCorrection().setCorrVector(corr_vector); // updated system corr vector with the current updated one
					
					
					
				} else {
					if (debugLevel > -3) {
						System.out.println("No LY data is available, using global data for quadCLT_main ("+
								(quadCLT_main.getImageName())+")");
					}				
				}
				// operation_mode - should limit number of scenes to process
				if (debugLevel > -3) {
					System.out.println("buildSeries(): Running OpticalFlow.buildRefDSI() for last_index scene # "+last_index+", : "+quadCLTs[last_index].getImageName());
				}
 
				buildRefDSI( // returned is a different instance than input -FIXED
						clt_parameters,                   // CLTParameters                                 clt_parameters,
						fast,                             // boolean  fast,
						false,                            // boolean                                       skip_photo,
						colorProcParameters,              // ColorProcParameters                           colorProcParameters,
					    rgbParameters, // EyesisCorrectionParameters.RGBParameters      rgbParameters,
						batch_mode,                       // boolean                             batch_mode,
						set_channels[last_index].set_name, // String                              set_name,
						quadCLT_main,                     // QuadCLT                             quadCLT_main, // tiles should be set
						quadCLTs[last_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);
				earliest_scene = 0;  // reset failures, try to use again all scenes
				// Copy source files for other scenes Single-threaded, accelerate
				for (int scene_index =  last_index - 1; scene_index >= 0 ; scene_index--) {
					TwoQuadCLT.copyJP4src( // actually there is no sense to process multiple image sets. Combine with other
							// processing?
							set_channels[scene_index].set_name, // String set_name
							quadCLT_main, // QuadCLT quadCLT_main,
							null, // QuadCLT quadCLT_aux,
							null, // QuadCLT quadCLT_this, // Will not setup IMS
							clt_parameters, // EyesisCorrectionParameters.DCTParameters dct_parameters,
							true, // boolean                                  skip_existing,					
							false, // true, // false, // boolean                                  search_KML,
							debugLevel);
				} // split cycles to remove output clutter
			} else {// if (build_ref_dsi) {
				// need to read photometric from reference scene -INTERFRAME.corr-xml, and if it exists - set and propagate to main?
				// try to run reuseRefDSI() even for center? 
//				QuadCLT source_CLT = (center_CLT != null) ? center_CLT :quadCLTs[last_index];
				reuseRefDSI( // if (dsi[TwoQuadCLT.DSI_SPREAD_AUX] == null) System.out.println("DSI_MAIN file has old format and does not have spread data, will recalculate.");
						clt_parameters,      // CLTParameters  clt_parameters,
						colorProcParameters, // ColorProcParameters colorProcParameters,
						quadCLT_main,        // QuadCLT        quadCLT_main, // tiles should be set
						source_CLT,          // QuadCLT        quadCLT_ref, // tiles should be set
						batch_mode,			 // final boolean       batch_mode,
						threadsMax, 		 // final int           threadsMax,
						updateStatus,		 // final boolean       updateStatus,
						debugLevel);         // int            debugLevel)
				if (center_CLT != null) {
					System.out.println ("In CUAS mode dsi[TwoQuadCLT.DSI_SPREAD_AUX] == null), breaking loop");
					break;
				}
				// source_CLT
				extr_corr = quadCLTs[last_index].restoreFieldCalibration( // restore properties for interscene processing (extrinsics, ers, ...)
						true, // false,        // boolean apply,     // Apply to the current instance
						debugLevel); // int     debugLevel)
				if (extr_corr != null) { // exists
					// set reference to scene properties and quadCLT_main
					CorrVector corr_vector = quadCLTs[last_index].getGeometryCorrection().getCorrVector(); // set in restoreFieldCalibration(true,...)
					quadCLT_main.getGeometryCorrection().setCorrVector(corr_vector); // updated system corr vector with the current updated one
				} else {
					if (debugLevel > -3) {
						System.out.println("No LY data is available, using global data for quadCLT_main ("+
								(quadCLT_main.getImageName())+")");
					}				
				}
				
				
				
				if (center_reference) {
//					center_ts = getReferenceTimestamp(); //??
				}
			}
///			if ((quadCLTs[last_index] != null) && source_CLT.hasBlueSky()) { // null FIXME 07.08.2025. added && !use_cuas to prevent forever loop. Restores - needs once at least, will break
			
			if ((quadCLTs[last_index] != null) && (((source_CLT != null) && source_CLT.hasBlueSky()) || !sky_extract)) { // null FIXME 07.08.2025. added && !use_cuas to prevent forever loop. Restores - needs once at least, will break
				break; // was while header
			}
			if (use_cuas) {
				System.out.println("Breaking in cuas mode without hasBlueSky() - fix later?");
				break;
			}
		} // while (blue_sky == null)
		boolean early_try_back = false; //  true;
//		if (!force_initial_orientations) { // moved here as setDSRBG fails with .dsi={null, null,...,null};
		// by now if it was center_reference the last ref_dsi is restored with optional blue sky.
		boolean bypass_last_index = (debugLevel > 1000); //  && (quadCLTs[last_index] != null); // always. DEbugging 12/06/2025
		if (center_CLT == null) { // does not work in CUAS mode
			ref_blue_sky = quadCLTs[last_index].getDoubleBlueSky(); // new
			if (!bypass_last_index) {
				quadCLTs[last_index] = (QuadCLT) quadCLT_main.spawnQuadCLT( // restores dsi from "DSI-MAIN", sets quat_corr, sets it in quadCLTs[last_index] 
						set_channels[last_index].set_name,
						clt_parameters,
						colorProcParameters, //
						threadsMax,
						debugLevel);
			}
			quadCLTs[last_index].setQuadClt(); // just in case ?
			quadCLTs[last_index].setBlueSky(ref_blue_sky); //quadCLTs[ref_index].dsi has it 
			quadCLTs[last_index].setDSRBG( // DSI is needed in 	I244: cent_index = setInitialOrientationsIms(

					clt_parameters, // CLTParameters  clt_parameters,
					threadsMax,     // int            threadsMax,  // maximal number of threads to launch
					updateStatus,   // boolean        updateStatus,
					debugLevel);    // int            debugLevel)
		}
		
		
		if (!force_initial_orientations) {
			// use master_clt? - where to look for stage. in CUAS mode build directory name and look there
			if (center_CLT != null) {
				if (center_CLT.getNumOrient() == 0) {
					if (debugLevel >-4) {
						System.out.println("center_CLT exists, but does not have egomotion data. Forcing its calculation.");
					}
					force_initial_orientations = true;
				}
			} else {

				if (center_reference) { // check if initial orientation is needed. last_index may be the actual "center" as provided by caller
					int center_index =quadCLTs[last_index].getReferenceIndex(null);
					if (center_index == -1) {
						force_initial_orientations = true;
					} else { // always -2
						QuadCLT try_ref_scene = (QuadCLT) quadCLT_main.spawnNoModelQuadCLT( // will conditionImageSet
								quadCLTs[last_index].getReferenceTimestamp(), // set_channels[last_index].set_name,
								clt_parameters,
								colorProcParameters, //
								threadsMax,
								debugLevel-2);

						if (try_ref_scene == null) {
							force_initial_orientations = true;
						} else {
							try_ref_scene.restoreInterProperties( // restore properties for interscene processing (extrinsics, ers, ...) // get relative poses (98)
									null, // String path,             // full name with extension or null to use x3d directory
									false, // boolean all_properties,//				null, // Properties properties,   // if null - will only save extrinsics)
									debugLevel);
							int [] first_last_index = try_ref_scene.getFirstLastIndex(quadCLTs);
							if ((first_last_index == null)) { //  ||(first_last_index[0] < 0) || (first_last_index[1] < 0)) {
								force_initial_orientations = true;
							}
						}
					}

				} else {
					if ((quadCLTs[last_index].getNumOrient() == 0) ||(!quadCLTs[last_index].propertiesContainString (".scenes_"))) {
						if (debugLevel >-3) {
							System.out.println("Egomotion data for scene "+set_channels[last_index].set_name+" does not exist, forcing its calculation.");
						}
						force_initial_orientations = true;
					}
				}
			} // if (center_CLT != null) {} else {
		} // if (!force_initial_orientations) {
		boolean is_first_scene = false;
		if (index_scenes[0] == null) {
			is_first_scene = true;
			if (debugLevel >-3) {
				System.out.println("===== Setting sequence index scene that will store a list of all reference scene in this sequence. =====");
			}
			index_scenes[0] = quadCLTs[last_index]; // will store a set of all reference scenes in this series
			if (!clt_parameters.imp.keep_segments) {
				if (debugLevel >-3) {
					System.out.println("===== Resetting reference scenes index, starting a fresh one. =====");
				}
				index_scenes[0].resetRefScenes(); // reset list of the reference scenes before rebuilding it
			} else {
				if (debugLevel >-3) {
					System.out.println("===== Keeping existing reference scenes index. =====");
				}
			}
			if (index_scenes[1] == null) { // not yet implemented, to create an index of all sequences indices ) 
				index_scenes[1] = quadCLTs[last_index];
			}
			if ((index_scenes[0] != null) && (LogTee.getSceneLog() == null)) {
				index_scenes[0].startLogging();
			}
	   	}
		/*
  LogTee.install();         // once, early
  LogTee.setSceneLog(path); // when you want to start per‑scene logging
  LogTee.clearSceneLog();   // stop per‑scene logging
  LogTee.getSceneLog()
		 */
		
		
		// reduce some conditions as they are implied by (operation_mode == RESULTS_BUILD_SEQ_LY)
		// && run_ly && (run_ly_mode > 0)  && (run_ly_mode <= 3)  && is_first_scene 
		boolean exec_ly_correction = (operation_mode == RESULTS_BUILD_SEQ_LY) &&
				(force_initial_orientations || (extr_corr == null));
		
		if (operation_mode == RESULTS_BUILD_SEQ_LY) {
			if (debugLevel >-3) {
				System.out.println("===== LY field calibration is requested. force_ly="+force_ly+",  extr_corr "+ ((extr_corr ==null)?" ==":"!=")+"null, continue_ly="+continue_ly+"  =====");
			}
//			if (!force_ly && (extr_corr != null) && !continue_ly ) { // this is valid even if it is not force_initial_orientations
//			Maybe if  (continue_ly) still return with null, and the caller will restart. It will do initial disparity fro the reference scene twice, but the logic will be simpler 
			if (!force_ly && (extr_corr != null) ) { // this is valid even if it is not force_initial_orientations
				if (!continue_ly) {
					exec_ly_correction = false;
					operation_mode = RESULTS_BUILD_SEQ_DEFAULT; // so adjustment range would not be shortened for LY
					if (debugLevel >-3) {
						System.out.println("===== LY calibration data already available, continue_ly is TRUE, and force_ly is FALSE, contiunue in normal mode =====");
					}
				} else {
					if (debugLevel >-3) {
						System.out.println("===== But LY calibration data already available, continue_ly is FALSE, and force_ly is FALSE, terminating processing =====");
					}
					if (build_series_result != null) {
						build_series_result[0] = RESULTS_BUILD_SEQ_LY;
					}
					return null;
				}
			}
		}
		if (debugLevel >-3) {
			System.out.println("===== Considering building initial orientations, force_initial_orientations="+force_initial_orientations+". =====");
		}
		
		// Build initial orientations
		int ref_index = last_index; // old versions
//		double [][] xyzatr_ims_center = null;
		double [] atr_ims_center = null;
		
		if (force_initial_orientations && !reuse_video) {
			boolean OK = false;
			int es1 = -1;
			if (center_CLT != null) {
				double [][] center_xyzatr = {ZERO3,atr_ims_center};
				// int [] start_ref_pointers = {earliest_scene, last_index};
				es1 = Interscene.setInitialOrientationsCuas(
						clt_parameters,      // final CLTParameters          clt_parameters,
						operation_mode,      // final int                    operation_mode, // 0 - normal, 1 - prepare for LY field calibration (reduced scenes)
						center_xyzatr, // final double [][]            center_xyzatr, // center_xyzatr[0]=ZERO3 - for this sequence
						center_CLT, // final QuadCLT                center_CLT,    // contains center CLT and DSI
						use_ims_rotation,    // final boolean                compensate_ims_rotation,
						inertial_only,       // final boolean                                inertial_only,
						min_num_scenes,      // int                          min_num_scenes,
						colorProcParameters, // final ColorProcParameters    colorProcParameters,
						quadCLTs,            // final QuadCLT[]              quadCLTs, //
						set_channels,        // final SetChannels [] set_channels,
						batch_mode,          // final boolean                batch_mode,
						start_ref_pointers,  // int []                       start_ref_pointers, // [0] - earliest valid scene, [1] ref_index. Set if non-null
						updateStatus,        // final boolean                updateStatus,
						debugLevel);
				OK = earliest_scene >=0;
			} else if (center_reference) { // currently the main branch, others may be broken
				es1 = Interscene.setInitialOrientationsCenterIms(
						clt_parameters,      // final CLTParameters          clt_parameters,
						operation_mode,      // final int                    operation_mode, // 0 - normal, 1 - prepare for LY field calibration (reduced scenes)
						use_ims_rotation,    // final boolean                compensate_ims_rotation,
						inertial_only,       // final boolean                                inertial_only,
						min_num_scenes,      // int                          min_num_scenes,
						colorProcParameters, // final ColorProcParameters    colorProcParameters,
						rgbParameters,       // EyesisCorrectionParameters.RGBParameters     rgbParameters,
						quadCLT_main,        // QuadCLT                                      quadCLT_main, // tiles should be set			
						quadCLTs,            // final QuadCLT[]              quadCLTs, //
						last_index,           // final int                    ref_index,
						set_channels,        // final SetChannels [] set_channels,
						batch_mode,          // final boolean                batch_mode,
						earliest_scene,      // int                          ,
						start_ref_pointers,  // int []                       start_ref_pointers, // [0] - earliest valid scene, [1] ref_index
						threadsMax,          // final int                    threadsMax,
						updateStatus,        // final boolean                updateStatus,
						debugLevel);
				OK = es1 >= 0;
				if (OK) {
					ref_index =quadCLTs[last_index].getReferenceIndex(quadCLTs);
					int [] first_last_index = quadCLTs[ref_index].getFirstLastIndex(quadCLTs);
					earliest_scene = first_last_index[0];
					last_index =  first_last_index[1];
				} else {
					earliest_scene = start_ref_pointers[0];
				}
				
				if (generate_egomotion) {
					String  ego_path = quadCLTs[ref_index].getX3dDirectory(true)+Prefs.getFileSeparator()+
							quadCLTs[ref_index].getImageName()+
							"-ego-"+quadCLTs[ref_index].getNumOrient()+".csv"; 
					String  ego_comment = null;
				    Interscene.generateEgomotionTable(
				    		clt_parameters, // CLTParameters  clt_parameters,
							quadCLTs, // QuadCLT []     quadCLTs,
							ref_index, //-1, //ref_index,//            ref_indx,
							quadCLTs[ref_index], //QuadCLT        ref_scene, // may be one of quadCLTs or center_CLT
							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);
					}
				}				
			} else if (ims_use) {	// not center reference
				earliest_scene =	Interscene.setInitialOrientationsIms( // not center reference !!
						clt_parameters,      // final CLTParameters          clt_parameters,
						operation_mode,      // final int                    operation_mode, // 0 - normal, 1 - prepare for LY field calibration (reduced scenes)
						use_ims_rotation,    // final boolean                compensate_ims_rotation,
						inertial_only,       // final boolean                                inertial_only,
						min_num_scenes,      // int                          min_num_scenes,
						colorProcParameters, // final ColorProcParameters    colorProcParameters,
						quadCLTs,            //  final QuadCLT[]              quadCLTs, //
						last_index,           // final int                    ref_index,
						set_channels,        // final SetChannels [] set_channels,
						batch_mode,          // final boolean                batch_mode,
						earliest_scene,      // int                          earliest_scene,
						start_ref_pointers,  // int []                       start_ref_pointers, // [0] - earliest valid scene, [1] ref_index
						threadsMax,          // final int                    threadsMax,
						updateStatus,        // final boolean                updateStatus,
						debugLevel);         // final int                    debugLevel)
				OK = earliest_scene >=0;
			} else {
				earliest_scene=	Interscene.setInitialOrientations(
						clt_parameters,      // final CLTParameters          clt_parameters,
						operation_mode,      // final int                    operation_mode, // 0 - normal, 1 - prepare for LY field calibration (reduced scenes)
						min_num_scenes,      // int                          min_num_scenes,
						colorProcParameters, // final ColorProcParameters    colorProcParameters,
						quadCLTs,            //  final QuadCLT[]              quadCLTs, //
						last_index,           // final int                    ref_index,
						set_channels,        // final SetChannels [] set_channels,
						batch_mode,          // final boolean                batch_mode,
						earliest_scene,      // int                          earliest_scene,
						start_ref_pointers,  // int []                       start_ref_pointers, // [0] - earliest valid scene, [1] ref_index
						threadsMax,          // final int                    threadsMax,
						updateStatus,        // final boolean                updateStatus,
						debugLevel);         // final int                    debugLevel)
				OK = earliest_scene >=0;
			}
			if (!OK) {
				if (use_cuas) {
					System.out.println(
							"buildSeries(): aborting CUAS sequence after failed initial orientations. " +
							"Input IMS/orientation data appear inconsistent, so no automatic recovery will be attempted.");
				}
				return null;
			}
			// export csv
		} else {// if (build_orientations) {
			if (center_CLT != null) {
				if (!reuse_video) { // reuse_video only uses reference scene
					boolean changed = false; // quadCLTs[ref_index].isPhotometricUpdatedAndReset(); //*** It should use quadCLT_main.isPhotometricUpdatedAndReset()
					if (changed) {
						System.out.println ("0.re-spawning with updated photogrammetric calibration of reference scene.");
					} else {
						System.out.println ("0.no update, spawning only over null");
					}
					
					for (int scene_index =  last_index; scene_index >= earliest_scene ; scene_index--) {
						// should we skip if already exists? Or need to re-run to apply new photometric calibration?
						// Or should photogrammetric calibration be saved with center_CLT?
						// to include ref scene photometric calibration
						if (changed || (quadCLTs[scene_index] == null)) {
							quadCLTs[scene_index] = quadCLTs[ref_index].spawnNoModelQuadCLT( // restores image data
									set_channels[scene_index].set_name,
									clt_parameters,
									colorProcParameters, //
									threadsMax,
									debugLevel-2);
						}
					}
				}
			} else {
				if (!reuse_video) { // reuse_video only uses reference scene
					boolean changed = quadCLTs[ref_index].isPhotometricUpdatedAndReset();
					if (changed) {
						System.out.println ("1.re-spawning with updated photogrammetric calibration of reference scene.");
					} else {
						System.out.println ("1.no update, spawning only over null");
					}
					//	Improve - only read from the earliest to the last
					if (center_reference) {
						int ri = quadCLTs[ref_index].getRefIndex(scene_names);
						if (ri >= 0) {
							ref_index = ri;
							if (changed || (quadCLTs[ref_index] == null)) {
								/*
								quadCLTs[ref_index] = quadCLT_main.spawnQuadCLT( // does not restore IMS and image_data!
										scene_names[ref_index],     // String            set_name,
										clt_parameters, // CLTParameters       clt_parameters,
										debugLevel);    // int                 debugLevel);
								*/
								quadCLTs[ref_index] = quadCLT_main.spawnNoModelQuadCLT( // restores image data NEED IT!
										scene_names[ref_index],     // String            set_name,
										clt_parameters,
										colorProcParameters, //
										threadsMax,
										debugLevel-2);

							}
							quadCLTs[ref_index].restoreAnyDSI(debugLevel);
//							quadCLTs[ref_index].restoreInterProperties(null, false, debugLevel); //null
							int [] first_last_index = quadCLTs[ref_index].getFirstLastIndex(scene_names);//
							earliest_scene = first_last_index[0]; // null pointer if does not exist
							last_index =  first_last_index[1];
							// nothing for the earliest scene - wrong? 2.19.2026
							if (changed || (quadCLTs[last_index] == null)) {
								/*
								quadCLTs[last_index] = quadCLT_main.spawnQuadCLT(
										scene_names[last_index],     // String            set_name,
										clt_parameters, // CLTParameters       clt_parameters,
										debugLevel);    // int                 debugLevel);
								*/
								quadCLTs[last_index] = quadCLT_main.spawnNoModelQuadCLT( // restores image data NEED IT!
										scene_names[last_index],     // String            set_name,
										clt_parameters,
										colorProcParameters, //
										threadsMax,
										debugLevel-2);
							}
							
							for (int scene_index =  last_index-1; scene_index >= earliest_scene ; scene_index--) if (scene_index != ref_index){
								if (changed || (quadCLTs[scene_index] == null)) {
									// does not read ims data !
///									quadCLTs[scene_index] =  new QuadCLT(quadCLTs[ref_index], scene_names[scene_index]);
									quadCLTs[scene_index] = quadCLT_main.spawnNoModelQuadCLT( //   quadCLTs[ref_index].spawnNoModelQuadCLT( // restores image data NEED IT!
											set_channels[scene_index].set_name,
											clt_parameters,
											colorProcParameters, //
											threadsMax,
											debugLevel-2);

								}
							}
						}
					}
					if (center_reference && (ref_index == last_index)) { // is it needed? Old way (pre 02/19/2026)
						for (int scene_index =  last_index; scene_index >= earliest_scene ; scene_index--) {
							if (scene_index != ref_index) {
								// to include ref scene photometric calibration
								if (changed || (quadCLTs[scene_index] == null)) {
									quadCLTs[scene_index] = quadCLTs[ref_index].spawnNoModelQuadCLT( // restores image data
											set_channels[scene_index].set_name,
											clt_parameters,
											colorProcParameters, //
											threadsMax,
											debugLevel-2);
								}
							}
						}
					}
				}
				/*
				if (center_reference) {
					ref_index =quadCLTs[last_index].getReferenceIndex(quadCLTs);
					quadCLTs[ref_index].restoreInterProperties(null, false, debugLevel); //null
					int [] first_last_index = quadCLTs[ref_index].getFirstLastIndex(quadCLTs);
					earliest_scene = first_last_index[0]; // null pointer if does not exist
					last_index =  first_last_index[1];
				}
				*/
			}
			// TODO: 10.17.2023 - verify OK to remove this - next should be already tested during initial orientation
		}
		if (debugLevel >-3) {
			System.out.println("===== Initial orientations are either calculated or loaded. =====");
		}
		// both after initial orientations and if loaded TODO:
		if (use_cuas && (center_CLT == null)) {
			QuadCLT        ref_scene =quadCLTs[ref_index];
			int [] first_last_index = ref_scene.getFirstLastIndex(quadCLTs);
			/*
			center_CLT = Cuas.makeCenterClt( // not used
					quadCLTs,                // QuadCLT []     quadCLTs,
					ref_scene,               // QuadCLT        ref_scene,
					first_last_index,        // int     []     range, // or null
					debugLevel);             // int            debugLevel) 
			*/
			boolean dbg_created=true; // false;
			int sensor_mask_clt = -1; // all
			boolean  condition_dsi = true;
			if (combo_dsn_final == null) {
				combo_dsn_final =quadCLTs[ref_index].restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky
			}
			if (debugLevel > -4) {
				System.out.println("Creating center_CLT from the reference one (ref_index="+ref_index);
			}
			center_CLT = Cuas.createCenterClt( // assuming cuas_rotation is true
					clt_parameters,  // CLTParameters  clt_parameters,
					quadCLTs,        // QuadCLT []     quadCLTs,
					quadCLTs[ref_index], // QuadCLT        ref_scene, // where combo_dsi is
					null,            // int     []     range, // or null
					combo_dsn_final, // double  [][]   ref_dsi, // DSI data for the reference scene (or null to read it from file)
					condition_dsi,   // boolean        condition_dsi,
					sensor_mask_clt, // int            sensor_mask, // -1 - all;
		    		true,            // boolean          // new_average,
					debugLevel);     // int            debugLevel)
			if (debugLevel > -4) {
				System.out.println("Cuas.createCenterClt() done, center_CLT="+((center_CLT==null)? "null":center_CLT.toString()));
			}			
		}
		
		
		
		// verify quat_corr is set here from the file
		if (ref_index != last_index) { // do that for reference (center) scene again (was for the last scene) Why is it needed? Bypass in airplane mode
			System.out.println("ref_index != last_index: ref_index="+ref_index+", last_index="+last_index);
			// FIXME: Will deal with blue sky later - not needed for drones.
///				ref_blue_sky = quadCLTs[ref_index].getDoubleBlueSky();
///// maybe just re-read DSI_MAIN?
			quadCLTs[ref_index].restoreAnyDSI(debugLevel);
			// readDsiMain(); // maybe it is used when not the first time?
			/*
			quadCLTs[ref_index] = (QuadCLT) quadCLT_main.spawnQuadCLT( // restores dsi from "DSI-MAIN" . Will it forget quat_corr?
					set_channels[ref_index].set_name,
					clt_parameters,
					colorProcParameters, //
					threadsMax,
					debugLevel);
			*/
			quadCLTs[ref_index].setQuadClt(); // just in case ?
///				quadCLTs[ref_index].setBlueSky(ref_blue_sky); //quadCLTs[ref_index].dsi has it 
/*
setDSRBG () removed, it was resulting in null pointer:  
java.lang.NullPointerException
	at com.elphel.imagej.tileprocessor.CLTPass3d.setTileOpDisparity(CLTPass3d.java:1097)
	at com.elphel.imagej.tileprocessor.CLTPass3d.setTileOpDisparity(CLTPass3d.java:1086)
	at com.elphel.imagej.tileprocessor.QuadCLTCPU.getTileRBG(QuadCLTCPU.java:6152)
	at com.elphel.imagej.tileprocessor.QuadCLTCPU.setDSRBG(QuadCLTCPU.java:6088)
	at com.elphel.imagej.tileprocessor.QuadCLTCPU.setDSRBG(QuadCLTCPU.java:6058)
	at com.elphel.imagej.tileprocessor.OpticalFlow.buildSeries(OpticalFlow.java:5776)
			quadCLTs[ref_index].setDSRBG( // is it needed? 
					clt_parameters, // CLTParameters  clt_parameters,
					threadsMax,     // int            threadsMax,  // maximal number of threads to launch
					updateStatus,   // boolean        updateStatus,
					debugLevel);    // int            debugLevel)
			 */
			int [] first_last_index = quadCLTs[ref_index].getFirstLastIndex(quadCLTs);
			if (first_last_index == null) {
				System.out.println("BUG: saved reference scene "+quadCLTs[ref_index].getImageName()+
						" is not really a reference");
				System.out.println();
				start_ref_pointers[0] = 0;
				start_ref_pointers[1] = 0;
				return quadCLTs[ref_index].getX3dTopDirectory(); //bailing out
			} else 	if ((first_last_index [0] < 0) || (first_last_index [1] < 0)) {
				System.out.println("BUG: saved reference scene "+quadCLTs[ref_index].getImageName()+
						" does not have both first/last scenes defined: ["+first_last_index [0]+", "+first_last_index [1]+"]");
				System.out.println();
				start_ref_pointers[0] = 0;
				start_ref_pointers[1] = 0;
				return quadCLTs[ref_index].getX3dTopDirectory(); //bailing out
				
			} else {
				earliest_scene = first_last_index[0];
				last_index =  first_last_index[1];
			}
		}
		
		// just in case that orientations were calculated before:
		//		earliest_scene = getEarliestScene(quadCLTs);
		// below ref_index is not necessary the last (fix all where it is supposed to be the last
		QuadCLT master_CLT = (center_CLT != null)? center_CLT : quadCLTs[ref_index];
		
		ErsCorrection ers_reference = master_CLT.getErsCorrection(); // only used in ml_export
		if (!reuse_video) {
			if (clt_parameters.imp.cuas_overwrite) {
				if (debugLevel > -3) {
					System.out.println("Overwriting master_CLT.getNumOrient()="+master_CLT.getNumOrient()+
							" of "+min_num_orient+", master_CLT.getNumAccum()="+master_CLT.getNumAccum()+
							" of "+min_num_interscene);
				}
				master_CLT.set_orient(Math.min(clt_parameters.imp.cuas_num_orient, master_CLT.getNumOrient()));
				master_CLT.set_accum (Math.min(clt_parameters.imp.cuas_num_accum,  master_CLT.getNumAccum()));

				if (debugLevel > -3) {
					System.out.println("New master_CLT.getNumOrient()="+master_CLT.getNumOrient()+
							" of "+min_num_orient+", master_CLT.getNumAccum()="+master_CLT.getNumAccum()+
							" of "+min_num_interscene);
				}
				
				
			}
			
			// FPN here - poses are known
//			boolean changed = quadCLT_main.isPhotometricUpdatedAndReset();
			if ((center_CLT != null) && center_CLT.hasCenterClt()) { // float [] fclt
				int []               scene_range = new int [] {earliest_scene, last_index};
				double [][][] fpn = CorrectionFPN.cuasSubtractFpn( // returns fpn // null
						clt_parameters,      // CLTParameters        clt_parameters,
						cuas_subtract_fpn,   // boolean              cuas_subtract_fpn, //
						false, // changed,             //boolean              changed,           // 	        boolean changed = quadCLT_main.isPhotometricUpdatedAndReset();
						colorProcParameters, // ColorProcParameters  colorProcParameters,
						center_CLT,          // QuadCLT              center_CLT,        // where combo_dsi is. Should have hasCenterClt()   (run makeCenterClt() before)
						quadCLTs,            // QuadCLT []           quadCLTs,
						set_channels,        // SetChannels []       set_channels,
//						boolean              condition_dsi,
						scene_range,         // int []               scene_range, // first/last
						debugLevel);        // int                  debugLevel)
				if (cuas_subtract_rowcol) {
					CorrectionFPN.cuasSubtractRowColNoise(
							clt_parameters, // CLTParameters        clt_parameters,
							cuas_subtract_rowcol, // boolean              cuas_subtract_rowcol,
							center_CLT,           // QuadCLT              center_CLT,        // where combo_dsi is. Should have hasCenterClt()   (run makeCenterClt() before)
							quadCLTs,             // QuadCLT []           quadCLTs,
							fpn,                  // final double [][][]  fpn, // if null - already applied (from cuasSubtractFpn() output)
							debugLevel);          // int                  debugLevel)
				}
			}
			if (debugLevel >-3) {
				System.out.println("===== Performing additional orientation/disparity adjustment cycles. =====");
			}
			
			while ((master_CLT.getNumOrient() < min_num_orient) || (master_CLT.getNumAccum() < min_num_interscene)) {
				if (debugLevel > -3) {
					System.out.println("master_CLT.getNumOrient()="+master_CLT.getNumOrient()+
							" of "+min_num_orient+", master_CLT.getNumAccum()="+master_CLT.getNumAccum()+
							" of "+min_num_interscene);
				}
				if ((master_CLT.getNumAccum() < min_num_interscene) &&
						((master_CLT.getNumAccum() <  master_CLT.getNumOrient())||
								(master_CLT.getNumOrient() >= min_num_orient))) {
					boolean done_sfm = false;
					double [][] sfm_dsn = null;
					int mb_gain_index_depth =  clt_parameters.imp.mb_gain_index_depth; // depth map refine pass (SfM) to switch to full mb_max_gain from mb_max_gain_inter
					if (master_CLT.getNumAccum() > 0) {
						if (!clt_parameters.imp.lock_position) { // SFM mode, not with cuas
							double mb_max_gain = clt_parameters.imp.mb_max_gain;
							if (master_CLT.getNumOrient() < mb_gain_index_depth) { // (min_num_orient - 1)) {
								mb_max_gain = clt_parameters.imp.mb_max_gain_inter;
							}
							int      num_avg_pairs = clt_parameters.imp.sfm_num_pairs;  // number of scene pairs to average
							if (num_avg_pairs > ((last_index - earliest_scene)/2)) {
								num_avg_pairs = (last_index - earliest_scene)/2;
								if (debugLevel > -3) {
									System.out.println("**** The specified num_avg_pairs="+clt_parameters.imp.sfm_num_pairs+
											" is more than half-range. Reducing to "+num_avg_pairs);
								}
							}
							QuadCLT[][][] scenes_seq_pairs = new QuadCLT[3][num_avg_pairs][2];
							int ref_index_mod1 = Math.max(ref_index, earliest_scene + num_avg_pairs - 1);
							int ref_index_mod2 = Math.min(ref_index, last_index-num_avg_pairs + 1);

							// TODO: calculate horizontal offset and compare with sfm_min_base
							for (int i = 0; i < num_avg_pairs; i++) {
								scenes_seq_pairs[0][i][0] = quadCLTs[last_index - i];
								scenes_seq_pairs[0][i][1] = quadCLTs[earliest_scene + num_avg_pairs - 1 - i];

								scenes_seq_pairs[1][i][0] = quadCLTs[last_index - i];
								scenes_seq_pairs[1][i][1] = quadCLTs[ref_index_mod1 - i];

								scenes_seq_pairs[2][i][0] = quadCLTs[ref_index_mod2 + num_avg_pairs - 1 - i];
								scenes_seq_pairs[2][i][1] = quadCLTs[earliest_scene + num_avg_pairs - 1 - i];
							}
							sfm_dsn = StructureFromMotion.sfmPairsSet(
									clt_parameters,           // final CLTParameters          clt_parameters,
									quadCLTs[ref_index],      // final QuadCLT                ref_scene,
									scenes_seq_pairs,         // scenes_pairs,             // final QuadCLT [][]           scenes_pairs,
									mb_max_gain,              // double                       mb_max_gain,
									batch_mode,               // final boolean                batch_mode,
									debugLevel);              // final int                    debugLevel)
							if (sfm_dsn != null) {
								combo_dsn_final = sfm_dsn;
								done_sfm = true;
							}
							if (!done_sfm && sfm_only) {
								System.out.println("\nsfm_only is set and no SfM is available, skipping farther processing of this scene series.");
								System.out.println("quadCLTs["+ref_index+"].getNumOrient()="+quadCLTs[ref_index].getNumOrient()+
										" of "+min_num_orient+", quadCLTs["+ref_index+"].getNumAccum()="+quadCLTs[ref_index].getNumAccum()+
										" of "+min_num_interscene+"\n");
								break;
							}
						}
					} 
					if (!done_sfm) { // first pass or sfm failed. Always here in CUAS mode
						boolean              compensate_dsi = false; // true;
						// should skip scenes w/o orientation 06/29/2022
						
						// TODO: Implement for CUAS
						// may be possible to split in shorter sequences with scene_range
						combo_dsn_final = intersceneExport( // result indexed by COMBO_DSN_TITLES, COMBO_DSN_INDX_***
								true, // boolean              save_result,   // false - do not save
								null, // int []               scene_range,         // if null -> {0,scenes_in.length-1}
								clt_parameters,      // CLTParameters        clt_parameters,
								compensate_dsi,      // boolean              compensate_dsi,
////							ref_index,           // int                  ref_index,
								master_CLT, // QuadCLT              ref_scene,
								quadCLTs,            // QuadCLT []           scenes,
								colorProcParameters, // ColorProcParameters  colorProcParameters,
								debugLevel);         // int                  debug_level
						if (clt_parameters.imp.sky_recalc) { // force blue sky recalculation even if it exists
							reuseRefDSI( // processes blue sky
									clt_parameters,      // CLTParameters  clt_parameters,
									colorProcParameters, // ColorProcParameters colorProcParameters,
									quadCLT_main,        // QuadCLT        quadCLT_main, // tiles should be set
									master_CLT,          // QuadCLT        quadCLT_ref, // tiles should be set
									batch_mode,			 // final boolean       batch_mode,
									threadsMax, 		 // final int           threadsMax,
									updateStatus,		 // final boolean       updateStatus,
									debugLevel);         // int            debugLevel)
						}
					}
					master_CLT.inc_accum();
					// save with updated num_accum
					master_CLT.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);
				}
				double [] reduced_strength = new double[1];
				if (master_CLT.getNumOrient() < min_num_orient) {
					boolean [] reliable_ref = null;
					boolean show_reliable_ref = !batch_mode; // false;
					double min_ref_str_sel = use_combo_reliable? min_ref_str_lma: min_ref_str;
					if (min_ref_str > 0.0) {
						reliable_ref = master_CLT.getReliableTiles( // will be null if does not exist.
								use_combo_reliable, // boolean use_combo,
								min_ref_str_sel,    // double min_strength,
								min_ref_frac,       // double min_ref_frac,
								ref_need_lma,       // boolean needs_lma);
								ref_need_lma_combo, // boolean needs_lma);
								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_minabs,      // double    sfm_minabs,
								sfm_fracall,     // double  sfm_fracall,// minimal relative area of the SfM-enabled tiles (do not apply filter if less)
								min_ref_str_sfm, // double    min_ref_str_sfm,
								reduced_strength,  // if not null will return >0 if had to reduce strength (no change if did not reduce)
								debugLevel); // int       debugLevel)
						if (show_reliable_ref) {
							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,
									master_CLT.getTilesX(), // getTileProcessor().getTilesX(),
									master_CLT.getTilesY(), // .getTileProcessor().getTilesY(),
									"reliable_ref-"+master_CLT.getImageName());
						}
					}
					// TODO: move to config
					// boolean freeze_xy_pull = true; // false; // true; // debugging freezing xy to xy_pull
					boolean        configured_lma =  clt_parameters.imp.configured_lma;  // false;
					boolean        lpf_xy =          clt_parameters.imp.lpf_xy;          // false;       // lpf x and y, re-adjust X,Y,Z,A,T,R with pull for X,Y. Disables  
					double         avg_rlen =        clt_parameters.imp.avg_len;         //  3.0;
					boolean        readjust_xy_ims = clt_parameters.imp.readjust_xy_ims; //true; // false;
					double         reg_weight_xy =   clt_parameters.imp.reg_weight_xy;    // 0; // 10.0; // 1.0; // 10.0; // 0.05; // TODO: find out reasonable values
					int            mb_ers_index =    clt_parameters.imp.mb_ers_index;
					int            mb_ers_y_index =  clt_parameters.imp.mb_ers_y_index;
					int            mb_ers_r_index =  clt_parameters.imp.mb_ers_r_index;
					int            mb_all_index =    clt_parameters.imp.mb_all_index;
					int            mb_gain_index_pose =  clt_parameters.imp.mb_gain_index_pose; // pose readjust pass to switch to full mb_max_gain from mb_max_gain_inter
					boolean        disable_ers =   (master_CLT.getNumOrient() <  mb_ers_index);
					boolean        disable_ers_y = (master_CLT.getNumOrient() <  mb_ers_y_index);
					boolean        disable_ers_r = (master_CLT.getNumOrient() <  mb_ers_r_index);
					boolean        lma_xyzatr =    (master_CLT.getNumOrient() == mb_all_index);
					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        ers_from_ims =    true; // false; // change later
					
					int ers_mode = 0; // keep
					// with IMS it is already set during initial orientation. In non-IMS mode
					if (!ers_from_ims && (master_CLT.getNumOrient() < 2)) {
						ers_mode = 1; // calculate velocity
						if (debugLevel > -3) {
							System.out.println("Setting ERS data from velocity");
						}
					}
					if (debugLevel > -3) {
						System.out.println("ers_mode="+ers_mode);
					}
					// on last pass use final max MB correction same as for render (mb_max_gain - typical =5.0),
					// for earlier - mb_max_gain_inter (which may be smaller - typical = 2.0)
					double mb_max_gain = clt_parameters.imp.mb_max_gain;
					if (master_CLT.getNumOrient() < mb_gain_index_pose) { // (min_num_orient - 1)) {
						mb_max_gain = clt_parameters.imp.mb_max_gain_inter;
					}
					// TODO: Implement for CUAS **************************************************
					if (center_CLT != null) {
						earliest_scene =  Interscene.reAdjustPairsLMAIntersceneCuas( // after combo dsi is available and preliminary poses are known
								clt_parameters, // CLTParameters  clt_parameters,
								mb_max_gain,    // double         mb_max_gain,
								lma_use_R,      // boolean use_R,
								disable_ers,    // boolean        disable_ers,
								disable_ers_y,  // boolean        disable_ers_y,
								disable_ers_r,  // boolean        disable_ers_r,
								lma_xyzatr,     // boolean        lma_xyzatr,
								configured_lma, // boolean        configured_lma,
								avg_rlen,       // double         avg_rlen,
								reg_weight_xy,  // double         reg_weight_xy, // regularization weight for X and Y
								reliable_ref,   // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
								quadCLTs,       // QuadCLT []     quadCLTs,
								center_CLT,     // QuadCLT        center_CLT,    // contains center CLT and DSI. It has zeros fo coordinates, scenes are already around it 
								new int [] {earliest_scene, last_index},  //int     []     range,
								ers_mode,       // int            ers_mode, // 0 - keep, 1 - set from velocity, 2 - set from IMS
								!batch_mode,    // boolean        test_motion_blur,
								debugLevel) ;   // int            debugLevel)
					}else {
						boolean freeze_xy_pull =    clt_parameters.imp.freeze_xy_pull;    // true; // false; // true; // debugging freezing xy to xy_pull
						boolean lma_use_XY = ( !freeze_xy_pull && (!readjust_xy_ims || (reg_weight_xy != 0)));
						boolean lma_use_AT = ( readjust_xy_ims || lpf_xy);

						if (clt_parameters.imp.air_mode_en) {
							readjust_xy_ims  = false;
							int min_adjust_atr = 2;
							if (master_CLT.getNumOrient() < min_adjust_atr) {
								disable_ers =   true;
								disable_ers_y = true;
								disable_ers_r = true;
								lma_xyzatr =    false;
//								lma_use_Z =     false;
								lma_use_R =     false;
								lma_use_XY =    true;
								lma_use_AT =    false;
							}
//							boolean        disable_ers =   (master_CLT.getNumOrient() <  mb_ers_index);
//							boolean        disable_ers_y = (master_CLT.getNumOrient() <  mb_ers_y_index);
//							boolean        disable_ers_r = (master_CLT.getNumOrient() <  mb_ers_r_index);
//							boolean        lma_xyzatr =    (master_CLT.getNumOrient() == mb_all_index);
//							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 apply_disparity_ims =  clt_parameters.imp.air_disp_corr;
							boolean use_global_lma =      clt_parameters.iglp.glob_en;
							boolean [] param_sel6 =       clt_parameters.iglp.param_sel;
							double [] param_regweights6 = clt_parameters.iglp.param_regweights;
							double [] param_lpf6 =        clt_parameters.iglp.param_lpf;
							
							
							int [] range = new int [] {earliest_scene, last_index};
							
							if (use_global_lma) {
								final QuadCLT centerRefScene = ((ref_index >= 0) && (ref_index < quadCLTs.length)) ? quadCLTs[ref_index] : null;
								if (centerRefScene != null) {
									final int overlapScenes = Math.max(1, Math.min(4, (range[1] - range[0] + 1) / 20 + 1));
									final int q1RangeStart = range[0];
									final int q1RangeEnd = Math.min(range[1], ref_index + overlapScenes);
									final int q3RangeStart = Math.max(range[0], ref_index - overlapScenes);
									final int q3RangeEnd = range[1];
									final int q1Guess = Math.max(q1RangeStart, Math.min(ref_index - 1, (q1RangeStart + ref_index) / 2));
									final int q3Guess = Math.min(q3RangeEnd, Math.max(ref_index + 1, (q3RangeEnd + ref_index) / 2));
									final int q1Index = resolveQuarterRefIndex(
											quadCLTs,
											q1RangeStart,
											q1RangeEnd,
											q1Guess,
											centerRefScene.timestamp_quarter1);
									final int q3Index = resolveQuarterRefIndex(
											quadCLTs,
											q3RangeStart,
											q3RangeEnd,
											q3Guess,
											centerRefScene.timestamp_quarter3);
									String q1Ts = (quadCLTs[q1Index] != null) ?
											quadCLTs[q1Index].getImageName() :
											set_channels[q1Index].set_name;
									String q3Ts = (quadCLTs[q3Index] != null) ?
											quadCLTs[q3Index].getImageName() :
											set_channels[q3Index].set_name;
									boolean tsUpdated = false;
									if ((centerRefScene.timestamp_quarter1 == null) || !centerRefScene.timestamp_quarter1.equals(q1Ts)) {
										centerRefScene.timestamp_quarter1 = q1Ts;
										tsUpdated = true;
									}
									if ((centerRefScene.timestamp_quarter3 == null) || !centerRefScene.timestamp_quarter3.equals(q3Ts)) {
										centerRefScene.timestamp_quarter3 = q3Ts;
										tsUpdated = true;
									}
									if (tsUpdated) {
										centerRefScene.saveInterProperties(
												null,
												debugLevel + 1);
									}
									String q1Path = resolveQuarterInterIntraPath(
											quadCLTs,
											q1Index,
											q1Index,
											centerRefScene.timestamp_quarter1);
									String q3Path = resolveQuarterInterIntraPath(
											quadCLTs,
											q3Index,
											q3Index,
											centerRefScene.timestamp_quarter3);
									final String q1MsgPath = (q1Path != null) ? q1Path :
											(centerRefScene.getX3dDirectory() + Prefs.getFileSeparator() + q1Ts +
													QuadCLTCPU.DSI_SUFFIXES[QuadCLTCPU.INDEX_INTER_LMA] + ".tiff");
									final String q3MsgPath = (q3Path != null) ? q3Path :
											(centerRefScene.getX3dDirectory() + Prefs.getFileSeparator() + q3Ts +
													QuadCLTCPU.DSI_SUFFIXES[QuadCLTCPU.INDEX_INTER_LMA] + ".tiff");
									final boolean forceQuarterRecalc = clt_parameters.iglp.glob_recalculate_quarter_refs;
									final boolean q1Exists = (q1Path != null) && (new File(q1Path).exists());
									final boolean q3Exists = (q3Path != null) && (new File(q3Path).exists());
									final boolean q1Missing = !q1Exists || forceQuarterRecalc;
									final boolean q3Missing = !q3Exists || forceQuarterRecalc;
									if (debugLevel > -3) {
										System.out.println("buildSeries(): quarter refs selected: q1 ts=" + q1Ts +
												", idx=" + q1Index + ", range=[" + q1RangeStart + "," + q1RangeEnd +
												"], path=" + q1MsgPath + ", exists=" + q1Exists);
										System.out.println("buildSeries(): quarter refs selected: q3 ts=" + q3Ts +
												", idx=" + q3Index + ", range=[" + q3RangeStart + "," + q3RangeEnd +
												"], path=" + q3MsgPath + ", exists=" + q3Exists);
										if (forceQuarterRecalc) {
											System.out.println("buildSeries(): quarter-reference force regeneration is enabled");
										}
									}
									if (q1Missing) {
										if (q1Exists && forceQuarterRecalc) {
											System.out.println("buildSeries(): quarter1 INTER-INTRA-LMA exists, but force regeneration is enabled, will regenerate " + q1MsgPath);
										} else {
											System.out.println("buildSeries(): quarter1 INTER-INTRA-LMA missing, will generate " + q1MsgPath);
										}
									} else if (debugLevel > -3) {
										System.out.println("buildSeries(): quarter1 INTER-INTRA-LMA present, using " + q1MsgPath);
									}
									if (q3Missing) {
										if (q3Exists && forceQuarterRecalc) {
											System.out.println("buildSeries(): quarter3 INTER-INTRA-LMA exists, but force regeneration is enabled, will regenerate " + q3MsgPath);
										} else {
											System.out.println("buildSeries(): quarter3 INTER-INTRA-LMA missing, will generate " + q3MsgPath);
										}
									} else if (debugLevel > -3) {
										System.out.println("buildSeries(): quarter3 INTER-INTRA-LMA present, using " + q3MsgPath);
									}
									if (q1Missing || q3Missing) {
										System.out.println("buildSeries(): quarter reference prerequisites missing, generating before Global LMA");
										boolean q1Ok = true;
										boolean q3Ok = true;
										if (q1Missing) {
											q1Ok = ensureQuarterReferenceProducts(
													clt_parameters,
													colorProcParameters,
													rgbParameters,
													quadCLT_main,
													quadCLTs,
													set_channels,
													ref_index,
													q1Index,
													q1RangeStart,
													q1RangeEnd,
													forceQuarterRecalc,
													clt_parameters.iglp.glob_quarter_refs_sfm_only,
													batch_mode,
													threadsMax,
													updateStatus,
													debugLevel);
										}
										if (q3Missing) {
											q3Ok = ensureQuarterReferenceProducts(
													clt_parameters,
													colorProcParameters,
													rgbParameters,
													quadCLT_main,
													quadCLTs,
													set_channels,
													ref_index,
													q3Index,
													q3RangeStart,
													q3RangeEnd,
													forceQuarterRecalc,
													clt_parameters.iglp.glob_quarter_refs_sfm_only,
													batch_mode,
													threadsMax,
													updateStatus,
													debugLevel);
										}
										q1Path = resolveQuarterInterIntraPath(
												quadCLTs,
												q1Index,
												q1Index,
												centerRefScene.timestamp_quarter1);
										q3Path = resolveQuarterInterIntraPath(
												quadCLTs,
												q3Index,
												q3Index,
												centerRefScene.timestamp_quarter3);
										if (!q1Ok || (q1Path == null) || !(new File(q1Path).exists())) {
											System.out.println("buildSeries(): WARNING quarter1 generation failed or output missing: " + q1MsgPath);
										}
										if (!q3Ok || (q3Path == null) || !(new File(q3Path).exists())) {
											System.out.println("buildSeries(): WARNING quarter3 generation failed or output missing: " + q3MsgPath);
										}
										if ((q1Path != null) && new File(q1Path).exists()) {
											System.out.println("buildSeries(): generated quarter1 INTER-INTRA-LMA " + q1Path);
										}
										if ((q3Path != null) && new File(q3Path).exists()) {
											System.out.println("buildSeries(): generated quarter3 INTER-INTRA-LMA " + q3Path);
										}
									}
								}
								IntersceneGlobalRefine.Result igr_result = Interscene.reAdjustPairsLMAIntersceneGlobalReference(
										clt_parameters,    // final CLTParameters clt_parameters,
										quadCLTs,          // final QuadCLT[] quadCLTs,
										ref_index,         // final int ref_index, // center_index,
										range,             // final int[] range,
										param_sel6,        // param_select,   // final boolean[] param_select,
										param_regweights6, // param_regweights, // final double[] param_regweights,
										param_lpf6,        // final double[] param_lpf,
										reliable_ref,      // final boolean[] reliable_ref,
										disable_ers,       // final boolean disable_ers,
										5.0,               // final double mb_max_gain,
										debugLevel);       // final int debugLevel);
								if (clt_parameters.iglp.glob_exit_after_test) {
									System.out.println("Exiting after testing reAdjustPairsLMAIntersceneGlobalReference()");
									return null;
								}
								earliest_scene = (igr_result == null)? -1:range[0];
								
							} else {
							// So far the same parameters. Modify - use IMS A,T,R,(Z ?), adjust X,Y, (Z?), same as for initial adjustment
							earliest_scene = Interscene.reAdjustPairsLMAInterscene( // after combo dsi is available and preliminary poses are known
									clt_parameters, // CLTParameters  clt_parameters,
									mb_max_gain,    // double         mb_max_gain,
									lma_use_Z,      // boolean use_Z,
									lma_use_R,      // boolean use_R,
									lma_use_XY,     // boolean        use_XY,
									lma_use_AT,     // boolean        use_AT,
									disable_ers,    // boolean        disable_ers,
									disable_ers_y,  // boolean        disable_ers_y,
									disable_ers_r,  // boolean        disable_ers_r,
									lma_xyzatr,     // boolean        lma_xyzatr,
									configured_lma, // boolean        configured_lma,
									lpf_xy,         // boolean        lpf_xy,       // lpf x and y, re-adjust X,Y,Z,A,T,R with pull for X,Y. Disables  
									avg_rlen,       // double         avg_rlen,
									readjust_xy_ims,// 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
									reg_weight_xy,  // double         reg_weight_xy, // regularization weight for X and Y
									reliable_ref,   // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
									quadCLTs,       // QuadCLT []     quadCLTs,
									ref_index,      // int            ref_index,
									range, // new int [] {earliest_scene, last_index},  //int     []     range,
									ers_mode,       // int            ers_mode, // 0 - keep, 1 - set from velocity, 2 - set from IMS
									!batch_mode,    // boolean        test_motion_blur,
									debugLevel) ;   // int            debugLevel)
							}
							if ((earliest_scene >= 0) && (earliest_scene < ref_index)) {
								// adjust xyz from IMS, increment quadCLTs[ref_index] disparity at infinity (to be used by sfm)
						    	double scale_img = OpticalFlow.getImgImsScale( // correctInfinityFromIMS(
						    			quadCLTs, // QuadCLT []       quadCLTs,
						    			quadCLTs[ref_index], // QuadCLT          master_CLT)
						    			earliest_scene,     // int              earliest_scene)
						    			-1);                 // int              latest_scene) { // -1 will use quadCLTs.length -1;
								double inf_disp = OpticalFlow.getImsDisparityCorrection(
										scale_img,           // double           scale_img,
										quadCLTs[ref_index], // QuadCLT          master_CLT,
										clt_parameters.imp.use_lma_dsi, // boolean          use_lma_dsi,
										debugLevel);         // final int        debugLevel) {
						    	if (debugLevel > -3) {
						    		System.out.println("Disparity at infinity ="+inf_disp+" in reference scene "+quadCLTs[ref_index].getImageName()+", scale_img="+scale_img);
						    	}
						    	if (apply_disparity_ims) {
						    		// Update DSI_MAIN with disparity at infinity. Store it somewhere in quadCLTs[ref_index]
						    		// Modify to update INTER-INTRA, not DSI_MAIN. Or always update both (if exists)
						    		boolean updated = quadCLTs[ref_index].offsetComboDSI(
						    				clt_parameters, // CLTParameters clt_parameters,
						    				inf_disp,       // double        inf_disp,
						    				false);         // boolean       silent) {
						    		if (!updated) {
						    			quadCLTs[ref_index].offsetDSI(
							    				inf_disp);     // double        inf_disp,
							    		quadCLTs[ref_index].saveDSIAll (
							    				"-DSI_MAIN", // String suffix, // "-DSI_MAIN"
							    				quadCLTs[ref_index].dsi);
							    		if (debugLevel > -3) {
							    			System.out.println("combo_dsi did not exist, updating DSI-MAIN with updated disparity at infinity for reference scene "+quadCLTs[ref_index].getImageName());
							    		}
						    		} else {
							    		if (debugLevel > -3) {
							    			System.out.println("Updated combo_dsi (-INTER-INTRA) for reference scene "+quadCLTs[ref_index].getImageName());
							    		}
						    			
						    		}
						    		quadCLTs[ref_index].incDispInfinityRef( // add to previous value that was used to generate INTER-INTRA... (intersceneExport or SFM
						    				inf_disp); // double disp)
						    		// correct xyz
						    		OpticalFlow.scaleImgXYZ(
						    				1.0/scale_img, //  double           scale_xyz,
						    				quadCLTs, //, // QuadCLT []       quadCLTs,
						    				quadCLTs[ref_index]); //QuadCLT          master_CLT)
						    		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
						    				//							 	null, // Properties properties,   // if null - will only save extrinsics)
						    				debugLevel);
						    		/*
						    		if (debugLevel > -3) {
						    			System.out.println("Updating DSI-MAIN with updated disparity at infinity for reference scene "+quadCLTs[ref_index].getImageName());
						    		}
						    		quadCLTs[ref_index].saveDSIAll (
						    				"-DSI_MAIN", // String suffix, // "-DSI_MAIN"
						    				quadCLTs[ref_index].dsi);
						    				*/
						    	} else {
							    	if (debugLevel > -3) {
							    		System.out.println("Skipping application of disparity adjustment in reference scene "+quadCLTs[ref_index].getImageName()+", scale_img="+scale_img);
							    	}
						    	}

								
								
							}
							
							
						} else {
							earliest_scene = Interscene.reAdjustPairsLMAInterscene( // after combo dsi is available and preliminary poses are known
									clt_parameters, // CLTParameters  clt_parameters,
									mb_max_gain,    // double         mb_max_gain,
									lma_use_Z,      // boolean use_Z,
									lma_use_R,      // boolean use_R,
									lma_use_XY,     // boolean        use_XY,
									lma_use_AT,     // boolean        use_AT,
									disable_ers,    // boolean        disable_ers,
									disable_ers_y,  // boolean        disable_ers_y,
									disable_ers_r,  // boolean        disable_ers_r,
									lma_xyzatr,     // boolean        lma_xyzatr,
									configured_lma, // boolean        configured_lma,
									lpf_xy,         // boolean        lpf_xy,       // lpf x and y, re-adjust X,Y,Z,A,T,R with pull for X,Y. Disables  
									avg_rlen,       // double         avg_rlen,
									readjust_xy_ims,// 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
									reg_weight_xy,  // double         reg_weight_xy, // regularization weight for X and Y
									reliable_ref,   // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
									quadCLTs,       // QuadCLT []     quadCLTs,
									ref_index,      // int            ref_index,
									new int [] {earliest_scene, last_index},  //int     []     range,
									ers_mode,       // int            ers_mode, // 0 - keep, 1 - set from velocity, 2 - set from IMS
									!batch_mode,    // boolean        test_motion_blur,
									debugLevel) ;   // int            debugLevel)
						}
					}
					// should update earliest_scene
					if ((last_index - earliest_scene + 1) < min_num_scenes) {
						System.out.println("After reAdjustPairsLMAInterscene() 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 null; 
					}
					if (earliest_scene > 0) {
						System.out.println("After reAdjustPairsLMAInterscene() not all scenes matched, earliest useful scene = "+earliest_scene+
								" (total number of scenes = "+(ref_index - earliest_scene + 1)+").");
					}
					master_CLT.inc_orient(); 
					master_CLT.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);
					if (generate_egomotion) {
						String  ego_path = master_CLT.getX3dDirectory(true)+Prefs.getFileSeparator()+
								master_CLT.getImageName()+
								"-ego-"+master_CLT.getNumOrient()+".csv"; 
						String  ego_comment = null;
						Interscene.generateEgomotionTable(
								clt_parameters, // CLTParameters  clt_parameters,
								quadCLTs, // QuadCLT []     quadCLTs,
			                    -1, //            ref_indx,
								master_CLT, // quadCLTs[ref_index], //QuadCLT        ref_scene, // may be one of quadCLTs or center_CLT
								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);
						}
					}				
				}
				// do not run last time before exiting while
				if (clt_parameters.imp.cuas_step && (center_CLT != null) && ((master_CLT.getNumOrient() < min_num_orient) || (master_CLT.getNumAccum() < min_num_interscene))) {
					if (master_CLT.getNumAccum() > 0) {
						// update  center_CLT.center_clt
						int sensor_mask_clt = -1; // all
						if (debugLevel > -3) {
							System.out.println("buildSeries(): num_orient="+master_CLT.getNumOrient()+", num_accum="+master_CLT.getNumAccum()+": Running stepCenterClt()");
						}
						boolean  condition_dsi = true;
						Cuas.stepCenterClt( 
								clt_parameters,  // CLTParameters  clt_parameters,
								quadCLTs,        // QuadCLT []     quadCLTs,
								center_CLT,      // QuadCLT        center_CLT,
								null,            // int     []     range, // or null
								combo_dsn_final, // double  [][]   ref_dsi, // DSI data for the reference scene (or null to read it from file) USE NULL?
								condition_dsi,   // boolean        condition_dsi,
								sensor_mask_clt, // int            sensor_mask, // -1 - all;
								cuas_step_debug, // boolean        cuas_debug, // 
								debugLevel);     // int            debugLevel)
					} else {
						if (debugLevel > -3) {
							System.out.println("buildSeries(): num_orient="+master_CLT.getNumOrient()+", num_accum="+master_CLT.getNumAccum()+": Skipping stepCenterClt()");
						}
					}
				}
			} // while ((master_CLT.getNumOrient() < min_num_orient) || (master_CLT.getNumAccum() < min_num_interscene)) {
		} // if (!reuse_video) {
		// later move to the right place
		if (debugLevel >-3) {
			System.out.println("===== All adjustment cycles are done, considering one-time processing, such as lazy eye calibration. ====="); 
		}
		boolean     orient_save_details = true;
		if (adjust_imu_orient) { // (quadCLTs[ref_index].getNumOrient() >= clt_parameters.imp.mb_all_index)) {
			boolean orient_combo =     clt_parameters.imp.orient_combo; // use combined rotation+orientation for IMU/camera matching
			QuadCLT.adjustImuOrient(
					clt_parameters,     //CLTParameters clt_parameters,     // CLTParameters clt_parameters,
					orient_combo, // boolean orient_combo =     clt_parameters.imp.orient_combo; // use combined rotation+orientation for IMU/camera matching
					quadCLTs, // QuadCLT[]     quadCLTs,
					ref_index, // int           ref_index,
					earliest_scene, // int           earliest_scene,
					last_index, // int           last_index,
					orient_save_details, // boolean       save_details,
					debugLevel); // 			    		int debugLevel
			// Try both orient_combo/!orient_combo for the log!
			QuadCLT.adjustImuOrient(
					clt_parameters,     //CLTParameters clt_parameters,     // CLTParameters clt_parameters,
					!orient_combo, // boolean orient_combo =     clt_parameters.imp.orient_combo; // use combined rotation+orientation for IMU/camera matching
					quadCLTs, // QuadCLT[]     quadCLTs,
					ref_index, // int           ref_index,
					earliest_scene, // int           earliest_scene,
					last_index, // int           last_index,
					orient_save_details, // boolean       save_details,
					debugLevel); // 			    		int debugLevel
			
		}
		
		// Calculate infinity correction from IMS
		if (run_ly_ims) {
			correctInfinityFromIMS(
					clt_parameters,     // CLTParameters   clt_parameters,
					quadCLTs,           // QuadCLT []      quadCLTs,	    		
					master_CLT,         // QuadCLT         master_CLT,
					quadCLT_main,       // QuadCLT          quadCLT_main,
					earliest_scene,     // int              earliest_scene)
					debugLevel       ); // final int       debugLevel)
			return null;
		}
		
		if (match_ims_timing) {
			fineTuneIMSTiming(
					clt_parameters,     // CLTParameters   clt_parameters,
					quadCLTs,           // QuadCLT []      quadCLTs,	    		
					master_CLT,         // QuadCLT         master_CLT,
					quadCLT_main,       // QuadCLT          quadCLT_main,
					earliest_scene,     // int           earliest_scene,
					last_index,         // int           last_index,
					debugLevel       ); // final int       debugLevel)
			return null;
		}
		// only run adjust if it is the first in series, the corresponding file does not exist or forced is enabled
		// index_scenes[0] should not be null here, it is assigned earlier, but just in case it will change
		// Comparing names fore safety, it should be the same instance here.
		// only run correction if it is the first (latest) scene in the sequence. And only after initial orientation
		if (exec_ly_correction) {
				if (extr_corr != null) { // exists
					// set reference to scene properties and quadCLT_main
					CorrVector corr_vector = index_scenes[0].getGeometryCorrection().getCorrVector(); // set in restoreFieldCalibration(true,...)
					quadCLT_main.getGeometryCorrection().setCorrVector(corr_vector); // updated system corr vector with the current updated one
					if (!force_ly) {
						exec_ly_correction = false;
						if (debugLevel > -3) {
							System.out.println("Skipping LY correction as it it is already done for this sequence and force_ly is not set.");
						}
					}
			}
		}
		// Actually run the LY correction if needed.
		if (exec_ly_correction) { // check ref_index !!!
			if (debugLevel > -3) {
				System.out.println("**** Running LY adjustments *****");
			}
			int max_ly_length = clt_parameters.imp.run_ly_scenes;
			QuadCLT [] lim_quadCLTs = quadCLTs;
//			int        lim_ref_index = ref_index;
			if ((max_ly_length > 0) && (quadCLTs.length > max_ly_length)) {
				int [] lim_ref_indices = new int[1];
				lim_quadCLTs = limitScenes(
						max_ly_length,      // final int            max_num_scenes,
						quadCLTs,           // final QuadCLT []     scenes,
						master_CLT);        // final QuadCLT        ref_scene)
				// for real reference scenes it will remain in lim_quadCLTs, for virtual - it will not be added 
			}
			
			// Calculate and fill per-scene target disparities as scene.dsrbg
			double [] ref_target_disparity=master_CLT.getDLS()[0]; // 0];
			// filter scenes, keeping only processed 
			// now (11/24/2025) intepolateSceneDisparity() returns nulls for the scenes that do not have orientation
			/*
			double [][] interpolated_disparities = intepolateSceneDisparity(
					clt_parameters,            // final CLTParameters  clt_parameters,
					lim_quadCLTs,                  // final QuadCLT []     scenes,
					lim_ref_index,                 // final int            indx_ref,
					ref_target_disparity,      // final double []      disparity_ref,  // disparity in the reference view tiles (Double.NaN - invalid)
					debugLevel + 2);           //  final int            debug_level
			// using master_CLT to work for  CUAS?
*/
			// m,aster_CLT can be one of the scenes or not
			double [][] interpolated_disparities = intepolateSceneDisparity(
					clt_parameters,            // final CLTParameters  clt_parameters,
					lim_quadCLTs,              // final QuadCLT []     scenes,
					master_CLT,                // final QuadCLT        ref_scene,
					ref_target_disparity,      // final double []      disparity_ref,  // disparity in the reference view tiles (Double.NaN - invalid)
					debugLevel + 2);           //  final int            debug_level
			
			ArrayList<Integer> scene_list = new ArrayList<Integer>();
			for (int i = 0; i < lim_quadCLTs.length; i++) if (interpolated_disparities[i] != null){ // should be for ref_index
				double [][] dsrbg=lim_quadCLTs[i].getDSRBG();
				if (dsrbg == null) {
					lim_quadCLTs[i].dsrbg = new double[1][];
					lim_quadCLTs[i].dsrbg[0] = interpolated_disparities[i];
				}
				scene_list.add(i);
			}
//			if (oriented_ref_index < 0) {
//				throw new IllegalArgumentException ("buildSeries(): interpolated_disparities undefined for ref_index="+ref_index);
//			}
			QuadCLT []  oriented_scenes = new QuadCLT[scene_list.size()];
			for (int i = 0; i <oriented_scenes.length; i++) {
				oriented_scenes[i] =lim_quadCLTs[scene_list.get(i)];
			}
			if (debugLevel > -3) {
				System.out.println("Using "+oriented_scenes.length+" oriented scenes (of "+quadCLTs.length+") for LY adjustment.");
			}
			adjustLYSeries(
					quadCLT_main,              // QuadCLT                                              quadCLT_main, // update extrinsics here too
					oriented_scenes,           // QuadCLT []                                           quadCLTs, // was quadCLTs, 
					master_CLT,                // oriented_ref_index,        // int                                                  ref_index,
					clt_parameters,            // CLTParameters                                        clt_parameters,
					run_ly_mode,               // int                                                  run_ly_mode, // +1 - lazy eye, +2 - infinity 
					run_ly_ims,                // boolean                                              run_ly_ims, // adjust infinity (if enabled) using horizontal movement from the IMS
					colorProcParameters,       // ColorProcParameters                                  colorProcParameters,
					channelGainParameters,     // CorrectionColorProc.ColorGainsParameters             channelGainParameters,
					rgbParameters,             // EyesisCorrectionParameters.RGBParameters             rgbParameters,
					equirectangularParameters, // EyesisCorrectionParameters.EquirectangularParameters equirectangularParameters,
					properties,                // Properties                                           properties,
					reset_from_extrinsics,     // boolean                                              reset_from_extrinsics,
					threadsMax,                // final int        threadsMax,  // maximal number of threads to launch
					updateStatus,              // final boolean    updateStatus,
					debugLevel);               // final int        debugLevel)  throws Exception
			
//			oriented_scenes[oriented_ref_index].saveConfInModelDirectory(); // save all (global) configurations in model/version directory
			// temporary fix save/restore linkedModels, sourceDirectory, sourcePaths
			// that are copied main-> aux in EyesisCorrectionParameters.updateAuxFromMain()
			// quadCLT_main.correctionsParameters

			wrappedSaveConfInModelDirectory(
			quadCLTs,     // QuadCLT [] quadCLTs,
			quadCLT_main, // QuadCLT quadCLT_main,
			master_CLT,   // QuadCLT master_CLT,
			ref_index);   // int     ref_index)
			
			
			// Copy LY calibration to the index scene (last in the sequence)
//			CorrVector corr_vector = oriented_scenes[oriented_ref_index].getGeometryCorrection().getCorrVector();
			CorrVector corr_vector = master_CLT.getGeometryCorrection().getCorrVector();
			index_scenes[0].getGeometryCorrection().setCorrVector(corr_vector); // updated system corr vector with the current updated one
			// quadCLT_main.getGeometryCorrection().setCorrVector(corr_vector); // already set in adjustLYSeries()

			// save field calibration in the top (not versioned) model directory
			index_scenes[0].saveFieldCalibrationProperties(debugLevel); // index scene
			if (debugLevel > -3) {
				//				oriented_scenes[oriented_ref_index].saveConfInModelDirectory(); // save all (global) configurations in model/version directory
				System.out.println("Saving configuration with adjusted intrinsics");
				System.out.println("**** Exiting without output files generation after running LY adjustments *****");
			}
			if (build_series_result != null) {
				build_series_result[0] = RESULTS_BUILD_SEQ_LY;
			}
			// rename version file(s)
			String x3d_path_last =    index_scenes[0].getX3dDirectory();
			String x3d_path_ref =     quadCLTs[ref_index].getX3dDirectory();
			String x3d_path_last_ly = x3d_path_last+"-LY";
			String x3d_path_ref_ly =  x3d_path_ref+"-LY";
			QuadCLTCPU.moveDir(x3d_path_last, x3d_path_last_ly);
			QuadCLTCPU.moveDir(x3d_path_ref,  x3d_path_ref_ly);
			return null;
		}

		if (photo_en && !reuse_video) {
			if (debugLevel > -3) {
				System.out.println("**** Running photometric equalization *****");
			}
			if (combo_dsn_final == null) { // always re-read?
///				combo_dsn_final =quadCLTs[ref_index].readDoubleArrayFromModelDirectory( // always re-read?
///						"-INTER-INTRA-LMA", // String      suffix,
///						0, // int         num_slices, // (0 - all)
///						null); // int []      wh);
				combo_dsn_final =master_CLT.restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky
			}
			
			double [][] combo_dsn_final_filtered = 
					conditionComboDsnFinal(
							true,                // boolean        use_conf,       // use configuration parameters, false - use following  
							clt_parameters,      // CLTParameters  clt_parameters,
							combo_dsn_final,     // double [][]    combo_dsn_final, // dls,
							master_CLT, // QuadCLT        scene,
							debugLevel); // int            debugLevel);// > 0
			// replace
			runPhotometric(
					clt_parameters,                   // CLTParameters       clt_parameters,
					colorProcParameters,              // ColorProcParameters colorProcParameters,
					set_channels[ref_index].set_name, // String set_name
					quadCLTs,                         // QuadCLT[]           quadCLTs,
					earliest_scene,                   // int                 earliest_scene,
					last_index,                       // int                 last_scene,
					ref_index,                        // int                 ref_index,
					combo_dsn_final_filtered,         // double [][]         combo_dsn_final_filtered,
					threadsMax,                       // int                 threadsMax,  // maximal number of threads to launch
					updateStatus,                     //  boolean             updateStatus,
					debugLevel);                      // int                 debugLevel);

		} else {
			if (debugLevel> -3) {
				System.out.println("Using photometric calibration from scene "+master_CLT.getPhotometricScene());
			}
		}
		// only now copy photometric to main instance
		if (!reuse_video) {
			quadCLT_main.setLwirOffsets(master_CLT.getLwirOffsets());
			quadCLT_main.setLwirScales (master_CLT.getLwirScales ());
			quadCLT_main.setLwirScales2(master_CLT.getLwirScales2());
			quadCLT_main.setPhotometricScene(master_CLT.getPhotometricScene());
			if (debugLevel> -3) {
				System.out.println("Applied photometric calibration from scene "+master_CLT.getPhotometricScene()+
						" to quadCLT_main, so it will be applied to the next sequences and saved in config file");
			}
		}
		
		if (calc_quat_corr) {
			double [] quat_rms = new double [5];
///			double []      enu_corr = new double[3];
			
			int corr_index = -1;
			if ((ref_index >= 0) &&(quadCLTs[ref_index] == master_CLT)) {
				corr_index = ref_index;
			}
			double  scale_quat = 1.0; // clt_parameters.imp.imsq_scale_quat; // adjust to context
			double  reg_weight = clt_parameters.imp.imsq_reg_weight; // adjust to context
			double [] quatCorr = Interscene.getQuaternionCorrection(
					clt_parameters, // CLTParameters  clt_parameters,
					scale_quat,     // double         scale_quat, 
					reg_weight,     // double         reg_weight,
					quadCLTs,       // QuadCLT []     quadCLTs,
					corr_index, // 				ref_index,      // int            ref_index,
					master_CLT, // QuadCLT        ref_scene, // may be one of quadCLTs or center_CLT
					earliest_scene, // int            earliest_scene,
					-1,             // int            latest_scene,				
					quat_rms,           // double []      rms // null or double[2];
					null, // enu_corr,       //double []      enu_corr,
					debugLevel); // int            debugLevel
			if (quatCorr != null) {
				int num_iter = (int) quat_rms[4];
				if (debugLevel> -3) {
					System.out.println("LMA done on iteration "+num_iter+
							" full RMS="+quat_rms[0]+" ("+quat_rms[2]+"), pure RMS="+quat_rms[1]+" ("+quat_rms[3]+")");
					QuadCLTCPU.showQuatCorr(quatCorr,null); // enu_corr);
				}
				master_CLT.setQuatCorr(quatCorr);
				quadCLT_main.setQuatCorr(quatCorr);
//				master_CLT.setENUCorrMetric(enu_corr);
//				quadCLT_main.setENUCorrMetric(enu_corr);
				master_CLT.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);
	        	StringBuffer sb = new StringBuffer();
	        	sb.append(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime())+"\n");
	        	Rotation rot = new Rotation(quatCorr[0],quatCorr[1],quatCorr[2],quatCorr[3], false); // no normalization - see if can be scaled
				sb.append("Applying correction to the IMS to world orientation (rotating around IMS vertical) 1:\n");
				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("compass:     quatCorr=["+quatCorr[0]+", "+quatCorr[1]+", "+quatCorr[2]+", "+quatCorr[3]+"]\n");
				sb.append("compass:     ATR(rad)=["+corr_angles[0]+", "+corr_angles[1]+", "+corr_angles[2]+"]\n");
				sb.append("compass:     ATR(deg)=["+corr_degrees[0]+", "+corr_degrees[1]+", "+corr_degrees[2]+"]\n");
///				sb.append("compass: ENU corr (m)=["+enu_corr[0]+", "+enu_corr[1]+", "+enu_corr[2]+"]\n");
	        	sb.append("------------------------\n\n");
	        	master_CLT.appendStringInModelDirectory(sb.toString(),QuadCLT.IMU_CALIB_LOGS_SUFFIX); // String  suffix)
				if (debugLevel > -3) {
					System.out.print(sb.toString());
				}
			} else {
				if (debugLevel> -3) {
					System.out.println("Failed to perform attitude correction with QuaternionLma.");
				}
				
			}
		}
		boolean extract_center_orientation = false; //  clt_parameters.imp.extract_center_orientation; // true; // false; // true; 
		double [][] center_ATR = null; //  {{center_A, center_T, average_R},{radius_A, radius_T}}
		double [] cuas_atr = ZERO3;
		if (extract_center_orientation && clt_parameters.imp.lock_position) {
			// cuas_atr will be use for rendering combo images, the individual coordinate will be already image-based, not ims-based
			center_ATR = CuasCenterLma.getCenterATR(
					quadCLTs,                                 // QuadCLT [] quadCLTs,
					quadCLTs[ref_index],                      // QuadCLT    ref_scene,ref_index,   //int        ref_index,
					new int [] {earliest_scene, last_index},  // int     []     range,
    				true, // 			boolean    disable_AT_omegas,
					debugLevel); // int debugLevel);
			cuas_atr = new double [] { center_ATR[0][0], center_ATR[0][1], center_ATR[0][2]};
			// Check omegas here !
			System.out.println ("Omegas ATR: "+center_ATR[2][0]+", "+center_ATR[2][1]+", "+center_ATR[2][2]);
		}
		
		// TODO: Refine center_CLT if it exists, not create
		
		
		if (combine_clt) {
			// see if it exists
			/*
			QuadCLT center_CLT = QuadCLT.restoreCenterClt(quadCLTs[ref_index]); // QuadCLT ref_clt);
			*/
			
			// if (cuas_reset_first)
			boolean dbg_created=false;
			int sensor_mask_clt = -1; // all
			if (center_CLT == null) {
				boolean  condition_dsi = true;
				if (combo_dsn_final == null) {
					combo_dsn_final =quadCLTs[ref_index].restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky
				}
				center_CLT = Cuas.createCenterClt( // assuming cuas_rotation is true
						clt_parameters,  // CLTParameters  clt_parameters,
						quadCLTs,        // QuadCLT []     quadCLTs,
						quadCLTs[ref_index], // QuadCLT        ref_scene, // where combo_dsi is
						null,            // int     []     range, // or null
						combo_dsn_final, // double  [][]   ref_dsi, // DSI data for the reference scene (or null to read it from file)
						condition_dsi,   // boolean        condition_dsi,
						sensor_mask_clt, // int            sensor_mask, // -1 - all;
			    		true,            // boolean          // new_average,
						debugLevel);     // int            debugLevel)
				if (cuas_centers != null) {
					cuas_centers[1] = center_CLT.getImagePath();
				}
				if (debugLevel > -4) {
					System.out.println("Created/saved center CLT "+center_CLT.getImagePath());
				}
				dbg_created=true;
			} else if (update_existing) {
				if (debugLevel > -4) {
					System.out.println("Updating existing CLT (FIXME: accumulates errors - needs fixing)");
				}
				boolean  condition_dsi = true;
				if (combo_dsn_final == null) {
					combo_dsn_final =center_CLT.restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky
				}
				center_CLT = Cuas.createCenterClt( // assuming cuas_rotation is true
						clt_parameters,   // CLTParameters  clt_parameters,
						quadCLTs,         // QuadCLT []     quadCLTs,
						center_CLT,       // QuadCLT        ref_scene, // where combo_dsi is
						null,             // int     []     range, // or null
						combo_dsn_final,  // double  [][]   ref_dsi, // DSI data for the reference scene (or null to read it from file)
						condition_dsi,    // boolean        condition_dsi,
						sensor_mask_clt,  // int            sensor_mask, // -1 - all;
						cuas_reset_first, // boolean          // new_average,
						debugLevel);      // int            debugLevel)
				if (cuas_centers != null) {
					cuas_centers[1] = center_CLT.getImagePath();
				}
				if (debugLevel > -4) {
					System.out.println("Updated/saved center CLT "+center_CLT.getImagePath());
				}

			} else {
				if (debugLevel > -4) {
					System.out.println("Skipping update of center CLT (it is read from "+center_CLT.getImagePath()+").");
				}
				if (cuas_centers != null) {
					cuas_centers[1] = center_CLT.getImagePath();
				}
				
			}
			//		setCenterAverage();
			ImagePlus imp_center_clt= center_CLT.showCenterClt(
    				null, // float [][] fclt, // may be null
					clt_parameters,            // CLTParameters clt_parameters,
					false); // true);          // 						boolean       show);
			center_CLT.setCenterAverage(imp_center_clt);
			// just for verification
			if (cuas_debug) { // show_clt && !clt_parameters.batch_run) {
				if (!clt_parameters.batch_run) {
					imp_center_clt.show();
				}
				if (imp_center_clt != null) {
					String suffix =dbg_created?"-CLT-CREATED":"CLT-UPDATED";
					center_CLT.saveImagePlusInModelDirectory(
							suffix,          // String      suffix, // null - use title from the imp
							imp_center_clt); // ImagePlus   imp)
				}
			}
		}		
		// using master_CLT instead of the quadCLTs[ref_index]

		if (generate_egomotion) {
			if (debugLevel >-3) {
				System.out.println("===== Generating composite egomotion CSV.==== ");
			}
			boolean ego_show = !clt_parameters.batch_run; //true; 
			String  ego_path = master_CLT.getX3dDirectory(true)+Prefs.getFileSeparator()+
					master_CLT.getImageName()+
					master_CLT.correctionsParameters.egomotionSuffix;
			String  ego_comment = null;
		    Interscene.generateEgomotionTable(
		    		clt_parameters, // CLTParameters  clt_parameters,
					quadCLTs, // QuadCLT []     quadCLTs,
                    -1, //            ref_indx,
					master_CLT, // quadCLTs[ref_index], //QuadCLT        ref_scene, // may be one of quadCLTs or center_CLT
					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 (ego_show) {
		    	Interscene.generateEgomotionTable(
		    			clt_parameters, // CLTParameters  clt_parameters,
		    			quadCLTs, // QuadCLT []     quadCLTs,
	                    -1, //            ref_indx,
		    			master_CLT, // quadCLTs[ref_index], //QuadCLT        ref_scene, // may be one of quadCLTs or center_CLT
						earliest_scene, // int            earliest_scene,
		    			ego_path, // String         path,
		    			ego_comment, // String         comment);
						debugLevel); // int debugLevel)
		    }
		}
		if (debugLevel >-3) {
			System.out.println("===== All adjustment done, starting output files generation. =====");
		}

		// All done with refining orientations and disparities
		boolean air_mode_en = clt_parameters.imp.air_mode_en; // fast airplane mode
		if (air_mode_en) {
			double []  gnd_disp = new double [master_CLT.getTilesX() * master_CLT.getTilesY()]; 
			GroundPlane gp = GroundPlane.getGroundPlane(
					clt_parameters,  // CLTParameters  clt_parameters,
					master_CLT,      // final QuadCLT    ref_scene,
					gnd_disp,        // final double []  gnd_disp, // if not null
					debugLevel);     // final int        debugLevel)
			master_CLT.setGroundPlane(gp);
			master_CLT.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 master_CLT to a list of the reference frames and save its configuration
			// debugging - see if index_scenes[0] is now a different instance than the one that has reference_timestamp
			if (index_scenes[0].getReferenceTimestamp() == null) {
				System.out.println ("***** index_scenes[0].getReferenceTimestamp() == null, reading its configuration.");
				index_scenes[0].restoreInterProperties( // restore properties for interscene processing (extrinsics, ers, ...) // get relative poses (98)
						null, // String path,             // full name with extension or null to use x3d directory
						false, // boolean all_properties,//				null, // Properties properties,   // if null - will only save extrinsics)
						debugLevel);
			} else {
				System.out.println ("***** index_scenes[0].getReferenceTimestamp() = "+index_scenes[0].getReferenceTimestamp()+
						", properties are preserved, no need to re-read them.");
			}
			
			index_scenes[0].addRefScene(master_CLT.getImageName());
			index_scenes[0].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);
			if (clt_parameters.imp.fgnd_hist_gen) {
				boolean hist_ok = GroundPlane.saveElevationsHistogram(
						clt_parameters, // final CLTParameters clt_parameters,
						master_CLT, // final QuadCLT       ref_Clt,
						debugLevel); // final int           debugLevel)
				
				if (hist_ok) {
					if (debugLevel > -3) {
						System.out.println("Elevations relative to the flat ground surface calculated and saved."); 
					}
				} else {
					if (debugLevel > -3) {
						System.out.println("FAILED to calculate elevations relative to the flat ground surface."); 
					}
				}
			}
			
		}
		
		boolean test_ground = false; // true; // false; // true;
		if (test_ground) {
			final boolean   use_lma =         clt_parameters.gmap_use_lma ; // true;  // ;
			final double    discard_low =     clt_parameters.gmap_discard_low ; //0.01;  // fraction of all pixels
			final double    discard_high =    clt_parameters.gmap_discard_high ; //0.5;   // fraction of all pixels
			final double    discard_adisp =   clt_parameters.gmap_discard_adisp ; //0.2;   // discard above/below this fraction of average height 
			final double    discard_rdisp =   clt_parameters.gmap_discard_rdisp ; //0.02;  // discard above/below this fraction of average height
			final double    pix_size =        clt_parameters.gmap_pix_size ; //0.005; // hdr_x0y0,       // in meters
			final int       max_image_width = clt_parameters.gmap_max_image_width ; //4000; // 3200;  // increase pixel size as a power of 2 until image fits
//			final double  range_disparity_offset = clt_parameters.imp.range_disparity_offset;// double  range_disparity_offset
    		boolean       use_parallel_proj = false; // true;
			int []    hdr_whs = new int[3];
			double [] hdr_x0y0 = new double[2];
			
	     	double [][] dls = master_CLT.getDLS();
	     	if (dls==null) {
	     		return null;
	     	}
	     	double [][] ds = new double [][] {dls[use_lma?1:0].clone(), dls[2]};
			
			final boolean [] good_tiles = new boolean[ds[0].length];
			String     dbg_title = "-ground_tilts"; 
			
			double [][] to_ground_xyzatr0= GroundPlane.getPlaneDualPassMetric( // returns to_ground_xyzatr (rotated around the ground point nadir of teh drone) 
					clt_parameters,        // final CLTParameters    clt_parameters,
					master_CLT,            // final QuadCLT    ref_Clt,
					master_CLT.getTilesX(),// final int        width,
					good_tiles,            // final boolean [] good_tiles, // null or boolean[data.length] // should all be false
					null, // 			final double []  gnd_disp, // if not null
					dbg_title,             // final String     dbg_title,
					debugLevel);           // final int        debugLevel)
			
			ErsCorrection.printVectors(to_ground_xyzatr0);
			
			master_CLT.getErsCorrection().printVectors    (to_ground_xyzatr0[0], to_ground_xyzatr0[1]); 		
			
			double [] plane_tilts = GroundPlane.getPlaneDualPass( // returns tiltX, tiltY, disp_center, frac_good 
					clt_parameters,        // final CLTParameters clt_parameters,
					ds[0],                 // final double []  data,
					ds[1],                 // final double []  weights,
					master_CLT.getTilesX(),// final int        width,
					good_tiles,            // final boolean [] good_tiles, // null or boolean[data.length] // should all be false
					dbg_title,             // final String     dbg_title,
					master_CLT,            // final QuadCLT    dbg_scene,
					debugLevel);           // final int        debugLevel)
			if (debugLevel >-3) {
				System.out.println("Ground plane: tiltX="+plane_tilts[0]+
						" tiltY="+plane_tilts[1]+" offset="+plane_tilts[2]+" fraction good="+plane_tilts[3]);
			}
			
					
			double [][] to_ground_xyzatr_airplane = master_CLT.getGroundNoImsAirplane(
					clt_parameters,  // CLTParameters                            clt_parameters,
					use_lma,         // boolean use_lma,
					use_parallel_proj, // boolean       use_parallel_proj,
					range_disparity_offset,// double  range_disparity_offset
					discard_low,     // double discard_low,    // fraction of all pixels
					discard_high,    // double discard_high,   // fraction of all pixels
					discard_adisp,   // double discard_adisp,  // discard above/below this fraction of average height 
					discard_rdisp,   // double discard_rdisp   // discard above/below this fraction of average height
					pix_size,        // double pix_size,       // in meters
					max_image_width, // int    max_image_width // increase pixel size as a power of 2 until image fits
		    		hdr_x0y0,        // double [] x0y0,        // initialize to double[2] to return width, height
		    		hdr_whs,         // int [] whs,            // initialize to int[3] to return {width, height, scale reduction}
		    		debugLevel);     // int debug_level
			
			
			double [][] to_ground_xyzatr = master_CLT.getGroundNoIms(
					clt_parameters,  // CLTParameters                            clt_parameters,
					use_lma,         // boolean use_lma,
					use_parallel_proj, // boolean       use_parallel_proj,
					range_disparity_offset,// double  range_disparity_offset
					discard_low,     // double discard_low,    // fraction of all pixels
					discard_high,    // double discard_high,   // fraction of all pixels
					discard_adisp,   // double discard_adisp,  // discard above/below this fraction of average height 
					discard_rdisp,   // double discard_rdisp   // discard above/below this fraction of average height
					pix_size,        // double pix_size,       // in meters
					max_image_width, // int    max_image_width // increase pixel size as a power of 2 until image fits
		    		hdr_x0y0,        // double [] x0y0,        // initialize to double[2] to return width, height
		    		hdr_whs,         // int [] whs,            // initialize to int[3] to return {width, height, scale reduction}
		    		debugLevel);     // int debug_level
			master_CLT.getErsCorrection().printVectors    (to_ground_xyzatr[0], to_ground_xyzatr[1]); 		
			double [][] to_ground_xyzatr_ims = master_CLT.getGroundIms(
					clt_parameters,  // CLTParameters clt_parameters,
					use_lma,         // boolean use_lma,
					use_parallel_proj, // boolean       use_parallel_proj,
					range_disparity_offset,// double  range_disparity_offset
					discard_low,     // double discard_low,    // fraction of all pixels
					discard_high,    // double discard_high,   // fraction of all pixels
					discard_adisp,   // double discard_adisp,  // discard above/below this fraction of average height 
					discard_rdisp,   // double discard_rdisp   // discard above/below this fraction of average height
					pix_size,        // double pix_size,       // in meters
					max_image_width, // int    max_image_width // increase pixel size as a power of 2 until image fits
		    		hdr_x0y0,        // double [] x0y0,        // initialize to double[2] to return width, height
		    		hdr_whs,         // int [] whs,            // initialize to int[3] to return {width, height, scale reduction}
		    		debugLevel);     // int debug_level
			master_CLT.getErsCorrection().printVectors    (to_ground_xyzatr_ims[0], to_ground_xyzatr_ims[1]); 		
			
			System.out.println("getGroundIms Done");
		}
		
		if (test_ers) { // only debug feature
			test_ers0 = quadCLTs.length -1; // make it always == reference !
			testERS(
					clt_parameters, // CLTParameters clt_parameters,
					test_ers0,      // int           indx0, // reference scene in a pair
					test_ers1,      // int           indx1, // other scene in a pair
					quadCLTs,       // QuadCLT []    quadCLTs,
					ref_index,      // int           ref_index,
					debugLevel);    // int           debugLevel)
			System.out.println("buildSeries(): ABORTED after test_ers"); //
			return master_CLT.getX3dTopDirectory();
		}
		
		// generates 3-d modes, colors, stereos, tiffs/videos
		
		// Testing vegetation, for debugging supposing that terrain layer is already set in *-INTER-INTRA-LMA.tiff - normally it is only set during 3d model generation
		// Moved to the very end, after 3D
//		boolean test_vegetation = true;
		
		if (master_CLT.hasCenterClt() && clt_parameters.imp.cuas_targets_en) { // cuas mode
			if (debugLevel >-3) {
				System.out.println("===== Running CUAS ranging. =====");
			}
			CuasRanging cuasRanging = new CuasRanging 	(
					clt_parameters, // CLTParameters     clt_parameters,
					master_CLT, // QuadCLT           center_CLT,
					quadCLTs, // QuadCLT []        scenes,
					debugLevel); // int               debugLevel) {
			CuasMotion cuasMotion = cuasRanging.detectTargets(
					uasLogReader, // UasLogReader    uasLogReader,
					batch_mode);  // boolean         batch_mode)
			if (cuasMotion == null) {
				System.out.println("Failed target detection. Probably, radar mode was selected but target file does not exist (created with \"CUAS Combine\" command)");
			} else {
				if (debugLevel > -4) {
					System.out.println("Target detection DONE");
				}
			}
		}		

		if (generate_mapped || reuse_video) { // modifies combo_dsn_final ?
			int tilesX =  master_CLT.getTileProcessor().getTilesX();
	        int tilesY =  master_CLT.getTileProcessor().getTilesY();
	        double [] disparity_fg =  null;
	        double [] strength_fg =   null;
	        double [] disparity_bg =  null;
	        double [] strength_bg =   null;
	        double [] disparity_raw = null;
	        if (generate_mapped) {
	        	disparity_raw = new double [tilesX * tilesY];
	        	Arrays.fill(disparity_raw,clt_parameters.disparity);
	        	if (combo_dsn_final == null) {
					combo_dsn_final =master_CLT.restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky
	        	}
	        	double [][] dls = { 
	        			combo_dsn_final[COMBO_DSN_INDX_DISP], // **** null on second scene sequence
	        			combo_dsn_final[COMBO_DSN_INDX_LMA],
	        			combo_dsn_final[COMBO_DSN_INDX_STRENGTH]
	        	};
	        	double [][] ds = conditionInitialDS(
	        			true,                // boolean        use_conf,       // use configuration parameters, false - use following  
	        			clt_parameters,      // CLTParameters  clt_parameters,
	        			dls,                 // double [][]    dls
	        			master_CLT, // quadCLTs[ref_index], // QuadCLT        scene,
	        			debugLevel);			

	        	disparity_fg = ds[0]; // combo_dsn_final[COMBO_DSN_INDX_DISP_FG];
	        	strength_fg =  ds[1];
	        	
	        	
	        	// BG mode
	        	double [] bg_lma = combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL].clone();
	        	double [] bg_str = combo_dsn_final[COMBO_DSN_INDX_STRENGTH].clone();

	        	for (int i = 0; i < bg_lma.length; i++) {
	        		if (Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_LMA][i])){
	        			bg_lma[i] = Double.NaN;
	        		}
	        		if (!Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_DISP_BG][i])){
	        			bg_lma[i] = combo_dsn_final[COMBO_DSN_INDX_LMA_BG][i];
	        		}
	        		if (!Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_STRENGTH_BG][i])){
	        			bg_str[i] = combo_dsn_final[COMBO_DSN_INDX_STRENGTH_BG][i];
	        		}
	        	}
	        	double [][] dls_bg = {
	        			combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL],
	        			bg_lma,
	        			bg_str
	        	};
	        	double [][] ds_bg = conditionInitialDS(
	        			true, // boolean        use_conf,       // use configuration parameters, false - use following  
	        			clt_parameters,         // CLTParameters  clt_parameters,
	        			dls_bg,                 // double [][]    dls
	        			master_CLT, // quadCLTs[ref_index],    // QuadCLT        scene,
	        			debugLevel);			
	        	disparity_bg = ds_bg[0]; // combo_dsn_final[COMBO_DSN_INDX_DISP_FG];
	        	strength_bg =  ds_bg[1];
	        	// for now using disparity for just standard size (90x64), later may use full size and at
	        	// minimum fill peripheral areas with Laplacian?
	        	double [][] dxyzatr_dt = new double [quadCLTs.length][];
	        	int [][] min_max_vel = getERSStats(
	        			clt_parameters,   // CLTParameters clt_parameters,
	        			quadCLTs,         // QuadCLT []    quadCLTs,
	        			ref_index,        // int           ref_index, **** here it needs to be among scenes
	        			dxyzatr_dt);      // double [][] dxyzatr_dt);
	        	double [] vel_avg = new double [min_max_vel.length];
	        	int navg = 0;
	        	for (int n = 0; n < dxyzatr_dt.length; n++) if (dxyzatr_dt[n] != null){
	        		for (int i = 0; i < vel_avg.length; i++) {
	        			vel_avg[i] += dxyzatr_dt[n][i];
	        		}
	        		navg++;
	        	}
	        	for (int i = 0; i < vel_avg.length; i++) {
	        		vel_avg[i] /= navg;
	        	}

	        	String [] vel_names = {"Vx","Vy","Vz","Vaz","Vtl","Vrl"};
	        	String [] vel_units = {"m/s","m/s","m/s","mrad/s","mrad/s","mrad/s","mrad/s"};
	        	System.out.println("Minimal/maximal/average linear and angular velocities of the scenes (ref. scene: "+
	        			quadCLTs[quadCLTs.length - 1].getImageName()+")");
	        	for (int i = 0; i < min_max_vel.length; i++) {
	        		if ((min_max_vel[i][0] < 0) || (min_max_vel[i][1]<0)) {
	        			System.out.println("Bug: min_max_vel["+i+"][0]="+min_max_vel[i][0]);
	        			System.out.println("Bug: min_max_vel["+i+"][1]="+min_max_vel[i][1]);
	        			continue;
	        		}
	        		double v0 = dxyzatr_dt[min_max_vel[i][0]][i];
	        		double v1 = dxyzatr_dt[min_max_vel[i][1]][i];
	        		double va = vel_avg[i];
	        		if (i == 2) { // forward movement
	        			v0*=-1;
	        			v1*=-1;
	        			va*=-1;
	        		}
	        		if (i > 2) { // angular
	        			v0*=1000;
	        			v1*=1000;
	        			va*=1000;
	        		}
	        		System.out.println(String.format(
	        				"%3s: min: %3d (%8.3f%-6s), max: %3d (%8.3f%-6s), average: %8.3f%-6s",
	        				vel_names[i], min_max_vel[i][0], v0, vel_units[i],
	        				min_max_vel[i][1], v1, vel_units[i],
	        				va, vel_units[i]));
	        	}
	        }
	        
	        // for older compatibility mode3d = -1 for RAW, 0 - INF, 1 - FG, 2 BG
	        for (int mode3d = -1; mode3d < (generate_modes3d.length-1); mode3d++) if (generate_modes3d[mode3d+1]) {
	        	Rectangle fov_tiles = new Rectangle(
	        			extra_hor_tile,
	        			extra_vert_tile,
	        			tilesX + 2 * extra_hor_tile,
	        			tilesY + 2 * extra_vert_tile);
	        	if ((mode3d < 0) || (crop_3d && (mode3d > 0))) {
	        		fov_tiles = null; // use sensor dimensions
	        	}
	        	boolean is_3d = mode3d > 0;
	        	boolean gen_stereo = is_3d && generate_stereo;
	        	double [][]  views = (gen_stereo)? stereo_views : new double [][] {{0.0,0.0,0.0}};
	        	
	        	for (int ibase = 0; ibase < views.length; ibase++) if (!gen_stereo || generate_stereo_var[ibase]) {
	        		double stereo_baseline = gen_stereo? views[ibase][0] : 0.0;
	        		boolean is_stereo = gen_stereo && stereo_baseline > 0;
	        		double  stereo_baseline_meters =    0.001 * stereo_baseline;
	        		double  view_height_meters =        0.001 * views[ibase][1];
	        		double  view_back_meters =          0.001 * views[ibase][2];
	        		// col_mode: 0 - mono, 1 - color
	        		for (int col_mode = 0; col_mode < 2; col_mode++) if (gen_seq_mono_color[col_mode]){ // skip if not needed
	        			double[] selected_disparity = (mode3d > 1)?disparity_bg:((mode3d > 0)?disparity_fg: disparity_raw); 
	        			double[] selected_strength =  (mode3d > 1)?strength_bg:((mode3d > 0)?strength_fg: null);
	        			if (selected_strength != null) { // for FG/BG only, fixing for transformCameraVew()
	        				for (int i = 0; i < selected_disparity.length; i++) {
	        					if (!Double.isNaN(selected_disparity[i]) && (selected_strength[i] == 0)) selected_strength[i] = 0.01; // transformCameraVew ignores strength= 0
	        				}
	        			}
	        			final boolean       toRGB = col_mode > 0;
	        			String scenes_suffix = master_CLT.getImageName()+ // uadCLTs[quadCLTs.length-1].getImageName()+
	        					"-SEQ-" + IntersceneMatchParameters.MODES3D[mode3d+1] + "-"+(toRGB?"COLOR":"MONO");
	        			String um_suffix = "";
	        			if (!toRGB && um_mono) {
	        				if (mono_fixed) {
	        					um_suffix = String.format("-UM%.1f_%.3f_%.0f",um_sigma,um_weight,mono_range);
///		        				scenes_suffix+=String.format("-UM%.1f_%.3f_%.0f",um_sigma,um_weight,mono_range);
	        				} else {
	        					um_suffix = String.format("-UM%.1f_%.3f_A",um_sigma,um_weight);
///		        				scenes_suffix+=String.format("-UM%.1f_%.3f_A",um_sigma,um_weight);
	        				}
	        			}
	        			int num_stereo = (is_stereo && (mode3d > 0))? 2:1; // only for 3D views
	        			boolean combine_left_right = (num_stereo > 1) && (stereo_merge || (anaglyth_en && !toRGB));
	        			ImagePlus [] imp_scenes_pair = new ImagePlus[num_stereo];
	        			String scenes_suffix_pair = scenes_suffix;
	        			for (int nstereo = 0; nstereo < num_stereo; nstereo++) {
	        				double [] xyz_offset = {
	        						-stereo_baseline_meters * (nstereo - 0.5) * (num_stereo - 1), // x offset
	        						-view_height_meters,  // Y offset
	        						-view_back_meters};   // Z offset
	        				if (num_stereo > 1) {
	        					scenes_suffix = scenes_suffix_pair + ((nstereo > 0)?"-RIGHT":"-LEFT"); // check if opposite
	        					scenes_suffix += "-B"+String.format("%.0f",views[ibase][0]);
	        				}
	        				if (views[ibase][1] != 0) {
	        					scenes_suffix += "-Y"+String.format("%.0f",views[ibase][1]);
	        				}
	        				if (views[ibase][2] != 0) {
	        					scenes_suffix += "-Z"+String.format("%.0f",views[ibase][2]);
	        				}
	        				if (generate_mapped) {
		        				double [][] ds_vantage = new double[][] {selected_disparity,selected_strength};
		        				if ((views[ibase][0] != 0) || (views[ibase][1] != 0) || (views[ibase][2] != 0) || (master_CLT.hasCenterClt()) && (mode3d > 0)) {
		        					boolean debug_vantage = false; // true;
		        					//dls
		        			    	double [][] dbg_vantage = debug_vantage ? (new double[7][]): null;
		        					if (dbg_vantage != null) {
		        			        	double [][] dls = { 
		        			        			combo_dsn_final[COMBO_DSN_INDX_DISP], // **** null on second scene sequence
		        			        			combo_dsn_final[COMBO_DSN_INDX_LMA],
		        			        			combo_dsn_final[COMBO_DSN_INDX_STRENGTH]
		        			        	};
		        						
		        						for (int i = 0; i < 3; i++) {
		        							dbg_vantage[i] = dls[i].clone();
		        						}
		        						for (int i = 0; i < 2; i++) {
		        							dbg_vantage[i+3] = ds_vantage[i].clone();
		        						}
		        					}
		        					ds_vantage = transformCameraVew(
		        							null, // (debug_ds_fg_virt?"transformCameraVew":null),     // final String    title,
		        							ds_vantage,                 // final double [][] dsrbg_camera_in,
		        							xyz_offset, // _inverse[0], // final double [] scene_xyz, // camera center in world coordinates
		        							ZERO3, // _inverse[1], // final double [] scene_atr, // camera orientation relative to world frame
		        							master_CLT, // quadCLTs[ref_index],      // final QuadCLT   scene_QuadClt,
		        							master_CLT, // quadCLTs[ref_index],      // final QuadCLT   reference_QuadClt,
		        							8); // iscale);                  // final int       iscale);
		        					if (dbg_vantage != null) {
		        						for (int i = 0; i < 2; i++) {
		        							dbg_vantage[i+5] = ds_vantage[i].clone();
		        						}
		        			    		ShowDoubleFloatArrays.showArrays(
		        			    				dbg_vantage,
		        			    				master_CLT.getTileProcessor().getTilesX(),
		        			    				master_CLT.getTileProcessor().getTilesY(),
		        			    				true,
		        			    				center_CLT.getImageName()+"-ds_vantage-old", // "-corr2d"+"-"+frame0+"-"+frame1+"-"+corr_pairs,
		        			    				new String[] {"disp0","lma0", "str0", "disp","str","virt_disp", "virt_str"});
		        					}
		        					
		        				}
		        				if (master_CLT.getFPN() != null) {
		        					scenes_suffix += "-FPN";
		        				}
		        				float [] average_pixels = (master_CLT.getCenterAverage() != null) ? ((float []) master_CLT.getCenterAverage().getProcessor().getPixels()):null;
		        				float [][] average_channels = new float [][] {average_pixels}; // for future color images
		        				imp_scenes_pair[nstereo]= renderSceneSequence(
		        						clt_parameters,     // CLTParameters clt_parameters,
		        						master_CLT.hasCenterClt(), // boolean        mode_cuas,
		        						false, // clt_parameters.imp.um_mono, // boolean        um_mono,
		        						clt_parameters.imp.calculate_average,      // boolean        insert_average, // then add new parameter, keep add average
							    		null, // int []         average_range,
		        						average_channels,     //  average_slice,
		        						clt_parameters.imp.subtract_average, // boolean    subtract_average,
		        						clt_parameters.imp.running_average,  // int            running_average,
		        						fov_tiles,          // Rectangle     fov_tiles,
		        						mode3d,             // int           mode3d,
		        						toRGB,              // boolean       toRGB,
		        						xyz_offset,         // double []     stereo_offset, // offset reference camera {x,y,z}
		        						cuas_atr,           // double []      stereo_atr, // offset reference orientation (cuas)
		        						sensor_mask,        // int           sensor_mask,
		        						scenes_suffix,      // String        suffix,
		        						ds_vantage[0],      // selected_disparity, // double []     ref_disparity,			
		        						quadCLTs,           // QuadCLT []    quadCLTs,
		        						master_CLT, // ref_index,          // int           ref_index,
		        						threadsMax,         // int           threadsMax,
		        						debugLevel);        // int           debugLevel);
		        				if (toRGB || um_mono_linear || !um_mono) { // save linear w/o UM 
	        						if (save_mapped_mono_color[col_mode]) {	        	
	        							master_CLT.saveImagePlusInModelDirectory(
	        									null, // "GPU-SHIFTED-D"+clt_parameters.disparity, // String      suffix,
	        									imp_scenes_pair[nstereo]); // imp_scenes); // ImagePlus   imp)
	        						}
	        					}
	        					if (!toRGB && um_mono) {
	        						scenes_suffix = imp_scenes_pair[nstereo].getTitle();
	        						imp_scenes_pair[nstereo]=applyUM ( // apply UM
	        								scenes_suffix+ um_suffix , // final String title, // should include -UM...
	        								imp_scenes_pair[nstereo],  // final ImagePlus imp,
	        					    		um_sigma,                  // final double um_sigma,
	        					    		um_weight);                // final double um_weight)
	        						if (save_mapped_mono_color[col_mode]) {	// save with UM        	
	        							master_CLT.saveImagePlusInModelDirectory(
	        									null, // "GPU-SHIFTED-D"+clt_parameters.disparity, // String      suffix,
	        									imp_scenes_pair[nstereo]); // imp_scenes); // ImagePlus   imp)
	        							if (master_CLT.hasCenterClt()) { // cuas mode
	        								boolean new_cuas_mode = (debugLevel < 1000);
	        								if (new_cuas_mode) {
	        									System.out.println("CuasMotion should be already calculated, disabling old mode below.");
	        								} else {
	        									System.out.println("Will not be detecting targets in old mode.");
	        									/*
	        									boolean insert_average = (master_CLT.getCenterAverage() != null) || clt_parameters.imp.calculate_average;
	        									boolean subtract_average = clt_parameters.imp.subtract_average && insert_average;
	        									if (subtract_average && clt_parameters.imp.cuas_targets && (mode3d > 0)) {
	        										System.out.println("Will generate targets images/videos, mode3d="+mode3d);
	        										ImagePlus imp_targets = imp_scenes_pair[nstereo];
	        										int first_corr = insert_average? 1:0; // skip average
	        										int num_scenes = imp_targets.getStack().getSize()- first_corr; // includes average
	        										float [][] fpixels = new float[num_scenes][];
	        										String [] scene_titles = new String [num_scenes];
	        										for (int nscene = 0; nscene < fpixels.length; nscene++) {
	        											fpixels[nscene] = (float[]) imp_targets.getStack().getPixels(nscene+first_corr+1);
	        											String s = imp_targets.getStack().getSliceLabel(nscene+first_corr+1);
	        											if (s.indexOf("-0") >=0) {
	        												s=s.substring(0, s.indexOf("-0"));
	        											}
	        											scene_titles[nscene] = s;// imp_targets.getStack().getSliceLabel(nscene+first_corr+1);
	        										}
	        										master_CLT.processMovingTargets(
	        												clt_parameters, // CLTParameters         clt_parameters,
	        												batch_mode,     // final boolean         batch_mode,
	        												fpixels,       // final float [][]      fpixels,
	        												uasLogReader,  // UasLogReader          uasLogReader,	        											 
	        												scene_titles,  // String []             scene_titles, // recreate slice_titles from scene titles?
	        												debugLevel);   // final int             debugLevel) 
	        									}
	        									*/
	        								}
	        							}
	        						}
	        					}
	        				} else {
	        					if (fov_tiles==null) {
	        						fov_tiles = new Rectangle(0, 0, tilesX, tilesY);
	        					}
	        					FloatProcessor fp = new FloatProcessor(
	        							fov_tiles.width*master_CLT.getTileProcessor().getTileSize(),
	        							fov_tiles.height*master_CLT.getTileProcessor().getTileSize());

	        					boolean merge_all = clt_parameters.imp.merge_all;
	        					if (mode3d < 1) {
	        						merge_all = false;
	        					}
	        					imp_scenes_pair[nstereo]=new ImagePlus(scenes_suffix+((mode3d > 0)?(merge_all?"-MERGED":"-SINGLE"):""), fp);
	        				}
	        				// Save as AVI	        	
	        				if (gen_avi_mono_color[col_mode]) {
	        					if (annotate_mono_color[col_mode] && generate_mapped) {
	        						if (!toRGB) {
	        							// If it is mono, first convert to color
	        							if (mono_fixed && um_mono) {
	        								imp_scenes_pair[nstereo].getProcessor().setMinAndMax(-mono_range/2, mono_range/2);
	        							}
	        							ImageConverter imageConverter = new ImageConverter(imp_scenes_pair[nstereo]);
	        							imageConverter.convertToRGB(); // Did it convert imp_scenes ? YES
	        						}

	        						final Color fcolor = toRGB ? annotate_color_color: annotate_color_mono;
	        						final ImageStack fstack_scenes = imp_scenes_pair[nstereo].getImageStack();
	        						final int width =  imp_scenes_pair[nstereo].getWidth();
	        						final int height = imp_scenes_pair[nstereo].getHeight();
	        						final int posX= width - 119; // 521;
	        						final int posY= height + 1;  // 513;
	        						final Font font = new Font("Monospaced", Font.PLAIN, 12);
	        						final int nSlices = fstack_scenes.getSize();
	        						final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
	        						final AtomicInteger ai = new AtomicInteger(0);
	        						for (int ithread = 0; ithread < threads.length; ithread++) {
	        							threads[ithread] = new Thread() {
	        								public void run() {
	        									for (int nSlice = ai.getAndIncrement(); nSlice < nSlices; nSlice = ai.getAndIncrement()) {
	        										String scene_title = fstack_scenes.getSliceLabel(nSlice+1);
	        										ImageProcessor ip = fstack_scenes.getProcessor(nSlice+1);
	        										ip.setColor(fcolor); // Color.BLUE);
	        										ip.setFont(font);
	        										if (toRGB || !annotate_transparent_mono) {
	        											ip.drawString(scene_title, posX, posY,Color.BLACK);
	        										} else {
	        											ip.drawString(scene_title, posX, posY); // transparent
	        										}
	        									}
	        								}
	        							};
	        						}		      
	        						ImageDtt.startAndJoin(threads);
	        					}
	        					if (combine_left_right && (nstereo == 0)) {
	        						continue;
	        					}
	        					// no_combine, stereo_2_images, stereo_anaglyth
	        					ImagePlus imp_video = imp_scenes_pair[nstereo];
	        					boolean [] combine_modes = {!combine_left_right, stereo_merge && combine_left_right, anaglyth_en && !toRGB && combine_left_right };
	        					for (int istereo_mode = 0; istereo_mode < combine_modes.length; istereo_mode++) if(combine_modes[istereo_mode]) {
	        						if (istereo_mode == 1) { // combine pairs for "Google" VR 
	        							final int left_width = imp_scenes_pair[0].getWidth();
	        							final int right_width = imp_scenes_pair[1].getWidth();
	        							final int stereo_width =  left_width + right_width+stereo_gap;
	        							final int stereo_height = imp_scenes_pair[0].getHeight();
	        							final ImageStack stereo_stack = new ImageStack(stereo_width, stereo_height);
	        							final int nSlices = imp_scenes_pair[0].getStack().getSize();
	        							for (int i = 0; i < nSlices; i++) {
	        								stereo_stack.addSlice(
	        										imp_scenes_pair[0].getStack().getSliceLabel(i+1),
	        										new int[stereo_width * stereo_height]);
	        							}
	        							if (generate_mapped) {
	        								final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
	        								final AtomicInteger ai = new AtomicInteger(0);
	        								for (int ithread = 0; ithread < threads.length; ithread++) {
	        									threads[ithread] = new Thread() {
	        										public void run() {
	        											for (int nSlice = ai.getAndIncrement(); nSlice < nSlices; nSlice = ai.getAndIncrement()) {
	        												int[] pixels_stereo = (int[]) stereo_stack.getPixels(nSlice+1);
	        												int[] pixels_left = (int[]) imp_scenes_pair[0].getStack().getPixels(nSlice+1);
	        												int[] pixels_right = (int[]) imp_scenes_pair[1].getStack().getPixels(nSlice+1);
	        												for (int row = 0; row < stereo_height; row++) {
	        													System.arraycopy(
	        															pixels_left,
	        															left_width * row,
	        															pixels_stereo,
	        															stereo_width * row,
	        															left_width);
	        													System.arraycopy(
	        															pixels_right,
	        															right_width * row,
	        															pixels_stereo,
	        															stereo_width * row + left_width + stereo_gap,
	        															right_width);
	        												}
	        											}
	        										}
	        									};
	        								}		      
	        								ImageDtt.startAndJoin(threads);
	        							}
	        							imp_video = new ImagePlus();
	        							imp_video.setImage(imp_scenes_pair[1]); // copy many attributes
	        							imp_video.setStack(stereo_stack);
	        							String title = imp_scenes_pair[1].getTitle();
	        							imp_video.setTitle(title.replace("-RIGHT","-STEREO"));
	        							// convert stereo_stack to imp_scenes_pair[1], keeping calibration and fps?
	        						} else if (istereo_mode == 2) { // combine anaglyph
	        							final double [] left_rgb= {
	        									anaglyph_left.getRed()/255.0,
	        									anaglyph_left.getGreen()/255.0,
	        									anaglyph_left.getBlue()/255.0};  
	        							final double [] right_rgb= {
	        									anaglyph_right.getRed()/255.0,
	        									anaglyph_right.getGreen()/255.0,
	        									anaglyph_right.getBlue()/255.0};  
	        							final int left_width =  imp_scenes_pair[0].getWidth();
	        							final int left_height = imp_scenes_pair[0].getHeight();
	        							final int nSlices = imp_scenes_pair[0].getStack().getSize();
	        							final ImageStack stereo_stack = new ImageStack(left_width, left_height);
	        							for (int i = 0; i < nSlices; i++) {
	        								stereo_stack.addSlice(
	        										imp_scenes_pair[0].getStack().getSliceLabel(i+1),
	        										new int[left_width * left_height]);
	        							}
	        							
	        							if (generate_mapped) {
	        								final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
	        								final AtomicInteger ai = new AtomicInteger(0);
	        								for (int ithread = 0; ithread < threads.length; ithread++) {
	        									threads[ithread] = new Thread() {
	        										public void run() {
    													int [] rgb = new int [3]; 
	        											for (int nSlice = ai.getAndIncrement(); nSlice < nSlices; nSlice = ai.getAndIncrement()) {
	        												int[] pixels_stereo = (int[]) stereo_stack.getPixels(nSlice+1);
	        												int[] pixels_left =   (int[]) imp_scenes_pair[0].getStack().getPixels(nSlice+1);
	        												int[] pixels_right =  (int[]) imp_scenes_pair[1].getStack().getPixels(nSlice+1);
	        												for (int pix = 0; pix < pixels_left.length; pix++) {
	        													int gl = ((pixels_left[pix] & 0xff00) >> 8);
	        													int gr = ((pixels_right[pix] & 0xff00) >> 8);
	        													rgb[0] = ((int) Math.min(gl * left_rgb[0] + gr * right_rgb[0], 255)) & 0xff;
	        													rgb[1] = ((int) Math.min(gl * left_rgb[1] + gr * right_rgb[1], 255)) & 0xff;
	        													rgb[2] = ((int) Math.min(gl * left_rgb[2] + gr * right_rgb[2], 255)) & 0xff;
	        													pixels_stereo[pix] = 0xff000000 + (rgb[0] << 16) + (rgb[1] << 8)  + rgb[2]; 
	        												}
	        											}
	        										}
	        									};
	        								}		      
	        								ImageDtt.startAndJoin(threads);
	        							}
	        							imp_video = new ImagePlus();
	        							imp_video.setImage(imp_scenes_pair[1]); // copy many attributes
	        							imp_video.setStack(stereo_stack);
	        							String title = imp_scenes_pair[1].getTitle();
	        							imp_video.setTitle(title.replace("-RIGHT","-ANAGLYPH"));
	        						} // if (istereo_mode == 1) {if (combine_left_right) { // combine pairs multi-threaded
	        						String avi_path=null;
	        						video:
	        						{
	        							try {
	        								avi_path=master_CLT.saveAVIInModelDirectory(
	        										!generate_mapped, // boolean     dry_run, 
	        										null,             // "GPU-SHIFTED-D"+clt_parameters.disparity, // String      suffix,
	        										mode_avi,         // int         avi_mode,
	        										avi_JPEG_quality, // int         avi_JPEG_quality,
	        										video_fps,        // double      fps,
	        										imp_video); // imp_scenes_pair[nstereo]);      // ImagePlus   imp)
	        							} catch (IOException e) {
	        								// TODO Auto-generated catch block
	        								e.printStackTrace();
	        								break video;

	        							}
	        							// Convert with ffmpeg?
	        							if (avi_path == null) {
	        								break video;
	        							}
	        							int img_width=imp_video.getWidth();
	        							int stereo_width = combine_left_right? img_width:0;
	        							stereo_widths_list.add(stereo_width);
	        							if (!run_ffmpeg) {
	        								video_list.add(avi_path);
	        								break video; // webm not requested
	        							}

	        							String webm_path = avi_path.substring(0, avi_path.length()-4)+video_ext;
	        							// added -y not to as "overwrite y/n?"
	        				    		//ffmpeg -i input_file.mkv -c copy -metadata:s:v:0 stereo_mode=1 output_file.mkv 
	        				    		//https://ffmpeg.org/ffmpeg-formats.html
	        				    		//ffmpeg -i sample_left_right_clip.mpg -an -c:v libvpx -metadata stereo_mode=left_right -y stereo_clip.webm
	        				    		//anaglyph_cyan_red 
	        							String stereo_meta = "";
	        							if ((webm_path.contains("-STEREO-")) || (webm_path.contains("-SBS-"))) {
	        								if (stereo_gap == 0) {
	        									if (debugLevel > -3) {
	        										System.out.println("Adding 3D meta: stereo_mode=left_right");
	        									}
	        									stereo_meta = " -metadata:s:v:0 stereo_mode=left_right ";
	        								} else {
	        									if (debugLevel > -3) {
	        										System.out.println("stereo_gap !=0, skipping 3D meta as firefox/vlc halves display width");
	        									}
	        								}
	        							} else if (webm_path.contains("-ANAGLYPH-")) {
        									if (debugLevel > -3) {
        										System.out.println("Adding 3D meta: stereo_mode=anaglyph_cyan_red");
        									}
		        							stereo_meta = " -metadata:s:v:0 stereo_mode=anaglyph_cyan_red ";
	        							}

	        							String shellCommand = String.format("ffmpeg -y -i %s -c %s -b:v %fM -crf %d %s %s",
	        									avi_path, video_codec, video_bitrate_m, video_crf, stereo_meta, webm_path);
	        							Process p = null;
	        							if (generate_mapped) {
	        								int exit_code = -1;
	        								try {
	        									p = Runtime.getRuntime().exec(shellCommand);
	        								} catch (IOException e) {
	        									System.out.println("Failed shell command: \""+shellCommand+"\"");
	        								}
	        								if (p != null) {
	        									p.waitFor();
	        									exit_code = p.exitValue();
	        								}
	        								System.out.println("Ran shell command: \""+shellCommand+"\" -> "+exit_code);
	        								// Check if webm file exists
	        								if ((exit_code != 0) || !(new File(webm_path)).exists()) {
	        									System.out.println("Failed to create : \""+webm_path+"\"");
	        									video_list.add(avi_path);
	        									break video;
	        								}
	        							} else {
	        								System.out.println("Simulated shell command: \""+shellCommand);

	        							}
	        							video_list.add(webm_path);
	        							if (remove_avi && generate_mapped) {
	        								(new File(avi_path)).delete();
	        								System.out.println("Deleted AVI video file: \""+avi_path+"\"");
	        							}
	        						}

	        					} // for (int istereo_mode = 0; istereo_mode < stereo_modes.length; istereo_mode++) if(combine_modes[istereo_mode]) {
	        				} // if (gen_avi_mono_color[col_mode])
	        				if (show_mono_color[col_mode]  && generate_mapped) {
	        					imp_scenes_pair[nstereo].show();
	        				}
	        			} // for (int nstereo = 0; nstereo < num_stereo; nstereo++)
	        		} // for (int col_mode = 0; col_mode<2; col_mode++) {
	        	} // for (int ibase = 0; ibase < baselines.length; ibase++) {
	        }// for (int mode3d = 0; mode3d<generate_modes3d.length; mode3d++) if (generate_modes3d[mode3d]) {
		} // if (generate_mapped) {
		if (export_dsi_image || show_dsi_image) {
			if (combo_dsn_final == null) {
///				combo_dsn_final =quadCLTs[ref_index].readDoubleArrayFromModelDirectory(
///						"-INTER-INTRA-LMA", // String      suffix,
///						0, // int         num_slices, // (0 - all)
///						null); // int []      wh);
				combo_dsn_final =master_CLT.restoreComboDSI(false); // Let it show that file is not available
				if (combo_dsn_final == null) {
					System.out.println("combo_dsn is not available (-INTER-INTRA-LMA.tiff) - bailing out");
					return null;
				}
			}
			
			// re-load , should create quadCLTs[ref_index].dsi
			double [][] dls = {
					combo_dsn_final[COMBO_DSN_INDX_DISP],
					combo_dsn_final[COMBO_DSN_INDX_LMA],
					combo_dsn_final[COMBO_DSN_INDX_STRENGTH]
			};
			int dbg_condition = debug_ranges ? Math.max(1, debugLevel) : debugLevel;
			double [][] ds = conditionInitialDS( // Add debug szxy by providing debugLevel=1
					true,                   // boolean        use_conf,       // use configuration parameters, false - use following  
					clt_parameters,         // CLTParameters  clt_parameters,
					dls,                    // double [][]    dls
					master_CLT,    // QuadCLT        scene,
					dbg_condition);         // int debug_level)
			double [] disparity = ds[0];
			double [] strength = ds[1];		
			double [][]szxy = getSceneSZXY(
					master_CLT,    // QuadCLT scene,
					range_disparity_offset, // double    disparity_offset,
					range_min_strength,     // double    min_strength,
					range_max,              // double    max_range,
					disparity,              // double [] disparity,
					strength);              // double [] strength)
			String [] szxy_titles = {"strength", "Z(m)", "X(m)", "Y(m)"};
			TileProcessor tp = master_CLT.getTileProcessor();
			int tilesX =         tp.getTilesX();
	        int tilesY =         tp.getTilesY();
	        ImagePlus impSZXY = ShowDoubleFloatArrays.makeArrays(
        			szxy,
        			tilesX,
        			tilesY,
        			master_CLT.getImageName()+"_SZXY",
        			szxy_titles);
	        if (export_dsi_image) {
	        	master_CLT.saveImagePlusInModelDirectory(
	        			null, // "GPU-SHIFTED-BACKGROUND", // String      suffix,
	        			impSZXY); // ImagePlus   imp)
	        }
	        if (show_dsi_image) {
	        	impSZXY.show();

	        }
		}
		double terr_rthrsh_abv = clt_parameters.imp.terr_rthrsh_abv;

		if (export_CT) {
			double [][][] ct_scans = new double [(int) Math.ceil((ct_max-ct_min)/ct_step) + 1][][];
			boolean ok_ct = TexturedModel.output3d( // quadCLTs have same image name, and everything else
					clt_parameters,      // CLTParameters                            clt_parameters,
					colorProcParameters, // ColorProcParameters                      colorProcParameters,
					rgbParameters,       // EyesisCorrectionParameters.RGBParameters rgbParameters,
					master_CLT,          // quadCLTs.length-1], // quadCLT_main,        // final QuadCLT                            parameter_scene, // to use for rendering parameters in multi-series sequences
		            // if null - use reference scene 
					quadCLTs,            // QuadCLT []                               scenes,
					ref_index,           // final int                                ref_index,
					combo_dsn_final,     //double [][]                              combo_dsn_final, // null OK, will read file
					ct_scans,            // final double [][]                        ct_scans,
					ct_min,              // final double                             ct_min,
					ct_step,             // final double                             ct_step,
					ct_expand,           // final int                                ct_expand,
					false,               // final boolean                            terrain_mode,
					terr_rthrsh_abv,     // final double                             terrain_threshold, // in disparity so may depend on average, sfm, etc.
					null, // final double [][]                        hdr_render_size, // { hdr_whs[3],hdr_x0y0[2]}; save/use rendering parameters
					false, // final boolean                            hdr_render_slave,// use rendering parameters (to match other mode)
					updateStatus,        // final boolean                            updateStatus,
					debugLevel); //  + 1);         // final int                                debugLevel)
			System.out.println ("CT scan-> "+ok_ct);
			String [] titles = new String[ct_scans.length];
			for (int i = 0; i < titles.length; i++) {
				titles[i] = String.format("disparity=%.3f pix",ct_min+ct_step*i);
			}
			String suffix = String.format("-CT_SCAN_%.3f_%.3f_%.3f_%d",ct_min,ct_max,ct_step,ct_expand);
			final int transform_size=           master_CLT.getTileProcessor().getTileSize();
	 		final int tilesX =                  master_CLT.getTileProcessor().getTilesX();
			final int tilesY =                  master_CLT.getTileProcessor().getTilesY();
			int nslices = 0;
			for (int n = 0; n < ct_scans.length; n++) {
				nslices = Math.max(nslices,  ct_scans[n].length);
			}
			for (int nslice = 0; nslice < nslices; nslice++) {
				double [][] ct_scan_slice = new double [ct_scans.length][];
				for (int n = 0; n < ct_scan_slice.length; n++) {
//					int i = Math.min(nslice, ct_scans[n].length);
					if (nslice < ct_scans[n].length) {
						ct_scan_slice[n] = ct_scans[n][nslice];
					}
				}
				master_CLT.saveDoubleArrayInModelDirectory(
						suffix+"-SLICE"+nslice,   // String      suffix,
						titles,                   // String []   labels, // or null
						ct_scan_slice,         // double [][] data,
						tilesX * transform_size,  // int            width, // int tilesX,
						tilesY * transform_size); // int            height, // int tilesY,
			}
		}
		
		// 3D model
		int [] whs = new int[3];
		double [] x0y0 = new double[2];
		double [][] hdr_render_size = new double[2][];
		if (export3d) { //combo_dsn_final had strength 1.0e-4 where it should not? Reset it?
			boolean ok_3d = TexturedModel.output3d( // quadCLTs have same image name, and everything else
					clt_parameters,      // CLTParameters                            clt_parameters,
					colorProcParameters, // ColorProcParameters                      colorProcParameters,
					rgbParameters,       // EyesisCorrectionParameters.RGBParameters rgbParameters,
					master_CLT, // quadCLTs.length-1], // quadCLT_main,        // final QuadCLT                            parameter_scene, // to use for rendering parameters in multi-series sequences
		            // if null - use reference scene 
					quadCLTs,            // QuadCLT []                               scenes,
					ref_index,           // final int                                ref_index,
					combo_dsn_final,     //double [][]                              combo_dsn_final, // null OK, will read file
					null,                // final double [][]                        ct_scans,
					0,                   // final double                             ct_min,
					0,                   // final double                             ct_step,
					0,                   // final int                                ct_expand,
					false,               // final boolean                            terrain_mode,
					terr_rthrsh_abv,     // final double                             terrain_threshold, // in disparity so may depend on average, sfm, etc.
					hdr_render_size,     // final double [][]                        hdr_render_size, // { hdr_whs[3],hdr_x0y0[2]}; save/use rendering parameters
					false,               // final boolean                            hdr_render_slave,// use rendering parameters (to match other mode)
					updateStatus,        // final boolean                            updateStatus,
					debugLevel); //  + 1);         // final int                                debugLevel)
			System.out.println ("TexturedModel.output3d() -> "+ok_3d+" (full, with vegetation)");
		}
		if (export3dterrain) { //combo_dsn_final had strength 1.0e-4 where it should not? Reset it?
			if (clt_parameters.imp.lock_position) {
				boolean  hdr_render_slave= true;
				boolean ok_3d = TexturedModel.output3d( // quadCLTs have same image name, and everything else
						clt_parameters,      // CLTParameters                            clt_parameters,
						colorProcParameters, // ColorProcParameters                      colorProcParameters,
						rgbParameters,       // EyesisCorrectionParameters.RGBParameters rgbParameters,
						master_CLT,          // quadCLTs.length-1], // quadCLT_main,        // final QuadCLT                            parameter_scene, // to use for rendering parameters in multi-series sequences
						// if null - use reference scene 
						quadCLTs,            // QuadCLT []                               scenes,
						ref_index,           // final int                                ref_index,
						combo_dsn_final,     //double [][]                              combo_dsn_final, // null OK, will read file
						null,                // final double [][]                        ct_scans,
						0,                   // final double                             ct_min,
						0,                   // final double                             ct_step,
						0,                   // final int                                ct_expand,
						false, // true,      // final boolean                            terrain_mode,
						terr_rthrsh_abv,     // final double                             terrain_threshold, // in disparity so may depend on average, sfm, etc.
						hdr_render_size,     // final double [][]                        hdr_render_size, // { hdr_whs[3],hdr_x0y0[2]}; save/use rendering parameters
						hdr_render_slave,    // final boolean                            hdr_render_slave,// use rendering parameters (to match other mode)
						updateStatus,        // final boolean                            updateStatus,
						debugLevel); //  + 1);         // final int                                debugLevel)
				System.out.println ("TexturedModel.output3d() -> "+ok_3d+" (lock_position)");
			} else {
				boolean  hdr_render_slave= true;
				boolean ok_3d = TexturedModel.output3d( // quadCLTs have same image name, and everything else
						clt_parameters,      // CLTParameters                            clt_parameters,
						colorProcParameters, // ColorProcParameters                      colorProcParameters,
						rgbParameters,       // EyesisCorrectionParameters.RGBParameters rgbParameters,
						master_CLT,          // quadCLTs.length-1], // quadCLT_main,        // final QuadCLT                            parameter_scene, // to use for rendering parameters in multi-series sequences
						// if null - use reference scene 
						quadCLTs,            // QuadCLT []                               scenes,
						ref_index,           // final int                                ref_index,
						combo_dsn_final,     //double [][]                              combo_dsn_final, // null OK, will read file
						null,                // final double [][]                        ct_scans,
						0,                   // final double                             ct_min,
						0,                   // final double                             ct_step,
						0,                   // final int                                ct_expand,
						true,                // final boolean                            terrain_mode,
						terr_rthrsh_abv,     // final double                             terrain_threshold, // in disparity so may depend on average, sfm, etc.
						hdr_render_size,     // final double [][]                        hdr_render_size, // { hdr_whs[3],hdr_x0y0[2]}; save/use rendering parameters
						hdr_render_slave,    // final boolean                            hdr_render_slave,// use rendering parameters (to match other mode)
						updateStatus,        // final boolean                            updateStatus,
						debugLevel); //  + 1);         // final int                                debugLevel)
				System.out.println ("TexturedModel.output3d() -> "+ok_3d+" (terrain only, no vegetation)");
			}
		}
		boolean export_terrain_sequence = true; // check master_CLT
		if (export_terrain_sequence && !clt_parameters.imp.lock_position) {
			final double        test_bottom = 0.0; // 0.5; // disable half-tilted
			if (clt_parameters.imp.fgnd_gen_tilted){
				// No rotations orthogonal to ground, using tilted disparity
				if (clt_parameters.imp.fgnd_gen_scan){
					ImagePlus imp_terrain_hyper_tilted =  renderSceneSequenceHyper(
							clt_parameters,                      // CLTParameters  clt_parameters,
				    		true,                                // boolean        use_tilted_disparity, // tilted disparity: anisotropic distortions but full image fill
				    		false,                               // boolean        um_mono,
				    		clt_parameters.imp.subtract_average, // boolean        subtract_average,
				    		clt_parameters.imp.running_average,  // int            running_average,
				    		false,                               // boolean        toRGB,
				    		"-TERRAIN-DISP_CT",                  // String         suffix_in, // "-TERRAIN_CT"
				    		quadCLTs,                            // QuadCLT []     quadCLTs,
				    		master_CLT,                          // QuadCLT        ref_scene,
				    		debugLevel);                         //int            debugLevel)
			    	if (imp_terrain_hyper_tilted == null) {
			    		System.out.println("!!! Failed to create a hyper-stack terrain image for "+master_CLT.getImageName());
			    	} else {
			    		master_CLT.saveImagePlusInModelDirectory(
			    				null,               // "GPU-SHIFTED-D"+clt_parameters.disparity, // String      suffix,
			    				imp_terrain_hyper_tilted);       // imp_scenes); // ImagePlus   imp)
			    	}
				} else {
					GroundPlane.prepareTerrainRender(
							clt_parameters, // final CLTParameters clt_parameters,
							master_CLT,     // final QuadCLT       ref_Clt,
							true,           // final boolean       tilted_plane,
							0.0, // final double        offset,
							test_bottom,    // final double        test_bottom,
							debugLevel);    // final int           debugLevel)

					combo_dsn_final =master_CLT.restoreComboDSI(true);
					if ((combo_dsn_final.length <= COMBO_DSN_INDX_TERRAIN) || (combo_dsn_final[COMBO_DSN_INDX_TERRAIN] == null)) {
						System.out.println ("No terrain data available");
					} else {
						double [] terrain_disparity = combo_dsn_final[COMBO_DSN_INDX_TERRAIN];
						String scenes_suffix_disp = master_CLT.getImageName()+"-TERRAIN-DISP";
						ImagePlus imp_terrain_disp = renderSceneSequence(
					    		clt_parameters,                      // CLTParameters  clt_parameters,
					    		false,                               // boolean        um_mono,
					    		null,                                // float [][]     average_slice, // [channel][pixel]
								clt_parameters.imp.subtract_average, // boolean    subtract_average,
								clt_parameters.imp.running_average,  // int            running_average,
					    		null,                                // Rectangle      fov_tiles,
					    		1,                                   // int            mode3d, // for older compatibility mode3d = -1 for RAW, 0 - INF, 1 - FG, 2 BG
					    		false,                               // boolean        toRGB,
					    		null,                                // double [][]    ground_xyzatr,
					    		1,                                   // int            sensor_mask,
					    		scenes_suffix_disp,                  // String         suffix_in,
					    		terrain_disparity,                   // double []      ref_disparity,			
					    		quadCLTs,                            // QuadCLT []     quadCLTs,
					    		master_CLT,                          // QuadCLT        ref_scene, // int            ref_index,
					    		debugLevel);                         // int            debugLevel) {

						master_CLT.saveImagePlusInModelDirectory(
								null,               // "GPU-SHIFTED-D"+clt_parameters.disparity, // String      suffix,
								imp_terrain_disp);       // imp_scenes); // ImagePlus   imp)
					}
				}
			}
			
			if (clt_parameters.imp.fgnd_gen_optho){
				// rotating orthogonal to ground, using constant disparity
				if (clt_parameters.imp.fgnd_gen_scan){
					ImagePlus imp_terrain_hyper =  renderSceneSequenceHyper(
							clt_parameters,                      // CLTParameters  clt_parameters,
				    		false,                               // boolean        use_tilted_disparity, // tilted disparity: anisotropic distortions but full image fill
				    		false,                               // boolean        um_mono,
				    		clt_parameters.imp.subtract_average, // boolean        subtract_average,
				    		clt_parameters.imp.running_average,  // int            running_average,
				    		false,                               // boolean        toRGB,
				    		"-TERRAIN_CT",                  // String         suffix_in, // "-TERRAIN_CT"
				    		quadCLTs,                            // QuadCLT []     quadCLTs,
				    		master_CLT,                          // QuadCLT        ref_scene,
				    		debugLevel);                         //int            debugLevel)
			    	if (imp_terrain_hyper == null) {
			    		System.out.println("!!! Failed to create a hyper-stack terrain image for "+master_CLT.getImageName());
			    	} else {
			    		master_CLT.saveImagePlusInModelDirectory(
			    				null,               // "GPU-SHIFTED-D"+clt_parameters.disparity, // String      suffix,
			    				imp_terrain_hyper);       // imp_scenes); // ImagePlus   imp)
			    	}
				} else { // if (clt_parameters.imp.fgnd_gen_scan){
					double [][] gnp = GroundPlane.prepareTerrainRender(
							clt_parameters, // final CLTParameters clt_parameters,
							master_CLT,     // final QuadCLT       ref_Clt,
							false,   // final boolean       tilted_plane,
							0.0, // final double        offset,
							test_bottom, // final double        test_bottom,
							debugLevel);    // final int           debugLevel)
					double [][] ignp =  null;
					if (gnp != null) {
						ignp = ErsCorrection.invertXYZATR(gnp);
						if (debugLevel > -3) {
							System.out.println("Using airplane mode terrane as a single horizontal plane");
							System.out.println("stereo_offset = ["+gnp[0][0]+", "+gnp[0][1]+", "+gnp[0][2]+"]");
							System.out.println("stereo_atr =    ["+gnp[1][0]+", "+gnp[1][1]+", "+gnp[1][2]+   "]");
							// TODO add provisions to the non-flat terrain -modify terrain slice, keeping ground_normal_pose as a horizontal plane.
							System.out.println("ignp[0] = ["+ignp[0][0]+", "+ignp[0][1]+", "+ignp[0][2]+"]");
							System.out.println("ignp[1] = ["+ignp[1][0]+", "+ignp[1][1]+", "+ignp[1][2]+"]");
						}
					}
					combo_dsn_final =master_CLT.restoreComboDSI(true);
					if ((combo_dsn_final.length <= COMBO_DSN_INDX_TERRAIN) || (combo_dsn_final[COMBO_DSN_INDX_TERRAIN] == null)) {
						System.out.println ("No terrain data available");

					} else {
						double [] terrain_disparity = combo_dsn_final[COMBO_DSN_INDX_TERRAIN];
						String scenes_suffix = master_CLT.getImageName()+"-TERRAIN"; // quadCLTs[quadCLTs.length-1][quadCLTs.length-1].getImageName()+"-TERRAIN";
						ImagePlus imp_terrain = renderSceneSequence(
					    		clt_parameters,                      // CLTParameters  clt_parameters,
					    		false,                               // boolean        um_mono,
					    		null,                                // float [][]     average_slice, // [channel][pixel]
								clt_parameters.imp.subtract_average, // boolean    subtract_average,
								clt_parameters.imp.running_average,  // int            running_average,
					    		null,                                // Rectangle      fov_tiles,
					    		1,                                   // int            mode3d, // for older compatibility mode3d = -1 for RAW, 0 - INF, 1 - FG, 2 BG
					    		false,                               // boolean        toRGB,
					    		ignp,                                // double [][]    ground_xyzatr,
					    		1,                                   // int            sensor_mask,
					    		scenes_suffix,                       // String         suffix_in,
					    		terrain_disparity,                   // double []      ref_disparity,			
					    		quadCLTs,                            // QuadCLT []     quadCLTs,
					    		master_CLT,                          // QuadCLT        ref_scene, // int            ref_index,
					    		debugLevel);                         // int            debugLevel) {
						master_CLT.saveImagePlusInModelDirectory(
								null,               // "GPU-SHIFTED-D"+clt_parameters.disparity, // String      suffix,
								imp_terrain);       // imp_scenes); // ImagePlus   imp)
					}
				}
			}


			if ((combo_dsn_final== null) || (combo_dsn_final.length <= COMBO_DSN_INDX_TERRAIN) || (combo_dsn_final[COMBO_DSN_INDX_TERRAIN] == null)) {
				System.out.println ("No terrain data available");
			} else {
				double [] elevation_disparity = combo_dsn_final[COMBO_DSN_INDX_DISP];
				String scenes_suffix = master_CLT.getImageName()+"-ELEVATION"; // quadCLTs[quadCLTs.length-1].getImageName()+"-ELEVATION";
				ImagePlus imp_elevation = renderSceneSequence(
						clt_parameters,     // CLTParameters clt_parameters,
						master_CLT.hasCenterClt(), // boolean        mode_cuas,
						false,              // boolean        um_mono,
						clt_parameters.imp.add_average,      // boolean        insert_average,
			    		null, // int []         average_range,
						null,               //  average_slice,
						clt_parameters.imp.subtract_average, // boolean    subtract_average,
						clt_parameters.imp.running_average,  // int            running_average,
						null,               // Rectangle     fov_tiles,
						1,                  // int           mode3d,
						false,              // boolean       toRGB,
						ZERO3,              // double []     stereo_offset, // offset reference camera {x,y,z}
						null,               // double []     stereo_atr, // offset reference orientation (cuas)
						1,                  // int           sensor_mask,
						scenes_suffix,      // String        suffix,
						elevation_disparity,  // selected_disparity, // double []     ref_disparity,			
						quadCLTs,           // QuadCLT []    quadCLTs,
						master_CLT, // ref_index,          // int           ref_index,
						threadsMax,         // int           threadsMax,
						debugLevel);        // int           debugLevel);
				master_CLT.saveImagePlusInModelDirectory(
						null,               // "GPU-SHIFTED-D"+clt_parameters.disparity, // String      suffix,
						imp_elevation);     // imp_scenes); // ImagePlus   imp)
			}
		}

//		boolean test_vegetation = true;
		if (export_vegetation && !clt_parameters.imp.lock_position) { // limit start of the quadCLTs by reading start/end from the reference scene
			System.out.println("Preparing vegetation data.");//			int [] first_last = quadCLTs[ref_index].getFirstLastIndex(quadCLTs);
			QuadCLT [] quadCLT_tail = new QuadCLT [quadCLTs.length - earliest_scene];
			System.arraycopy(quadCLTs, earliest_scene, quadCLT_tail, 0, quadCLT_tail.length);
			VegetationModel.prepareVegetationData(
					clt_parameters,                // CLTParameters  clt_parameters,
					quadCLT_tail,                  // QuadCLT []     quadCLTs,
					ref_index-earliest_scene,      // int            ref_index,
					debugLevel);                   // int debugLevel)
			if (videos != null) {
				videos[0] = new String[0];
			}
			// temporarily - exiting now
		}
		
		if (export_images) {
			if (combo_dsn_final == null) {
				combo_dsn_final =master_CLT.restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky

			}
			if (combo_dsn_final == null) {
				System.out.println("No ComboDSI available");
			} else {

				double [][] dls = {
						combo_dsn_final[COMBO_DSN_INDX_DISP],
						combo_dsn_final[COMBO_DSN_INDX_LMA],
						combo_dsn_final[COMBO_DSN_INDX_STRENGTH]
				};
				double [][] ds_fg = conditionInitialDS(
						true, // boolean        use_conf,       // use configuration parameters, false - use following  
						clt_parameters,      // CLTParameters  clt_parameters,
						dls,                 // double [][]    dls
						master_CLT, // QuadCLT        scene,
						debugLevel);			

				double [] fg_disparity = ds_fg[0]; // combo_dsn_final[COMBO_DSN_INDX_DISP_FG];

				double [] bg_lma = combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL].clone();
				double [] bg_str = combo_dsn_final[COMBO_DSN_INDX_STRENGTH].clone();

				for (int i = 0; i < bg_lma.length; i++) {
					if (Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_LMA][i])){
						bg_lma[i] = Double.NaN;
					}
					if (!Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_DISP_BG][i])){
						bg_lma[i] = combo_dsn_final[COMBO_DSN_INDX_LMA_BG][i];
					}
					if (!Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_STRENGTH_BG][i])){
						bg_str[i] = combo_dsn_final[COMBO_DSN_INDX_STRENGTH_BG][i];
					}
				}
				double [][] dls_bg = {
						combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL],
						bg_lma,
						bg_str
				};
				double [][] ds_bg = conditionInitialDS(
						true, // boolean        use_conf,       // use configuration parameters, false - use following  
						clt_parameters,      // CLTParameters  clt_parameters,
						dls_bg,                 // double [][]    dls
						master_CLT, // QuadCLT        scene,
						debugLevel);			
				double [] bg_disparity = ds_bg[0]; // combo_dsn_final[COMBO_DSN_INDX_DISP_FG];


				double [] constant_disparity = new double [fg_disparity.length];
				Arrays.fill(constant_disparity,clt_parameters.disparity);
				//Rectangle testr = new Rectangle(10, 8, 100,80);
				ImagePlus imp_constant = QuadCLT.renderGPUFromDSI(
						-1,                  // final int         sensor_mask,
						false, // final boolean     merge_channels,
						null, // testr, // null,                // final Rectangle   full_woi_in,      // show larger than sensor WOI (or null)
						clt_parameters,      // CLTParameters     clt_parameters,
						constant_disparity,  // double []         disparity_ref,
						null, // double [][]       ref_pXpYD,    // alternative to disparity_ref when reference is not uniform
						ZERO3,               // final double []   scene_xyz, // camera center in world coordinates
						ZERO3, // new double[] {.1,0.1,.1}, // ZERO3,               // final double []   scene_atr, // camera orientation relative to world frame
						master_CLT, // final QuadCLT     scene,
						master_CLT, // final QuadCLT     ref_scene, // now - may be null - for testing if scene is rotated ref
						true, // toRGB,               // final boolean     toRGB,
						clt_parameters.imp.show_color_nan, // boolean show_nan
						"GPU-SHIFTED-D"+clt_parameters.disparity, // String            suffix,
						threadsMax,          // int               threadsMax,
						debugLevel);         // int         debugLevel)
				master_CLT.saveImagePlusInModelDirectory(
						null, // "GPU-SHIFTED-D"+clt_parameters.disparity, // String      suffix,
						imp_constant); // ImagePlus   imp)
				ImagePlus imp_constant_mono = QuadCLT.renderGPUFromDSI(
						-1,                  // final int         sensor_mask,
						false, // final boolean     merge_channels,
						null, // testr, // null,                // final Rectangle   full_woi_in,      // show larger than sensor WOI (or null)
						clt_parameters,      // CLTParameters     clt_parameters,
						constant_disparity,  // double []         disparity_ref,
						null, // double [][]       ref_pXpYD,    // alternative to disparity_ref when reference is not uniform
						ZERO3,               // final double []   scene_xyz, // camera center in world coordinates
						ZERO3,               // final double []   scene_atr, // camera orientation relative to world frame
						master_CLT,          // final QuadCLT     scene,
						master_CLT,          // final QuadCLT     ref_scene, // now - may be null - for testing if scene is rotated ref
						false, // toRGB,               // final boolean     toRGB,
						clt_parameters.imp.show_mono_nan, // boolean show_nan
						"GPU-SHIFTED-D"+clt_parameters.disparity, // String            suffix,
						threadsMax,          // int               threadsMax,
						debugLevel);         // int         debugLevel)
				master_CLT.saveImagePlusInModelDirectory(
						null, // "GPU-SHIFTED-D"+clt_parameters.disparity, // String      suffix,
						imp_constant_mono); // ImagePlus   imp)
				if (show_images) {
					imp_constant.show();
					if (show_images_mono) {
						imp_constant_mono.show();
					}
				}

				boolean offset_fg_image = false; // true; // config later, generate FG image for all stereo views
				double [][] img_views = offset_fg_image ? stereo_views : (new double [][] {{0,0,0}});
				double min_str = 0.01; 
				for (int i = 0; i < ds_fg[0].length; i++) {
					if (!Double.isNaN(ds_fg[0][i]) && (ds_fg[1][i] == 0)) ds_fg[1][i] = min_str; // transformCameraVew ignores strength= 0
					if (!Double.isNaN(ds_bg[0][i]) && (ds_bg[1][i] == 0)) ds_bg[1][i] = min_str;
				}
				for (int ibase = 0; ibase < img_views.length; ibase++) if (!offset_fg_image || generate_stereo_var[ibase]) {
					double  stereo_baseline_meters =    0.001 * img_views[ibase][0];
					double  view_height_meters =        0.001 * img_views[ibase][1];
					double  view_back_meters =          0.001 * img_views[ibase][2];
					double [] xyz_offset = {
							-stereo_baseline_meters, // x offset
							-view_height_meters,     // Y offset
							-view_back_meters};      // Z offset
					double [] atr_offset = ZERO3; 
					String scenes_suffix = "";
					if (img_views[ibase][0] != 0) {
						scenes_suffix += "-B"+String.format("%.0f",img_views[ibase][0]);

					}
					if (img_views[ibase][1] != 0) {
						scenes_suffix += "-Y"+String.format("%.0f",img_views[ibase][1]);

					}
					if (img_views[ibase][2] != 0) {
						scenes_suffix += "-Z"+String.format("%.0f",img_views[ibase][2]);

					}
					// calculate virtual view fg_ds_virt from the reference ds_fg;
					boolean debug_ds_fg_virt = false; // false;
					double [][] ds_fg_virt = ds_fg;
					if ((img_views[ibase][0] != 0) || (img_views[ibase][1] != 0) || (img_views[ibase][2] != 0)) {
						ds_fg_virt = transformCameraVew(
								(debug_ds_fg_virt?"transformCameraVew":null),     // final String    title,
								ds_fg,                    // final double [][] dsrbg_camera_in,
								xyz_offset, // _inverse[0], // final double [] scene_xyz, // camera center in world coordinates
								atr_offset, // _inverse[1], // final double [] scene_atr, // camera orientation relative to world frame
								master_CLT,      // final QuadCLT   scene_QuadClt,
								master_CLT,      // final QuadCLT   reference_QuadClt,
								8); // iscale);                  // final int       iscale);
						if (debug_ds_fg_virt){
							int dbgX =  master_CLT.getTileProcessor().getTilesX();
							int dbgY =  master_CLT.getTileProcessor().getTilesY();
							double [][] dbg_img = new double[][] {ds_fg[0],ds_fg_virt[0],ds_fg[1],ds_fg_virt[1]};
							ShowDoubleFloatArrays.showArrays(
									dbg_img,
									dbgX,
									dbgY,
									true,
									"virtual-view-ds",
									new String[] {"d-ref","d-virt","s-ref","s-virt"}); //	dsrbg_titles);
						}
					}

					ImagePlus imp_fg = 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,
							ds_fg_virt[0],       // fg_disparity,  // double []         disparity_ref,
							null, // double [][]       ref_pXpYD,    // alternative to disparity_ref when reference is not uniform
							xyz_offset,          // ZERO3,               // final double []   scene_xyz, // camera center in world coordinates
							ZERO3,               // final double []   scene_atr, // camera orientation relative to world frame
							master_CLT,          // final QuadCLT     scene,
							master_CLT,          // final QuadCLT     ref_scene, // now - may be null - for testing if scene is rotated ref
							true, // toRGB,               // final boolean     toRGB,
							clt_parameters.imp.show_color_nan,
							scenes_suffix+"GPU-SHIFTED-FOREGROUND", // String            suffix,
							threadsMax,          // int               threadsMax,
							debugLevel);         // int         debugLevel)
					master_CLT.saveImagePlusInModelDirectory(
							null, // "GPU-SHIFTED-FOREGROUND", // String      suffix,
							imp_fg); // ImagePlus   imp)
					ImagePlus imp_fg_mono = 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,
							ds_fg_virt[0], // fg_disparity,  // double []         disparity_ref,
							null, // double [][]       ref_pXpYD,    // alternative to disparity_ref when reference is not uniform
							xyz_offset, // ZERO3,               // final double []   scene_xyz, // camera center in world coordinates
							ZERO3,               // final double []   scene_atr, // camera orientation relative to world frame
							master_CLT,          // final QuadCLT     scene,
							master_CLT,          // final QuadCLT     ref_scene, // now - may be null - for testing if scene is rotated ref
							false, // toRGB,               // final boolean     toRGB,
							clt_parameters.imp.show_mono_nan,
							scenes_suffix+"GPU-SHIFTED-FOREGROUND", // String            suffix,
							threadsMax,          // int               threadsMax,
							debugLevel);         // int         debugLevel)
					master_CLT.saveImagePlusInModelDirectory(
							null, // "GPU-SHIFTED-FOREGROUND", // String      suffix,
							imp_fg_mono); // ImagePlus   imp)
					if (show_images && show_images_bgfg) {
						imp_fg.show();
						if (show_images_mono) {
							imp_fg_mono.show();
						}
					}
				}


				ImagePlus imp_bg = 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,
						bg_disparity,        // double []         disparity_ref,
						null, // double [][]       ref_pXpYD,    // alternative to disparity_ref when reference is not uniform
						ZERO3,               // final double []   scene_xyz, // camera center in world coordinates
						ZERO3,               // final double []   scene_atr, // camera orientation relative to world frame
						master_CLT,          // final QuadCLT     scene,
						master_CLT,          // final QuadCLT     ref_scene, // now - may be null - for testing if scene is rotated ref
						true,                // final boolean     toRGB,
						clt_parameters.imp.show_color_nan,
						"GPU-SHIFTED-BACKGROUND", // String            suffix,
						threadsMax,          // int               threadsMax,
						debugLevel);         // int         debugLevel)
				master_CLT.saveImagePlusInModelDirectory(
						null, // "GPU-SHIFTED-BACKGROUND", // String      suffix,
						imp_bg); // ImagePlus   imp)
				ImagePlus imp_bg_mono = 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,
						bg_disparity,        // double []         disparity_ref,
						null, // double [][]       ref_pXpYD,    // alternative to disparity_ref when reference is not uniform
						ZERO3,               // final double []   scene_xyz, // camera center in world coordinates
						ZERO3,               // final double []   scene_atr, // camera orientation relative to world frame
						master_CLT,          // final QuadCLT     scene,
						master_CLT,          // final QuadCLT     ref_scene, // now - may be null - for testing if scene is rotated ref
						false,               // final boolean     toRGB,
						clt_parameters.imp.show_mono_nan,
						"GPU-SHIFTED-BACKGROUND", // String            suffix,
						threadsMax,          // int               threadsMax,
						debugLevel);         // int         debugLevel)
				master_CLT.saveImagePlusInModelDirectory(
						null, // "GPU-SHIFTED-BACKGROUND", // String      suffix,
						imp_bg_mono); // ImagePlus   imp)
				if (show_images && show_images_bgfg) {
					imp_bg.show();
					if (show_images_mono) {
						imp_bg_mono.show();
					}
				}
			}
		}
		
		
		if (export_ml_files) { 
			if (combo_dsn_final == null) { // always re-read?
///				combo_dsn_final =quadCLTs[ref_index].readDoubleArrayFromModelDirectory( // always re-read?
///						"-INTER-INTRA-LMA", // String      suffix,
///						0, // int         num_slices, // (0 - all)
///						null); // int []      wh);
				combo_dsn_final =master_CLT.restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky
				
			}
			double [][] combo_dsn_final_filtered = 
					conditionComboDsnFinal(
							true,                // boolean        use_conf,       // use configuration parameters, false - use following  
							clt_parameters,      // CLTParameters  clt_parameters,
							combo_dsn_final,     // double [][]    combo_dsn_final, // dls,
							master_CLT, // QuadCLT        scene,
							debugLevel); // int            debugLevel);// > 0
			
			
			intersceneMlExport(
					clt_parameters,      // CLTParameters        clt_parameters,
					ers_reference,       // ErsCorrection        ers_reference,
					quadCLTs,            // QuadCLT []           scenes,
					colorProcParameters, // ColorProcParameters  colorProcParameters,
					combo_dsn_final_filtered, // double [][]          combo_dsn_final,
					debugLevel); // int                  debug_level
					
			
		}
		if (videos != null) {
			videos[0] = video_list.toArray(new String[0]);
		}
		if (stereo_widths != null) {
			stereo_widths[0] = new int [stereo_widths_list.size()];
			for (int i = 0; i < stereo_widths[0].length; i++) {
				stereo_widths[0][i] = stereo_widths_list.get(i);
			}
		}
		if (start_ref_pointers != null) {
			start_ref_pointers[0] = earliest_scene;
			start_ref_pointers[1] = ref_index;
		}
		wrappedSaveConfInModelDirectory(
		quadCLTs,     // QuadCLT [] quadCLTs,
		quadCLT_main, // QuadCLT quadCLT_main,
		master_CLT,   // QuadCLT master_CLT,
		ref_index);   // int     ref_index)
		System.out.println("buildSeries(): DONE"); //
		return quadCLTs[ref_index].getX3dTopDirectory(); // top_dir; // keep?
    }
    
    
    public static void wrappedSaveConfInModelDirectory(
    		QuadCLT [] quadCLTs,  // may be null
    		QuadCLT quadCLT_main, // should be != null
    		QuadCLT master_CLT,   // may be null
    		int     ref_index     // if <0 - will be ignored
    		) {
		// temporary fix save/restore linkedModels, sourceDirectory, sourcePaths
		// that are copied main-> aux in EyesisCorrectionParameters.updateAuxFromMain()
		// quadCLT_main.correctionsParameters
    	
    	String bkp_linkedModels = quadCLT_main.correctionsParameters.linkedModels;
    	String bkp_linkedCenters = quadCLT_main.correctionsParameters.linkedCenters;
    	///		String bkp_cuasSeed = quadCLT_main.correctionsParameters.cuasSeed;
    	String bkp_cuasSeedDir = quadCLT_main.correctionsParameters.cuasSeedDir;
    	String bkp_videoDirectory = quadCLT_main.correctionsParameters.videoDirectory;
    	String bkp_x3dDirectory = quadCLT_main.correctionsParameters.x3dDirectory;
    	String bkp_mlDirectory = quadCLT_main.correctionsParameters.mlDirectory;
    	String bkp_sourceDirectory =  quadCLT_main.correctionsParameters.sourceDirectory;
    	String [] bkp_sourcePaths =   quadCLT_main.correctionsParameters.sourcePaths;
    	boolean bkp_use_set_dirs = quadCLT_main.correctionsParameters.use_set_dirs;
    	String bkp_cuasUasLogs =            quadCLT_main.correctionsParameters.cuasUasLogs;      // TIFF image 640x512 where 1.0 - sky, 0.0 - ground, blurred with GB (now sigma==2.0)
    	String bkp_cuasSkyMask =            quadCLT_main.correctionsParameters.cuasSkyMask;      // json file path containing UAS logs
    	double bkp_cuasUasTimeStamp =       quadCLT_main.correctionsParameters.cuasUasTimeStamp; // 0.0; // timestamp corresponding to the UAS time 0.0
    	double [] bkp_cuasCameraATR =       quadCLT_main.correctionsParameters.cuasCameraATR;    //{0, 0, 0};
    	double [] bkp_cuasUASHome =       quadCLT_main.correctionsParameters.cuasUASHome;    //{0, 0, 0};
    	boolean bkp_cuasSetHome = quadCLT_main.correctionsParameters.cuasSetHome;
    	if (master_CLT != null) {
    		master_CLT.saveConfInModelDirectory(); // save all (global) configurations in model/version directory
    	}
    	if ((quadCLTs != null) && (ref_index >= 0) && (master_CLT != quadCLTs[ref_index])) {
    		quadCLTs[ref_index].saveConfInModelDirectory(); // save all (global) configurations in model/version directory
    	}
    	quadCLT_main.correctionsParameters.linkedModels =     bkp_linkedModels;
    	quadCLT_main.correctionsParameters.linkedCenters =    bkp_linkedCenters;
    	quadCLT_main.correctionsParameters.cuasSeedDir =      bkp_cuasSeedDir;
    	quadCLT_main.correctionsParameters.videoDirectory =   bkp_videoDirectory;
    	quadCLT_main.correctionsParameters.x3dDirectory =     bkp_x3dDirectory;
    	quadCLT_main.correctionsParameters.mlDirectory =      bkp_mlDirectory;
    	quadCLT_main.correctionsParameters.sourceDirectory =  bkp_sourceDirectory;
    	quadCLT_main.correctionsParameters.sourceDirectory =  bkp_sourceDirectory;
    	quadCLT_main.correctionsParameters.sourcePaths =      bkp_sourcePaths;
    	quadCLT_main.correctionsParameters.use_set_dirs =     bkp_use_set_dirs;
    	quadCLT_main.correctionsParameters.cuasUasLogs =      bkp_cuasUasLogs;   
    	quadCLT_main.correctionsParameters.cuasUasTimeStamp = bkp_cuasUasTimeStamp;
    	quadCLT_main.correctionsParameters.cuasCameraATR =    bkp_cuasCameraATR;
    	quadCLT_main.correctionsParameters.cuasUASHome =      bkp_cuasUASHome;
    	quadCLT_main.correctionsParameters.cuasSetHome =      bkp_cuasSetHome;
    	quadCLT_main.correctionsParameters.cuasSkyMask =      bkp_cuasSkyMask;
    	return;
    }
    
    public static double getImsDisparityCorrection(
    		QuadCLT []       quadCLTs,
    		QuadCLT          master_CLT,
    		final int        debugLevel,
			int              earliest_scene) {
    	double scale_img = getImgImsScale( // correctInfinityFromIMS(
    			quadCLTs, // QuadCLT []       quadCLTs,
    			master_CLT, // QuadCLT          master_CLT)
    			earliest_scene,     // int              earliest_scene)
    			-1);                 // int              latest_scene) { // -1 will use quadCLTs.length -1;

		double [] disparity_dsi = master_CLT.getDLS()[1]; // null
		double [] strength = master_CLT.getDLS()[2];
		double sw=0,swd=0; // old, new-old
		for (int i = 0; i < disparity_dsi.length; i++) {
			if (!Double.isNaN(disparity_dsi[i])) {
				sw   += strength[i];
				swd  += strength[i]*disparity_dsi[i];
			}
		}
		double disp_avg = swd/sw;
    	double inf_disp_ref = disp_avg * (1.0 - scale_img);
    	if (debugLevel > -3) {
    		System.out.println("Disparity at infinity ="+inf_disp_ref+" in reference scene "+master_CLT.getImageName());
    	}
    	return inf_disp_ref;
    }
    
    public static double getImsDisparityCorrection(
    		double           scale_img,
    		QuadCLT          master_CLT,
    		boolean          use_lma_dsi,
    		final int        debugLevel) {
    	boolean dbg=debugLevel > 1000;
		double [] disparity_dsi = master_CLT.getDLS()[use_lma_dsi?1:0]; // [1]; // null
		double [] strength = master_CLT.getDLS()[2];
		double sw=0,swd=0; // old, new-old
		int num_good = 0;
		if (dbg) {
			ShowDoubleFloatArrays.showArrays(
					new double[][] {disparity_dsi, strength},
					master_CLT.getTilesX(),
					master_CLT.getTilesY(),
					true,
					master_CLT.getImageName()+"getImsDisparityCorrection",
					new String[] {"disparity","strength"}); //	dsrbg_titles);
		}
		
		for (int i = 0; i < disparity_dsi.length; i++) {
			if (!Double.isNaN(disparity_dsi[i])) {
				if (Double.isNaN(strength[i])) {
					System.out.println("getImsDisparityCorrection(): strength["+i+"]=NaN");
				} else {
					sw   += strength[i];
					swd  += strength[i]*disparity_dsi[i];
					num_good++;
				}
			}
		}
		double disp_avg = swd/sw;
    	if (debugLevel > -3) {
    		System.out.println("getImsDisparityCorrection() sw ="+sw+" swd="+swd+" disp_avg="+disp_avg+
    				" num_good="+num_good+" in reference scene "+master_CLT.getImageName());
    	}
		
    	double inf_disp_ref = disp_avg * (1.0 - scale_img);
    	if (debugLevel > -3) {
    		System.out.println("Disparity at infinity ="+inf_disp_ref+" in reference scene "+master_CLT.getImageName());
    	}
    	return inf_disp_ref;
    }
    /*
    public static void correctInfinity(
    		CLTParameters    clt_parameters,
    		QuadCLT          master_CLT,
    		QuadCLT          quadCLT_main,
    		double           inf_disp_ref, 
    		final int        debugLevel) {
    	double magic_coeff = 0.365; // how much disparity increases per 1 pixel sym_vector[0]
    	System.out.println("inf_disp_ref="+inf_disp_ref);
		GeometryCorrection gc = master_CLT.getGeometryCorrection();
		double [] sym_vect = (new CorrVector(gc)).toSymArray(null); // returns zero sym array
		double sym_vec_pixels = 1000.0*gc.focalLength/gc.pixelSize;
		sym_vect[0] = -inf_disp_ref/magic_coeff/sym_vec_pixels; // just for testing
		CorrVector diff_corr = new CorrVector(gc, sym_vect,null);
        boolean apply_corr = true;
        if (apply_corr) {
        	master_CLT.gpuResetCorrVector(); // next time GPU will need to set correction vector (and re-calculate offsets?)
			CorrVector master_vector = master_CLT.getGeometryCorrection().getCorrVector();
			master_vector.incrementVector(diff_corr, 1.0); // no scale here
			master_CLT.getGeometryCorrection().setCorrVector(master_vector);
			quadCLT_main.getGeometryCorrection().setCorrVector(master_vector); // updated system corr vector with the current updated one
			System.out.println("Extrinsic correction updated");
        }
    	return ;
    }
    */
    public static void correctInfinityMain(
    		CLTParameters    clt_parameters,
    		QuadCLT          quadCLT_main,
    		double           inf_disp_ref, 
    		final int        debugLevel) {
    	double magic_coeff = 0.365; // how much disparity increases per 1 pixel sym_vector[0]
    	System.out.println("correctInfinityMain(): inf_disp_ref="+inf_disp_ref);
		GeometryCorrection gc = quadCLT_main.getGeometryCorrection();
		double [] sym_vect = (new CorrVector(gc)).toSymArray(null); // returns zero sym array
		double sym_vec_pixels = 1000.0*gc.focalLength/gc.pixelSize;
		sym_vect[0] = -inf_disp_ref/magic_coeff/sym_vec_pixels; // just for testing
		CorrVector diff_corr = new CorrVector(gc, sym_vect,null);
        boolean apply_corr = true;
        if (apply_corr) {
        	quadCLT_main.gpuResetCorrVector(); // next time GPU will need to set correction vector (and re-calculate offsets?)
			CorrVector master_vector = quadCLT_main.getGeometryCorrection().getCorrVector();
			master_vector.incrementVector(diff_corr, 1.0); // no scale here
			quadCLT_main.getGeometryCorrection().setCorrVector(master_vector); // updated system corr vector with the current updated one
			System.out.println("Extrinsic correction updated");
        }
    	return ;
    }
    
    
    
    
    
    public static boolean correctInfinityFromIMS(
    		CLTParameters    clt_parameters,
    		QuadCLT []       quadCLTs,
    		QuadCLT          master_CLT,
    		QuadCLT          quadCLT_main,
    		int              earliest_scene,
    		final int        debugLevel) {
    	double magic_coeff = 0.365; // how much disparity increases per 1 pixel sym_vector[0]
    	double scale_img = getImgImsScale( // correctInfinityFromIMS(
    			quadCLTs, // QuadCLT []       quadCLTs,
    			master_CLT, // QuadCLT          master_CLT)
    			earliest_scene,     // int              earliest_scene)
    			-1);                 // int              latest_scene) { // -1 will use quadCLTs.length -1;
		double [] disparity_dsi = master_CLT.getDLS()[1]; // null
		double [] strength = master_CLT.getDLS()[2];
		double sw=0,swd=0; // old, new-old
		for (int i = 0; i < disparity_dsi.length; i++) {
			if (!Double.isNaN(disparity_dsi[i])) {
				sw   += strength[i];
				swd  += strength[i]*disparity_dsi[i];
			}
		}
		double disp_avg = swd/sw;
    	double inf_disp_ref = disp_avg * (1.0 - scale_img);
    	System.out.println("inf_disp_ref="+inf_disp_ref);
		
		double avg_z = master_CLT.getGeometryCorrection().getZFromDisparity(disp_avg);
		GeometryCorrection gc = master_CLT.getGeometryCorrection();
		
		double [] sym_vect = (new CorrVector(gc)).toSymArray(null); // returns zero sym array
		
		double sym_vec_pixels = 1000.0*gc.focalLength/gc.pixelSize;
		sym_vect[0] = -inf_disp_ref/magic_coeff/sym_vec_pixels; // just for testing
		CorrVector diff_corr = new CorrVector(gc, sym_vect,null);
		
        boolean apply_corr = true;
        if (apply_corr) {
        	master_CLT.gpuResetCorrVector(); // next time GPU will need to set correction vector (and re-calculate offsets?)
        	
			CorrVector master_vector = master_CLT.getGeometryCorrection().getCorrVector();
			master_vector.incrementVector(diff_corr, 1.0); // no scale here
			master_CLT.getGeometryCorrection().setCorrVector(master_vector);
			/*
			// Apply correction to all scenes (adding, as ERS can be different)
			// will need to update all scenes GC (write back to disk), but only after all are done
			for (int i = 0; i < quadCLTs.length; i++) {
				QuadCLT scene = quadCLTs[i];
				CorrVector scene_vector = scene.getGeometryCorrection().getCorrVector();
				scene_vector.incrementVector(diff_corr, 1.0); // no scale here
				scene.getGeometryCorrection().setCorrVector(scene_vector);
			}
//			master_CLT.getGeometryCorrection().setCorrVector(corr_vector); // updated system corr vector with the current updated one
			// was in TwoQuadCLT:
			CorrVector main_vector = quadCLT_main.getGeometryCorrection().getCorrVector();
			*/
			
			quadCLT_main.getGeometryCorrection().setCorrVector(master_vector); // updated system corr vector with the current updated one
			System.out.println("Extrinsic correction updated");
        }
    	return !Double.isNaN(scale_img);
    }
    
    public static double getImgImsScale( // correctInfinityFromIMS(
			QuadCLT []       quadCLTs,
			QuadCLT          master_CLT,
			int              earliest_scene,
			int              latest_scene) { // -1 will use quadCLTs.length -1;
    	if (latest_scene < 0) {
    		latest_scene += quadCLTs.length;
    	}
		ErsCorrection ers_reference = master_CLT.getErsCorrection();
		// find first and last initialized scenes, assuming approximate moving in the same direction
		double [] xyz0 = null, xyz1 = null;
		Did_ins_2 d2_0 = null,   d2_1 = null;
		for (int nscene = earliest_scene; nscene < quadCLTs.length; nscene++) {
			QuadCLT scene = quadCLTs[nscene];
			String ts = scene.getImageName();
			if ((ers_reference.getSceneXYZ(ts) != null) && (scene.did_ins_2 != null)) {
				if (xyz0 == null) {
					xyz0 = ers_reference.getSceneXYZ(ts);
					d2_0 = scene.did_ins_2;
				} else {
					xyz1 = ers_reference.getSceneXYZ(ts);
					d2_1 = scene.did_ins_2;
				}
			}
		}
		if (xyz1 == null) {
			System.out.println("correctInfinityFromIMS(): not enough data");
			return Double.NaN;
		}
		double travel_visual2 = 0;
		double travel_ims2 = 0;
		double [] dned = Imx5.nedFromLla (d2_1.lla, d2_0.lla);
		for (int i = 0; i < 3; i++) {
			double dv = xyz1[i]-xyz0[i];
			travel_visual2 += dv*dv;
			travel_ims2 += dned[i]*dned[i]; 
		}
    	double img_scale = Math.sqrt(travel_visual2/travel_ims2);
    	return img_scale;
    }
    
    public static void scaleImgXYZ(
    		double           scale_xyz,
			QuadCLT []       quadCLTs,
			QuadCLT          master_CLT) {
		ErsCorrection ers_reference = master_CLT.getErsCorrection();
		for (int nscene = 0; nscene < quadCLTs.length; nscene++) {
			QuadCLT scene = quadCLTs[nscene];
			String ts = scene.getImageName();
			if (ers_reference.getSceneXYZ(ts) != null) {
				double [] xyz = ers_reference.getSceneXYZ(ts);
				if (xyz != null) {
					for (int i = 0; i < xyz.length; i++) {
						xyz[i] *= scale_xyz;
					}
				}
			}
		}
		return;
    }
    
   
    public static boolean fineTuneIMSTiming(
    		CLTParameters    clt_parameters,
    		QuadCLT []       quadCLTs,
    		QuadCLT          master_CLT,
    		QuadCLT          quadCLT_main,
			int              earliest_scene,
			int              last_index,
    		final int        debugLevel) {
    	// need to get V{a,t,r} and camv_{a,t,r}
		ErsCorrection ers_reference = master_CLT.getErsCorrection();
		double [][][] scenes_xyzatr =    new double [quadCLTs.length][][];
		for (int nscene = earliest_scene; nscene < last_index; nscene++) {
			QuadCLT scene = quadCLTs[nscene];
			String ts = scene.getImageName();
			scenes_xyzatr[nscene] =    new double [][] {ers_reference.getSceneXYZ(ts),ers_reference.getSceneATR(ts)};
		}
		
		double [][][] dxyzatr_dt = OpticalFlow.getVelocitiesFromScenes(
					quadCLTs,          // QuadCLT []     scenes, // ordered by increasing timestamps
//					ref_index,
					master_CLT,// QuadCLT        ref_scene, // may be one of scenes or center

					earliest_scene,    // int            start_scene,
					last_index, // 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);
    	
		double [][][] scenes_dxyzatr = QuadCLTCPU.getDxyzatrPIMU( // velocities and omegas from IMU 
				clt_parameters, // CLTParameters clt_parameters,
				quadCLTs,       // QuadCLT[]     quadCLTs,
				null); // quat_corr); // double []  quat_corr,

		
		int num_samples = last_index-earliest_scene + 1;
		double scene_time = (quadCLTs[last_index].getTimeStamp() - quadCLTs[earliest_scene].getTimeStamp())/(num_samples-1);
		double [] cos_wnd = new double[num_samples];
		for (int i = 0; i < num_samples; i++) {
			cos_wnd[i] = Math.sin(Math.PI * (i+0.5)/num_samples);
		}
		int hrange = num_samples/2; // may be different // index of zero offset
		double [] corrs = new double [2 * hrange + 1];
		for (int n = -hrange; n <= hrange; n++) {
			double sw = 0, swd=0;
			for (int i = 0; i < num_samples; i++) {
				int j = i + n;
				if ((j >= 0) && (j < num_samples)){
					double w =  cos_wnd[i] * cos_wnd[j];
					double d = 0;
					for (int k = 0; k <3; k++) {
						d += dxyzatr_dt[earliest_scene + i][1][k] * scenes_dxyzatr[earliest_scene + j][1][k];
					}
					swd += w * d;
					sw += w;	
				}
			}
			if (sw > 0) {
				corrs[hrange+n] = swd/sw;
			} else {
				System.out.println ("fineTuneIMSTiming(): n="+n);
			}
		}
		if (debugLevel > -3) {
			System.out.println("n, corr");
			for (int n = -hrange; n <= hrange; n++) {
				System.out.println(n+","+corrs[hrange+n]);
			}
		}
		// find argmax
		int imax = 0;
		for (int i = 0; i <  corrs.length; i++) {
			if (corrs[i] > corrs[imax]) {
				imax = i;
			}
		}
		
		double b = (corrs[imax+1]-corrs[imax-1])/2;
		double a = (corrs[imax+1]+corrs[imax-1])/2-corrs[imax];
		double xmax= imax - b/(2 * a) - hrange;
		double toffset = xmax * scene_time;
		
		clt_parameters.imp.ims_master_ts -= toffset;
		
		System.out.println("fineTuneIMSTiming(): clt_parameters.imp.ims_master_ts "+((toffset>0)? "decreased":"incresed")+" by "+Math.abs(toffset)+" s.");
		System.out.println("fineTuneIMSTiming(): Save updated parameters and re-run fitting with reset of IMS data");
		
		
		
    	
    	/*
		// find first and last initialized scenes, assuming approximate moving in the same direction
		double [] xyz0 = null, xyz1 = null;
		Did_ins_2 d2_0 = null,   d2_1 = null;
		for (int nscene = 0; nscene < quadCLTs.length; nscene++) {
			QuadCLT scene = quadCLTs[nscene];
			String ts = scene.getImageName();
			if ((ers_reference.getSceneXYZ(ts) != null) && (scene.did_ins_2 != null)) {
				if (xyz0 == null) {
					xyz0 = ers_reference.getSceneXYZ(ts);
					d2_0 = scene.did_ins_2;
				} else {
					xyz1 = ers_reference.getSceneXYZ(ts);
					d2_1 = scene.did_ins_2;
				}
			}
		}
		if (xyz1 == null) {
			System.out.println("correctInfinityFromIMS(): not enough data");
			return false;
		}
    	*/
    	
    	
    	
     	return true;
    }
    
    
    /*
	public void adjustLYSeries(
			QuadCLT                                              quadCLT_main, // update extrinsics here too
			QuadCLT []                                           quadCLTs,
			int                                                  ref_index,
			CLTParameters                                        clt_parameters,
			int                                                  run_ly_mode, // +1 - lazy eye, +2 - infinity
			boolean                                              run_ly_ims, // adjust infinity (if enabled) using horizontal movement from the IMS
			ColorProcParameters                                  colorProcParameters,
			CorrectionColorProc.ColorGainsParameters             channelGainParameters,
			EyesisCorrectionParameters.RGBParameters             rgbParameters,
			EyesisCorrectionParameters.EquirectangularParameters equirectangularParameters,
			Properties                                           properties,
			boolean                                              reset_from_extrinsics,
			final int        threadsMax,  // maximal number of threads to launch
			final boolean    updateStatus,
			final int        debugLevel)  throws Exception
	{
		
		MultisceneLY.MSLY_MODE adjust_mode = MultisceneLY.MSLY_MODE.INF_NOINF;
		switch (run_ly_mode) {
		case 1: adjust_mode = MultisceneLY.MSLY_MODE.NOINF_ONLY; break;
		case 2: adjust_mode = MultisceneLY.MSLY_MODE.INF_ONLY;   break;
		case 3: adjust_mode = MultisceneLY.MSLY_MODE.INF_NOINF;  break;
		default:
			System.out.println("Invalid LY/infinity adjustment mode");
			return;
		}
		if (clt_parameters.ofp.pattern_mode) {
			adjust_mode = MultisceneLY.MSLY_MODE.NOINF_ONLY;	
		}
		boolean pattern_mode = clt_parameters.ofp.pattern_mode;
		this.startTime=System.nanoTime();
		
		boolean proc_infinity = (adjust_mode == MultisceneLY.MSLY_MODE.INF_ONLY) || (adjust_mode == MultisceneLY.MSLY_MODE.INF_NOINF); //  true; 
		boolean lma_only = true; // use clt_parameters
		double        dbg_disparity_offset = 0.0; // 0.1
		
		double        inf_disp_ref =   0.0;
///		int ref_scene_index = quadCLTs.length-1;
		
///		if (pattern_mode) {
///			ref_scene_index = clt_parameters.ofp.center_index;
///        }		
		
//		QuadCLT ref_scene = quadCLTs[ref_scene_index];
		QuadCLT ref_scene = quadCLTs[ref_index]; // 05.04.2025
		String composite_suffix =    "-INTER-INTRA-LMA"; // is already read if available!
		String num_corr_max_suffix = "-NUM-CORR-MAX";
		int [] wh = new int[2];
		double [][] composite_ds = ref_scene.readDoubleArrayFromModelDirectory(
				composite_suffix, // String      suffix,
				0,                // int         num_slices, // (0 - all)
				wh);              // int []      wh)
		// composite_ds == null if no file

		MultisceneLY multisceneLY = new MultisceneLY (
				quadCLTs[0].getNumSensors(),
				threadsMax,                                // int            threadsMax,  // maximal number of threads to launch
				updateStatus);                             // boolean        updateStatus);
		// read interscene composite data
		boolean [][] is_scene_infinity = null; // per scene, per tile - is infinity.

		int tilesX = ref_scene.tp.getTilesX();
		int tilesY = ref_scene.tp.getTilesY();
		int clustersX = (int) Math.ceil(1.0 * tilesX / clt_parameters.lyms_clust_size);
		int clustersY = (int) Math.ceil(1.0 * tilesY / clt_parameters.lyms_clust_size);
		int clusters = clustersX * clustersY;

		boolean       debug      = debugLevel > -3; 

		if (proc_infinity) {
			if (run_ly_ims) {
				// make sure ref_scene is reference (center if selected)
				double [] disparity_dsi = ref_scene.getDLS()[1]; // null
				double [] strength = ref_scene.getDLS()[2];
				double sw=0,swd=0;
				for (int i = 0; i < disparity_dsi.length; i++) {
					if (!Double.isNaN(disparity_dsi[i])) {
						sw   += strength[i];
						swd  += strength[i]*disparity_dsi[i];
					}
				}
				double disp_avg = swd/sw;
				double avg_z = ref_scene.getGeometryCorrection().getZFromDisparity(disp_avg);
		    	double scale_img = getImgImsScale( // correctInfinityFromIMS(
		    			quadCLTs,              // QuadCLT []       quadCLTs,
		    			ref_scene,             // QuadCLT          master_CLT)
		    			0, // earliest_scene);       // int              earliest_scene)
		    			-1);                 // int              latest_scene) { // -1 will use quadCLTs.length -1;

		    	inf_disp_ref = disp_avg * (1.0 - scale_img);
		    	System.out.println("inf_disp_ref="+inf_disp_ref);
				
				

			} else {
				double [] inf_avg = new double[1];
				boolean [] ref_inf = MultisceneLY.getComboInfinity(
						ref_scene.tp,   // TileProcessor tp,
						composite_ds,   // double [][]   composite_ds,
						clt_parameters.lyms_far_inf,        // double        far_inf,
						clt_parameters.lyms_near_inf,       // double        near_inf,
						clt_parameters.lyms_far_fract,      // double        far_fract,
						clt_parameters.lyms_inf_range_offs, // double        inf_range_offs,
						clt_parameters.lyms_inf_range,      // double        inf_range,
						clt_parameters.lyms_min_inf_str,    // double        min_inf_str,
						clt_parameters.lyms_min_fg_str,     // double        min_fg_str,
						inf_avg,        // double []     inf_avg,
						debug);         // boolean       debug)

				is_scene_infinity =  MultisceneLY.infinityPerScene( // null pointer
						quadCLTs,    // QuadCLT [] scenes,
						inf_avg[0],  // double     inf_disp_ref, // average disparity at infinity for ref scene
						ref_inf,     // boolean [] infinity_ref,
						debug,       // boolean    debug,
						threadsMax); // int        threadsMax)
				inf_disp_ref = inf_avg[0];
			}
		}
		// Read or generate+save number of correlation maximums per scene, per tile
		// Will use only tiles with one and only one correlation maximum 
		int [][] numCorrMax = new int [quadCLTs.length][]; // -> 
		double [][] dNumCorrMax =ref_scene.readDoubleArrayFromModelDirectory(
				num_corr_max_suffix, // String      suffix,
				0,                // int         num_slices, // (0 - all)
				wh);              // int []      wh)
		if ((dNumCorrMax != null) && (dNumCorrMax.length == numCorrMax.length)) {
			for (int nscene = 0; nscene < numCorrMax.length; nscene++) {
				numCorrMax[nscene] = new int[dNumCorrMax[nscene].length];
				for (int i = 0; i < dNumCorrMax[nscene].length; i++) {
					numCorrMax[nscene][i] = (int) dNumCorrMax[nscene][i];
				}
			}
		} else {
			for (int i = 0; i < quadCLTs.length; i++) { // require DSI for each model
				numCorrMax[i] = multisceneLY.getNumCorrMax(
						clt_parameters, // CLTParameters  clt_parameters,
						lma_only, // boolean lma_only,
						quadCLTs[i], // QuadCLT scene, // ordered by increasing timestamps
						debugLevel); // int debug_level)
				if (debugLevel > -3) {
					System.out.println("\n============ ran multisceneLY.getNumCorrMax for i = "+i+" of "+quadCLTs.length);
				}
			}
			dNumCorrMax = new double[numCorrMax.length][numCorrMax[0].length];
			for (int nscene = 0; nscene < numCorrMax.length; nscene++) {
				dNumCorrMax[nscene] = new double[dNumCorrMax[nscene].length];
				for (int i = 0; i < dNumCorrMax[nscene].length; i++) {
					dNumCorrMax[nscene][i] = numCorrMax[nscene][i];
				}
			}

			ref_scene.saveDoubleArrayInModelDirectory( // error
					num_corr_max_suffix,           // String      suffix,
					null, // null,          // String []   labels, // or null
					dNumCorrMax,       // dbg_data,         // double [][] data,
					ref_scene.tp.getTilesX(),                // int         width,
					ref_scene.tp.getTilesY());               // int         height)
		}
		// valid tile - one and only one maximum
		boolean [][] valid_tile = new boolean[numCorrMax.length][numCorrMax[0].length];
		for (int nscene = 0; nscene < numCorrMax.length; nscene++) {
			for (int nTile = 0; nTile < numCorrMax[nscene].length; nTile++) {
				valid_tile[nscene][nTile] = numCorrMax[nscene][nTile] == 1; // only single-maximum
				if (clt_parameters.lyms_margin > 0) {
					int tileX = nTile % tilesX;
					int tileY = nTile / tilesX;
					if (    (tileY < clt_parameters.lyms_margin) ||
							(tileX < clt_parameters.lyms_margin) ||
							(tileY >= (tilesY - clt_parameters.lyms_margin)) ||
							(tileX >= (tilesX - clt_parameters.lyms_margin))) {
						valid_tile[nscene][nTile] = false;	
					}
				}
			}
		}

		int [][] num_tiles2 = new int[2][];
		double [][][] target_disparities = new double [2][][]; 

		boolean                 use_tarz = false; // true; //false;
		double                  delta = 0.001; // 0.01;
		boolean debug_derivs = false;

		ExtrinsicAdjustment ea = new ExtrinsicAdjustment (
				ref_scene.getErsCorrection(),  // GeometryCorrection gc,
				clt_parameters.lyms_clust_size, // int         clusterSize,
				clustersX,                      // int         clustersX,
				clustersY);                     // int         clustersY)
		boolean [] force_disparity = new boolean[clusters];
		boolean apply_extrinsic = (clt_parameters.ly_corr_scale != 0.0);
//		int max_tries = 5; // organize cycle with comparing results
		int max_tries =                   clt_parameters.lym_iter; // 25;

		double inf_min = -1.0;
		double inf_max =  1.0;
		double [] old_new_rms = new double [2];
		double min_sym_update =           clt_parameters.getLymChange(ref_scene.isAux()); //  4e-6; // stop iterations if no angle changes more than this
		double comp_diff = min_sym_update + 1; // (> min_sym_update)
		
		double []     disparity_offset = new double [clusters]; // 0.1
		if (dbg_disparity_offset != 0.0) {
			Arrays.fill(disparity_offset, dbg_disparity_offset);
		}
		int            nrefine = 4;
		
		for (int num_iter = 0; num_iter < max_tries; num_iter++){
			// (re-)measure LY here, update all scenes? or just accumulater difference?
			double [][][] lazy_eye_data2 = 	MultisceneLY.getLYDataInfNoinf(
					clt_parameters,       // final CLTParameters  clt_parameters,
					quadCLTs,             // final QuadCLT []     scenes,            // ordered by increasing timestamps
					valid_tile,           // final boolean [][]   valid_tile,        // tile with lma and single correlation maximum
					inf_disp_ref,         // final double         inf_disp_ref,      // average disparity at infinity for ref scene // is_scene_infinity
					is_scene_infinity,    // final boolean [][]   is_scene_infinity, // may be null, if not - may be infinity from the composite depth map
					target_disparities,   // final double[][]     target_disparities,
					dbg_disparity_offset, // final double         dbg_disparity_offset,
					num_tiles2,           // final int [][]       in_num_tiles,     // null or number of tiles per cluster to multiply strength
					null,                 // final CorrVector     corr_vector_delta, // null or extrinsic vector offset, applied to all scenes
					nrefine,              // final int            nrefine,           // number of disparity refines for non-inf
					threadsMax,           // final int            threadsMax,
					debugLevel);          // final int            debug_level);
			if (debug_derivs && (num_iter == 0)) { // only once if any
				MultisceneLY.debugLYDerivatives(
						clt_parameters, // final CLTParameters     clt_parameters,
						quadCLTs,             // final QuadCLT []        scenes,            // ordered by increasing timestamps
						lazy_eye_data2,        // double [][][]           lazy_eye_data2, // inf, no_inf
						valid_tile,           // final boolean [][]      valid_tile,        // tile with lma and single correlation maximum
						inf_disp_ref,         // final double            inf_disp_ref,      // average disparity at infinity for ref scene // is_scene_infinity
						is_scene_infinity,    //final boolean [][]      is_scene_infinity, // may be null, if not - may be infinity from the composite depth map
						threadsMax,           // final int               threadsMax,  // maximal number of threads to launch
						delta,                // double                  delta,
						use_tarz,             // boolean                 use_tarz,  // derivatives by tarz, not symmetrical vectors
						debugLevel); // final int               debugLevel);
			}
			if (clt_parameters.lyms_show_images) {
				for (int nly = 00; nly < lazy_eye_data2.length; nly++) if (lazy_eye_data2[nly] != null ) {
					ea.showInput(
							lazy_eye_data2[nly], // double[][] data,
							quadCLTs[ref_index].getImageName()+"-drv_reference-"+MultisceneLY.SINF_NOINF[nly]);// String title);
				}
			}
			double [][]  lazy_eye_data = MultisceneLY.mergeLY(
					adjust_mode,         // MSLY_MODE      adjust_mode,	
					lazy_eye_data2,      // double [][][]  lazy_eye_data2,
					force_disparity);    // boolean []     force_disparity // null or [clusters]
			ea.setForceDisparity(force_disparity);
			if (clt_parameters.lyms_show_images) {
				ea.showInput(
						lazy_eye_data, // double[][] data,
						quadCLTs[ref_index].getImageName()+"-ly_combo");// String title);
			}
			CorrVector corr_vector =   ea.solveCorr (
					clt_parameters.ly_marg_fract, 	  // double      marg_fract,        // part of half-width, and half-height to reduce weights
					clt_parameters.ly_inf_en,           // boolean     use_disparity,     // adjust disparity-related extrinsics
					// 1.0 - to skip filtering infinity
					inf_min, //double      inf_min_disparity, // minimal disparity for infinity 
					inf_max, // double      inf_max_disparity, // minimal disparity for infinity
					clt_parameters.ly_inf_min_broad, // inf_min_disp_abs,  // minimal disparity for infinity (absolute) 
					clt_parameters.ly_inf_max_broad, // maximal disparity for infinity (absolute)
					clt_parameters.ly_inf_tilt,      //   boolean     en_infinity_tilt,  // select infinity tiles form right/left tilted (false - from average)  
					clt_parameters.ly_right_left,    //   boolean     infinity_right_left, // balance weights between right and left halves of infinity

					clt_parameters.ly_aztilt_en,        // boolean     use_aztilts,       // Adjust azimuths and tilts excluding disparity
					clt_parameters.ly_diff_roll_en,     // boolean     use_diff_rolls,    // Adjust differential rolls (3 of 4 angles)
					//					  clt_parameters.ly_inf_force,        // boolean     force_convergence, // if true try to adjust convergence (disparity, symmetrical parameter 0) even with no disparity
					clt_parameters.ly_min_forced,       // int         min_num_forced,    // minimal number of clusters with forced disparity to use it
					// data, using just radial distortions
					clt_parameters.ly_com_roll,         // boolean     common_roll,       // Enable common roll (valid for high disparity range only)
					clt_parameters.ly_focalLength ,     // boolean     corr_focalLength,  // Correct scales (focal length temperature? variations)
					clt_parameters.ly_ers_rot,          // boolean     ers_rot,           // Enable ERS correction of the camera rotation
					clt_parameters.getErsForw(),        // boolean     ers_forw,          // Enable ERS correction of the camera linear movement in z direction
					clt_parameters.getErsSide(),        // boolean     ers_side,          // Enable ERS correction of the camera linear movement in x direction
					clt_parameters.getErsVert(),        // boolean     ers_vert,          // Enable ERS correction of the camera linear movement in y direction
					// add balancing-related here?
					clt_parameters.ly_par_sel,          // 	int         manual_par_sel,    // Manually select the parameter mask bit 0 - sym0, bit1 - sym1, ... (0 - use boolean flags, != 0 - ignore boolean flags)
					clt_parameters.ly_weight_infinity,     //0.3, // double      weight_infinity,     // 0.3, total weight of infinity tiles fraction (0.0 - 1.0) 
					clt_parameters.ly_weight_disparity,    //0.0, // double      weight_disparity,    // 0.0 disparity weight relative to the sum of 8 lazy eye values of the same tile 
					clt_parameters.ly_weight_disparity_inf,//0.5, // double      weight_disparity_inf,// 0.5 disparity weight relative to the sum of 8 lazy eye values of the same tile for infinity 
					clt_parameters.ly_max_disparity_far,   //5.0, // double      max_disparity_far,   // 5.0 reduce weights of near tiles proportional to sqrt(max_disparity_far/disparity) 
					clt_parameters.ly_max_disparity_use,   //5.0, // double      max_disparity_use,   // 5.0 (default 1000)disable near objects completely - use to avoid ERS
					0.0, // clt_parameters.ly_inf_min_dfe,         //1.75,// double      min_dfe, // = 1.75;
					0.0, // clt_parameters.ly_inf_max_dfe,         //5.0, // double      max_dfe, // = 5.0; // <=0 - disable feature

					// moving objects filtering
					false, // clt_parameters.ly_moving_en,  // 	boolean     moving_en,         // enable filtering areas with potentially moving objects 
					clt_parameters.ly_moving_apply,  // 	boolean     moving_apply,      // apply filtering areas with potentially moving objects 
					clt_parameters.ly_moving_sigma,   // 	double      moving_sigma,      // blurring sigma for moving objects = 1.0;
					clt_parameters.ly_max_mov_disparity,  //		double      max_mov_disparity, // disparity limit for moving objects detection = 75.0;
					clt_parameters.ly_rad_to_hdiag_mov,   // 	double      rad_to_hdiag_mov,  // radius to half-diagonal ratio to remove high-distortion corners = 0.7 ; // 0.8
					clt_parameters.ly_max_mov_average,   //		double      max_mov_average,   // do not attempt to detect moving objects if ERS is not accurate for terrain = .25;
					clt_parameters.ly_mov_min_L2,  // 	double      mov_min_L2,        // threshold for moving objects = 0.75;
					lazy_eye_data, // scan.getLazyEyeData(),              // dsxy, // double [][] measured_dsxy,
					force_disparity, // scan.getLazyEyeForceDisparity(),    // null, //	boolean [] force_disparity,    // boolean [] force_disparity,
					false, // 	boolean     use_main, // corr_rots_aux != null;
					ref_scene.getGeometryCorrection().getCorrVector(), // CorrVector corr_vector,
					old_new_rms, // double [] old_new_rms, // should be double[2]
					debugLevel); //  + 5);// int debugLevel) >=2 to show images
			if (debugLevel > -2){
				System.out.println("Old extrinsic corrections:");
				System.out.println(ref_scene.getGeometryCorrection().getCorrVector().toString());
			}
			if (corr_vector != null) {
				CorrVector diff_corr = corr_vector.diffFromVector(ref_scene.getGeometryCorrection().getCorrVector());
				comp_diff = diff_corr.getNorm(); // apply this to all scenes
				if (debugLevel > -2){
					System.out.println("New extrinsic corrections:");
					System.out.println(corr_vector.toString());
				}
				if (debugLevel > -3){
					System.out.println("Increment extrinsic corrections:");
					System.out.println(diff_corr.toString());
				}
				ref_scene.gpuResetCorrVector(); // next time GPU will need to set correction vector (and re-calculate offsets?)
				if (apply_extrinsic){
					// Apply correction to all scenes (adding, as ERS can be different)
					// will need to update all scenes GC (write back to disk), but only after all are done
					for (int i = 0; i < quadCLTs.length; i++) {
						QuadCLT scene = quadCLTs[i];
						CorrVector scene_vector = scene.getGeometryCorrection().getCorrVector();
						scene_vector.incrementVector(diff_corr, 1.0); // no scale here
						scene.getGeometryCorrection().setCorrVector(scene_vector);
					}
					quadCLTs[ref_index].getGeometryCorrection().setCorrVector(corr_vector); // updated system corr vector with the current updated one
					// was in TwoQuadCLT:
					quadCLT_main.getGeometryCorrection().setCorrVector(corr_vector); // updated system corr vector with the current updated one
					System.out.println("Extrinsic correction updated (can be disabled by setting clt_parameters.ly_corr_scale = 0.0) ");
				} else {
					System.out.println("Correction is not applied according clt_parameters.ly_corr_scale == 0.0) ");
				}
			} else {
				if (debugLevel > -3){
					System.out.println("LMA failed"); // What now?
				}
			}
			boolean done = (comp_diff < min_sym_update) || (num_iter == (max_tries - 1));
			//				  System.out.println("done="+done);
			if (debugLevel > -10) { // should work even in batch mode
				System.out.println("#### extrinsicsCLT(): iteration step = "+(num_iter + 1) + " ( of "+max_tries+") change = "+
						comp_diff + " ("+min_sym_update+"), previous RMS = " + old_new_rms[0]+
						" final RMS = " + old_new_rms[1]+ " (debugLevel = "+debugLevel+")");
			}
			if (debugLevel > -10) {
				if ((debugLevel > -3) || done) {
					System.out.println("New extrinsic corrections:");
					System.out.println(ref_scene.getGeometryCorrection().getCorrVector().toString());
				}
			}
			if (comp_diff < min_sym_update) {
				break;
			}
			inf_disp_ref = 0.0; // after first adjustment set infinity to 0.0
		}
// Now update correction vector for all scenes to disk - how was it read?
// update 		
		
		if (debugLevel > -200) {
			if (debugLevel > -199) {
				return;
			}
		}


		// debug images
		if (debugLevel > -2) {
			double [][] dbg_numCorrMax = new double[numCorrMax.length][];
			for (int i = 0; i < dbg_numCorrMax.length; i++) {
				dbg_numCorrMax[i] = new double[numCorrMax[i].length];
				for (int ntile=0; ntile < numCorrMax[i].length; ntile++) {
					dbg_numCorrMax[i][ntile] = numCorrMax[i][ntile];
				}
			}
			ShowDoubleFloatArrays.showArrays(
					dbg_numCorrMax,
					tilesX,
					tilesY,
					true,
					"numCorrMax");
			double [][] dbg_num_tiles = new double[2][]; 
			for (int i = 0; i < num_tiles2.length; i++ ) {
				dbg_num_tiles[i] = new double [clusters];
				for (int nClust = 0; nClust < clusters; nClust++) {
					dbg_num_tiles[i][nClust] = num_tiles2[i][nClust];
				}
			}
			ShowDoubleFloatArrays.showArrays(
					dbg_num_tiles,
					clustersX,
					clustersY,
					true,
					"clusters_num_inf_boinf",
					new String[] {"inf tiles", "noinf tiles"});

		}
		if (debugLevel > -2) {
			System.out.println("adjustLYSeries() Done");
		}

		System.out.println("End of adjustLYSeries()");
	}
    */
    
	public void adjustLYSeries(
			QuadCLT                                              quadCLT_main, // update extrinsics here too
			QuadCLT []                                           quadCLTs,
			QuadCLT                                              ref_scene,
			CLTParameters                                        clt_parameters,
			int                                                  run_ly_mode, // +1 - lazy eye, +2 - infinity
			boolean                                              run_ly_ims, // adjust infinity (if enabled) using horizontal movement from the IMS
			ColorProcParameters                                  colorProcParameters,
			CorrectionColorProc.ColorGainsParameters             channelGainParameters,
			EyesisCorrectionParameters.RGBParameters             rgbParameters,
			EyesisCorrectionParameters.EquirectangularParameters equirectangularParameters,
			Properties                                           properties,
			boolean                                              reset_from_extrinsics,
			final int        threadsMax,  // maximal number of threads to launch
			final boolean    updateStatus,
			final int        debugLevel)  throws Exception
	{
		
		MultisceneLY.MSLY_MODE adjust_mode = MultisceneLY.MSLY_MODE.INF_NOINF;
		switch (run_ly_mode) {
		case 1: adjust_mode = MultisceneLY.MSLY_MODE.NOINF_ONLY; break;
		case 2: adjust_mode = MultisceneLY.MSLY_MODE.INF_ONLY;   break;
		case 3: adjust_mode = MultisceneLY.MSLY_MODE.INF_NOINF;  break;
		default:
			System.out.println("Invalid LY/infinity adjustment mode");
			return;
		}
		if (clt_parameters.ofp.pattern_mode) {
			adjust_mode = MultisceneLY.MSLY_MODE.NOINF_ONLY;	
		}
		boolean pattern_mode = clt_parameters.ofp.pattern_mode;
		this.startTime=System.nanoTime();
		
		boolean proc_infinity = (adjust_mode == MultisceneLY.MSLY_MODE.INF_ONLY) || (adjust_mode == MultisceneLY.MSLY_MODE.INF_NOINF); //  true; 
		boolean lma_only = true; // use clt_parameters
		double        dbg_disparity_offset = 0.0; // 0.1
		
		double        inf_disp_ref =   0.0;
///		int ref_scene_index = quadCLTs.length-1;
		
///		if (pattern_mode) {
///			ref_scene_index = clt_parameters.ofp.center_index;
///        }		
		
//		QuadCLT ref_scene = quadCLTs[ref_scene_index];
///		QuadCLT ref_scene = quadCLTs[ref_index]; // 05.04.2025
		String composite_suffix =    "-INTER-INTRA-LMA"; // is already read if available!
		String num_corr_max_suffix = "-NUM-CORR-MAX";
		int [] wh = new int[2];
		double [][] composite_ds = ref_scene.readDoubleArrayFromModelDirectory(
				composite_suffix, // String      suffix,
				0,                // int         num_slices, // (0 - all)
				wh);              // int []      wh)
		// composite_ds == null if no file

		MultisceneLY multisceneLY = new MultisceneLY (
				quadCLTs[0].getNumSensors(),
				threadsMax,                                // int            threadsMax,  // maximal number of threads to launch
				updateStatus);                             // boolean        updateStatus);
		// read interscene composite data
		boolean [][] is_scene_infinity = null; // per scene, per tile - is infinity.

		int tilesX = ref_scene.tp.getTilesX();
		int tilesY = ref_scene.tp.getTilesY();
		int clustersX = (int) Math.ceil(1.0 * tilesX / clt_parameters.lyms_clust_size);
		int clustersY = (int) Math.ceil(1.0 * tilesY / clt_parameters.lyms_clust_size);
		int clusters = clustersX * clustersY;

		boolean       debug      = debugLevel > -3; 

		if (proc_infinity) {
			if (run_ly_ims) {
				// make sure ref_scene is reference (center if selected)
				double [] disparity_dsi = ref_scene.getDLS()[1]; // null
				double [] strength = ref_scene.getDLS()[2];
				double sw=0,swd=0;
				for (int i = 0; i < disparity_dsi.length; i++) {
					if (!Double.isNaN(disparity_dsi[i])) {
						sw   += strength[i];
						swd  += strength[i]*disparity_dsi[i];
					}
				}
				double disp_avg = swd/sw;
				double avg_z = ref_scene.getGeometryCorrection().getZFromDisparity(disp_avg);
		    	double scale_img = getImgImsScale( // correctInfinityFromIMS(
		    			quadCLTs,              // QuadCLT []       quadCLTs,
		    			ref_scene,             // QuadCLT          master_CLT)
		    			0, // earliest_scene);       // int              earliest_scene)
		    			-1);                 // int              latest_scene) { // -1 will use quadCLTs.length -1;

		    	inf_disp_ref = disp_avg * (1.0 - scale_img);
		    	System.out.println("inf_disp_ref="+inf_disp_ref);
				
				

			} else {
				double [] inf_avg = new double[1];
				boolean [] ref_inf = MultisceneLY.getComboInfinity(
						ref_scene.tp,   // TileProcessor tp,
						composite_ds,   // double [][]   composite_ds,
						clt_parameters.lyms_far_inf,        // double        far_inf,
						clt_parameters.lyms_near_inf,       // double        near_inf,
						clt_parameters.lyms_far_fract,      // double        far_fract,
						clt_parameters.lyms_inf_range_offs, // double        inf_range_offs,
						clt_parameters.lyms_inf_range,      // double        inf_range,
						clt_parameters.lyms_min_inf_str,    // double        min_inf_str,
						clt_parameters.lyms_min_fg_str,     // double        min_fg_str,
						inf_avg,        // double []     inf_avg,
						debug);         // boolean       debug)

				is_scene_infinity =  MultisceneLY.infinityPerScene( // null pointer
						quadCLTs,    // QuadCLT [] scenes,
						inf_avg[0],  // double     inf_disp_ref, // average disparity at infinity for ref scene
						ref_inf,     // boolean [] infinity_ref,
						debug,       // boolean    debug,
						threadsMax); // int        threadsMax)
				inf_disp_ref = inf_avg[0];
			}
		}
		// Read or generate+save number of correlation maximums per scene, per tile
		// Will use only tiles with one and only one correlation maximum 
		int [][] numCorrMax = new int [quadCLTs.length][]; // -> 
		double [][] dNumCorrMax =ref_scene.readDoubleArrayFromModelDirectory(
				num_corr_max_suffix, // String      suffix,
				0,                // int         num_slices, // (0 - all)
				wh);              // int []      wh)
		if ((dNumCorrMax != null) && (dNumCorrMax.length == numCorrMax.length)) {
			for (int nscene = 0; nscene < numCorrMax.length; nscene++) {
				numCorrMax[nscene] = new int[dNumCorrMax[nscene].length];
				for (int i = 0; i < dNumCorrMax[nscene].length; i++) {
					numCorrMax[nscene][i] = (int) dNumCorrMax[nscene][i];
				}
			}
		} else {
			for (int i = 0; i < quadCLTs.length; i++) { // require DSI for each model
				numCorrMax[i] = multisceneLY.getNumCorrMax(
						clt_parameters, // CLTParameters  clt_parameters,
						lma_only, // boolean lma_only,
						quadCLTs[i], // QuadCLT scene, // ordered by increasing timestamps
						debugLevel); // int debug_level)
				if (debugLevel > -3) {
					System.out.println("\n============ ran multisceneLY.getNumCorrMax for i = "+i+" of "+quadCLTs.length);
				}
			}
			dNumCorrMax = new double[numCorrMax.length][numCorrMax[0].length];
			for (int nscene = 0; nscene < numCorrMax.length; nscene++) {
				dNumCorrMax[nscene] = new double[dNumCorrMax[nscene].length];
				for (int i = 0; i < dNumCorrMax[nscene].length; i++) {
					dNumCorrMax[nscene][i] = numCorrMax[nscene][i];
				}
			}

			ref_scene.saveDoubleArrayInModelDirectory( // error
					num_corr_max_suffix,           // String      suffix,
					null, // null,          // String []   labels, // or null
					dNumCorrMax,       // dbg_data,         // double [][] data,
					ref_scene.tp.getTilesX(),                // int         width,
					ref_scene.tp.getTilesY());               // int         height)
		}
		// valid tile - one and only one maximum
		boolean [][] valid_tile = new boolean[numCorrMax.length][numCorrMax[0].length];
		for (int nscene = 0; nscene < numCorrMax.length; nscene++) {
			for (int nTile = 0; nTile < numCorrMax[nscene].length; nTile++) {
				valid_tile[nscene][nTile] = numCorrMax[nscene][nTile] == 1; // only single-maximum
				if (clt_parameters.lyms_margin > 0) {
					int tileX = nTile % tilesX;
					int tileY = nTile / tilesX;
					if (    (tileY < clt_parameters.lyms_margin) ||
							(tileX < clt_parameters.lyms_margin) ||
							(tileY >= (tilesY - clt_parameters.lyms_margin)) ||
							(tileX >= (tilesX - clt_parameters.lyms_margin))) {
						valid_tile[nscene][nTile] = false;	
					}
				}
			}
		}

		int [][] num_tiles2 = new int[2][];
		double [][][] target_disparities = new double [2][][]; 

		boolean                 use_tarz = false; // true; //false;
		double                  delta = 0.001; // 0.01;
		boolean debug_derivs = false;

		ExtrinsicAdjustment ea = new ExtrinsicAdjustment (
				ref_scene.getErsCorrection(),  // GeometryCorrection gc,
				clt_parameters.lyms_clust_size, // int         clusterSize,
				clustersX,                      // int         clustersX,
				clustersY);                     // int         clustersY)
		boolean [] force_disparity = new boolean[clusters];
		boolean apply_extrinsic = (clt_parameters.ly_corr_scale != 0.0);
//		int max_tries = 5; // organize cycle with comparing results
		int max_tries =                   clt_parameters.lym_iter; // 25;

		double inf_min = -1.0;
		double inf_max =  1.0;
		double [] old_new_rms = new double [2];
		double min_sym_update =           clt_parameters.getLymChange(ref_scene.isAux()); //  4e-6; // stop iterations if no angle changes more than this
		double comp_diff = min_sym_update + 1; // (> min_sym_update)
		
		double []     disparity_offset = new double [clusters]; // 0.1
		if (dbg_disparity_offset != 0.0) {
			Arrays.fill(disparity_offset, dbg_disparity_offset);
		}
		int            nrefine = 4;
		
		for (int num_iter = 0; num_iter < max_tries; num_iter++){
			// (re-)measure LY here, update all scenes? or just accumulater difference?
			double [][][] lazy_eye_data2 = 	MultisceneLY.getLYDataInfNoinf(
					clt_parameters,       // final CLTParameters  clt_parameters,
					quadCLTs,             // final QuadCLT []     scenes,            // ordered by increasing timestamps
					valid_tile,           // final boolean [][]   valid_tile,        // tile with lma and single correlation maximum
					inf_disp_ref,         // final double         inf_disp_ref,      // average disparity at infinity for ref scene // is_scene_infinity
					is_scene_infinity,    // final boolean [][]   is_scene_infinity, // may be null, if not - may be infinity from the composite depth map
					target_disparities,   // final double[][]     target_disparities,
					dbg_disparity_offset, // final double         dbg_disparity_offset,
					num_tiles2,           // final int [][]       in_num_tiles,     // null or number of tiles per cluster to multiply strength
					null,                 // final CorrVector     corr_vector_delta, // null or extrinsic vector offset, applied to all scenes
					nrefine,              // final int            nrefine,           // number of disparity refines for non-inf
					threadsMax,           // final int            threadsMax,
					debugLevel);          // final int            debug_level);
			if (debug_derivs && (num_iter == 0)) { // only once if any
				MultisceneLY.debugLYDerivatives(
						clt_parameters, // final CLTParameters     clt_parameters,
						quadCLTs,             // final QuadCLT []        scenes,            // ordered by increasing timestamps
						lazy_eye_data2,        // double [][][]           lazy_eye_data2, // inf, no_inf
						valid_tile,           // final boolean [][]      valid_tile,        // tile with lma and single correlation maximum
						inf_disp_ref,         // final double            inf_disp_ref,      // average disparity at infinity for ref scene // is_scene_infinity
						is_scene_infinity,    //final boolean [][]      is_scene_infinity, // may be null, if not - may be infinity from the composite depth map
						threadsMax,           // final int               threadsMax,  // maximal number of threads to launch
						delta,                // double                  delta,
						use_tarz,             // boolean                 use_tarz,  // derivatives by tarz, not symmetrical vectors
						debugLevel); // final int               debugLevel);
			}
			if (clt_parameters.lyms_show_images) {
				for (int nly = 00; nly < lazy_eye_data2.length; nly++) if (lazy_eye_data2[nly] != null ) {
					ea.showInput(
							lazy_eye_data2[nly], // double[][] data,
//							quadCLTs[ref_index].getImageName()+"-drv_reference-"+MultisceneLY.SINF_NOINF[nly]);// String title);
					ref_scene.getImageName()+"-drv_reference-"+MultisceneLY.SINF_NOINF[nly]);// String title);
				}
			}
			double [][]  lazy_eye_data = MultisceneLY.mergeLY(
					adjust_mode,         // MSLY_MODE      adjust_mode,	
					lazy_eye_data2,      // double [][][]  lazy_eye_data2,
					force_disparity);    // boolean []     force_disparity // null or [clusters]
			ea.setForceDisparity(force_disparity);
			if (clt_parameters.lyms_show_images) {
				ea.showInput(
						lazy_eye_data, // double[][] data,
						ref_scene.getImageName()+"-ly_combo");// String title);
			}
			CorrVector corr_vector =   ea.solveCorr (
					clt_parameters.ly_marg_fract, 	  // double      marg_fract,        // part of half-width, and half-height to reduce weights
					clt_parameters.ly_inf_en,           // boolean     use_disparity,     // adjust disparity-related extrinsics
					// 1.0 - to skip filtering infinity
					inf_min, //double      inf_min_disparity, // minimal disparity for infinity 
					inf_max, // double      inf_max_disparity, // minimal disparity for infinity
					clt_parameters.ly_inf_min_broad, // inf_min_disp_abs,  // minimal disparity for infinity (absolute) 
					clt_parameters.ly_inf_max_broad, // maximal disparity for infinity (absolute)
					clt_parameters.ly_inf_tilt,      //   boolean     en_infinity_tilt,  // select infinity tiles form right/left tilted (false - from average)  
					clt_parameters.ly_right_left,    //   boolean     infinity_right_left, // balance weights between right and left halves of infinity

					clt_parameters.ly_aztilt_en,        // boolean     use_aztilts,       // Adjust azimuths and tilts excluding disparity
					clt_parameters.ly_diff_roll_en,     // boolean     use_diff_rolls,    // Adjust differential rolls (3 of 4 angles)
					//					  clt_parameters.ly_inf_force,        // boolean     force_convergence, // if true try to adjust convergence (disparity, symmetrical parameter 0) even with no disparity
					clt_parameters.ly_min_forced,       // int         min_num_forced,    // minimal number of clusters with forced disparity to use it
					// data, using just radial distortions
					clt_parameters.ly_com_roll,         // boolean     common_roll,       // Enable common roll (valid for high disparity range only)
					clt_parameters.ly_focalLength ,     // boolean     corr_focalLength,  // Correct scales (focal length temperature? variations)
					clt_parameters.ly_ers_rot,          // boolean     ers_rot,           // Enable ERS correction of the camera rotation
					clt_parameters.getErsForw(),        // boolean     ers_forw,          // Enable ERS correction of the camera linear movement in z direction
					clt_parameters.getErsSide(),        // boolean     ers_side,          // Enable ERS correction of the camera linear movement in x direction
					clt_parameters.getErsVert(),        // boolean     ers_vert,          // Enable ERS correction of the camera linear movement in y direction
					// add balancing-related here?
					clt_parameters.ly_par_sel,          // 	int         manual_par_sel,    // Manually select the parameter mask bit 0 - sym0, bit1 - sym1, ... (0 - use boolean flags, != 0 - ignore boolean flags)
					clt_parameters.ly_weight_infinity,     //0.3, // double      weight_infinity,     // 0.3, total weight of infinity tiles fraction (0.0 - 1.0) 
					clt_parameters.ly_weight_disparity,    //0.0, // double      weight_disparity,    // 0.0 disparity weight relative to the sum of 8 lazy eye values of the same tile 
					clt_parameters.ly_weight_disparity_inf,//0.5, // double      weight_disparity_inf,// 0.5 disparity weight relative to the sum of 8 lazy eye values of the same tile for infinity 
					clt_parameters.ly_max_disparity_far,   //5.0, // double      max_disparity_far,   // 5.0 reduce weights of near tiles proportional to sqrt(max_disparity_far/disparity) 
					clt_parameters.ly_max_disparity_use,   //5.0, // double      max_disparity_use,   // 5.0 (default 1000)disable near objects completely - use to avoid ERS
					0.0, // clt_parameters.ly_inf_min_dfe,         //1.75,// double      min_dfe, // = 1.75;
					0.0, // clt_parameters.ly_inf_max_dfe,         //5.0, // double      max_dfe, // = 5.0; // <=0 - disable feature

					// moving objects filtering
					false, // clt_parameters.ly_moving_en,  // 	boolean     moving_en,         // enable filtering areas with potentially moving objects 
					clt_parameters.ly_moving_apply,  // 	boolean     moving_apply,      // apply filtering areas with potentially moving objects 
					clt_parameters.ly_moving_sigma,   // 	double      moving_sigma,      // blurring sigma for moving objects = 1.0;
					clt_parameters.ly_max_mov_disparity,  //		double      max_mov_disparity, // disparity limit for moving objects detection = 75.0;
					clt_parameters.ly_rad_to_hdiag_mov,   // 	double      rad_to_hdiag_mov,  // radius to half-diagonal ratio to remove high-distortion corners = 0.7 ; // 0.8
					clt_parameters.ly_max_mov_average,   //		double      max_mov_average,   // do not attempt to detect moving objects if ERS is not accurate for terrain = .25;
					clt_parameters.ly_mov_min_L2,  // 	double      mov_min_L2,        // threshold for moving objects = 0.75;
					lazy_eye_data, // scan.getLazyEyeData(),              // dsxy, // double [][] measured_dsxy,
					force_disparity, // scan.getLazyEyeForceDisparity(),    // null, //	boolean [] force_disparity,    // boolean [] force_disparity,
					false, // 	boolean     use_main, // corr_rots_aux != null;
					ref_scene.getGeometryCorrection().getCorrVector(), // CorrVector corr_vector,
					old_new_rms, // double [] old_new_rms, // should be double[2]
					debugLevel); //  + 5);// int debugLevel) >=2 to show images
			if (debugLevel > -2){
				System.out.println("Old extrinsic corrections:");
				System.out.println(ref_scene.getGeometryCorrection().getCorrVector().toString());
			}
			if (corr_vector != null) {
				CorrVector diff_corr = corr_vector.diffFromVector(ref_scene.getGeometryCorrection().getCorrVector());
				comp_diff = diff_corr.getNorm(); // apply this to all scenes
				if (debugLevel > -2){
					System.out.println("New extrinsic corrections:");
					System.out.println(corr_vector.toString());
				}
				if (debugLevel > -3){
					System.out.println("Increment extrinsic corrections:");
					System.out.println(diff_corr.toString());
				}
				ref_scene.gpuResetCorrVector(); // next time GPU will need to set correction vector (and re-calculate offsets?)
				if (apply_extrinsic){
					// Apply correction to all scenes (adding, as ERS can be different)
					// will need to update all scenes GC (write back to disk), but only after all are done
					for (int i = 0; i < quadCLTs.length; i++) {
						QuadCLT scene = quadCLTs[i];
						CorrVector scene_vector = scene.getGeometryCorrection().getCorrVector();
						scene_vector.incrementVector(diff_corr, 1.0); // no scale here
						scene.getGeometryCorrection().setCorrVector(scene_vector);
					}
//					quadCLTs[ref_index].getGeometryCorrection().setCorrVector(corr_vector); // updated system corr vector with the current updated one
					ref_scene.getGeometryCorrection().setCorrVector(corr_vector); // updated system corr vector with the current updated one
					// was in TwoQuadCLT:
					quadCLT_main.getGeometryCorrection().setCorrVector(corr_vector); // updated system corr vector with the current updated one
					System.out.println("Extrinsic correction updated (can be disabled by setting clt_parameters.ly_corr_scale = 0.0) ");
				} else {
					System.out.println("Correction is not applied according clt_parameters.ly_corr_scale == 0.0) ");
				}
			} else {
				if (debugLevel > -3){
					System.out.println("LMA failed"); // What now?
				}
			}
			boolean done = (comp_diff < min_sym_update) || (num_iter == (max_tries - 1));
			//				  System.out.println("done="+done);
			if (debugLevel > -10) { // should work even in batch mode
				System.out.println("#### extrinsicsCLT(): iteration step = "+(num_iter + 1) + " ( of "+max_tries+") change = "+
						comp_diff + " ("+min_sym_update+"), previous RMS = " + old_new_rms[0]+
						" final RMS = " + old_new_rms[1]+ " (debugLevel = "+debugLevel+")");
			}
			if (debugLevel > -10) {
				if ((debugLevel > -3) || done) {
					System.out.println("New extrinsic corrections:");
					System.out.println(ref_scene.getGeometryCorrection().getCorrVector().toString());
				}
			}
			if (comp_diff < min_sym_update) {
				break;
			}
			inf_disp_ref = 0.0; // after first adjustment set infinity to 0.0
		}
// Now update correction vector for all scenes to disk - how was it read?
// update 		
		
		if (debugLevel > -200) {
			if (debugLevel > -199) {
				return;
			}
		}


		// debug images
		if (debugLevel > -2) {
			double [][] dbg_numCorrMax = new double[numCorrMax.length][];
			for (int i = 0; i < dbg_numCorrMax.length; i++) {
				dbg_numCorrMax[i] = new double[numCorrMax[i].length];
				for (int ntile=0; ntile < numCorrMax[i].length; ntile++) {
					dbg_numCorrMax[i][ntile] = numCorrMax[i][ntile];
				}
			}
			ShowDoubleFloatArrays.showArrays(
					dbg_numCorrMax,
					tilesX,
					tilesY,
					true,
					"numCorrMax");
			double [][] dbg_num_tiles = new double[2][]; 
			for (int i = 0; i < num_tiles2.length; i++ ) {
				dbg_num_tiles[i] = new double [clusters];
				for (int nClust = 0; nClust < clusters; nClust++) {
					dbg_num_tiles[i][nClust] = num_tiles2[i][nClust];
				}
			}
			ShowDoubleFloatArrays.showArrays(
					dbg_num_tiles,
					clustersX,
					clustersY,
					true,
					"clusters_num_inf_boinf",
					new String[] {"inf tiles", "noinf tiles"});

		}
		if (debugLevel > -2) {
			System.out.println("adjustLYSeries() Done");
		}
		System.out.println("End of adjustLYSeries()");
	}
    
  
    
    
    public static void testERS(
    		CLTParameters clt_parameters,
    		int           indx0, // reference scene in a pair
    		int           indx1, // other scene in a pair
    		QuadCLT []    quadCLTs,
    		int           ref_index,
    		int           debugLevel) {
    	// First create a pair of images, similar to renderSceneSequence()
		boolean show_color =  clt_parameters.imp.show_mapped_color;
		boolean show_mono =   clt_parameters.imp.show_mapped_mono;
	    boolean use_combo_dsi =        clt_parameters.imp.use_combo_dsi;
	    boolean use_lma_dsi =          clt_parameters.imp.use_lma_dsi;
		
    	indx0 = ref_index; // disregard initial setting, set to reference
		int tilesX =  quadCLTs[ref_index].getTileProcessor().getTilesX();
        int tilesY =  quadCLTs[ref_index].getTileProcessor().getTilesY();
        double [] disparity_raw = new double [tilesX * tilesY];
        Arrays.fill(disparity_raw,clt_parameters.disparity);
///        double [][] combo_dsn_final = quadCLTs[ref_index].readDoubleArrayFromModelDirectory(
///     			"-INTER-INTRA-LMA", // String      suffix,
///        			0, // int         num_slices, // (0 - all)
///        			null); // int []      wh);
        double [][] combo_dsn_final =quadCLTs[ref_index].restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky

        double [][] dls = {
        		combo_dsn_final[COMBO_DSN_INDX_DISP],
        		combo_dsn_final[COMBO_DSN_INDX_LMA],
        		combo_dsn_final[COMBO_DSN_INDX_STRENGTH]
        };
        double [][] ds = 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 [] disparity_fg = ds[0]; // combo_dsn_final[COMBO_DSN_INDX_DISP_FG];
        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;
        			}
        		}
        	}
        }
        
        int [] other_ref = {indx1, indx0};
		ErsCorrection ers_reference = quadCLTs[ref_index].getErsCorrection();
        ImageStack stack_scenes_color = null;
        ImageStack stack_scenes_mono = null;
        int sensor_num = 0;
        int sensor_mask = 1 << sensor_num;
        
        String suffix_color = quadCLTs[indx0].getImageName()+"-"+quadCLTs[indx1].getImageName()+"-ERS_TEST-COLOR";
        String suffix_mono = quadCLTs[indx0].getImageName()+"-"+quadCLTs[indx1].getImageName()+"-ERS_TEST-MONO";
        double [][] dxyzatr_dt = new double[quadCLTs.length][];
        for (int iscene = 0; iscene <2; iscene++) {
        	int nscene = other_ref[iscene];
			String ts = quadCLTs[nscene].getImageName();
        	int nscene0 = nscene - ((nscene >0)? 1:0);
        	int nscene1 = nscene + ((nscene < ref_index)? 1:0);
        	double dt = quadCLTs[nscene1].getTimeStamp() - quadCLTs[nscene0].getTimeStamp();
        	String ts0 = quadCLTs[nscene0].getImageName();
        	String ts1 = quadCLTs[nscene1].getImageName();
    		double [] scene_xyz0 = ers_reference.getSceneXYZ(ts0);
    		double [] scene_atr0 = ers_reference.getSceneATR(ts0);
    		double [] scene_xyz1 = (nscene1== ref_index)? ZERO3:ers_reference.getSceneXYZ(ts1);
    		double [] scene_atr1 = (nscene1== ref_index)? ZERO3:ers_reference.getSceneATR(ts1);
    		dxyzatr_dt[nscene] = new double[6];
    		for (int i = 0; i < 3; i++) {
    			dxyzatr_dt[nscene][i] = (scene_xyz1[i]-scene_xyz0[i])/dt;
    			dxyzatr_dt[nscene][i + 3] = (scene_atr1[i]-scene_atr0[i])/dt;
    		}
			double []   scene_xyz = ZERO3;
			double []   scene_atr = ZERO3;
			if (nscene != ref_index) {
				scene_xyz = ers_reference.getSceneXYZ(ts);
				scene_atr = ers_reference.getSceneATR(ts);
				double []   scene_ers_xyz_dt = ers_reference.getSceneErsXYZ_dt(ts);
				double []   scene_ers_atr_dt = ers_reference.getSceneErsATR_dt(ts);
				quadCLTs[nscene].getErsCorrection().setErsDt(
						scene_ers_xyz_dt, // double []    ers_xyz_dt,
						scene_ers_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
			}
			
			if (show_color) {
				ImagePlus imp_scene_color = QuadCLT.renderGPUFromDSI(
						sensor_mask,         // 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,
						disparity_fg,        // double []         disparity_ref,
						null, // double [][]       ref_pXpYD,    // alternative to disparity_ref when reference is not uniform
						scene_xyz,           // final double []   scene_xyz, // camera center in world coordinates
						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
						true,                // final boolean     toRGB,
						clt_parameters.imp.show_color_nan,

						"",                  // String            suffix, no suffix here
						THREADS_MAX,          // int               threadsMax,
						debugLevel);         // int         debugLevel)
				if (stack_scenes_color == null) {
					stack_scenes_color = new ImageStack(imp_scene_color.getWidth(),imp_scene_color.getHeight());
				}
				stack_scenes_color.addSlice(
						nscene+":"+ts,
						imp_scene_color.getStack().getPixels(sensor_num+1));
			}
			if (show_mono) {
				ImagePlus imp_scene_mono = QuadCLT.renderGPUFromDSI(
						sensor_mask,         // 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,
						disparity_fg,        // double []         disparity_ref,
						null, // double [][]       ref_pXpYD,    // alternative to disparity_ref when reference is not uniform
						scene_xyz,           // final double []   scene_xyz, // camera center in world coordinates
						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,               // final boolean     toRGB,
						clt_parameters.imp.show_mono_nan,
						"",                  // String            suffix, no suffix here
						THREADS_MAX,          // int               threadsMax,
						debugLevel);         // int         debugLevel)
				if (stack_scenes_mono == null) {
					stack_scenes_mono = new ImageStack(imp_scene_mono.getWidth(),imp_scene_mono.getHeight());
				}
				stack_scenes_mono.addSlice(
						nscene+":"+ts,
						imp_scene_mono.getStack().getPixels(sensor_num+1));
			}
        }
        if (show_color) {
        	ImagePlus imp_scenes_color = new ImagePlus(suffix_color, stack_scenes_color);
        	imp_scenes_color.getProcessor().resetMinAndMax();
        	imp_scenes_color.show();
        }
        if (show_mono) {
        	ImagePlus imp_scenes_mono = new ImagePlus(suffix_mono, stack_scenes_mono);
        	imp_scenes_mono.getProcessor().resetMinAndMax();
        	imp_scenes_mono.show();
        }
		
        String [] vel_names = {"Vx","Vy","Vz","Vaz","Vtl","Vrl"};
        String [] vel_units = {"m/s","m/s","m/s","rad/s","rad/s","rad/s","rad/s"};
        System.out.println("Pair: ref. scene "+indx0+": ("+quadCLTs[indx0].getImageName()+")"+
        ", other scene "+indx1+": ("+quadCLTs[indx1].getImageName()+")");

		for (int i = 0; i < vel_names.length; i++) {
        	System.out.println(String.format(
        			"%3s: other: %3d (%8.3f%-6s), ref: %3d (%8.3f%-6s)",
        			vel_names[i], other_ref[0], dxyzatr_dt[other_ref[0]][i], vel_units[i],
        			other_ref[1], dxyzatr_dt[other_ref[1]][i], vel_units[i]));
		}
		double []   scene_xyz_pre = ZERO3;
		double []   scene_atr_pre = ZERO3;
		String ts_other = quadCLTs[other_ref[0]].getImageName(); // other scene

		scene_xyz_pre = ers_reference.getSceneXYZ(ts_other);
		scene_atr_pre = ers_reference.getSceneATR(ts_other);
		double []   scene_ers_xyz_dt = ers_reference.getSceneErsXYZ_dt(ts_other);
		double []   scene_ers_atr_dt = ers_reference.getSceneErsATR_dt(ts_other);
		quadCLTs[other_ref[0]].getErsCorrection().setErsDt(
				scene_ers_xyz_dt, // double []    ers_xyz_dt,
				scene_ers_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
		double []      lma_rms = new double[2];

		double []ref_dt =   dxyzatr_dt[other_ref[1]]; 
		double []scene_dt = dxyzatr_dt[other_ref[0]]; 
		double []ref_xyz_dt = ZERO3; // {ref_dt[0],ref_dt[1],ref_dt[2]}; 
//		double []ref_xyz_dt = {ref_dt[0],ref_dt[1],ref_dt[2]}; 
//		double []ref_xyz_dt = {-ref_dt[0],-ref_dt[1],-ref_dt[2]}; 
		double k = 1.0;
		double []ref_atr_dt = {k*ref_dt[3],k*ref_dt[4],k*ref_dt[5]}; 
		double []scene_xyz_dt = ZERO3; // {scene_dt[0],scene_dt[1],scene_dt[2]}; 
//		double []scene_xyz_dt = {scene_dt[0],scene_dt[1],scene_dt[2]}; 
//		double []scene_xyz_dt = {-scene_dt[0],-scene_dt[1],-scene_dt[2]}; 
		double []scene_atr_dt = {k*scene_dt[3],k*scene_dt[4],k*scene_dt[5]};
		// first - try ATR only
		quadCLTs[other_ref[0]].getErsCorrection().setErsDt( // scene
				scene_xyz_dt, // double []    ers_xyz_dt,
				scene_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
		quadCLTs[other_ref[1]].getErsCorrection().setErsDt( // reference
				ref_xyz_dt,  // double []    ers_xyz_dt,
				ref_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
		
		double [][] adjusted_xyzatr = Interscene.adjustPairsLMAInterscene(
				clt_parameters,                                 // CLTParameters  clt_parameters,
			    clt_parameters.imp.use_lma_dsi,
				false,               //	boolean        fpn_disable,   // disable fpn filter if images are known to be too close
				false,               // boolean        disable_ers,
				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
				quadCLTs[other_ref[1]],                         // QuadCLT reference_QuadCLT,
				null, // double []        ref_disparity, // null or alternative reference disparity
				null, // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
				quadCLTs[other_ref[0]],                         // QuadCLT scene_QuadCLT,
				scene_xyz_pre,                                // xyz
				scene_atr_pre,                                // atr
				null, //					double []      scene_xyz_pull, // if both are not null, specify target values to pull to 
				null, //					double []      scene_atr_pull, // 
				clt_parameters.ilp.ilma_lma_select,                                  // final boolean[]   param_select,
				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);
//		System.out.println("lma_rms={"+lma_rms[0]+","+lma_rms[1]+"}");
		
        ImageStack stack_adjusted_color = null;
        ImageStack stack_adjusted_mono = null;
        String suffix_adjusted_color = quadCLTs[indx0].getImageName()+"-"+quadCLTs[indx1].getImageName()+"-ERS_TEST_ADJUSTED-COLOR";
        String suffix_adjusted_mono = quadCLTs[indx0].getImageName()+"-"+quadCLTs[indx1].getImageName()+"-ERS_TEST_ADJUSTED-MONO";
        for (int iscene = 0; iscene <2; iscene++) {
        	int nscene = other_ref[iscene];
			String ts = quadCLTs[nscene].getImageName();
			double []   scene_xyz = ZERO3;
			double []   scene_atr = ZERO3;
			if (nscene != ref_index) {
				// keep current/last adjusted
				scene_xyz = adjusted_xyzatr[0]; // ers_reference.getSceneXYZ(ts);
				scene_atr = adjusted_xyzatr[1]; // ers_reference.getSceneATR(ts);
				/*
				double []   scene_ers_xyz_dt1 = ers_reference.getSceneErsXYZ_dt(ts);
				double []   scene_ers_atr_dt1 = ers_reference.getSceneErsATR_dt(ts);
				quadCLTs[nscene].getErsCorrection().setErsDt(
						scene_ers_xyz_dt1, // double []    ers_xyz_dt,
						scene_ers_atr_dt1); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
				*/
			}
			if (show_color) {
				ImagePlus imp_scene_color = QuadCLT.renderGPUFromDSI(
						sensor_mask,         // 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,
						disparity_fg,        // double []         disparity_ref,
						null, // double [][]       ref_pXpYD,    // alternative to disparity_ref when reference is not uniform
						scene_xyz,           // final double []   scene_xyz, // camera center in world coordinates
						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
						true,                // final boolean     toRGB,
						clt_parameters.imp.show_color_nan,
						"",                  // String            suffix, no suffix here
						THREADS_MAX,          // int               threadsMax,
						debugLevel);         // int         debugLevel)
				if (stack_adjusted_color == null) {
					stack_adjusted_color = new ImageStack(imp_scene_color.getWidth(),imp_scene_color.getHeight());
				}
				stack_adjusted_color.addSlice(
						nscene+":"+ts,
						imp_scene_color.getStack().getPixels(sensor_num+1));
			}
			if (show_mono) {
				ImagePlus imp_scene_mono = QuadCLT.renderGPUFromDSI(
						sensor_mask,         // 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,
						disparity_fg,        // double []         disparity_ref,
						null, // double [][]       ref_pXpYD,    // alternative to disparity_ref when reference is not uniform
						scene_xyz,           // final double []   scene_xyz, // camera center in world coordinates
						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,               // final boolean     toRGB,
						clt_parameters.imp.show_mono_nan,
						"",                  // String            suffix, no suffix here
						THREADS_MAX,          // int               threadsMax,
						debugLevel);         // int         debugLevel)
				if (stack_adjusted_mono == null) {
					stack_adjusted_mono = new ImageStack(imp_scene_mono.getWidth(),imp_scene_mono.getHeight());
				}
				stack_adjusted_mono.addSlice(
						nscene+":"+ts,
						imp_scene_mono.getStack().getPixels(sensor_num+1));
			}
        }
        if (show_color) {
        	ImagePlus imp_adjusted_color = new ImagePlus(suffix_adjusted_color, stack_adjusted_color);
        	imp_adjusted_color.getProcessor().resetMinAndMax();
        	imp_adjusted_color.show();
        }
        if (show_mono) {
        	ImagePlus imp_adjusted_mono = new ImagePlus(suffix_adjusted_mono, stack_adjusted_mono);
        	imp_adjusted_mono.getProcessor().resetMinAndMax();
        	imp_adjusted_mono.show();
        }
		return;
    }
    
  
    public static int [][] getERSStats(
    		CLTParameters clt_parameters,
    		QuadCLT []    quadCLTs,
    		int           ref_index,
    		double [][]   dxyzatr_dt) {
        int earlies_scene = quadCLTs[ref_index].getEarliestScene(quadCLTs);
		ErsCorrection ers_reference = quadCLTs[ref_index].getErsCorrection();
        int dbg_scene = -95;
        int [][] min_max_xyzatr = new int [6][2];
        for (int i = 0; i < min_max_xyzatr.length; i++) {
        	Arrays.fill(min_max_xyzatr[i],-1);
        }
		if (dxyzatr_dt == null) {
			dxyzatr_dt = new double[quadCLTs.length][];
		} else {
			Arrays.fill(dxyzatr_dt, null);
		}
		for (int nscene =  earlies_scene; nscene < quadCLTs.length ; nscene++) if (quadCLTs[nscene] != null){
			if (nscene== dbg_scene) {
				System.out.println("renderSceneSequence(): nscene = "+nscene);
			}
			String ts = quadCLTs[nscene].getImageName();
			if ((ers_reference.getSceneXYZ(ts) != null) && (ers_reference.getSceneATR(ts) != null)) {
				int nscene0 = nscene - ((nscene > earlies_scene)? 1:0);
				int nscene1 = nscene + ((nscene < ref_index)? 1:0);
				String ts0 = quadCLTs[nscene0].getImageName();
				if ((ers_reference.getSceneXYZ(ts0) == null) || (ers_reference.getSceneATR(ts0) == null)) {
					nscene0 = nscene;
					ts0 = quadCLTs[nscene0].getImageName();
				}
				double dt = quadCLTs[nscene1].getTimeStamp() - quadCLTs[nscene0].getTimeStamp();
//				String ts0 = quadCLTs[nscene0].getImageName();
				String ts1 = quadCLTs[nscene1].getImageName();
				double [] scene_xyz0 = ers_reference.getSceneXYZ(ts0);
				double [] scene_atr0 = ers_reference.getSceneATR(ts0);
				double [] scene_xyz1 = (nscene1== ref_index)? ZERO3:ers_reference.getSceneXYZ(ts1);
				double [] scene_atr1 = (nscene1== ref_index)? ZERO3:ers_reference.getSceneATR(ts1);
				dxyzatr_dt[nscene] = new double[6];
				for (int i = 0; i < 3; i++) {
					dxyzatr_dt[nscene][i] = (scene_xyz1[i]-scene_xyz0[i])/dt;
					dxyzatr_dt[nscene][i + 3] = (scene_atr1[i]-scene_atr0[i])/dt;
				}
				for (int i = 0; i < dxyzatr_dt[nscene].length; i++) {
					if ((min_max_xyzatr[i][0] < 0) || // update index of minimum
							(dxyzatr_dt[nscene][i] < dxyzatr_dt[min_max_xyzatr[i][0]][i]) ) {
						min_max_xyzatr[i][0] = nscene;
					}
					if ((min_max_xyzatr[i][1] < 0) || // update index of maximum
							(dxyzatr_dt[nscene][i] > dxyzatr_dt[min_max_xyzatr[i][1]][i]) ) {
						min_max_xyzatr[i][1] = nscene;
					}
				}
			}   
		}
		return min_max_xyzatr;
    }
    
    public static ImagePlus renderSceneSequenceHyper(
    		CLTParameters  clt_parameters,
    		boolean        use_tilted_disparity, // tilted disparity: anisotropic distortions but full image fill
    		boolean        um_mono,
    		boolean        subtract_average,
    		int            running_average,
    		boolean        toRGB,
    		String         suffix_in, // "-TERRAIN_CT"
    		QuadCLT []     quadCLTs,
    		QuadCLT        ref_scene,
    		int            debugLevel) {
    	suffix_in += clt_parameters.imp.fgnd_ct_low +":"+clt_parameters.imp.fgnd_ct_high+":"+clt_parameters.imp.fgnd_ct_step;
    	int width=0;
    	double [][] combo_dsn_final = null;
    	int num_ct_samples =(int) Math.round((clt_parameters.imp.fgnd_ct_high - clt_parameters.imp.fgnd_ct_low)/clt_parameters.imp.fgnd_ct_step);
    	float [][][] hyper_fpixels = new float [num_ct_samples][][];
    	String [] hyper_titles = new String [num_ct_samples];
    	String [] slice_titles = null; // will get from the first image, they all should be the same
    	ImagePlus imp_terrain_hyper = null;
    	String title = null;
    	for (int nct = 0; nct < num_ct_samples; nct++) {
    		double gnd_offset = clt_parameters.imp.fgnd_ct_low + nct* clt_parameters.imp.fgnd_ct_step;
    		hyper_titles[nct] = "GND"+((gnd_offset >0)? "+":"")+String.format("%.3fm", gnd_offset);
    		double [][] gnp = GroundPlane.prepareTerrainRender(
    				clt_parameters,       // final CLTParameters clt_parameters,
    				ref_scene,            // final QuadCLT       ref_Clt,
    				use_tilted_disparity, // final boolean       tilted_plane,
    				gnd_offset,           // final double        offset,
    				0.0,                  // final double        test_bottom,
    				debugLevel-2);          // final int           debugLevel)
    		double [][] ignp =  null;
    		if (gnp != null) {
    			ignp = ErsCorrection.invertXYZATR(gnp); // not used with tilted disparity
    			if (debugLevel > -1) {
    				System.out.println("Using airplane mode terrane as a single horizontal plane");
    				System.out.println(" ignp = [["+gnp[0][0]+", "+gnp[0][1]+", "+gnp[0][2]+"]["+gnp[1][0]+", "+gnp[1][1]+", "+gnp[1][2]+"]]");
    				System.out.println("ignp = [["+ignp[0][0]+", "+ignp[0][1]+", "+ignp[0][2]+"],["+ignp[1][0]+", "+ignp[1][1]+", "+ignp[1][2]+"]]");
    			}
    			combo_dsn_final =ref_scene.restoreComboDSI(true);
    			if ((combo_dsn_final.length <= COMBO_DSN_INDX_TERRAIN) || (combo_dsn_final[COMBO_DSN_INDX_TERRAIN] == null)) {
    				System.out.println ("No terrain data available");
    			} else {
    				double [] terrain_disparity = combo_dsn_final[COMBO_DSN_INDX_TERRAIN];
    				String scenes_suffix = ref_scene.getImageName()+suffix_in;
    				ImagePlus imp_terrain = renderSceneSequence(
    						clt_parameters,                      // CLTParameters  clt_parameters,
    						um_mono,                             // boolean        um_mono,
    						null,                                // float [][]     average_slice, // [channel][pixel]
    						subtract_average,                    // boolean        subtract_average,
    						running_average,                     // int            running_average,
    						null,                                // Rectangle      fov_tiles,
    						1,                                   // int            mode3d, // for older compatibility mode3d = -1 for RAW, 0 - INF, 1 - FG, 2 BG
    						false,                               // boolean        toRGB,
    						(use_tilted_disparity? null: ignp),  // double [][]    ground_xyzatr,
    						1,                                   // int            sensor_mask,
    						scenes_suffix,                       // String         suffix_in,
    						terrain_disparity,                   // double []      ref_disparity,			
    						quadCLTs,                            // QuadCLT []     quadCLTs,
    						ref_scene,                          // QuadCLT        ref_scene, // int            ref_index,
    						debugLevel-2);                         // int            debugLevel) {
    				int num_slices = imp_terrain.getStack().getSize();
    				hyper_fpixels[nct] = new float [num_slices][];
    				if (nct == 0) {
    					slice_titles = new String [num_slices];
    					for (int i = 0; i < num_slices; i++) {
    						slice_titles[i] = imp_terrain.getStack().getSliceLabel(i+1);
    					}
    					width = imp_terrain.getWidth();
    					title = imp_terrain.getTitle();
    				}
					for (int i = 0; i < num_slices; i++) {
						hyper_fpixels[nct][i] = (float[]) imp_terrain.getStack().getPixels(i+1);
					}

    			}
    		} // if (gnp != null) {
		} // for (double gnd_offset
    	imp_terrain_hyper = ShowDoubleFloatArrays.showArraysHyperstack(
    			hyper_fpixels, // float[][][] pixels,
    			width,          // int          width,
    			title,        // String       title,
    			slice_titles, // String []    titles, // all slices*frames titles or just slice titles or null
    			hyper_titles, // String []    frame_titles, // frame titles or null
    			false);       // boolean      show)
    	return imp_terrain_hyper;
    }
  
    public static ImagePlus renderSceneSequence(
    		CLTParameters  clt_parameters,
    		boolean        um_mono,
    		float [][]     average_slice, // [channel][pixel]
    		boolean        subtract_average,
    		int            running_average,
    		Rectangle      fov_tiles,
    		int            mode3d, // for older compatibility mode3d = -1 for RAW, 0 - INF, 1 - FG, 2 BG
    		boolean        toRGB,
    		double [][]    ground_xyzatr,
    		int            sensor_mask,
    		String         suffix_in,
    		double []      ref_disparity,			
    		QuadCLT []     quadCLTs,
    		QuadCLT        ref_scene, // int            ref_index,
    		int            debugLevel) {
    	QuadCLT[] selected_scenes = selectScenes(quadCLTs, ref_scene);
    	int num_scenes = selected_scenes.length;
    	if (num_scenes == 0) {
    		return null;
    	}
		boolean  add_average =        clt_parameters.imp.add_average;        // calculate and add average of all scenes
		boolean  add_center_average = clt_parameters.imp.add_center_average; // additionally, calculate and add average of the center fraction of all scenes
		double   center_avg_frac =     clt_parameters.imp.center_avg_frac;// center fraction of all scenes
		int []  average_range = (add_center_average && add_average) ? new int[2]:null;
		if (average_range != null) {
			average_range[0] = (int) Math.round (num_scenes * (1 - center_avg_frac)/2);  
			average_range[0] = Math.max(0, average_range[0]);
			average_range[0] = Math.min(num_scenes-1, average_range[0]);
			average_range[1] = num_scenes-1 - average_range[0]; 
			average_range[1] = Math.max(average_range[0],average_range[1]);
			average_range[1] = Math.min(num_scenes-1,    average_range[1]);
		}
    	return  renderSceneSequence(
    			clt_parameters,       // CLTParameters  clt_parameters,
    			false,                // boolean        mode_cuas,
    			um_mono,              // boolean        um_mono,
    			add_average,          // boolean        calculate_average, // now only with float pixels
    			average_range,        // int []         average_range,
    			average_slice,        // float [][]     average_slice, // [channel][pixel]
    			subtract_average,     // boolean        subtract_average,
    			running_average,      // int            running_average,
    			fov_tiles,            // Rectangle      fov_tiles,
    			mode3d,               // int            mode3d, // for older compatibility mode3d = -1 for RAW, 0 - INF, 1 - FG, 2 BG
    			toRGB,                // boolean        toRGB,
    			ground_xyzatr,        // double [][]    ground_xyzatr,
    			ZERO3,                // double []      stereo_xyz, // offset reference camera {x,y,z}
    			null,                 // double []      stereo_atr_in, // offset reference orientation (cuas)
    			null,                 // double [][]    post_rotate,
    			sensor_mask,          // int            sensor_mask,
    			suffix_in,            // String         suffix_in,
    			ref_disparity,        // double []      ref_disparity,			
    			selected_scenes,      // QuadCLT []     quadCLTs,
    			ref_scene,            // QuadCLT        ref_scene, // int            ref_index,
    			ImageDtt.THREADS_MAX, // int            threadsMax,
    			debugLevel);          // int            debugLevel)
    }
    

    public static ImagePlus renderSceneSequence(
    		CLTParameters  clt_parameters,
    		boolean        mode_cuas,
    		boolean        um_mono,
    		boolean        calculate_average, // now only with float pixels
    		int []         average_range,
    		float [][]     average_slice, // [channel][pixel]
    		boolean        subtract_average,
    		int            running_average,
    		Rectangle      fov_tiles,
    		int            mode3d, // for older compatibility mode3d = -1 for RAW, 0 - INF, 1 - FG, 2 BG
    		boolean        toRGB,
    		double []      stereo_xyz, // offset reference camera {x,y,z}
    		double []      stereo_atr_in, // offset reference orientation (cuas)
    		int            sensor_mask,
    		String         suffix_in,
    		double []      ref_disparity,			
    		QuadCLT []     quadCLTs,
    		QuadCLT        ref_scene, // int            ref_index,
    		int            threadsMax,
    		int            debugLevel) {
    	return  renderSceneSequence(
    			clt_parameters,    // CLTParameters  clt_parameters,
    			mode_cuas,         // boolean        mode_cuas,
    			um_mono,           // boolean        um_mono,
    			calculate_average, // boolean        calculate_average, // now only with float pixels
    			average_range,     // int []         average_range,
    			average_slice,     //float [][]     average_slice, // [channel][pixel]
    			subtract_average,  // boolean        subtract_average,
    			running_average,   // int            running_average,
    			fov_tiles,         // Rectangle      fov_tiles,
    			mode3d,            // int            mode3d, // for older compatibility mode3d = -1 for RAW, 0 - INF, 1 - FG, 2 BG
    			toRGB,             // boolean        toRGB,
    			null,              // double [][]    ground_xyzatr,
    			stereo_xyz,        // double []      stereo_xyz, // offset reference camera {x,y,z}
    			stereo_atr_in,     // double []      stereo_atr_in, // offset reference orientation (cuas)
    			null,              // double [][]    post_rotate,
    			sensor_mask,       // int            sensor_mask,
    			suffix_in,         // String         suffix_in,
    			ref_disparity,     // double []      ref_disparity,			
    			quadCLTs,          // QuadCLT []     quadCLTs,
    			ref_scene,         // QuadCLT        ref_scene, // int            ref_index,
    			threadsMax,        // int            threadsMax,
    			debugLevel);       // int            debugLevel)
    }
    public static ImagePlus renderSceneSequence(
    		CLTParameters  clt_parameters,
    		boolean        mode_cuas,
    		boolean        um_mono,
    		boolean        calculate_average, // now only with float pixels
    		int []         average_range,
    		float [][]     average_slice, // [channel][pixel]
    		boolean        subtract_average,
    		int            running_average,
    		Rectangle      fov_tiles,
    		int            mode3d, // for older compatibility mode3d = -1 for RAW, 0 - INF, 1 - FG, 2 BG
    		boolean        toRGB,
    		double [][]    ground_xyzatr,
    		double []      stereo_xyz, // offset reference camera {x,y,z}
    		double []      stereo_atr_in, // offset reference orientation (cuas)
    		double [][]    post_rotate,
    		int            sensor_mask,
    		String         suffix_in,
    		double []      ref_disparity, // may be ground disparity 			
    		QuadCLT []     quadCLTs,
    		QuadCLT        ref_scene, // int            ref_index,
    		int            threadsMax,
    		int            debugLevel) {
		boolean insert_average = ((average_slice != null) && ((average_slice[0] != null))) || calculate_average; // now only with float pixels
    	boolean corr_raw_ers = true;
    	double [] stereo_atr = (stereo_atr_in != null)? stereo_atr_in: ZERO3; // maybe later play with rotated camera
    	///    	boolean mode_cuas = (stereo_atr[0] != 0) || (stereo_atr[1] != 0) || (stereo_atr[2] != 0);
    	//		boolean um_mono =            clt_parameters.imp.um_mono;
    	double  um_sigma =           clt_parameters.imp.um_sigma;
    	double  um_weight =          clt_parameters.imp.um_weight;
    	boolean mb_en =       clt_parameters.imp.mb_en && (fov_tiles==null) && (mode3d > 0);
    	double  mb_tau =      clt_parameters.imp.mb_tau;      // 0.008; // time constant, sec
    	double  mb_max_gain = clt_parameters.imp.mb_max_gain; // 5.0;   // motion blur maximal gain (if more - move second point more than a pixel
    	///		insert_average = (mode3d == 1); // merged
    	insert_average &= (mode3d == 1); // merged
    	if (mode3d != 1) {
    		running_average = 0;
    	}
    	final float fum_weight = (float)  um_weight; 
    	boolean merge_all = clt_parameters.imp.merge_all || !um_mono; // no unsharp mask -> terrain->merge_all
    	if (mode3d < 1) {
    		merge_all = false;
    	}
    	if (merge_all) {
    		sensor_mask = 1;
    	}
    	String        suffix = suffix_in+((mode3d > 0)?(merge_all?"-MERGED":"-SINGLE"):"");
    	if ((mode3d <0) && (corr_raw_ers)) {
    		suffix+="-RAW_ERS";
    	}
    	if (running_average > 1) {
    		suffix+="-RA"+running_average;
    	}
    	if (subtract_average && insert_average) {
    		suffix+="-DIFFAVG"+running_average;
    	}
    	if (mode_cuas) {
    		suffix+="-CUAS"; // add properties too? include offsets
    		///    		suffix+=String.format("%6f:%6f:%6f", stereo_atr[0],stereo_atr[1],stereo_atr[2]);
    	}
    	if (!mb_en) {
    		suffix+="-NOMB"; // no motion blur
    	}
    	int num_sens = ref_scene.getNumSensors();
    	ErsCorrection ers_reference = ref_scene.getErsCorrection();
    	int num_used_sens = 0;
    	for (int i = 0; i < num_sens; i++) if (((sensor_mask >> i) & 1) != 0) num_used_sens++;
    	int [] channels = new int [num_used_sens];
    	int nch = 0;
    	for (int i = 0; i < num_sens; i++) if (((sensor_mask >> i) & 1) != 0) channels[nch++] = i;
    	ImageStack stack_scenes = null;
    	int dbg_scene = -95;
    	double [][] ref_pXpYD;
    	double [][] ref_pXpYD_or_null = null; // debugging cuas mode keeping old
    	if (ground_xyzatr != null) { // extract to method
    		// try replacement
    		double [][] fgnd_pXpYD =refDisparityFromGroundDisparity(
    				ref_disparity, // final double [] ground_disparity,
    				ground_xyzatr, // final double [][] ground_xyzatr,   		
    				ref_scene);    // final QuadCLT ref_scene)
    		/*
    		final double [] ground_disparity = ref_disparity; // input
        	final int tilesX=ref_scene.getTilesX();
        	final int tilesY=ref_scene.getTilesY();
        	final int transform_size = ref_scene.getTileSize();
        	final int tiles = tilesX * tilesY;
    		final Thread[] threads = ImageDtt.newThreadArray();
    		final AtomicInteger ai = new AtomicInteger(0);
    		final double [][] fgnd_pXpYD = new double [tiles][];
			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 (!Double.isNaN(ground_disparity[nTile])){
							double disparity = ground_disparity[nTile];
							int tileY = nTile / tilesX;  
							int tileX = nTile % tilesX;
							double centerX = (tileX + 0.5) * transform_size; //  - shiftX;
							double centerY = (tileY + 0.5) * transform_size; //  - shiftY;
							if (disparity < 0) {
								disparity = 1.0* disparity; // 0.0;
							}
							fgnd_pXpYD[nTile] = new double[] {centerX, centerY, disparity};
//							fgnd_pXpYD[nTile] = new double[] {transform_size*tilesX-centerX, transform_size*tilesY-centerY, disparity};
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
			*/
			ref_pXpYD = OpticalFlow.transformToScenePxPyD(
					fgnd_pXpYD,         // final double [][] reference_pXpYD,// invalid tiles - NaN in disparity. Should be no nulls, no NaN disparity
					ground_xyzatr[0],   // final double []   scene_xyz,         // camera center in world (reference) coordinates
					ground_xyzatr[1],   // final double []   scene_atr,         // camera orientation relative to world (reference) frame
					ref_scene,          // final QuadCLT     reference_QuadClt) //
					null);              // final QuadCLT     scene_QuadClt) //
			
			/*
			double [] disp_dbg = new double[tiles];
			Arrays.fill(disp_dbg, Double.NaN);
			for (int i = 0; i < tiles; i++) {
				if (ref_pXpYD[i] != null) {
					disp_dbg[i] = ref_pXpYD[i][2]; 
				}
			}			
			for (int i = 0; i < tiles; i++) {
				int i1 = tiles-i-1;
				if (ref_pXpYD[i] != null) {
					if (ref_pXpYD[i1] != null) {
						ref_pXpYD[i][2]=disp_dbg[i1];
					} else {
						ref_pXpYD[i] = null;
					}
				}
			}
			*/
			
			ref_pXpYD_or_null = ref_pXpYD;
    		ref_scene.getErsCorrection().setupERS();
    		if (debugLevel > -3) {
    			System.out.println("Calculated reference ref_pXpYD from virtual_PxPyD");
    		}
    		boolean debug_virtual_PxPyD = (debugLevel > 1000);
    		if (debug_virtual_PxPyD) {
    			String [] dbg_titles = {"gnd-pX", "gnd-pY", "gnd-D","ref-pX", "ref-pY", "ref-D"};
    			double [][] dbg_pXpYD = new double[dbg_titles.length][ref_pXpYD.length];
    			for (int i = 0; i < dbg_pXpYD.length; i++) Arrays.fill(dbg_pXpYD[i], Double.NaN);
    			for (int i = 0; i < fgnd_pXpYD.length; i++) if (fgnd_pXpYD[i] != null) {
    				for (int j = 0; j < fgnd_pXpYD[i].length; j++) dbg_pXpYD[j][i] = fgnd_pXpYD[i][j];
    			}
    			for (int i = 0; i < ref_pXpYD.length; i++) if (ref_pXpYD[i] != null) {
    				for (int j = 0; j < ref_pXpYD[i].length; j++) dbg_pXpYD[j+3][i] = ref_pXpYD[i][j];
    			}
    			ImagePlus imp_virtual_PxPyD= ShowDoubleFloatArrays.makeArrays(
    					dbg_pXpYD,
    					ref_scene.getTilesX(),
    					ref_scene.getTilesY(),
    					ref_scene.getImageName()+"-ref_from_virtual_PxPyD",
    					dbg_titles);
    			ref_scene.saveImagePlusInModelDirectory(
    					null, // String      suffix,
    					imp_virtual_PxPyD); // ImagePlus   imp)
    		}
    	} else if (mode_cuas) { //  && (dbg_scene > 0)) {
    		int         around =              2;
    		double      around_sigma =        4.0;
    		int         num_virtual_refines = 2;
    		String      debugSuffix=         null; // "virtual";
    		ref_pXpYD= Cuas.transformFromVirtual(
    				clt_parameters,      // CLTParameters  clt_parameters,
    				ref_disparity,       // double []         disparity_ref,
    				stereo_xyz,          // final double []   scene_xyz,         // camera center in world (reference) coordinates
    				stereo_atr,          // final double []   scene_atr,         // camera orientation relative to world (reference) frame
    				ref_scene,           // final QuadCLT     reference_QuadClt,
    				around,              // final int         around,            // 2 search around for interpolation
    				around_sigma,        // final double      sigma,
    				num_virtual_refines, // final int         num_refines,
    				debugSuffix);        // final String      debugSuffix)
    		ref_pXpYD_or_null = ref_pXpYD;
    		ref_scene.getErsCorrection().setupERS();
    		System.out.println("Calculated virtual_PxPyD");
    		boolean debug_virtual_PxPyD = (debugLevel > 1000);
    		if (debug_virtual_PxPyD) {
    			String [] dbg_titles = {"pX", "pY", "D"};
    			double [][] dbg_pXpYD = new double[dbg_titles.length][ref_pXpYD.length];
    			for (int i = 0; i < dbg_pXpYD.length; i++) Arrays.fill(dbg_pXpYD[i], Double.NaN);
    			for (int i = 0; i < ref_pXpYD.length; i++) if (ref_pXpYD[i] != null) {
    				for (int j = 0; j < ref_pXpYD[i].length; j++) dbg_pXpYD[j][i] = ref_pXpYD[i][j];
    			}
    			ImagePlus imp_virtual_PxPyD= ShowDoubleFloatArrays.makeArrays(
    					dbg_pXpYD,
    					ref_scene.getTilesX(),
    					ref_scene.getTilesY(),
    					ref_scene.getImageName()+"-virtual_PxPyD",
    					dbg_titles);
    			ref_scene.saveImagePlusInModelDirectory(
    					null, // String      suffix,
    					imp_virtual_PxPyD); // ImagePlus   imp)
    		}
    	} else {
    		ref_pXpYD = transformToScenePxPyD( // now should work with offset ref_scene
    				fov_tiles,            // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
    				ref_disparity,        // final double []   disparity_ref, // invalid tiles - NaN in disparity
    				ZERO3, // stereo_xyz, // ZERO3,                // final double []   scene_xyz, // camera center in world coordinates
    				ZERO3, // stereo_atr, // ZERO3,                // final double []   scene_atr, // camera orientation relative to world frame
    				ref_scene,  // final QuadCLT     scene_QuadClt,
    				ref_scene,  // final QuadCLT     reference_QuadClt, // now - may be null - for testing if scene is rotated ref
    				threadsMax);          // int               threadsMax)
    	}
    	int num_avg_slices = 0;
		int avg_first_scene = 0;
		int avg_last_scene = quadCLTs.length - 1;
    	
    	for (int nscene =  0; nscene < quadCLTs.length ; nscene++) if (quadCLTs[nscene] != null){
    		if (nscene== dbg_scene) {
    			System.out.println("renderSceneSequence(): nscene = "+nscene);
    		}
    		String ts = quadCLTs[nscene].getImageName();
    		double []   scene_xyz = ZERO3;
    		double []   scene_atr = ZERO3;
    		if (quadCLTs[nscene] != ref_scene) { // Check even for raw, so video frames will match in all modes 
    			scene_xyz = ers_reference.getSceneXYZ(ts);
    			scene_atr = ers_reference.getSceneATR(ts);
    			if ((scene_atr==null) || (scene_xyz == null)) {
    				continue;
    			}
    			if ((mode3d >= 0) || corr_raw_ers) {
    				double []   scene_ers_xyz_dt = ers_reference.getSceneErsXYZ_dt(ts);
    				double []   scene_ers_atr_dt = ers_reference.getSceneErsATR_dt(ts);
    				quadCLTs[nscene].getErsCorrection().setErsDt(
    						scene_ers_xyz_dt, // double []    ers_xyz_dt,
    						scene_ers_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
    				if (mode3d < 0) { // velocities != 0, but offset=0
    					scene_xyz = ZERO3;
    					scene_atr = ZERO3;
    				}
    			} else { // ugly, restore for raw mode that should not be rotated/shifted
    				scene_xyz = ZERO3;
    				scene_atr = ZERO3;
    			}
    		}
    		if (!mode_cuas && (stereo_xyz != null)) { // offset all, including reference scene - now always, it is never null
    			double [][] combo_xyzatr = ErsCorrection.combineXYZATR(
    					stereo_xyz,  // double [] reference_xyz,
    					stereo_atr,  // double [] reference_atr, 
    					scene_xyz,   // double [] scene_xyz,
    					scene_atr);  // double [] scene_atr) 
    			scene_xyz = combo_xyzatr[0];
    			scene_atr = combo_xyzatr[1];
    		}
    		if (!mode_cuas && (post_rotate != null)) {
    			double [][] combo_xyzatr = ErsCorrection.combineXYZATR(
    					scene_xyz,       // double [] reference_xyz,
    					scene_atr,       // double [] reference_atr, 
    					post_rotate[0],  // double [] scene_xyz,
    					post_rotate[1]); // double [] scene_atr) 
    			scene_xyz = combo_xyzatr[0];
    			scene_atr = combo_xyzatr[1];
    		}
    		int sm = merge_all? -1: sensor_mask;
    		ImagePlus imp_scene = null;
    		double [][] dxyzatr_dt = null;
    		// should get velocities from HashMap at reference scene from timestamp , not re-calculate.
    		if (mb_en) {
    			dxyzatr_dt = new double[][] { // for all, including ref
    				quadCLTs[nscene].getErsCorrection().getErsXYZ_dt(),
    				quadCLTs[nscene].getErsCorrection().getErsATR_dt()};				
    		}


    		if (mb_en && (dxyzatr_dt != null)) {
    			double [][] motion_blur = getMotionBlur(
    					ref_scene, // 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!
    					scene_xyz,             // double []      camera_xyz,
    					scene_atr,             // double []      camera_atr,
    					dxyzatr_dt[0],         // double []      camera_xyz_dt,
    					dxyzatr_dt[1],         // double []      camera_atr_dt,
    					0,                     // int            shrink_gaps,  // will gaps, but not more that grow by this
    					debugLevel);           // int            debug_level)
    			imp_scene = QuadCLT.renderGPUFromDSI(
    					sm,                  // final int         sensor_mask,
    					merge_all,           // final boolean     merge_channels,
    					null,                // final Rectangle   full_woi_in,      // show larger than sensor WOI (or null)
    					clt_parameters,      // CLTParameters     clt_parameters,
    					ref_disparity,       // double []         disparity_ref,
    					ref_pXpYD_or_null,   // double [][]       ref_pXpYD,    // alternative to disparity_ref when reference is not uniform
    					// 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,  //

    					scene_xyz,           // final double []   scene_xyz, // camera center in world coordinates
    					scene_atr,           // final double []   scene_atr, // camera orientation relative to world frame
    					quadCLTs[nscene],    // final QuadCLT     scene,
    					ref_scene, // quadCLTs[ref_index], // final QuadCLT     ref_scene, // now - may be null - for testing if scene is rotated ref
    					toRGB,               // final boolean     toRGB,
    					(toRGB? clt_parameters.imp.show_color_nan : clt_parameters.imp.show_mono_nan),
    					"", // String            suffix, no suffix here
    					QuadCLT.THREADS_MAX,          // int               threadsMax,
    					debugLevel);         // int         debugLevel)

    		} else {
    			imp_scene = QuadCLT.renderGPUFromDSI(
    					sm,                  // final int         sensor_mask,
    					merge_all,           // final boolean     merge_channels,
    					fov_tiles,           // testr, // null,                // final Rectangle   full_woi_in,      // show larger than sensor WOI (or null)
    					clt_parameters,      // CLTParameters     clt_parameters,
    					ref_disparity,       // double []         disparity_ref,
    					ref_pXpYD_or_null,   // double [][]       ref_pXpYD,    // alternative to disparity_ref when reference is not uniform
    					// not used, just as null/not null now
    					// null means uniform grid, no view transform. even with 0 rot ERS was changing results
    					((!corr_raw_ers && (mode3d<0))? null:scene_xyz),           // final double []   scene_xyz, // camera center in world coordinates
    					((!corr_raw_ers && (mode3d<0))? null:scene_atr),           // final double []   scene_atr, // camera orientation relative to world frame
    					quadCLTs[nscene],    // final QuadCLT     scene,
    					ref_scene, // quadCLTs[ref_index], // final QuadCLT     ref_scene, // now - may be null - for testing if scene is rotated ref
    					toRGB,               // final boolean     toRGB,
    					(toRGB? clt_parameters.imp.show_color_nan : clt_parameters.imp.show_mono_nan),
    					"", // String            suffix, no suffix here
    					QuadCLT.THREADS_MAX,          // int               threadsMax,
    					debugLevel);         // int         debugLevel)
    		}
    		if (stack_scenes == null) { // not yet started the stack, got first image
    			stack_scenes = new ImageStack(imp_scene.getWidth(),imp_scene.getHeight());
    			if (insert_average) {
    				for (int i = 0; i < channels.length; i++) {
    					if (channels.length == 1) {
    						stack_scenes.addSlice(
    								"average",
    								new float[((float[])imp_scene.getStack().getPixels(i+1)).length]); // will be overwritten with actual averages
    					} else {
    						stack_scenes.addSlice(
    								"average-"+i,
    								new float[((float[])imp_scene.getStack().getPixels(i+1)).length]);
    					}
    				}
    				num_avg_slices++;
        			if (average_range != null) {
            			int last_predicted_scene =  quadCLTs.length - 1; // possible nulls will be skipped, so final stack may be smaller
        				if (average_range.length == 0) {
        					average_range = new int [] {last_predicted_scene/4, (3*last_predicted_scene)/4};  
        				}
        				for (int i = 0; i < channels.length; i++) {
        					if (channels.length == 1) {
        						stack_scenes.addSlice(
        								"average-"+average_range[0]+":"+average_range[1],
        								new float[((float[])imp_scene.getStack().getPixels(i+1)).length]); // will be overwritten with actual averages
        					} else {
        						stack_scenes.addSlice(
        								"average-chn"+i+"-"+average_range[0]+":"+average_range[1],
        								new float[((float[])imp_scene.getStack().getPixels(i+1)).length]);
        					}
        				}
        				num_avg_slices++;
        			}
    			}
    			
    		}
    		for (int i = 0; i < channels.length; i++) {
    			stack_scenes.addSlice(
    					ts+"-"+channels[i],
    					imp_scene.getStack().getPixels(i+1));
    		}
    	}
    	if (insert_average) { // calculate average to average slices (one per channel)
    		// calculate average from all scenes
    		int num_scenes = (stack_scenes.getSize() /  channels.length) - num_avg_slices; // remove averages (1 or 2)
    		for (int navg = 0; navg < num_avg_slices; navg++) {
    			if ((navg == 0) && (average_slice != null)) { // pre-calculated average
    				for (int nchn = 0; nchn < channels.length; nchn++) {
    					float [] avg_slice = (float[]) stack_scenes.getPixels(nchn+1);
    					System.arraycopy(
    							average_slice[nchn],
    							0,
    							avg_slice,
    							0,
    							average_slice[nchn].length);
    				}				
    			} else {
    				int first_scene = (navg == 0) ? 0:            Math.max(0,            average_range[0]); 
    				int last_scene =  (navg == 0) ? num_scenes-1: Math.min(num_scenes-1, average_range[1]); 
    				for (int nchn = 0; nchn < channels.length; nchn++) {
    					float [] avg_slice = (float[]) stack_scenes.getPixels(nchn + navg*channels.length +1);
    					int [] avg_n = new int[avg_slice.length];
    					int num_pix = avg_slice.length;
    					for (int nscene = first_scene; nscene <= last_scene; nscene++) {
    						float [] fpixels = (float[]) stack_scenes.getPixels(nchn + (nscene + num_avg_slices)*channels.length + 1);
    						for (int npix = 0; npix < num_pix;  npix++) if (!Float.isNaN(fpixels[npix])){
    							avg_slice[npix] += fpixels[npix];
    							avg_n[npix]++;
    						}
    					}
    					for (int npix = 0; npix < num_pix;  npix++) {
    						if (avg_n[npix] > 0) {
    							avg_slice[npix] /= avg_n[npix];
    						}else {
    							avg_slice[npix] = Float.NaN;
    						}
    					}
    				}
    			}
    		}    		
/*
    		if (average_slice != null)  {
    			for (int nchn = 0; nchn < channels.length; nchn++) {
    				float [] avg_slice = (float[]) stack_scenes.getPixels(nchn+1);
    				System.arraycopy(
    						average_slice[nchn],
    						0,
    						avg_slice,
    						0,
    						average_slice[nchn].length);
    			}				
    		} else {
    			int num_scenes = (stack_scenes.getSize() /  channels.length) - num_avg_slices; // remove averages (1 or 2)
    			// single-threaded
    			int first_scene = 0;
    			int last_scene = num_scenes - 1;
    			if (average_range != null) {
    				if (average_range.length == 0) {
    					average_range = new int [] {last_scene/4, (3*last_scene)/4};  
    					
    				}
    				first_scene = Math.max(first_scene, average_range[0]);
    				last_scene =  Math.min(last_scene, average_range[1]);
    			}
    			for (int nchn = 0; nchn < channels.length; nchn++) {
    				String label_avg = (channels.length==1) ? "average":("average-"+nchn);
    				if ((first_scene != 0) || (last_scene != (num_scenes - 1))) {
    					label_avg+="-+"+first_scene+":"+last_scene;
    				}
    				stack_scenes.setSliceLabel(label_avg, nchn+1);
    				float [] avg_slice = (float[]) stack_scenes.getPixels(nchn+1);
    				int [] avg_n = new int[avg_slice.length];
    				int num_pix = avg_slice.length;
    				for (int nscene = first_scene; nscene <= last_scene; nscene++) {
    					float [] fpixels = (float[]) stack_scenes.getPixels(nchn + (nscene+1)*channels.length + 1);
    					for (int npix = 0; npix < num_pix;  npix++) if (!Float.isNaN(fpixels[npix])){
    						avg_slice[npix] += fpixels[npix];
    						avg_n[npix]++;
    					}
    				}
    				for (int npix = 0; npix < num_pix;  npix++) {
    					if (avg_n[npix] > 0) {
    						avg_slice[npix] /= avg_n[npix];
    					}else {
    						avg_slice[npix] = Float.NaN;
    					}
    				}
    			}
    		}
    		*/
    		// seems that fpixels are automatically updated in the images
    	}
    	if (running_average >1) {
    		int scene_0 = insert_average? 1:0;
    		int num_scenes = (stack_scenes.getSize() /  channels.length) - scene_0; // remove average
    		for (int nchn = 0; nchn < channels.length; nchn++) {
    			int num_pix = ((float[]) stack_scenes.getPixels(nchn+1)).length;
    			float [][] fpix_orig = new float [num_scenes][];
    			for (int nscene = 0; nscene < num_scenes; nscene++) {
    				fpix_orig[nscene] = ((float[]) stack_scenes.getPixels(nchn + (nscene + scene_0)*channels.length + 1)).clone();
    			}
    			int navg = 0;
    			float [] fpix_ra = new float [num_pix];
    			for (int nscene = 0; nscene < num_scenes; nscene++) {
    				int nscene_sub = nscene- running_average;
    				float [] new_fpix = fpix_orig[nscene];
    				// add new to ra
    				for (int npix = 0; npix < num_pix;  npix++) {
    					fpix_ra[npix]+=new_fpix[npix];
    				}
    				// subtract old (if >=0)
    				if (nscene_sub >= 0) {
    					float [] old_fpix = fpix_orig[nscene_sub];
    					for (int npix = 0; npix < num_pix;  npix++) {
    						fpix_ra[npix] -= old_fpix[npix];
    					}
    				} else {
    					navg++;
    				}
    				// update image
    				float [] fpix = (float[]) stack_scenes.getPixels(nchn + (nscene + scene_0)*channels.length + 1);
    				for (int npix = 0; npix < num_pix;  npix++) {
    					fpix[npix] = fpix_ra[npix]/navg;
    				}					
    			}				
    		}
    	}
    	if (subtract_average && insert_average) {
    		int num_scenes = (stack_scenes.getSize() /  channels.length) - 1; // remove average
    		for (int nchn = 0; nchn < channels.length; nchn++) {
    			float [] avg_slice = (float[]) stack_scenes.getPixels(nchn+1);
    			int num_pix = avg_slice.length;
    			for (int nscene = 0; nscene < num_scenes; nscene++) {
    				float [] fpix = (float[]) stack_scenes.getPixels(nchn + (nscene + 1)*channels.length + 1);
    				for (int npix = 0; npix < num_pix;  npix++) {
    					fpix[npix] -= avg_slice[npix];
    				}					
    			}
    		}			
    	}
    	ImagePlus imp_scenes = new ImagePlus(suffix, stack_scenes);
    	imp_scenes.getProcessor().resetMinAndMax();
    	// Apply unsharp mask here, in parallel
    	if (um_mono && !toRGB) {
    		imp_scenes = applyUM (
    				suffix, // final String title, // should include -UM...
    				imp_scenes, // final ImagePlus imp,
    				um_sigma, // final double um_sigma,
    				um_weight); // final double um_weight)
    	}		
    	return imp_scenes;
    }
    
    public static double [][] refDisparityFromGroundDisparity(
    		final double [] ground_disparity,
    		final double [][] ground_xyzatr,   		
    		final QuadCLT ref_scene){
    		//final double [] ground_disparity = ref_disparity; // input
        	final int tilesX=ref_scene.getTilesX();
        	final int tilesY=ref_scene.getTilesY();
        	final int transform_size = ref_scene.getTileSize();
        	final int tiles = tilesX * tilesY;
    		final Thread[] threads = ImageDtt.newThreadArray();
    		final AtomicInteger ai = new AtomicInteger(0);
    		final double [][] fgnd_pXpYD = new double [tiles][];
			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 (!Double.isNaN(ground_disparity[nTile])){
							double disparity = ground_disparity[nTile];
							int tileY = nTile / tilesX;  
							int tileX = nTile % tilesX;
							double centerX = (tileX + 0.5) * transform_size; //  - shiftX;
							double centerY = (tileY + 0.5) * transform_size; //  - shiftY;
							if (disparity < 0) {
								disparity = 1.0* disparity; // 0.0;
							}
							fgnd_pXpYD[nTile] = new double[] {centerX, centerY, disparity};
//							fgnd_pXpYD[nTile] = new double[] {transform_size*tilesX-centerX, transform_size*tilesY-centerY, disparity};
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
			return fgnd_pXpYD;
			/*
			double [][] ref_pXpYD = OpticalFlow.transformToScenePxPyD(
					fgnd_pXpYD,         // final double [][] reference_pXpYD,// invalid tiles - NaN in disparity. Should be no nulls, no NaN disparity
					ground_xyzatr[0],   // final double []   scene_xyz,         // camera center in world (reference) coordinates
					ground_xyzatr[1],   // final double []   scene_atr,         // camera orientation relative to world (reference) frame
					ref_scene,          // final QuadCLT     reference_QuadClt) //
					null);              // final QuadCLT     scene_QuadClt) //
			return ref_pXpYD;
			*/
    }
    
    
    public static QuadCLT[] selectScenes(
    		QuadCLT[] quadCLTs,
    		QuadCLT ref_scene) {
    	ArrayList<QuadCLT> scene_list = new ArrayList<QuadCLT>();
    	ErsCorrection ers_reference = ref_scene.getErsCorrection();
    	for (QuadCLT scene:quadCLTs) if (scene != null){
    		if (ers_reference.contains(scene.getImageName())) {
    			scene_list.add(scene);
    		}
    	}
    	return scene_list.toArray(new QuadCLT[0]);
    }
    
    public static ImagePlus applyUM (
    		final String title, // should include -UM...
    		final ImagePlus imp,
    		final double um_sigma,
    		final double um_weight) {
    	final float fum_weight = (float)  um_weight;
    	final ImageStack stack_scenes = imp.getStack();
		final int nSlices = stack_scenes.getSize();
		final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nSlice = ai.getAndIncrement(); nSlice < nSlices; nSlice = ai.getAndIncrement()) {
						FloatProcessor fp = (FloatProcessor) stack_scenes.getProcessor(nSlice+1);
						float [] fpixels = (float[]) stack_scenes.getPixels(nSlice+1);
						float [] fpixels_orig = fpixels.clone();
						(new GaussianBlur()).blurFloat(
								fp,       // FloatProcessor ip,
								um_sigma, // double sigmaX,
								um_sigma, // double sigmaY,
								0.01);    // double accuracy)
						for (int i = 0; i < fpixels.length; i++) {
							fpixels[i] = fpixels_orig[i] - fum_weight * fpixels[i];
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ImagePlus imp_scenes = new ImagePlus(title, stack_scenes);
		imp_scenes.getProcessor().resetMinAndMax();
    	return imp_scenes;
    }
  
    public static void applyUMDouble(
    		final double [] data,
    		final int       width,
    		final double um_sigma,
    		final double um_weight) {
    	double [] blurred = data.clone();
    	(new DoubleGaussianBlur()).blurDouble(
    			blurred,              // double[] pixels,
    			width,                // int width,
    			blurred.length/width, // int height,
    			um_sigma,             // double sigmaX,
    			um_sigma,             // double sigmaY,
    			0.01);                // double accuracy)
    	for (int i = 0; i<data.length; i++) {
    		data[i] -= blurred[i]*um_weight;
    	}
    }
    
 

    
    public static double [][] getSceneSZXY(
    		QuadCLT   scene,
    		double    disparity_offset,
    		double    min_strength,
    		double    max_range,
    		double [] disparity0,
    		double [] strength){ // may be null
    	double [] disparity = disparity0.clone();
    	for (int i = 0; i < disparity.length; i++) {
    		disparity[i] -= disparity_offset;
    	}
    	double [][] xyz = transformToWorldXYZ(
    			disparity, // [final double []   disparity_ref, // invalid tiles - NaN in disparity
    			scene, // final QuadCLT     quadClt, // now - may be null - for testing if scene is rotated ref
    			THREADS_MAX); // int               threadsMax);

    	double [][] szxy = new double [4][disparity.length];
    	szxy[0] = strength;
    	for (int i = 1; i < szxy.length; i++) {
    		Arrays.fill(szxy[i], Double.NaN);
    	}
    	for (int nTile = 0; nTile < xyz.length; nTile++) if (xyz[nTile] != null) {
    		if ((xyz[nTile][2] < 0) && (xyz[nTile][2] > -max_range) && ((strength == null) ||(strength[nTile] >= min_strength))) {
    			szxy[1][nTile] = -xyz[nTile][2];
    			szxy[2][nTile] = xyz[nTile][0];
    			szxy[3][nTile] = xyz[nTile][1];
    		}
    	}
    	return szxy;
    }
	@Deprecated
	public void adjustSeries(
			CLTParameters  clt_parameters,			
			double         k_prev, 
			QuadCLT []     scenes, // ordered by increasing timestamps
			int            ref_index,
			int            debug_level
			)
	{
		boolean show_results = clt_parameters.ilp.ilma_debug_adjust_series;
		boolean pattern_mode = clt_parameters.ofp.pattern_mode;
		boolean high_res_motion_vectors = true; // use new 05/20/2022 mode
		if (pattern_mode) {
			if (clt_parameters.ofp.center_index < 0) {
				double [][][] atrs = new double [scenes.length][][];
				atrs[scenes.length - 1] = new double[2][3]; 
				for (int i =  scenes.length - 2; i >= 0 ; i--) {
					String scene_ts =            scenes[i].getImageName(); // it should be present in the scenes[i+1] scenes
					ErsCorrection ers_scene_last_known = scenes[i+1].getErsCorrection();
					double [][] last_known_atr = atrs[i+1];
					double [] new_from_last_xyz = ers_scene_last_known.getSceneXYZ(scene_ts);
					double [] new_from_last_atr = ers_scene_last_known.getSceneATR(scene_ts);
					// combine two rotations and two translations (translations will be zero for pattern_mode) 
					atrs[i]=ErsCorrection.combineXYZATR(
							last_known_atr[0],  // double [] reference_xyz,
							last_known_atr[1],  // double [] reference_atr,
							new_from_last_xyz,  // double [] scene_xyz,
							new_from_last_atr); // double [] scene_atr)
				}
				double [][] xyzatr_avg = new double[2][3];
				for (int i = 0; i < atrs.length; i++) {
					for (int k = 0; k < xyzatr_avg.length; k++) {
						for (int j = 0; j < 3; j++) {
							xyzatr_avg[k][j] += atrs[i][k][j] / atrs.length;
						}
					}
				}
				double [] wxyzatr = {0.0, 1.0}; // weight of xyz offset - not used now 
				int nearest = 0;
				for (int i = 1; i < atrs.length; i++) {
					double d2best = 0, d2this=0;
					for (int k = 0; k < xyzatr_avg.length; k++) {
						for (int j = 0; j < 3; j++) {
							double d= atrs[i][k][j] - xyzatr_avg[k][j];
							d2this += wxyzatr[k] * d * d;
							double d1= atrs[nearest][k][j] - xyzatr_avg[k][j];
							d2best += wxyzatr[k] * d1 * d1;
							if (d2this < d2best) {
								nearest = i;
							}
						}
					}
				}
				clt_parameters.ofp.center_index = nearest;
			}
			ref_index = clt_parameters.ofp.center_index;
		}
		
		if (ref_index < 0) {
			ref_index += scenes.length;
		}
		double [][][] scenes_xyzatr = new double [scenes.length][][]; // previous scene relative to the next one
		QuadCLT reference_QuadClt = scenes[ref_index]; // scenes.length-1]; // last acquired
		ErsCorrection ers_reference = reference_QuadClt.getErsCorrection();
		// modify LMA parameters to freeze reference ERS, remove pull on scene ERS
		boolean[]   param_select2 =     clt_parameters.ilp.ilma_lma_select.clone();             // final boolean[]   param_select,
		boolean[]   param_select3 =     clt_parameters.ilp.ilma_lma_select.clone();             // final boolean[]   param_select,
		double []   param_regweights2 = clt_parameters.ilp.ilma_regularization_weights; //  final double []   param_regweights,
		double []   param_regweights3 = clt_parameters.ilp.ilma_regularization_weights; //  final double []   param_regweights,
		boolean delete_scene_asap = false; //  (debug_level < 10); // to save memory
		// freeze reference ERS, free scene ERS
		for (int j = 0; j <3; j++) {
			param_select2[ErsCorrection.DP_DVX  + j] = false;
			param_select2[ErsCorrection.DP_DVAZ + j] = false;
			param_regweights2[ErsCorrection.DP_DSVX +  j] = 0.0;
			param_regweights2[ErsCorrection.DP_DSVAZ + j] = 0.0;
		}
		for (int j = 0; j <3; j++) {
			param_select3[ErsCorrection.DP_DVX  + j] = false;
			param_select3[ErsCorrection.DP_DVAZ + j] = false;
			param_select3[ErsCorrection.DP_DSVX  + j] = clt_parameters.ilp.ilma_ers_adj_lin; // disabling, may check with high rot speed
			param_select3[ErsCorrection.DP_DSVAZ + j] = clt_parameters.ilp.ilma_ers_adj_ang; // so far ers correction noise is too high to compare
			param_regweights3[ErsCorrection.DP_DSVX +  j] = 0.0;
			param_regweights3[ErsCorrection.DP_DSVAZ + j] = 0.0;
		}
		
		if (show_results) { // useless before references to the ref_scene
			double [][][] ers_current = new double [scenes.length][][];
			double [] scene_xyz_dt;
			double [] scene_atr_dt;
			for (int nscene = 0; nscene < scenes.length; nscene++) {
				if (nscene == ref_index) {
					scene_xyz_dt = ers_reference.getErsXYZ_dt();
					scene_atr_dt = ers_reference.getErsATR_dt();
				} else {
					String sts = scenes[nscene].getImageName();
					scene_xyz_dt = ers_reference.getSceneErsXYZ_dt(sts); 
					scene_atr_dt = ers_reference.getSceneErsATR_dt(sts); 
				}
				if ((scene_xyz_dt != null) &&(scene_atr_dt != null)) {
					ers_current[nscene] = new double[][] {scene_xyz_dt, scene_atr_dt};
				} else {
					System.out.println("adjustSeries(): null for nscene="+nscene);
				}
			}
			
			int dbg_w = ers_current.length;
			int dbg_h = 6;
			double [] dbg_img = new double [dbg_w * dbg_h];
			Arrays.fill(dbg_img, Double.NaN);
			for (int dh = 0; dh  < dbg_h; dh++) {
				for (int dw = 0; dw  < dbg_w; dw++) {
					if (ers_current[dw] != null) {
						dbg_img[dh*dbg_w + dw] = ers_current[dw][dh / 3][dh % 3];
					}
				}
			}
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					dbg_w,
					dbg_h,
					"ers_pre_adjust"); //	dsrbg_titles);
			System.out.println("adjustSeries(): dbg");
		}
		
		// used only for debug
		TileProcessor tp = reference_QuadClt.getTileProcessor();
		int tilesX =         tp.getTilesX();
        int tilesY =         tp.getTilesY();
		int transform_size = tp.getTileSize();
		int macroTilesX =          tilesX/transform_size;
		int macroTilesY =          tilesY/transform_size;
		double [][] dbg_iterdata = show_results ? (new double [3][]): null;
		if (dbg_iterdata != null) {
			for (int ii = 0; ii < dbg_iterdata.length; ii++) {
				dbg_iterdata[ii] = new double [macroTilesY * scenes.length * macroTilesX * clt_parameters.ilp.ilma_num_corr];
			}
		}
		if (debug_level > -1000) {
			dbg_iterdata = null; // does not work now  (at 3964)
		}
		double []     dbg_rms_pre = new double [scenes.length];
		Arrays.fill(dbg_rms_pre, Double.NaN);
		// process scenes before reference
		int dbg_ref_index = 10; // wait for ref_index <= dbg_ref_index and print (manually update dbg_ref_index
		RMSEStats rmse_stats = new RMSEStats();
		RMSEStats rmse_stats1 = new RMSEStats();

		if (ref_index >  1) {
			for (int i =  ref_index - 2; i >= 0 ; i--) {
				QuadCLT scene_QuadClt =      scenes[i];
				String last_known_ts =           scenes[i+1].getImageName(); // it should be present in the reference scene scenes
				String scene_ts =                scenes[i].getImageName(); // it should be present in the scenes[i+1] scenes
				ErsCorrection ers_scene_last_known = scenes[i+1].getErsCorrection();
				ErsCorrection ers_scene =            scene_QuadClt.getErsCorrection();

				double [] last_known_xyz = ers_reference.getSceneXYZ(last_known_ts);
				double [] last_known_atr = ers_reference.getSceneATR(last_known_ts);

				double [] new_from_last_xyz = ers_scene_last_known.getSceneXYZ(scene_ts);
				double [] new_from_last_atr = ers_scene_last_known.getSceneATR(scene_ts);

				// combine two rotations and two translations 
				System.out.println("***** Processing scene "+i+": "+scene_QuadClt.getImageName()+" *****");
				double [][] combo_XYZATR = ErsCorrection.combineXYZATR(
						last_known_xyz,     // double [] reference_xyz,
						last_known_atr,     // double [] reference_atr, // null?
						new_from_last_xyz,  // double [] scene_xyz,
						new_from_last_atr); // double [] scene_atr)

				// before adjusting - save original ERS, restart afterwards
				double [] ers_scene_original_xyz_dt = ers_scene.getErsXYZ_dt();
				double [] ers_scene_original_atr_dt = ers_scene.getErsATR_dt();

				// ers should be correct for both
				double [] lma_rms = new double[2];
				double [][] dbg_img = (dbg_iterdata != null) ? (new double[3][]) : null;
				if(i <= dbg_ref_index) {
					System.out.println ("i = "+i+" <= dbg_ref_index");
				}
				if (high_res_motion_vectors) {
					scenes_xyzatr[i] = Interscene.adjustPairsLMAInterscene(
							clt_parameters,                                 // CLTParameters  clt_parameters,
						    clt_parameters.imp.use_lma_dsi,
							false,               //	boolean        fpn_disable,   // disable fpn filter if images are known to be too close
							false,               // boolean        disable_ers,
							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 reference_QuadCLT,
							null, // double []        ref_disparity, // null or alternative reference disparity
							null, // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
							scene_QuadClt,                                  // QuadCLT scene_QuadCLT,
							combo_XYZATR[0],                                // xyz
							combo_XYZATR[1],                                // atr
							null, //					double []      scene_xyz_pull, // if both are not null, specify target values to pull to 
							null, //					double []      scene_atr_pull, // 
							param_select2,                                  // final boolean[]   param_select,
							param_regweights2,                              //  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);
				} else {
					scenes_xyzatr[i] = adjustPairsLMA(
							clt_parameters,     // CLTParameters  clt_parameters,			
							reference_QuadClt, // QuadCLT reference_QuadCLT,
							scene_QuadClt, // QuadCLT scene_QuadCLT,
							combo_XYZATR[0], // xyz
							combo_XYZATR[1], // atr
							param_select2,             // final boolean[]   param_select,
							param_regweights2, //  final double []   param_regweights,
							lma_rms, // 			double []      rms, // null or double [2]
							dbg_img, // double [][]    dbg_img,
							0.0, // double         max_rms,
							debug_level); // int debug_level)
				}
				if (dbg_iterdata != null) {
					int dbg_width = clt_parameters.ilp.ilma_num_corr * macroTilesX;
					for (int kk = 0; kk < dbg_iterdata.length; kk++) {
						for (int ii = 0; ii < macroTilesY; ii++) {
							System.arraycopy(dbg_img[kk], // null pointer
									ii * dbg_width,
									dbg_iterdata[kk],
									(i * macroTilesY + ii) * dbg_width,
									dbg_width);
						}
					}
				}
				rmse_stats.add(lma_rms[0]);
				dbg_rms_pre[i] = lma_rms[0];
				ers_reference.addScene(scene_QuadClt.getImageName(),
						scenes_xyzatr[i][0],
						scenes_xyzatr[i][1],
						ers_scene.getErsXYZ_dt(),		
						ers_scene.getErsATR_dt()		
						);

				// restore original ers data
				ers_scene.setErsDt(
						ers_scene_original_xyz_dt, // double []    ers_xyz_dt,
						ers_scene_original_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
				ers_scene.setupERS();
				if (debug_level > -1) {
					System.out.println("Pass multi scene "+i+" (of "+ scenes.length+") "+
							reference_QuadClt.getImageName() + "/" + scene_QuadClt.getImageName()+
							" Done. RMS="+lma_rms[0]+
			                ", maximal so far was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());				}
				if (delete_scene_asap) {
					scenes[i+1] = null;
				}
			}
		}
		
		if (dbg_iterdata != null) {
			ShowDoubleFloatArrays.showArrays(
					dbg_iterdata,
					clt_parameters.ilp.ilma_num_corr * macroTilesX,
					macroTilesY * scenes.length,
					true,
					"first_pass_"+scenes[scenes.length-1].getImageName(),
					new String[] {"pX","pY","disparity"}); //	dsrbg_titles);
		}
		// process scenes after reference (if it is not the last
		if (ref_index <  (scenes.length - 1)) {
			for (int i =  ref_index + 1; i < scenes.length ; i++) {
				QuadCLT scene_QuadClt =      scenes[i];
				String last_known_ts =           scenes[i-1].getImageName(); // it should be present in the reference scene scenes
				ErsCorrection ers_scene =            scene_QuadClt.getErsCorrection();

				double [] last_known_xyz = ers_reference.getSceneXYZ(last_known_ts);
				double [] last_known_atr = ers_reference.getSceneATR(last_known_ts);
				double [] last_from_new_xyz = ers_scene.getSceneXYZ(last_known_ts);
				double [] last_from_new_atr = ers_scene.getSceneATR(last_known_ts);
				double [][] new_from_last =   ErsCorrection.invertXYZATR(last_from_new_xyz, last_from_new_atr);

				double [] new_from_last_xyz = new_from_last[0]; // ers_scene_last_known.getSceneXYZ(scene_ts);
				double [] new_from_last_atr = new_from_last[1]; // ers_scene_last_known.getSceneATR(scene_ts);
				double [][] combo_XYZATR = new_from_last;
				System.out.println("Processing scene "+i+": "+scene_QuadClt.getImageName());
				if (i > ( ref_index + 1)) {
					// combine two rotations and two translations 
					combo_XYZATR = ErsCorrection.combineXYZATR(
							last_known_xyz,     // double [] reference_xyz,
							last_known_atr,     // double [] reference_atr, // null?
							new_from_last_xyz,  // double [] scene_xyz,
							new_from_last_atr); // double [] scene_atr)
				}
				// before adjusting - save original ERS, restore afterwards
				double [] ers_scene_original_xyz_dt = ers_scene.getErsXYZ_dt();
				double [] ers_scene_original_atr_dt = ers_scene.getErsATR_dt();

				// ers should be correct for both

				double [] lma_rms = new double[2];
				if (high_res_motion_vectors) {
					scenes_xyzatr[i] = Interscene.adjustPairsLMAInterscene(
							clt_parameters,                                 // CLTParameters  clt_parameters,
						    clt_parameters.imp.use_lma_dsi,
							false,               //	boolean        fpn_disable,   // disable fpn filter if images are known to be too close
							false,               // boolean        disable_ers,
							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 reference_QuadCLT,
							null, // double []        ref_disparity, // null or alternative reference disparity
							null, // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
							scene_QuadClt,                                  // QuadCLT scene_QuadCLT,
							combo_XYZATR[0],                                // xyz
							combo_XYZATR[1],                                // atr
							null, //					double []      scene_xyz_pull, // if both are not null, specify target values to pull to 
							null, //					double []      scene_atr_pull, // 
							param_select2,                                  // final boolean[]   param_select,
							param_regweights2,                              //  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);
				} else {
					scenes_xyzatr[i] = adjustPairsLMA(
							clt_parameters,     // CLTParameters  clt_parameters,			
							reference_QuadClt, // QuadCLT reference_QuadCLT,
							scene_QuadClt, // QuadCLT scene_QuadCLT,
							combo_XYZATR[0], // xyz
							combo_XYZATR[1], // atr
							param_select2,             // final boolean[]   param_select,
							param_regweights2, //  final double []   param_regweights,
							lma_rms, // 			double []      rms, // null or double [2]
							null, // double [][]    dbg_img,
							0.0, // double         max_rms,
							debug_level); // int debug_level)
				}
				rmse_stats.add(lma_rms[0]);
				dbg_rms_pre[i] = lma_rms[0];
				ers_reference.addScene(scene_QuadClt.getImageName(),
						scenes_xyzatr[i][0],
						scenes_xyzatr[i][1],
						ers_scene.getErsXYZ_dt(),		
						ers_scene.getErsATR_dt()		
						);

				// restore original ers data
				ers_scene.setErsDt(
						ers_scene_original_xyz_dt, // double []    ers_xyz_dt,
						ers_scene_original_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
				ers_scene.setupERS();
				if (debug_level > -1) {
					System.out.println("Pass multi scene "+i+" (of "+ scenes.length+") "+
			                reference_QuadClt.getImageName() + "/" + scene_QuadClt.getImageName()+
			                " Done. RMS="+lma_rms[0]+
			                ", maximal so far was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
				}
				if (delete_scene_asap) {
					scenes[i-1] = null;
				}
			}		
		}
		if (debug_level > -3) {
			System.out.println("All multi scene passes are Done. Maximal RMSE was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
		}
		
//		boolean show_results = true;
		if (show_results) {
			int dbg_w = scenes_xyzatr.length;
			int dbg_h = 6;
			double [] dbg_img = new double [dbg_w * dbg_h];
			Arrays.fill(dbg_img, Double.NaN);
			for (int dh = 0; dh  < dbg_h; dh++) {
				for (int dw = 0; dw  < dbg_w; dw++) {
					if (scenes_xyzatr[dw] != null) {
						dbg_img[dh*dbg_w + dw] = scenes_xyzatr[dw][dh / 3][dh % 3];
					}
				}
			}
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					dbg_w,
					dbg_h,
					"scenes_xyzatr"); //	dsrbg_titles);
		}
		
		double [][][] scenes_xyzatr_preadjust = new double [scenes.length][][]; // debug only
		double [][][] scenes_xyzatr_dt_preadjust = new double [scenes.length][][]; // debug only
		double [] dbg_rms = new double [scenes.length];
		if ((clt_parameters.ofp.lpf_series > 1.0) && pattern_mode) { // no velocities in pattern mode
			System.out.println ("adjustSeries(): No processing velosities (\"Second pass\" in pattern_mode");
		}
		if ((clt_parameters.ofp.lpf_series > 1.0) && !pattern_mode) { // no velocities in pattern mode
			boolean ers_invert = false; // true; // false;
			// get current ers
			double [][][]ers_xyzatr =  getVelocitiesFromScenes(
					 scenes, // QuadCLT []     scenes, // ordered by increasing timestamps
					 scenes_xyzatr,                    // double [][][]  scenes_xyzatr,
					 clt_parameters.ofp.lpf_series);   // double         half_run_range
			if (ers_invert) {
				for (int nscene = 0; nscene < ers_xyzatr.length; nscene++) if (ers_xyzatr[nscene] != null) {
					for (int m= 0; m < ers_xyzatr[nscene].length; m++) {
						for (int n= 0; n < ers_xyzatr[nscene][m].length; n++) {
							ers_xyzatr[nscene][m][n] = - ers_xyzatr[nscene][m][n]; 
						}
					}
				}
			}
			
			if (show_results) {
				int dbg_w = ers_xyzatr.length;
				int dbg_h = 6;
				double [] dbg_img = new double [dbg_w * dbg_h];
				Arrays.fill(dbg_img, Double.NaN);
				for (int dh = 0; dh  < dbg_h; dh++) {
					for (int dw = 0; dw  < dbg_w; dw++) {
						if (ers_xyzatr[dw] != null) {
							dbg_img[dh*dbg_w + dw] = ers_xyzatr[dw][dh / 3][dh % 3];
						}
					}
				}
				ShowDoubleFloatArrays.showArrays(
						dbg_img,
						dbg_w,
						dbg_h,
						"scenes_xyzatr-ers_lpf"+clt_parameters.ofp.lpf_series); //	dsrbg_titles);
			}
			// Set reference frame ERS
			ers_reference.setErsDt_test(// scaled/sign
					(clt_parameters.ilp.ilma_ignore_ers ? (new double[3]) : (ers_xyzatr[ref_index][00])),  // double []    ers_xyz_dt,
					(clt_parameters.ilp.ilma_ignore_ers ? (new double[3]) : (ers_xyzatr[ref_index][1]))); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
			ers_reference.setupERS();
			Arrays.fill(dbg_rms, Double.NaN);
			for (int i =  00; i < scenes.length ; i++) if (i != ref_index){
				String scene_ts =          scenes[i].getImageName(); // it should be present in the scenes[i+1] scenes
				double [] scene_xyz =      ers_reference.getSceneXYZ(scene_ts);
				double [] scene_atr =      ers_reference.getSceneATR(scene_ts);
				ErsCorrection ers_scene =  scenes[i].getErsCorrection();
				ers_scene.setErsDt_test(// scaled/sign
						(clt_parameters.ilp.ilma_ignore_ers ? (new double[3]) : (ers_xyzatr[i][0])), // double []    ers_xyz_dt,
						(clt_parameters.ilp.ilma_ignore_ers ? (new double[3]) : (ers_xyzatr[i][1])));  // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
				ers_scene.setupERS();

				// Debug only
				scenes_xyzatr_dt_preadjust[i] = new double[][] {ers_scene.getErsXYZ_dt().clone(),ers_scene.getErsATR_dt().clone()};
				scenes_xyzatr_preadjust[i] =    new double[][] {scene_xyz.clone(), scene_atr.clone()};
				double [] lma_rms = new double[2];
				
				if (high_res_motion_vectors) {
					scenes_xyzatr[i] = Interscene.adjustPairsLMAInterscene(
							clt_parameters,                  // CLTParameters  clt_parameters,
						    clt_parameters.imp.use_lma_dsi,
							false,               //	boolean        fpn_disable,   // disable fpn filter if images are known to be too close
							false,               // boolean        disable_ers,
							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 reference_QuadCLT,
							null, // double []        ref_disparity, // null or alternative reference disparity 
							null, // boolean []     reliable_ref, // null or bitmask of reliable reference tiles
							scenes[i],                       // QuadCLT scene_QuadCLT,
							scene_xyz, // combo_XYZATR[0],   // xyz
							scene_atr, // combo_XYZATR[1],   // atr
							null, //					double []      scene_xyz_pull, // if both are not null, specify target values to pull to 
							null, //					double []      scene_atr_pull, // 
							param_select3, // 3, // 2,       // final boolean[]   param_select,
							param_regweights2,               // 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);
				} else {
					scenes_xyzatr[i] = adjustPairsLMA(
							clt_parameters,                  // CLTParameters  clt_parameters,			
							reference_QuadClt,               // QuadCLT reference_QuadCLT,
							scenes[i],                       // QuadCLT scene_QuadCLT,
							scene_xyz,                       // combo_XYZATR[0], // xyz
							scene_atr,                       // combo_XYZATR[1], // atr
							param_select3,                   // final boolean[]   param_select,
							param_regweights2,               // final double []   param_regweights,
							lma_rms,                         //	double []      rms, // null or double [2]
							null,                            // double [][]    dbg_img,
							0.0,                             // double         max_rms,
							debug_level);                    // int debug_level)
				}
			    rmse_stats1.add(lma_rms[0]);
				dbg_rms[i] = lma_rms[0];
				ers_reference.addScene(
						scene_ts,
						scenes_xyzatr[i][0],
						scenes_xyzatr[i][1],
						ers_scene.getErsXYZ_dt(),
						ers_scene.getErsATR_dt()		
						);
				
/* Was it just to undo LMA?
				// restore original ers data
				ers_scene.setErsDt(
						ers_scene_original_xyz_dt, // double []    ers_xyz_dt,
						ers_scene_original_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
				ers_scene.setupERS();
*/				
				if (debug_level > -1) {
					System.out.println("Pass 2 multi scene "+i+" (of "+ scenes.length+") "+
							reference_QuadClt.getImageName() + "/" + scene_ts+
			                " Done. RMS="+lma_rms[0]+
			                ", maximal so far was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage());
				}
				if (delete_scene_asap) {
					scenes[i-1] = null;
				}
			}		
		}
		double rms_pre_mean = 0.0, rms_mean = 0.0;
		int    rms_pre_num =  0, rms_num =    0;
		for (int i = 0; (i < dbg_rms_pre.length) && (i < dbg_rms.length); i++) {
			if (!Double.isNaN(dbg_rms_pre[i]) && !Double.isNaN(dbg_rms[i])) {
				rms_pre_num++;
				rms_num++;
				rms_pre_mean +=dbg_rms_pre[i];
				rms_mean +=    dbg_rms[i];
			}
		}
		rms_pre_mean /= rms_pre_num;
		rms_mean /= rms_num;
		if (debug_level > -3) {
			System.out.println("adjustSeries() rms_pre_mean="+rms_pre_mean+", rms_mean="+
					rms_mean+", maximal RMSE="+rmse_stats1.getMax()+", average was "+rmse_stats1.getAverage());
		}

		if (show_results) {
			int dbg_w = scenes_xyzatr_preadjust.length;
			int dbg_h = 6;
			double [] dbg_img = new double [dbg_w * (dbg_h + 1)];
			Arrays.fill(dbg_img, Double.NaN);
			for (int dh = 0; dh  < dbg_h; dh++) {
				for (int dw = 0; dw  < dbg_w; dw++) {
					if (scenes_xyzatr_preadjust[dw] != null) {
						dbg_img[dh*dbg_w + dw] = scenes_xyzatr_preadjust[dw][dh / 3][dh % 3];
					}
				}
			}
			for (int dw = 0; dw  < dbg_w; dw++) {
				dbg_img[dbg_h*dbg_w + dw] = dbg_rms_pre[dw];
			}
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					dbg_w,
					dbg_h + 1,
					"scenes_xyzatr_preadjust"); //	dsrbg_titles);
			System.out.println("adjustSeries(): dbg-2.5");
		}
		
		if (show_results) {
			int dbg_w = scenes_xyzatr_dt_preadjust.length;
			int dbg_h = 6;
			double [] dbg_img = new double [dbg_w * dbg_h];
			Arrays.fill(dbg_img, Double.NaN);
			for (int dh = 0; dh  < dbg_h; dh++) {
				for (int dw = 0; dw  < dbg_w; dw++) {
					if (scenes_xyzatr_dt_preadjust[dw] != null) {
						dbg_img[dh*dbg_w + dw] = scenes_xyzatr_dt_preadjust[dw][dh / 3][dh % 3];
					}
				}
			}
			ShowDoubleFloatArrays.showArrays(//
					dbg_img,
					dbg_w,
					dbg_h,
					"ers_preadjust-2"); //	dsrbg_titles);
			System.out.println("adjustSeries(): dbg-3");
		}

		if (show_results) {
			double [][][] xyzqtr_current = new double [scenes.length][][];
			double [] scene_xyz;
			double [] scene_atr;
			for (int nscene = 0; nscene < scenes.length; nscene++) {
				if (nscene == ref_index) {
					scene_xyz = ers_reference.getCameraXYZ();
					scene_atr = ers_reference.getCameraATR();
				} else {
					String sts = scenes[nscene].getImageName();
					scene_xyz = ers_reference.getSceneXYZ(sts); 
					scene_atr = ers_reference.getSceneATR(sts); 
				}
				if ((scene_xyz != null) &&(scene_atr != null)) {
					xyzqtr_current[nscene] = new double[][] {scene_xyz, scene_atr};
				} else {
					System.out.println("adjustSeries(): null for nscene="+nscene);
				}
			}
			
			int dbg_w = xyzqtr_current.length;
			int dbg_h = 6;
			double [] dbg_img = new double [dbg_w * (dbg_h + 1)];
			Arrays.fill(dbg_img, Double.NaN);
			for (int dh = 0; dh  < dbg_h; dh++) {
				for (int dw = 0; dw  < dbg_w; dw++) {
					if (xyzqtr_current[dw] != null) {
						dbg_img[dh*dbg_w + dw] = xyzqtr_current[dw][dh / 3][dh % 3];
					}
				}
			}
			for (int dw = 0; dw  < dbg_w; dw++) {
				dbg_img[dbg_h*dbg_w + dw] = dbg_rms[dw];
			}

			//dbg_rms
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					dbg_w,
					dbg_h+1,
					"scenes_xyzatr_adjust-ers"); //	dsrbg_titles);
			System.out.println("adjustSeries(): dbg-2.1");
		}
		
		// get add show current ERS, including reference
		if (show_results) {
			double [][][] ers_current = new double [scenes.length][][];
			double [] scene_xyz_dt;
			double [] scene_atr_dt;
			for (int nscene = 0; nscene < scenes.length; nscene++) {
				if (nscene == ref_index) {
					scene_xyz_dt = ers_reference.getErsXYZ_dt();
					scene_atr_dt = ers_reference.getErsATR_dt();
				} else {
					String sts = scenes[nscene].getImageName();
					scene_xyz_dt = ers_reference.getSceneErsXYZ_dt(sts); 
					scene_atr_dt = ers_reference.getSceneErsATR_dt(sts); 
				}
				if ((scene_xyz_dt != null) &&(scene_atr_dt != null)) {
					ers_current[nscene] = new double[][] {scene_xyz_dt, scene_atr_dt};
				} else {
					System.out.println("adjustSeries(): null for nscene="+nscene);
				}
			}
			
			int dbg_w = ers_current.length;
			int dbg_h = 6;
			double [] dbg_img = new double [dbg_w * dbg_h];
			Arrays.fill(dbg_img, Double.NaN);
			for (int dh = 0; dh  < dbg_h; dh++) {
				for (int dw = 0; dw  < dbg_w; dw++) {
					if (ers_current[dw] != null) {
						dbg_img[dh*dbg_w + dw] = ers_current[dw][dh / 3][dh % 3];
					}
				}
			}
			ShowDoubleFloatArrays.showArrays( //
					dbg_img,
					dbg_w,
					dbg_h,
					"ers_adjust-ers"); //	dsrbg_titles);
			System.out.println("adjustSeries(): dbg-2");
		}
		
// getVelocitiesFromScenes	
//		
		reference_QuadClt.saveInterProperties( // save properties for interscene processing (extrinsics, ers, ...)
	            null, // String path,             // full name with extension or w/o path to use x3d directory
	            debug_level+1);
		
		if (!delete_scene_asap && (debug_level > 1000)) { // Null pointer at compareRefSceneTiles ->ers_scene.setupERS();-> Quaternion quat_center1 = ...
			System.out.println("adjustSeries(): preparing image set...");
			int nscenes = scenes.length;
			int indx_ref = nscenes - 1; 
			double [][][] all_scenes_xyzatr = new double [scenes.length][][]; // includes reference (last)
			double [][][] all_scenes_ers_dt = new double [scenes.length][][]; // includes reference (last)
			all_scenes_xyzatr[indx_ref] = new double [][] {ZERO3,ZERO3};
			all_scenes_ers_dt[indx_ref] = new double [][] {
				ers_reference.getErsXYZ_dt(),
				ers_reference.getErsATR_dt()};
				for (int i = 0; i < nscenes; i++) if (i != indx_ref) {
					String ts = scenes[i].getImageName();
					all_scenes_xyzatr[i] = new double[][] {ers_reference.getSceneXYZ(ts),       ers_reference.getSceneATR(ts)}; 		
					all_scenes_ers_dt[i] = new double[][] {ers_reference.getSceneErsXYZ_dt(ts), ers_reference.getSceneErsATR_dt(ts)}; 		
				}
				compareRefSceneTiles(
						"" ,               // String suffix,
						false,             // boolean blur_reference,
						all_scenes_xyzatr, // double [][][] scene_xyzatr, // does not include reference
						all_scenes_ers_dt, // double [][][] scene_ers_dt, // does not include reference
						scenes,            // QuadCLT [] scenes,
						8);                // int iscale) // 8
		}		
		if (debug_level > -3) {
			System.out.println("adjustSeries() Done. Maximal RMSE in pass1 was "+rmse_stats.getMax()+", average was "+rmse_stats.getAverage()+
					", in pass2 - "+rmse_stats1.getMax()+", average was "+rmse_stats1.getAverage());
		}
	}
	
	/**
	 * Low-pass filter per-scene linear and angular velocities to be used for ERS 
	 * @param ers_xyzatr_in scene linear and angular velocities before filtering [scene][2][3]
	 * @param half_run_range number of scenes each way to average (0 - none,
	 *        1 - 3, 2 - 5) reduced when near the limits
	 * @return [scene][2][3] xyz/dt, atr/dt. atr[0] and atr[2] are inverted relative to input
	 */
	public static double [][][] LPFVelocities(
			double [][][]  ers_xyzatr_in,
			double         half_run_range
			){
		double [] weights = new double [(int) Math.floor(half_run_range)+1];
		weights[0] = 1;
		for (int i = 1; i < weights.length; i++) {
			weights[i] = 0.5* (Math.cos(i*Math.PI/half_run_range) + 1.0);
		}
		double [][][] ers_xyzatr = new double [ers_xyzatr_in.length][][];
		for (int nscene = 0; nscene < ers_xyzatr_in.length; nscene ++) {
			double sw = 0.0;
			double [][] swd =  new double[2][3]; 
			for (int ds = -weights.length + 1; ds < weights.length; ds++) {
				int ns = nscene + ds;
				if ((ns >= 0) && (ns < ers_xyzatr_in.length) && (ers_xyzatr_in[ns] != null)) {
					double w = (ds >= 0) ? weights[ds] : weights[-ds];
					sw += w;
					for (int m = 0; m < swd.length; m++) {
						for (int d = 0; d < swd[m].length; d++) {
							swd[m][d] += w * ers_xyzatr_in[ns][m][d];
						}
					}
				}
			}
			if (sw > 0) {
				ers_xyzatr[nscene] = new double[2][3];
				for (int m = 0; m < swd.length; m++) {
					for (int d = 0; d < swd[m].length; d++) {
						ers_xyzatr[nscene][m][d] = swd[m][d]/sw;
					}
				}
			}
		}
		return ers_xyzatr;
	}
	
	
	
	/**
	 * Calculate linear and angular velocities by running-average of the poses
	 * Inverts Azimuth and Roll directions 
	 * @param scenes Scene objects (to get timestamps)
	 * @param scenes_xyzatr scene linear and angular coordinate [scene][2][3]
	 * @param half_run_range number of scenes each way to average (0 - none,
	 *        1 - 3, 2 - 5) reduced when near the limits
	 * @return [scene][2][3] xyz/dt, atr/dt. atr[0] and atr[2] are inverted relative to input
	 */
	public static double [][][] getVelocitiesFromScenes(
			QuadCLT []     scenes, // ordered by increasing timestamps
			double [][][]  scenes_xyzatr,
			double         half_run_range
			){
		double [] weights = new double [(int) Math.floor(half_run_range)+1];
		weights[0] = 1;
		for (int i = 1; i < weights.length; i++) {
			weights[i] = 0.5* (Math.cos(i*Math.PI/half_run_range) +1.0);
		}
		double [][][] ers_xyzatr = new double [scenes_xyzatr.length][][];
		for (int nscene = 0; nscene < scenes_xyzatr.length; nscene ++) if (scenes[nscene] != null){
			double tref = scenes[nscene].getTimeStamp();
			double s0 = 0.0, sx = 0.0, sx2 = 0.0;
			double [][] sy =  new double[2][3]; 
			double [][] sxy = new double[2][3];
			int nds = 0;
			for (int ds = -weights.length + 1; ds < weights.length; ds++) {
				int ns = nscene + ds;
				if ((ns >= 0) && (ns < scenes_xyzatr.length) && (scenes_xyzatr[ns] != null)) {
					double w = (ds >= 0) ? weights[ds] : weights[-ds];
					s0 += w;
					double dt = scenes[ns].getTimeStamp() - tref;
					double wx = w * dt;
					sx +=  wx;
					sx2 += wx * dt;
					for (int m = 0; m < sy.length; m++) {
						for (int d = 0; d < sy[m].length; d++) {
							double y = scenes_xyzatr[ns][m][d];
							sy [m][d] += w *  y;
							sxy[m][d] += wx * y;
						}
					}
					nds++; // number of different timestamps used
				}
			}
			if ((nds > 1) && (s0 > 0)) {
				ers_xyzatr[nscene] = new double[2][3];
				for (int m = 0; m < sy.length; m++) {
					for (int d = 0; d < sy[m].length; d++) {
						ers_xyzatr[nscene][m][d] = (s0*sxy[m][d]-sx*sy[m][d])/(s0*sx2 - sx*sx);
					}
				}
				ers_xyzatr[nscene][1][0] *= -1.0;
				ers_xyzatr[nscene][1][2] *= -1.0;
			}
		}
		return ers_xyzatr;
	}
	
	/**
	 * Calculate linear and angular velocities by running-average of the poses
	 * Does not invert Azimuth and Roll directions or any other
 	 * @param scenes Scene objects (to get timestamps)
	 * @param ref_index reference scene index in scenes[]
	 * @param end_scene   end scene (inclusive) to process
	 * @param scenes_xyzatr scene linear and angular coordinate [scene][2][3]
	 * @param half_run_range number of scenes each way to average (0 - none,
	 *        1 - 3, 2 - 5) reduced when near the limits
	 * @return [scene][2][3] xyz/dt, atr/dt.
	 */
	public static double [][][] getVelocitiesFromScenes(
			QuadCLT []     scenes, // ordered by increasing timestamps
			int            ref_index,      
			int            start_scene,
			int            end_scene,
			double [][][]  scenes_xyzatr, // <=0 use +/-1 or +0 if other are not available
			double         half_run_range,
			int            debugLevel
			){
		double [][][] ers_xyzatr = new double [scenes_xyzatr.length][][];
		if (half_run_range <=0 ) {
			ErsCorrection ers_reference =scenes[ref_index].getErsCorrection();
			for (int nscene = start_scene; nscene <= end_scene; nscene ++) if (scenes[nscene] != null){
	        	int nscene0 = nscene - 1;
	        	if ((nscene0 < start_scene) ||
	        			(scenes[nscene0]== null)||
	        			(ers_reference.getSceneXYZ(scenes[nscene0].getImageName())== null) ||
	        			(ers_reference.getSceneATR(scenes[nscene0].getImageName())== null)) {
	        		nscene0 = nscene;
	        	}
	        	int nscene1 = nscene + 1;
	        	if ((nscene1 > end_scene) || (scenes[nscene1]== null)) {
	        		nscene1 = nscene;
	        	}
	        	if (nscene1 > nscene0) {
	            	String ts0 = scenes[nscene0].getImageName();
	            	String ts1 = scenes[nscene1].getImageName();
	            	double dt = scenes[nscene1].getTimeStamp() - scenes[nscene0].getTimeStamp();
	        		double [] scene_xyz0 = ers_reference.getSceneXYZ(ts0);
	        		double [] scene_atr0 = ers_reference.getSceneATR(ts0);
//	        		double [] scene_xyz1 = (nscene1== ref_index)? ZERO3:ers_reference.getSceneXYZ(ts1);
//	        		double [] scene_atr1 = (nscene1== ref_index)? ZERO3:ers_reference.getSceneATR(ts1);
	        		// now reference frame has its record too
	        		double [] scene_xyz1 = ers_reference.getSceneXYZ(ts1);
	        		double [] scene_atr1 = ers_reference.getSceneATR(ts1);
	        		// for very old?
	        		if (scene_xyz1 == null) {
	        			scene_xyz1 = ZERO3;
	        		}
	        		if (scene_atr1 == null) {
	        			scene_atr1 = ZERO3;
	        		}
	        		ers_xyzatr[nscene] = new double[2][3];
	        		for (int i = 0; i < 3; i++) {
	        			ers_xyzatr[nscene][0][i] = (scene_xyz1[i]-scene_xyz0[i])/dt;
	        			ers_xyzatr[nscene][1][i] = (scene_atr1[i]-scene_atr0[i])/dt;
	        		}
	        	} else {
	        		System.out.println("**** Isoloated scene!!! skipping... now may only happen for a ref_scene****");
	        	}
			}
		} else {
			double [] weights = new double [(int) Math.floor(half_run_range)+1];
			weights[0] = 1;
			for (int i = 1; i < weights.length; i++) {
				weights[i] = 0.5* (Math.cos(i*Math.PI/half_run_range) +1.0);
			}
			for (int nscene = start_scene; nscene <= end_scene; nscene ++) if (scenes[nscene] != null){
				double tref = scenes[nscene].getTimeStamp();
				double s0 = 0.0, sx = 0.0, sx2 = 0.0;
				double [][] sy =  new double[2][3]; 
				double [][] sxy = new double[2][3];
				int nds = 0;
				for (int ds = -weights.length + 1; ds < weights.length; ds++) {
					int ns = nscene + ds;
					if ((ns >= 0) && (ns < scenes_xyzatr.length) && (scenes_xyzatr[ns] != null)) {
						double w = (ds >= 0) ? weights[ds] : weights[-ds];
						s0 += w;
						double dt = scenes[ns].getTimeStamp() - tref;
						double wx = w * dt;
						sx +=  wx;
						sx2 += wx * dt;
						for (int m = 0; m < sy.length; m++) {
							for (int d = 0; d < sy[m].length; d++) {
								if (scenes_xyzatr[ns]== null ) {
									if (debugLevel > -1) {
										System.out.println("getVelocitiesFromScenes():scenes_xyzatr["+ns+"]== null");
									}
								}else if (scenes_xyzatr[ns][m]== null ) {
									if (debugLevel > -1) {
										System.out.println("getVelocitiesFromScenes():scenes_xyzatr["+ns+"]["+m+"] == null");
									}
								} else {
									double y = scenes_xyzatr[ns][m][d];
									sy [m][d] += w *  y;
									sxy[m][d] += wx * y;
								}
							}
						}
						nds++; // number of different timestamps used
					}
				}
				if ((nds > 1) && (s0 > 0)) {
					ers_xyzatr[nscene] = new double[2][3];
					for (int m = 0; m < sy.length; m++) {
						for (int d = 0; d < sy[m].length; d++) {
							ers_xyzatr[nscene][m][d] = (s0*sxy[m][d]-sx*sy[m][d])/(s0*sx2 - sx*sx);
						}
					}
				}
			}
		}
		return ers_xyzatr;
	}

	public static double [][][] getVelocitiesFromScenes(
			QuadCLT []     scenes, // ordered by increasing timestamps
//			int            ref_index,    
			QuadCLT        ref_scene, // may be one of scenes or center
			int            start_scene,
			int            end_scene,
			double [][][]  scenes_xyzatr, // <=0 use +/-1 or +0 if other are not available
			double         half_run_range,
			int            debugLevel
			){
		double [][][] ers_xyzatr = new double [scenes_xyzatr.length][][];
		if (half_run_range <=0 ) {
			ErsCorrection ers_reference = ref_scene.getErsCorrection();
			for (int nscene = start_scene; nscene <= end_scene; nscene ++) if (scenes[nscene] != null){
	        	int nscene0 = nscene - 1;
	        	if ((nscene0 < start_scene) ||
	        			(scenes[nscene0]== null)||
	        			(ers_reference.getSceneXYZ(scenes[nscene0].getImageName())== null) ||
	        			(ers_reference.getSceneATR(scenes[nscene0].getImageName())== null)) {
	        		nscene0 = nscene;
	        	}
	        	int nscene1 = nscene + 1;
	        	if ((nscene1 > end_scene) || (scenes[nscene1]== null)) {
	        		nscene1 = nscene;
	        	}
	        	if (nscene1 > nscene0) {
	            	String ts0 = scenes[nscene0].getImageName();
	            	String ts1 = scenes[nscene1].getImageName();
	            	double dt = scenes[nscene1].getTimeStamp() - scenes[nscene0].getTimeStamp();
	        		double [] scene_xyz0 = ers_reference.getSceneXYZ(ts0);
	        		double [] scene_atr0 = ers_reference.getSceneATR(ts0);
//	        		double [] scene_xyz1 = (nscene1== ref_index)? ZERO3:ers_reference.getSceneXYZ(ts1);
//	        		double [] scene_atr1 = (nscene1== ref_index)? ZERO3:ers_reference.getSceneATR(ts1);
	        		// now reference frame has its record too
	        		double [] scene_xyz1 = ers_reference.getSceneXYZ(ts1);
	        		double [] scene_atr1 = ers_reference.getSceneATR(ts1);
	        		// for very old?
	        		if (scene_xyz1 == null) {
	        			scene_xyz1 = ZERO3;
	        		}
	        		if (scene_atr1 == null) {
	        			scene_atr1 = ZERO3;
	        		}
	        		ers_xyzatr[nscene] = new double[2][3];
	        		for (int i = 0; i < 3; i++) {
	        			ers_xyzatr[nscene][0][i] = (scene_xyz1[i]-scene_xyz0[i])/dt;
	        			ers_xyzatr[nscene][1][i] = (scene_atr1[i]-scene_atr0[i])/dt;
	        		}
	        	} else {
	        		System.out.println("**** Isoloated scene!!! skipping... now may only happen for a ref_scene****");
	        	}
			}
		} else {
			double [] weights = new double [(int) Math.floor(half_run_range)+1];
			weights[0] = 1;
			for (int i = 1; i < weights.length; i++) {
				weights[i] = 0.5* (Math.cos(i*Math.PI/half_run_range) +1.0);
			}
			for (int nscene = start_scene; nscene <= end_scene; nscene ++) if (scenes[nscene] != null){
				double tref = scenes[nscene].getTimeStamp();
				double s0 = 0.0, sx = 0.0, sx2 = 0.0;
				double [][] sy =  new double[2][3]; 
				double [][] sxy = new double[2][3];
				int nds = 0;
				for (int ds = -weights.length + 1; ds < weights.length; ds++) {
					int ns = nscene + ds;
					if ((ns >= 0) && (ns < scenes_xyzatr.length) && (scenes_xyzatr[ns] != null)) {
						double w = (ds >= 0) ? weights[ds] : weights[-ds];
						s0 += w;
						double dt = scenes[ns].getTimeStamp() - tref;
						double wx = w * dt;
						sx +=  wx;
						sx2 += wx * dt;
						for (int m = 0; m < sy.length; m++) {
							for (int d = 0; d < sy[m].length; d++) {
								if (scenes_xyzatr[ns]== null ) {
									if (debugLevel > -1) {
										System.out.println("getVelocitiesFromScenes():scenes_xyzatr["+ns+"]== null");
									}
								}else if (scenes_xyzatr[ns][m]== null ) {
									if (debugLevel > -1) {
										System.out.println("getVelocitiesFromScenes():scenes_xyzatr["+ns+"]["+m+"] == null");
									}
								} else {
									double y = scenes_xyzatr[ns][m][d];
									sy [m][d] += w *  y;
									sxy[m][d] += wx * y;
								}
							}
						}
						nds++; // number of different timestamps used
					}
				}
				if ((nds > 1) && (s0 > 0)) {
					ers_xyzatr[nscene] = new double[2][3];
					for (int m = 0; m < sy.length; m++) {
						for (int d = 0; d < sy[m].length; d++) {
							ers_xyzatr[nscene][m][d] = (s0*sxy[m][d]-sx*sy[m][d])/(s0*sx2 - sx*sx);
						}
					}
				}
			}
		}
		return ers_xyzatr;
	}
	
	
	
	@Deprecated
	public void IntersceneAccumulate(
			CLTParameters        clt_parameters,
			ColorProcParameters  colorProcParameters,
			SetChannels [] set_channels,
			QuadCLT              ref_scene, // ordered by increasing timestamps
			NoiseParameters noise_sigma_level, // only comes with no-noise here
			int                  debug_level
			)
	{
		System.out.println("IntersceneAccumulate(), scene timestamp="+ref_scene.getImageName());
		ErsCorrection ers_reference = ref_scene.getErsCorrection();
		String [] sts = ers_reference.getScenes(); // should have all scenes selected, last being reference
		ArrayList<String> sts_list = new ArrayList<String>();
		ArrayList<String> scenes_list = new ArrayList<String>();
		for (String s:sts) sts_list.add(s);
		for (int i = 0; i < set_channels.length; i++) {
			String scene_name = set_channels[i].name();
			if (sts_list.contains(scene_name)) {
				scenes_list.add(scene_name);
			}
		}
		String [] scene_names = scenes_list.toArray(new String [0]); 
		// get list of all other scenes
		int num_scenes = scene_names.length + 1;
		int indx_ref = num_scenes - 1; 
		QuadCLT [] scenes = new QuadCLT [num_scenes];
		scenes[indx_ref] = ref_scene;
		
		for (int i = 0; i < scene_names.length; i++) {
			scenes[i] = ref_scene.spawnQuadCLTWithNoise( // spawnQuadCLT(
					scene_names[i],
					clt_parameters,
					colorProcParameters, //
					noise_sigma_level,   // double []            noise_sigma_level,only comes with non-noise here, so noise_variant is not needed
					-1,                  // int                  noise_variant, // <0 - no-variants, compatible with old code
					ref_scene,           // QuadCLTCPU           ref_scene, // may be null if scale_fpn <= 0
					THREADS_MAX,
					-1); // debug_level);
			scenes[i].setDSRBG(
					clt_parameters, // CLTParameters  clt_parameters,
					THREADS_MAX,     // int            threadsMax,  // maximal number of threads to launch
					updateStatus,   // boolean        updateStatus,
					-1); // debug_level);    // int            debugLevel)
		}
		
		final double [][] combo_dsn =  prepareInitialComboDS(
				clt_parameters,   // final CLTParameters       clt_parameters,
				scenes,           // final QuadCLT []          scenes,
////				indx_ref,         // final int                 indx_ref,
    			scenes[indx_ref], // final QuadCLT             ref_scene,
				debug_level-2);     // final int                 debug_level);
		// re-read ref's INTER if possible
		
		if (ref_scene.restoreDSI( // if there is already calculated with interscene - us it
				"-DSI_INTER",
				true // silent
				) >=0 ) {
			
///		if (ref_scene.restoreInterDSI( // if there is already calculated with interscene - us it
///					true // silent
///					) >=0 ) {
			System.out.println("IntersceneAccumulate(): Using previously calculated interscene DSI (*-DSI_INTER) as initial DSI");
			combo_dsn[0] = ref_scene.dsi[ref_scene.is_aux?TwoQuadCLT.DSI_DISPARITY_AUX:TwoQuadCLT.DSI_DISPARITY_MAIN];
			combo_dsn[1] = ref_scene.dsi[ref_scene.is_aux?TwoQuadCLT.DSI_STRENGTH_AUX:TwoQuadCLT.DSI_STRENGTH_MAIN];
		}
		
		final double [][] combo_dsn_change = new double [combo_dsn.length+1][];
		for (int i = 0; i < combo_dsn.length; i++) {
			combo_dsn_change[i] = combo_dsn[i];
		}

		final int margin = 8;
		final int tilesX = ref_scene.getTileProcessor().getTilesX();
		final int tilesY = ref_scene.getTileProcessor().getTilesY();

		String [] combo_dsn_titles = {"disp", "strength", "num_valid","change"};
	
		if (debug_level >-3) {
			ShowDoubleFloatArrays.showArrays(
					combo_dsn_change,
					tilesX,
					tilesY,
					true,
					"combo_dsn-initial"+ref_scene.getImageName(),
					combo_dsn_titles); //	dsrbg_titles);
		}
		
		final int max_refines = 10;
		final String [] iter_titles = {"disp", "diff", "strength"};
		final int [] iter_indices = {0,1,3};
		final int last_slices = combo_dsn_titles.length;
		final int last_initial_slices = last_slices + iter_titles.length;
		
		final double [][] refine_results = new double [last_slices + 3 * (max_refines + 1)][];
		String [] refine_titles = new String [refine_results.length];
		for (int i = 0; i < combo_dsn_titles.length; i++) {
			refine_results[i] = combo_dsn_change[i];
			refine_titles[i] = combo_dsn_titles[i]+"-last";
		}
		for (int i = 0; i < iter_titles.length; i++) {
			refine_titles[last_slices + i] = iter_titles[i]+"-initial";
			if (combo_dsn_change[iter_indices[i]] != null) {
				refine_results[last_slices + i] = combo_dsn_change[iter_indices[i]].clone();
			} else {
				refine_results[last_slices + i] = new double [tilesX * tilesY];
			}
		}
		for (int nrefine = 0; nrefine < max_refines; nrefine++) {
			for (int i = 0; i < iter_titles.length; i++) {
				refine_titles[last_initial_slices + i * max_refines + nrefine ] = combo_dsn_titles[i]+"-"+nrefine;
			}
		}		
		double [][] disparity_map = null;
		for (int nrefine = 0; nrefine < max_refines; nrefine++) {
	///		Runtime.getRuntime().gc();
	///		System.out.println("--- Free memory="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
			int mcorr_sel = ImageDttParameters.corrSelEncode(clt_parameters.img_dtt,scenes[indx_ref].getNumSensors());
			
			disparity_map = correlateInterscene(
					clt_parameters, // final CLTParameters  clt_parameters,
					scenes,         // final QuadCLT []     scenes,
////					indx_ref,       // final int            indx_ref,
					scenes[indx_ref],// final QuadCLT        ref_scene,					
					combo_dsn_change[0],   // final double []      disparity_ref,  // disparity in the reference view tiles (Double.NaN - invalid)
					null,           // final boolean []     selection, // may be null, if not null do not  process unselected tiles
					null,           // final double [][]    far_fgbg,        // null, or [nTile]{disp(fg)-disp(bg), str(fg)-str(bg)} hints for LMA FG/BG split 
					margin,         // final int            margin,
					nrefine,        // final int            nrefine, // just for debug title
					clt_parameters.inp.show_final_2d, // final boolean        show_2d_corr,
					mcorr_sel,      // final int            mcorr_sel, //  = Correlation2d.corrSelEncode(clt_parameters.img_dtt,scenes[nscene].getNumSensors());
					null,           // final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)
					false,          // final boolean        no_map, // do not generate disparity_map (time-consuming LMA)
		            false,          // final boolean        use_rms, // DISPARITY_STRENGTH_INDEX means LMA RMS (18/04/2023)
					debug_level-5);   // final int            debug_level)

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

			ShowDoubleFloatArrays.showArrays(
					disparity_map,
					tilesX,
					tilesY,
					true,
					"accumulated_disparity_map-"+nrefine,
					ImageDtt.getDisparityTitles(ref_scene.getNumSensors(),ref_scene.isMonochrome()) // ImageDtt.DISPARITY_TITLES
					);
			// update disparities
			final int disparity_index = ImageDtt.DISPARITY_INDEX_CM; // 2
			final int strength_index =  ImageDtt.DISPARITY_STRENGTH_INDEX; // 10
			for (int nTile =0; nTile < combo_dsn_change[0].length; nTile++) {
				if (!Double.isNaN(combo_dsn_change[0][nTile]) && !Double.isNaN(disparity_map[disparity_index][nTile])) {
					combo_dsn_change[0][nTile] += disparity_map[disparity_index][nTile];
					combo_dsn_change[1][nTile]  = disparity_map[strength_index][nTile];
				}
			}
			combo_dsn_change[combo_dsn_change.length -1] = disparity_map[disparity_index]; 
			
			for (int i = 0; i < iter_titles.length; i++) {
				refine_results[last_initial_slices + (i * max_refines) + nrefine] = combo_dsn_change[iter_indices[i]].clone();
			}
			
			if (debug_level >-3) {
				ShowDoubleFloatArrays.showArrays(
						combo_dsn_change,
						tilesX,
						tilesY,
						true,
						"combo_dsn-"+nrefine+"-"+ref_scene.getImageName(),
						combo_dsn_titles); //	dsrbg_titles);
			}
		}
		
		if (debug_level >-5) {
			ShowDoubleFloatArrays.showArrays(
					refine_results,
					tilesX,
					tilesY,
					true,
					"combo-"+max_refines+"-"+ref_scene.getImageName(),
					refine_titles); //	dsrbg_titles);
		}
		ref_scene.saveDoubleArrayInModelDirectory(
				"-results-nonoise",  // String      suffix,
				refine_titles,       // null,          // String []   labels, // or null
				refine_results,      // dbg_data,         // double [][] data,
				tilesX,              // int         width,
				tilesY);             // int         height)
		
		// save _DSI_INTER - same format, as _DSI_MAIN, it will be used instead of _DSI_MAIN next time
		double [][] dsi = new double [TwoQuadCLT.DSI_SLICES.length][];
		dsi[ref_scene.is_aux?TwoQuadCLT.DSI_DISPARITY_AUX:TwoQuadCLT.DSI_DISPARITY_MAIN] = combo_dsn_change[0];
		dsi[ref_scene.is_aux?TwoQuadCLT.DSI_STRENGTH_AUX:TwoQuadCLT.DSI_STRENGTH_MAIN] =   combo_dsn_change[1];
		double [] disp_lma = disparity_map[ImageDtt.DISPARITY_INDEX_POLY];
		if (disp_lma != null) { 
			int indx_lma = ref_scene.is_aux?TwoQuadCLT.DSI_DISPARITY_AUX_LMA:TwoQuadCLT.DSI_DISPARITY_MAIN_LMA; 
			dsi[indx_lma] = combo_dsn_change[0].clone();
			for (int i = 0; i < disp_lma.length; i++) {
				if (Double.isNaN(disp_lma[i])) {
					dsi[indx_lma][i] = Double.NaN;		
				}
			}
		}
		ref_scene.saveDSIAll ( "-DSI_INTER",dsi); // delete/rename this file to start iterations from reference lma

		// save combo_dsn_change to model directory
		if (debug_level >-100) {
			return;
		}
		
		System.out.println("IntersceneAccumulate(), got previous scenes: "+sts.length);
		if (debug_level > 1) { // tested OK
			System.out.println("IntersceneAccumulate(): preparing image set...");
			int nscenes = scenes.length;
			//			int indx_ref = nscenes - 1; 
			double [][][] all_scenes_xyzatr = new double [scenes.length][][]; // includes reference (last)
			double [][][] all_scenes_ers_dt = new double [scenes.length][][]; // includes reference (last)
			all_scenes_xyzatr[indx_ref] = new double [][] {ZERO3,ZERO3};
			all_scenes_ers_dt[indx_ref] = new double [][] {
				ers_reference.getErsXYZ_dt(),
				ers_reference.getErsATR_dt()};
				
				for (int i = 0; i < nscenes; i++) if (i != indx_ref) {
					String ts = scenes[i].getImageName();
					all_scenes_xyzatr[i] = new double[][] {ers_reference.getSceneXYZ(ts),       ers_reference.getSceneATR(ts)}; 		
					all_scenes_ers_dt[i] = new double[][] {ers_reference.getSceneErsXYZ_dt(ts), ers_reference.getSceneErsATR_dt(ts)}; 		
				}
				compareRefSceneTiles(
						"" ,               // String suffix,
						true, // false,             // boolean blur_reference,
						all_scenes_xyzatr, // double [][][] scene_xyzatr, // does not include reference
						all_scenes_ers_dt, // double [][][] scene_ers_dt, // does not include reference
						scenes,            // QuadCLT [] scenes,
						8);                // int iscale) // 8
		}
		// create initial disparity map for the reference scene
		
	}
	

	public static ImagePlus generateSceneOutlines(
			QuadCLT    ref_scene, // ordered by increasing timestamps
			QuadCLT [] scenes,
			int        extra, // add around largest outline
			int        scale,
			int        line_width_outline,
			int        line_width_corners,
			Color      line_color_outline,
			Color      line_color_corners
			) {
		int step_outline = 5;
		int tilesX = ref_scene.getTileProcessor().getTilesX();
		int tilesY = ref_scene.getTileProcessor().getTilesY();
		int transform_size = ref_scene.getTileProcessor().getTileSize();
		ErsCorrection ers_reference = ref_scene.getErsCorrection();
		double [][] corners = {//new double [4][3]; // for each corner {px, py, d}
				{0.00,                         0.0,                         0.0},
				{tilesX * transform_size - 1, 0.0,                         0.0}, 
				{tilesX * transform_size - 1, tilesY * transform_size - 1, 0.0}, 
				{0.0,                         tilesY * transform_size - 1, 0.0}, 
		};
		double [][][] outlines = new double [scenes.length][corners.length][];
		double minx = tilesX * transform_size, maxx = 0.0, miny = tilesY * transform_size, maxy = 0.0; //
		for (int nscene = 0; nscene < scenes.length; nscene++ ) {
			String ts = scenes[nscene].getImageName();
			for (int ncorn = 0; ncorn < corners.length; ncorn++) {
				double []   ref_xyz = ers_reference.getSceneXYZ(ts);
				double []   ref_atr = ers_reference.getSceneATR(ts);

				double [] pXpYD = scenes[nscene].getErsCorrection().getImageCoordinatesERS(
						ref_scene,          // QuadCLT cameraQuadCLT,    // camera station that got image to be to be matched 
						corners[ncorn][0],  // double px,                // pixel coordinate X in the reference view
						corners[ncorn][1],  // double py,                // pixel coordinate Y in the reference view
						corners[ncorn][2],  // double disparity,         // this reference disparity 
						true,               // boolean distortedView,    // This camera view is distorted (diff.rect), false - rectilinear
						ref_xyz,            // double [] reference_xyz,  // this view position in world coordinates (typically zero3)
						ref_atr,            // double [] reference_atr,  // this view orientation relative to world frame  (typically zero3)
						true,               // boolean distortedCamera,  // camera view is distorted (false - rectilinear)
						new double [3], // double [] camera_xyz,     // camera center in world coordinates
						new double [3], // double [] camera_atr,     // camera orientation relative to world frame
						1.0); // double    line_err)       // threshold error in scan lines (1.0)
				outlines[nscene][ncorn] = pXpYD;
				if (minx > pXpYD[0]) minx = pXpYD[0];
				if (miny > pXpYD[1]) miny = pXpYD[1];
				if (maxx < pXpYD[0]) maxx = pXpYD[0];
				if (maxy < pXpYD[1]) maxy = pXpYD[1];
				System.out.println(String.format("%3d:%1d: px= %8.2f py= %8.2f disparity= %8.5f",nscene, ncorn, pXpYD[0], pXpYD[1], pXpYD[2]));
			}
			System.out.println();
		}
		int ix0 =    (int) Math.floor(minx) - extra;  // original pixels, subtract from data
		int width =  scale * (((int) Math.ceil (maxx) - ix0 ) + extra);  // scaled
		int iy0 =    (int) Math.floor(miny) - extra; 
		int height = scale * (((int) Math.ceil (maxy) - iy0 ) + extra);
		ImageStack stack = new 		ImageStack(width, height);
		int [] blank = new int [width*height];
		for (int nscene = 0; nscene < (scenes.length + 2); nscene++ ) {
			String slice_title = "";
			if (nscene < scenes.length) {
				slice_title = scenes[nscene].getImageName();
			} else if (nscene == scenes.length) {
				slice_title = "corners";
			} else {
				slice_title = "all";
			}
			stack.addSlice(slice_title, blank.clone());
		}
		// Draw outline on each individual slice
		for (int nscene = 0; nscene < scenes.length; nscene++ ) {
			ImageProcessor ip = stack.getProcessor(nscene+1);
			ip.setLineWidth(line_width_outline);
			ip.setColor    (line_color_outline); // does not work?
			for (int icorn = 0; icorn < outlines[nscene].length; icorn++) {
				int icorn_next = icorn+1;
				if (icorn_next >= outlines[nscene].length) {
					icorn_next = 0;
				}
				double [][] se = {
						{scale * (outlines[nscene][icorn][0]-ix0),     scale * (outlines[nscene][icorn][1]-iy0)},
						{scale * (outlines[nscene][icorn_next][0]-ix0),scale * (outlines[nscene][icorn_next][1]-iy0)}};
				for (int dy = -line_width_outline+1; dy < line_width_outline; dy+=2) {
					for (int dx = -line_width_outline+1; dx < line_width_outline; dx+=2) {
						Line line = new Line(se[0][0] + 0.5*dx, se[0][1] + 0.5*dy, se[1][0] + 0.5*dx, se[1][1] + 0.5*dy);
						line.drawPixels(ip);
					}
				}
			}
		}
		int indx_corners = scenes.length;
		int indx_all =    indx_corners+1;
		// Trace corners
		{
			ImageProcessor ip = stack.getProcessor(indx_corners+1);
			ip.setLineWidth(line_width_corners);
			ip.setColor    (line_color_corners); // does not work?
			for (int icorn = 0; icorn < outlines[0].length; icorn++) {
				for (int nscene = 0; nscene < (scenes.length - 1); nscene++ ) {
					double [][] se = {
					{scale * (outlines[nscene][icorn][0]-ix0),     scale * (outlines[nscene][icorn][1]-iy0)},
					{scale * (outlines[nscene + 1][icorn][0]-ix0),scale * (outlines[nscene+1][icorn][1]-iy0)}};
					for (int dy = -line_width_corners+1; dy < line_width_corners; dy+=2) {
						for (int dx = -line_width_corners+1; dx < line_width_corners; dx+=2) {
							Line line = new Line(se[0][0] + 0.5*dx, se[0][1] + 0.5*dy, se[1][0] + 0.5*dx, se[1][1] + 0.5*dy);
							line.drawPixels(ip);
						}
					}
				}
			}			
		}
		// Combine all in last slice
		{
			// outlines
			ImageProcessor ip = stack.getProcessor(indx_all+1);
			ip.setLineWidth(line_width_outline);
			ip.setColor    (line_color_outline); // does not work?
			for (int nscene = 0; nscene < scenes.length; nscene++ ) if (((scenes.length - nscene -1) % step_outline) == 0){
				for (int icorn = 0; icorn < outlines[nscene].length; icorn++) {
					int icorn_next = icorn+1;
					if (icorn_next >= outlines[nscene].length) {
						icorn_next = 0;
					}
					double [][] se = {
							{scale * (outlines[nscene][icorn][0]-ix0),     scale * (outlines[nscene][icorn][1]-iy0)},
							{scale * (outlines[nscene][icorn_next][0]-ix0),scale * (outlines[nscene][icorn_next][1]-iy0)}};
					for (int dy = -line_width_outline+1; dy < line_width_outline; dy+=2) {
						for (int dx = -line_width_outline+1; dx < line_width_outline; dx+=2) {
							Line line = new Line(se[0][0] + 0.5*dx, se[0][1] + 0.5*dy, se[1][0] + 0.5*dx, se[1][1] + 0.5*dy);
							line.drawPixels(ip);
						}
					}

				}
			}
			// corners
			ip.setLineWidth(line_width_corners);
			ip.setColor    (line_color_corners); // does not work?
			for (int icorn = 0; icorn < outlines[0].length; icorn++) {
				for (int nscene = 0; nscene < (scenes.length - 1); nscene++ ) {
					double [][] se = {
					{scale * (outlines[nscene][icorn][0]-ix0),     scale * (outlines[nscene][icorn][1]-iy0)},
					{scale * (outlines[nscene + 1][icorn][0]-ix0),scale * (outlines[nscene+1][icorn][1]-iy0)}};
					for (int dy = -line_width_corners+1; dy < line_width_corners; dy+=2) {
						for (int dx = -line_width_corners+1; dx < line_width_corners; dx+=2) {
							Line line = new Line(se[0][0] + 0.5*dx, se[0][1] + 0.5*dy, se[1][0] + 0.5*dx, se[1][1] + 0.5*dy);
							line.drawPixels(ip);
						}
					}
				}
			}			
			
		}
		
        ImagePlus imp_stack = new ImagePlus("scene_outlines", stack);
//        imp_stack.getProcessor().resetMinAndMax();
//       imp_stack.show();
		return imp_stack;
	}
	
	public static double compensateDSI(
			CLTParameters        clt_parameters,
			boolean              compensate_dsi, // false - do not apply, just calculate
			QuadCLT              ref_scene,
			double []            disparity,      // new disparity          
			int                  debug_level
			) {
		double [] disparity_dsi = ref_scene.getDLS()[1]; // null
		double [] strength = ref_scene.getDLS()[2];
		double sw=0,swd=0, swdi=0; // old, new-old
		for (int i = 0; i < disparity.length; i++) {
			if (!Double.isNaN(disparity[i]) && !Double.isNaN(disparity_dsi[i])) {
				sw   += strength[i];
				swd  += strength[i]*disparity_dsi[i];
				swdi += strength[i]*disparity[i];
			}
		}
		double avg_zold = ref_scene.getGeometryCorrection().getZFromDisparity(swd/sw);
		double avg_znew = ref_scene.getGeometryCorrection().getZFromDisparity(swdi/sw);
		double zdiff = avg_znew- avg_zold; // negative in the case
		double scale =avg_znew/avg_zold;
		System.out.println("Scale = "+scale);
		if (compensate_dsi) {
			ErsCorrection  ers_reference = ref_scene.getErsCorrection();
			// TODO: use getScenes(first_ts, last_ts) to remove stray data
			String [] timestamps = ers_reference.getScenes();
			for (String ts:timestamps) {
				double [][] xyzatr = ers_reference.getSceneXYZATR(ts);
				double [][] xyzatr_dt = ers_reference.getSceneErsXYZATR_dt(ts);
				for (int i = 0; i < xyzatr[0].length; i++) {
					xyzatr[0][i] *= scale;
					xyzatr_dt[0][i] *= scale; // scale linear velocities too 06.25.2024
				}
//				xyzatr[0][2] -= zdiff;
//				ers_reference.addScene(ts,xyzatr); // should use addScene(String timestamp, double [][] xyzatr, double [][] ers_xyzatr_dt) {
				ers_reference.addScene(ts,xyzatr,xyzatr_dt); // should use addScene(String timestamp, double [][] xyzatr, double [][] ers_xyzatr_dt) {

			}
			ref_scene.saveInterProperties( // save properties for interscene processing (extrinsics, ers, ...)
					null, // String path,             // full name with extension or w/o path to use x3d directory
					debug_level+1);
			
		}
		return zdiff;
	}
	
	public static double [][] intersceneExport(
			boolean              save_result,   // false - do not save
			int []               scene_range,         // if null -> {0,scenes_in.length-1}
			CLTParameters        clt_parameters,
			boolean              compensate_dsi,
//			int                  ref_index,
			QuadCLT              ref_scene,
			QuadCLT []           scenes_in,
			ColorProcParameters  colorProcParameters,
			int                  debug_level
			)
	{
		final boolean fsplit_en =        clt_parameters.rig.fsplit_en;       // true; // enable split of far tiles into FG/BG with LMA
		final int     fsplit_mode =      clt_parameters.rig.fsplit_mode;     // 1; // 0: disable, 1:avg->fg + fg->fg +fg->bg, 2: avg->fg+fg->bg, 3: avg->fg, avg->bg
		final double  fsplit_str =       clt_parameters.rig.fsplit_str;      // 0.4; // minimal strength of the tile to split
		final int     fsplit_neibs =     clt_parameters.rig.fsplit_neibs;    // 3;   // minimal n umber of neighbors to consider split
		final int     fsplit_neibs_side =clt_parameters.rig.fsplit_neibs_side;// 2;   // minimal number of on each side (for thin FG over BG)
		final double  fsplit_neib_str =  clt_parameters.rig.fsplit_neib_str; // 0.4; // minimal strength of the neighbor to compare
		final double  fsplit_neib_diff = clt_parameters.rig.fsplit_neib_diff; // 1.5; // maximal disparity difference to neighbor to use 
		final double  fsplit_disp =      clt_parameters.rig.fsplit_disp;     // 3.0; // maximal tile disparity to consider split.
		final double  fsplit_adiff =     clt_parameters.rig.fsplit_adiff;    // 0.08;// minimal tile (combo) absolute disparity difference from neighbors
		final double  fsplit_rdiff =     clt_parameters.rig.fsplit_rdiff;    // 0.1; // minimal tile (combo) relative disparity difference from neighbors
		final double  fsplit_kfg_min =   clt_parameters.rig.fsplit_kfg_min;  // 0.1; // minimal fraction of kfg and (1.0-kfg)
 	    final double  fsplit_min_ratio = clt_parameters.rig.fsplit_min_ratio;// 0.8; // minimal ratio of the weakest to strongest of fg and bg strengths
	    final double  fsplit_min_diff =  clt_parameters.rig.fsplit_min_diff; // 0.08;// minimal FG to BG disparity difference
	    final double  fsplit_rms_gain =  clt_parameters.rig.fsplit_rms_gain; // 1.1; // minimal LMA RMS improvement of splitting to FG and BG
		final boolean fsplit_dbg =       clt_parameters.rig.fsplit_dbg; 
		int fsplit_refines = 0;
		if (fsplit_en) switch (fsplit_mode) {
		case 1:   fsplit_refines = 1; break;
		case 2:   fsplit_refines = 3; break;
		}
		QuadCLT [] scenes; //  = scenes_in.clone(); // ?
		if (scene_range == null) {
			scene_range = new int [] {0, scenes_in.length-1};
			scenes = scenes_in; // clone is not needed
		} else {
			scenes = new QuadCLT [scene_range[1]-scene_range[0] + 1];
			for (int i =0; i < scenes.length; i++) {
				scenes[i] = scenes_in[i+scene_range[0]];
			}
		}
		
		ErsCorrection        ers_reference = ref_scene.getErsCorrection();
//		int      indx_ref = scenes.length - 1; // Always added to the end even if out-of order
		/*
		if (ref_index != indx_ref) {
			System.arraycopy(
					scenes_in,
					ref_index+1,
					scenes,
					ref_index,
					indx_ref-ref_index);
			scenes[indx_ref] = scenes_in[ref_index];
		}
		*/
		final double scale_far_split_strength = 1.5;
		// empirical correction for both lma and non-lma step
	  	double corr_nonlma = 1.0; // 1.23;
	  	double corr_lma =    1.0; // 1.23;
		// reference scene is always added to the end, even is out of timestamp order
		
		
//		QuadCLT  ref_scene = scenes[indx_ref]; // ordered by increasing timestamps
		boolean generate_outlines = false; // true; // TODO: move to configs
		System.out.println("intersceneExport(), scene timestamp="+ref_scene.getImageName());
//		int num_scenes = scenes.length;
		String [] combo_dsn_titles_full = COMBO_DSN_TITLES.clone(); // keep all titles and slices
		String [] combo_dsn_titles = new String [COMBO_DSN_INDX_DISP_BG];
		/*
		for (int i = 0; i < combo_dsn_titles.length; i++) {
			combo_dsn_titles[i] = combo_dsn_titles_full[i]; 
		}
		if (clt_parameters.rig.mll_max_refines_bg <= 0) {
			combo_dsn_titles_full = combo_dsn_titles;
		}
		*/
		
		double min_disp_change = clt_parameters.rig.mll_min_disp_change_pre; // 0.001; // stop re-measure when difference is below
		final int max_refines_presplit =
				clt_parameters.rig.mll_max_refines_bg  +
				clt_parameters.rig.mll_max_refines_lma +
				clt_parameters.rig.mll_max_refines_pre;
		final int max_refines = max_refines_presplit + fsplit_refines;
		final int [] iter_indices = {
				COMBO_DSN_INDX_DISP,
				COMBO_DSN_INDX_STRENGTH,
				COMBO_DSN_INDX_LMA,
				COMBO_DSN_INDX_CHANGE}; // which to save for each iteration: {"disp", "strength","disp_lma","change"};
		final int [] initial_indices = {
				COMBO_DSN_INDX_DISP,
				COMBO_DSN_INDX_STRENGTH,
				COMBO_DSN_INDX_VALID}; // initial: "disp", "strength","num_valid"
		double [][] combo_dsn =  null;
		// USE reference scene if no individual DSI are available double [][] dsrbg = scene.getDSRBG(); Probably null for non-reference
		// make sure to use already integrated if available
		final int margin = 8;
		final int tilesX = ref_scene.getTileProcessor().getTilesX();
		final int tilesY = ref_scene.getTileProcessor().getTilesY();
		final int tiles = tilesX * tilesY;

    	double [][] combo_dsn0 =ref_scene.readComboDSI(false); // true); // scenes[indx_ref].dsi and blue sky are NOT updated 
    	// temporary fix - filtering sky/no bridges or dim branches
    	boolean use_cuas = clt_parameters.imp.cuas_rotation && clt_parameters.imp.ims_use;
    	boolean fix_sky = use_cuas; // clt_parameters
    	
//    	double  min_strength = 0.65;
//    	double  disp_above = 0.015;
//    	int     skip_bottom = 2;
    	int [] slices = {COMBO_DSN_INDX_DISP,COMBO_DSN_INDX_LMA,COMBO_DSN_INDX_DISP_BG,COMBO_DSN_INDX_LMA_BG,COMBO_DSN_INDX_DISP_FG,COMBO_DSN_INDX_DISP_BG_ALL};
    	if (fix_sky && (combo_dsn0 != null)) {
    		combo_dsn0= simpleBlueSky(
    				combo_dsn0,                           // final double [][] combo_dsn,
    				clt_parameters.imp.cuas_fg_strength,  // min_strength, // final double      min_strength,
    				clt_parameters.imp.cuas_disp_add,     //  disp_above,   // final double      disp_above,
    				slices,                               // final int []      slices,
    				tilesX,                               // final int         tilesX,
    				tilesY,                               // final int         tilesY,
    				clt_parameters.imp.cuas_skip_bottom );// skip_bottom); // final int         skip_bottom)
    		if (debug_level > 0) {
    			ShowDoubleFloatArrays.showArrays(
    					combo_dsn0,
    					tilesX,
    					tilesY,
    					true,
    					"combo_dsn-skyed"+ref_scene.getImageName(),
    					combo_dsn_titles); //	dsrbg_titles);
    		}
    	}
    	
    	
    	
        
        if (combo_dsn0 == null) {
        	combo_dsn0 = prepareInitialComboDS( // 3
        			clt_parameters,   // final CLTParameters       clt_parameters,
        			scenes,           // final QuadCLT []          scenes,
//        			indx_ref,         // final int                 indx_ref,
        			ref_scene,        // final QuadCLT             ref_scene,
        			debug_level-2);   // final int                 debug_level);
        } else {
        	// set blue sky from read from file ONLY if no BS was calculated for this instance
        	if ((combo_dsn0[COMBO_DSN_INDX_BLUE_SKY] != null) && !ref_scene.hasBlueSky()) {
        		if (debug_level > -2) {
        			System.out.println("There was no calculated Blue Sky, but a file had it. Will use the loaded one");
        			ref_scene.setBlueSky(combo_dsn0[COMBO_DSN_INDX_BLUE_SKY]);
        		}
        	}
        	combo_dsn0 = conditionComboDsnFinal(
							true,           // boolean        use_conf,       // use configuration parameters, false - use following  
							clt_parameters, // CLTParameters  clt_parameters,
							combo_dsn0,     // double [][]    combo_dsn_final, // dls,
							ref_scene,      // QuadCLT        scene,
							debug_level);   // int            debugLevel);
        }
        combo_dsn = new double[combo_dsn_titles.length - 1][];
        for (int i = 0; i < initial_indices.length; i++) {
        	combo_dsn[initial_indices[i]] = combo_dsn0[i]; // "disp", "strength", <null>, "num_valid"
        }
		
		
		// uses 2 GB - change format
		if (generate_outlines) { // debug_level > 100) { // add parameter?
			int        extra = 10; // pixels around largest outline
			int        scale = 4;

			int        line_width_outline = 1;
			int        line_width_corners = 3;
			Color      line_color_outline = new Color(0, 255, 0);   // green
			Color      line_color_corners = new Color(0, 255, 255); // cyan

			// generating scene outlines for results documentation
			ImagePlus imp_outlines = generateSceneOutlines(
					ref_scene,          // QuadCLT    ref_scene, // ordered by increasing timestamps
					scenes,             // QuadCLT [] scenes
					extra,              // int        extra // add around largest outline
					scale,              // int        scale,
					line_width_outline, // int        line_width_outline,
					line_width_corners, // int        line_width_corners,
					line_color_outline, // Color      line_color_outline,
					line_color_corners  // Color      line_color_corners
					);
			ref_scene.saveImagePlusInModelDirectory(
					"scene_outlines", // String      suffix,
					imp_outlines); // ImagePlus   imp)
//			imp_outlines.show();
		}
		final double [][] combo_dsn_change = new double [combo_dsn_titles.length] [tiles];
		for (int i = 0; i < combo_dsn.length; i++) { // 4 elements: "disp", "strength","disp_lma","num_valid"
			if (combo_dsn[i] != null) {
				combo_dsn_change[i] = combo_dsn[i]; // all but change
			}
		}
		
		if (debug_level > 0) {
			ShowDoubleFloatArrays.showArrays(
					combo_dsn_change,
					tilesX,
					tilesY,
					true,
					"combo_dsn-initial"+ref_scene.getImageName(),
					combo_dsn_titles); //	dsrbg_titles);
		}
		final int last_slices = combo_dsn_titles.length;
		final int last_initial_slices = last_slices + initial_indices.length;
		final boolean [] defined_tiles = new boolean [tiles];
		for (int i = 0; i < defined_tiles.length; i++) {
			defined_tiles[i] = !Double.isNaN(combo_dsn_change[COMBO_DSN_INDX_DISP][i]); // initially defined tiles 
		}
		final double [][] refine_results = new double [last_slices + 4 * (max_refines + 1)][];
		String [] refine_titles = new String [refine_results.length];
		for (int i = 0; i < combo_dsn_titles.length; i++) {
			refine_results[i] = combo_dsn_change[i]; // first 5 - references to 5-element combo_dsn_change
			refine_titles[i] =  combo_dsn_titles[i]+"-last"; // "disp", "strength","disp_lma","num_valid","change"
		}
		for (int i = 0; i < initial_indices.length; i++) {
			refine_titles[last_slices + i] = combo_dsn_titles[initial_indices[i]]+"-initial"; // "disp", "strength","num_valid"
			if (combo_dsn_change[initial_indices[i]] != null) {
				refine_results[last_slices + i] = combo_dsn_change[initial_indices[i]].clone();
			} else {
				refine_results[last_slices + i] = new double [tiles];
			}
		}
		for (int nrefine = 0; nrefine < max_refines; nrefine++) {
			for (int i = 0; i < iter_indices.length; i++) {
				refine_titles[last_initial_slices + i * max_refines + nrefine ] = combo_dsn_titles[iter_indices[i]]+"-"+nrefine;
			}
		}
		double [] target_disparity = combo_dsn_change[COMBO_DSN_INDX_DISP].clone();
///		double [] target_disparity_orig = target_disparity.clone(); // will just use NaN/not NaN to restore tasks before second pass with LMA
//		double [][] combo_dsn_final = new double [combo_dsn_titles.length][combo_dsn[0].length];
//		double [][] combo_dsn_final = 
//				new double [(clt_parameters.rig.mll_max_refines_bg > 0)?
//						combo_dsn_titles_full.length:combo_dsn_titles.length][combo_dsn[0].length];
		double [][] combo_dsn_final = 
				new double [COMBO_DSN_TITLES.length][combo_dsn[0].length];
		
		//COMBO_DSN_INDX_SFM_GAIN
		combo_dsn_final[COMBO_DSN_INDX_DISP]= combo_dsn[COMBO_DSN_INDX_DISP].clone();
		for (int i = 1; i < combo_dsn_final.length; i++) {
			if (    (i != COMBO_DSN_INDX_SFM_GAIN) &&
					(i != COMBO_DSN_INDX_STRENGTH) &&
					(i != COMBO_DSN_INDX_STRENGTH_BG)){
				Arrays.fill(combo_dsn_final[i], Double.NaN);
			}
		}
		// Save pair selection and minimize them for scanning, then restore;
		int num_sensors = ref_scene.getNumSensors();
		int save_pairs_selection = clt_parameters.img_dtt.getMcorr(num_sensors);
		int save_bimax_combine_mode = clt_parameters.img_dtt.bimax_combine_mode;
		boolean save_bimax_dual_only = clt_parameters.img_dtt.bimax_dual_only;
		if (clt_parameters.rig.mll_max_refines_bg > 0) {
			clt_parameters.img_dtt.bimax_combine_mode = Correlation2d.CAMEL_FG; // initially refine FG 
			clt_parameters.img_dtt.bimax_dual_only =    false; // not just dual only
			if (debug_level > -3) {
				System.out.println("intersceneExport(): set CAMEL_FG");
			}
		}
		
		// FIXME: uncomment next
		System.out.println ("++++ Uncomment next line (Done) ++++");
		clt_parameters.img_dtt.setMcorr(num_sensors, 0 ); // remove all
		clt_parameters.img_dtt.setMcorrNeib(num_sensors,true);
		clt_parameters.img_dtt.setMcorrSq  (num_sensors,true); // remove even more?
		clt_parameters.img_dtt.setMcorrDia (num_sensors,true); // remove even more?
		boolean save_run_lma = clt_parameters.correlate_lma;
		clt_parameters.correlate_lma = false;
		double []  disp_err =     new double [combo_dsn[0].length];  // last time measure disparity error from correlation (scaled for CM)
		double []  corr_scale =   new double [combo_dsn[0].length];  // applied correction scale
		boolean [] was_lma =      new boolean [combo_dsn[0].length]; // previous measurement used LMA (not to match LMA/non-LMA measurements)
		Arrays.fill(disp_err, Double.NaN);
		Arrays.fill(corr_scale, 1.0);
		double min_scale = 0.75, max_scale = 1.5; // reset correction scale to 1.0 if calculated is too big/small (jumping FG/BG)
		double [][] dbg_corr_scale = null;
		if (debug_level > 0) {
			dbg_corr_scale = new double[max_refines][];
		}
		boolean [] selection = new boolean [target_disparity.length];
		int nTiles = selection.length;

		for (int i = 0; i < target_disparity.length; i++) {
			selection[i] = !Double.isNaN(target_disparity[i]);
		}
		boolean [] selection_orig = selection.clone();
		
		// Refine disparity map, each time recalculating each scene "projection" pixel X, pixel Y and 
		// disparity in each scene image set that correspond to the reference scene uniform grid tile.
		// First use reduced number of pairs and no LMA, then continue with all pairs and LMA 
		boolean bg_refine= false;
		final double [][] far_fgbg = new double [selection.length][];
///		final double [][] pre_split_ds = new double [2][];
		double [][] far_fg_ds = null;
		double [][] far_bg_ds = null;
		boolean [] sel_split = new boolean [selection.length];
		boolean [] far_split = null; // new boolean [nTiles];
		double [][] far_fg_ds_merge = new double[2][];
		double [][] far_bg_ds_merge = new double[2][];
		double [] used_rms_lma = new double[selection.length]; // last rms from LMA, not including BG refine
		double [] split_rms_lma = new double[selection.length];

		Arrays.fill(used_rms_lma, Double.NaN);
		boolean        use_rms = true; // DISPARITY_STRENGTH_INDEX means LMA RMS (18/04/2023)
		for (int nrefine = 0; nrefine < max_refines; nrefine++) {
////		boolean need_fix_sky = false; // re-run simpleBlueSky() in this pass
			if (nrefine == clt_parameters.rig.mll_max_refines_pre) {
				min_disp_change = clt_parameters.rig.mll_min_disp_change_lma;				
				clt_parameters.img_dtt.setMcorr(num_sensors, save_pairs_selection); // restore
				clt_parameters.correlate_lma = save_run_lma; // restore
				selection = selection_orig.clone();
////				need_fix_sky = true;
				if (fix_sky) {
//					target_disparity 
					combo_dsn_change[COMBO_DSN_INDX_DISP]= simpleBlueSky(
							combo_dsn_change[COMBO_DSN_INDX_DISP], // final double [] disparity,
							combo_dsn_change[COMBO_DSN_INDX_STRENGTH],         // final double [] strength,
							clt_parameters.imp.cuas_fg_strength,      // final double    min_strength,
							clt_parameters.imp.cuas_disp_add,         // final double    disp_above,
							tilesX,                                   // final int       tilesX,
							tilesY,                                   // final int       tilesY,
							clt_parameters.imp.cuas_skip_bottom);     // final int         skip_bottom)
					if (debug_level > -2) {
						System.out.println ("Re-applied simpleBlueSky() - needed if target disparity remained wrong FG (from previous sequence)");
					}
					target_disparity = combo_dsn_change[COMBO_DSN_INDX_DISP].clone();
					for (int i = 0; i < target_disparity.length; i++) {
						selection[i] = !Double.isNaN(target_disparity[i]);
					}
				}
				
				if (debug_level > -2) {
					int num_tomeas = 0;
					for (int nt = 0; nt < target_disparity.length; nt++) if (selection[nt]) { // (!Double.isNaN(target_disparity[nt])){
						num_tomeas++;
					}
					System.out.println ("a. nrefine pass = "+nrefine+", remaining "+num_tomeas+" tiles to re-measure");
				}
			} else if (nrefine == (clt_parameters.rig.mll_max_refines_pre + clt_parameters.rig.mll_max_refines_lma)) {
				// Processing BG - single pass
				clt_parameters.img_dtt.bimax_dual_only =    true; // Camel-max tiles only
				clt_parameters.img_dtt.bimax_combine_mode = Correlation2d.CAMEL_BG;
				bg_refine= true;
				selection = selection_orig.clone(); // re-select all original tiles
				if (debug_level > -3) {
					System.out.println("intersceneExport(): set CAMEL_BG, bimax_dual_only=true @"+nrefine);
				}
			} else if (nrefine == (clt_parameters.rig.mll_max_refines_pre + clt_parameters.rig.mll_max_refines_lma + 1)) {
				clt_parameters.img_dtt.bimax_dual_only =    false; // not just dual only
				if (debug_level > -3) {
					System.out.println("intersceneExport(): set bimax_dual_only=false @"+nrefine);
				}
			}
			if (nrefine == (clt_parameters.rig.mll_max_refines_pre + clt_parameters.rig.mll_max_refines_lma + clt_parameters.rig.mll_max_bg_nearest)) {
				clt_parameters.img_dtt.bimax_combine_mode = Correlation2d.CAMEL_NEAREST;
				if (debug_level > -3) {
					System.out.println("intersceneExport(): set CAMEL_NEAREST @"+nrefine);
				}
			}
			int split_pass = nrefine - max_refines_presplit;
			double [][] avg_ds = new double[][] {
				combo_dsn_final[COMBO_DSN_INDX_DISP],
				combo_dsn_final[COMBO_DSN_INDX_STRENGTH]};
			int split_src = -1;
			if (split_pass >= 0) {

				if (split_pass == 0) {
					split_src= 0 ; // AVG
					target_disparity=avg_ds[0];
				} else if (split_pass == 1) {
					split_src= 1 ; clt_parameters.img_dtt.bimax_combine_mode = Correlation2d.CAMEL_FG;
					target_disparity=far_fg_ds[0];
				} else { //  if (split_pass == 2) {
					split_src= 2 ; clt_parameters.img_dtt.bimax_combine_mode = Correlation2d.CAMEL_BG;
					target_disparity=far_bg_ds[0];
				}
				// 04/11/2023: always outputs both FG and BG
				clt_parameters.img_dtt.bimax_combine_mode = Correlation2d.CAMEL_FG;
				sel_split = calcFarFgBg(
						split_src,        // final int           split_src,       // target disparity is for 0 - avg, 1 - fg, 2 - bg
						fsplit_str,       // final double        fsplit_str,      // minimal strength of the tile to split
						fsplit_neibs,     // 							final int           fsplit_neibs,    // minimal number of neighbors to consider split
						fsplit_neibs_side,// 							final int           fsplit_neibs_side,// minimal number of neighbors from each side to consider thin FG
						fsplit_neib_str, // final double        fsplit_neib_str, // minimal strength of the neighbor to use
						fsplit_neib_diff,// final double        fsplit_neib_diff,// maximal disparity difference to neighbor to use 
						fsplit_disp,     // final double        fsplit_disp,     // maximal tile disparity to consider split, also for neibs.
						fsplit_adiff,    // final double        fsplit_adiff,    // minimal tile (combo) absolute disparity difference from neighbors
						fsplit_rdiff,    // final double        fsplit_rdiff,    // minimal tile (combo) relative disparity difference from neighbors
						fsplit_kfg_min,  // final double        fsplit_kfg_min,  // minimal fraction of kfg and (1.0-kfg)
						far_fgbg,        // final double [][]   far_fgbg,        // should be initialized to [nTiles][?]
						avg_ds,          // final double [][]   avg_ds,          // always defined, used for selection
						far_fg_ds,       // final double [][]   fg_ds,
						far_bg_ds,       // final double [][]   bg_ds,
						far_split, // null,            // final boolean []    selection,    // may be null, does not apply to neighbors
						tilesX,          //	final int           tilesX,
						fsplit_dbg? (ref_scene.getImageName()+"-FGBG-"+split_pass):null); // final String        dbg_title);
				//far_split
				selection = sel_split; // for correlateInterscene()
				if (split_src == 0) {
					far_fg_ds = new double[2][];
					far_bg_ds = new double[2][];
					far_fg_ds[0] = new double[nTiles];
					far_bg_ds[0] = new double[nTiles];
					Arrays.fill(far_fg_ds[0], Double.NaN);
					Arrays.fill(far_bg_ds[0], Double.NaN);
					far_fg_ds_merge[0] = avg_ds[0].clone();
					far_bg_ds_merge[0] = avg_ds[0].clone();
					far_fg_ds_merge[1] = avg_ds[1].clone();
					far_bg_ds_merge[1] = avg_ds[1].clone();
					
				}
			}
			int mcorr_sel = ImageDttParameters.corrSelEncode(clt_parameters.img_dtt,num_sensors);
			
			boolean show_dbg_img = true;
			/*if (need_fix_sky) {
				double [][]  dbg_img = show_dbg_img? new double[3][]: null;
				if (dbg_img != null) {
					dbg_img[0] = target_disparity.clone();
					dbg_img[2] = combo_dsn_change[COMBO_DSN_INDX_STRENGTH].clone();
				}
				target_disparity = simpleBlueSky(
						target_disparity, // final double [] disparity,
						combo_dsn_change[COMBO_DSN_INDX_STRENGTH],         // final double [] strength,
						clt_parameters.imp.cuas_fg_strength,      // final double    min_strength,
						clt_parameters.imp.cuas_disp_add,         // final double    disp_above,
						tilesX,                                   // final int       tilesX,
						tilesY,                                   // final int       tilesY,
						clt_parameters.imp.cuas_skip_bottom);     // final int         skip_bottom)
				if (debug_level > -2) {
					System.out.println ("Re-applied simpleBlueSky() - needed if target disparity remained wrong FG (from previous sequence)");
				}
				if (dbg_img != null) {
					dbg_img[1] = target_disparity;
					String [] dbg_titles = {"td_was","td_now","strength"};
					ShowDoubleFloatArrays.showArrays(
							dbg_img,
							tilesX,
							tilesY,
							true,
							ref_scene.getImageName()+"-td_was_now-"+nrefine,
							dbg_titles); // ImageDtt.DISPARITY_TITLES
				}

			}
			*/
			double [][] disparity_map = 
					correlateInterscene( // should skip scenes w/o orientation 06/29/2022
							clt_parameters, // final CLTParameters  clt_parameters,
							scenes,         // final QuadCLT []     scenes,
							ref_scene,     // final QuadCLT        ref_scene,					
							target_disparity, // combo_dsn_change[combo_dsn_indx_disp],   // final double []      disparity_ref,  // disparity in the reference view tiles (Double.NaN - invalid)
							selection,      // final boolean []     selection, // may be null, if not null do not  process unselected tiles
							far_fgbg,       // final double [][]    far_fgbg,        // null, or [nTile]{disp(fg)-disp(bg), str(fg)-str(bg)} hints for LMA FG/BG split 
							margin,         // final int            margin,
							nrefine,        // final int            nrefine, // just for debug title
							false, // ( nrefine == (max_refines - 1)) && clt_parameters.inp.show_final_2d, // final boolean        show_2d_corr,
							mcorr_sel,      // final int            mcorr_sel, //  = 							
							null,           // final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)
							false,          // final boolean        no_map, // do not generate disparity_map (time-consuming LMA)
							use_rms,        // final boolean        use_rms, // DISPARITY_STRENGTH_INDEX means LMA RMS (18/04/2023)
							debug_level-8); // final int            debug_level)

//			if ((need_fix_sky && show_dbg_img) || (debug_level > 0)) { //-3) {
			if (debug_level > 0) { //-3) {
				ShowDoubleFloatArrays.showArrays(
						disparity_map,
						tilesX,
						tilesY,
						true,
						ref_scene.getImageName()+"accumulated_disparity_map-"+nrefine,
						ImageDtt.getDisparityTitles(ref_scene.getNumSensors(),ref_scene.isMonochrome()) // ImageDtt.DISPARITY_TITLES
						);
			}
			// update disparities
			double [] map_disparity =     disparity_map[ImageDtt.DISPARITY_INDEX_CM]; // 2
//			double [] map_strength =      disparity_map[ImageDtt.DISPARITY_STRENGTH_INDEX]; // 10
			double [] map_strength_lma =  disparity_map[ImageDtt.DISPARITY_INDEX_POLY+1]; // 9
			double [] map_strength =      disparity_map[ImageDtt.DISPARITY_INDEX_CM+1];    // 3
			
			if (use_rms && clt_parameters.correlate_lma) { // non-elegant way to build old DISPARITY_STRENGTH_INDEX
				map_strength = map_strength.clone();
				for (int i = 0; i < map_strength.length; i++) if (map_strength_lma[i] > 0.0) {
					map_strength[i] = map_strength_lma[i];
				}
			}
			double [] map_rms_lma =          disparity_map[ImageDtt.DISPARITY_STRENGTH_INDEX]; // 8
			
			double [] map_disparity_lma =    disparity_map[ImageDtt.DISPARITY_INDEX_POLY]; // 8
			// only for far split.
			double [] map_far_fg_disparity = disparity_map[ImageDtt.DISPARITY_INDEX_POLY];
			double [] map_far_fg_strength =  disparity_map[ImageDtt.DISPARITY_INDEX_POLY+1];
			double [] map_far_bg_disparity = disparity_map[ImageDtt.DISPARITY_INDEX_CM];
			double [] map_far_bg_strength =  disparity_map[ImageDtt.DISPARITY_INDEX_CM+1];

			if (split_pass >= 0) {
				if (debug_level > -2) {
					System.out.println ("split_pass = "+split_pass+", split_src="+split_src);
				}

				if (far_split == null) {
					far_split = new boolean [nTiles];
				}
				// single-threaded 
				if (split_src == 0) {
					for (int tile = 0; tile < nTiles; tile++) {
						if (!Double.isNaN(map_far_fg_disparity[tile]) && !Double.isNaN(map_far_bg_disparity[tile])) {
							far_split[tile] = true;
							far_fg_ds[0][tile] = target_disparity[tile] + map_far_fg_disparity[tile];  
							far_bg_ds[0][tile] = target_disparity[tile] + map_far_bg_disparity[tile];
							far_fg_ds_merge[0][tile] = far_fg_ds[0][tile]; 
							far_bg_ds_merge[0][tile] = far_bg_ds[0][tile]; 
							far_fg_ds_merge[1][tile] = map_far_fg_strength[tile]; 
							far_bg_ds_merge[1][tile] = map_far_bg_strength[tile]; 
						}
					}
				} else if (split_src == 1) {
					for (int tile = 0; tile < nTiles; tile++) {
						if (!Double.isNaN(map_far_fg_disparity[tile])) {
							far_split[tile] = true;
							far_fg_ds[0][tile] = target_disparity[tile] + map_far_fg_disparity[tile];  
							far_fg_ds_merge[0][tile] = far_fg_ds[0][tile]; 
							far_fg_ds_merge[1][tile] = map_far_fg_strength[tile]; 
						}
					}
				} else if (split_src == 2) {
					for (int tile = 0; tile < nTiles; tile++) {
						if (!Double.isNaN(map_far_bg_disparity[tile])) {
							far_split[tile] = true;
							far_bg_ds[0][tile] = target_disparity[tile] + map_far_bg_disparity[tile];
							far_bg_ds_merge[0][tile] = far_bg_ds[0][tile]; 
							far_bg_ds_merge[1][tile] = map_far_bg_strength[tile]; 
						}
					}
				}
				
				far_fg_ds[1] = map_far_fg_strength;
				far_bg_ds[1] = map_far_bg_strength;
				if (debug_level > 0) { //-3) {
					double [][] predicted_fg_ds = new double[2][nTiles];
					double [][] predicted_bg_ds = new double[2][nTiles];
					double [][] predicted_fg_ds_merge = new double[2][];
					double [][] predicted_bg_ds_merge = new double[2][];
					Arrays.fill(predicted_fg_ds[0], Double.NaN);
					Arrays.fill(predicted_bg_ds[0], Double.NaN);
					predicted_fg_ds_merge[0]= avg_ds[0].clone();
					predicted_bg_ds_merge[0]= avg_ds[0].clone();
					predicted_fg_ds_merge[1]= avg_ds[1].clone();
					predicted_bg_ds_merge[1]= avg_ds[1].clone();
					double [][] fg_predicted_err = new double[2][nTiles];
					double [][] bg_predicted_err = new double[2][nTiles];
					Arrays.fill(fg_predicted_err[0], Double.NaN);
					Arrays.fill(bg_predicted_err[0], Double.NaN);
					Arrays.fill(fg_predicted_err[1], Double.NaN);
					Arrays.fill(bg_predicted_err[1], Double.NaN);

					for (int tile = 0; tile < nTiles; tile++) {
						if (far_fgbg[tile] != null) {
							double fg_targ =  far_fgbg[tile][0];
							double bg_targ =  far_fgbg[tile][1];
							double kfg =      far_fgbg[tile][2];
							predicted_fg_ds[0][tile] = fg_targ + target_disparity[tile];   // disparity(FG)
							predicted_bg_ds[0][tile] = bg_targ + target_disparity[tile];   // disparity(BG)
							predicted_fg_ds[1][tile] = avg_ds[1][tile] * kfg;              // strength(FG)
							predicted_bg_ds[1][tile] = avg_ds[1][tile] * (1.0 - kfg);      // strength(BG)
							if (!Double.isNaN(far_fg_ds[0][tile]) && !Double.isNaN(far_fg_ds[0][tile])) {
								fg_predicted_err[0][tile] = predicted_fg_ds[0][tile] - far_fg_ds[0][tile];
								bg_predicted_err[0][tile] = predicted_bg_ds[0][tile] - far_bg_ds[0][tile];
								fg_predicted_err[1][tile] = predicted_fg_ds[1][tile] - far_fg_ds[1][tile];
								bg_predicted_err[1][tile] = predicted_bg_ds[1][tile] - far_bg_ds[1][tile];
							}
							predicted_fg_ds_merge[0][tile] = predicted_fg_ds[0][tile];   // disparity(FG)
							predicted_bg_ds_merge[0][tile] = predicted_bg_ds[0][tile];           // disparity(BG)
							predicted_fg_ds_merge[1][tile] = avg_ds[1][tile] * kfg;                      // strength(FG)
							predicted_bg_ds_merge[1][tile] = avg_ds[1][tile] * (1.0 - kfg);              // strength(BG)
							split_rms_lma[tile] = map_rms_lma[tile];
						}
					}				
					
					String [] dbg_far_spit_titles={
							"avg_disp",              //  0
							"fg_disp",               //  1
							"bg_disp",               //  2
							"fg-bg",                 //  3
							"fg_disp_only",          //  4 
							"bg_disp_only",          //  5
							"fg_disp_predicted_only",//  6
							"bg_disp_predicted_only",//  7
							"fg_disp_predicted_err", //  8
							"bg_disp_predicted_err", //  9
							"avg_str",               // 10
							"fg_str",                // 11
							"bg_str",                // 12
							"fg_str_only",           // 13
							"bg_str_only",           // 14
							"fg_str_predicted_only", // 15
							"bg_str_predicted_only", // 16
							"fg_str_predicted_err",  // 17
							"bg_str_predicted_err",  // 18
							"sel",                   // 19
							"target_disparity",      // 20
							"rrms_single",           // 21 
							"rrms_split",            // 22
							"rrms_gain",             // 23
							"str_ratio"};            // 24

					double [][] dbg_img = new double[dbg_far_spit_titles.length][];
					dbg_img[ 0] = avg_ds[0];
					dbg_img[ 1] = far_fg_ds_merge[0];
					dbg_img[ 2] = far_bg_ds_merge[0];		
					dbg_img[ 3] = new double [nTiles];
					dbg_img[ 4] = far_fg_ds[0];
					dbg_img[ 5] = far_bg_ds[0];
					dbg_img[ 6] = predicted_fg_ds[0];
					dbg_img[ 7] = predicted_bg_ds[0];
					dbg_img[ 8] = fg_predicted_err[0];
					dbg_img[ 9] = bg_predicted_err[0];
					dbg_img[10] = avg_ds[1];
					dbg_img[11] = far_fg_ds_merge[1];
					dbg_img[12] = far_bg_ds_merge[1];
					dbg_img[13] = far_fg_ds[1];
					dbg_img[14] = far_bg_ds[1];
					dbg_img[15] = predicted_fg_ds[1];
					dbg_img[16] = predicted_bg_ds[1];
					dbg_img[17] = fg_predicted_err[1];
					dbg_img[18] = bg_predicted_err[1];
					dbg_img[19] = new double [nTiles];
					dbg_img[21] = new double [nTiles];
					dbg_img[22] = new double [nTiles];
					dbg_img[23] = new double [nTiles];
					dbg_img[24] = new double [nTiles];
					for (int tile = 0; tile < nTiles; tile++) {
						// update index below !
						dbg_img[19][tile] = (selection[tile] ? 0.5:0.0) + (far_split[tile] ? 0.6:0.0);
						dbg_img[ 3][tile] = dbg_img[ 1][tile] - dbg_img[ 2][tile];
						dbg_img[21][tile] = 1.0/used_rms_lma[tile];
						dbg_img[22][tile] = 1.0/map_rms_lma[tile];
						dbg_img[23][tile] = used_rms_lma[tile]/map_rms_lma[tile];
						double sr = far_fg_ds[1][tile]/far_bg_ds[1][tile];
						if (sr > 1.0) {
							sr = 1.0/sr;
						}
						dbg_img[24][tile] = sr;
					}
					dbg_img[20] = target_disparity;
					
					ShowDoubleFloatArrays.showArrays(
							dbg_img,
							tilesX,
							tilesY,
							true,
							"far-split"+split_pass,
							dbg_far_spit_titles);
				}
			} else { //if (split_pass >= 0) { // pre-far_fgbg disparity processing
				int num_tomeas = 0; // number of tiles to measure
				Arrays.fill(selection, false);
				for (int nTile =0; nTile < combo_dsn_change[0].length; nTile++) {
					if (defined_tiles[nTile]) { // originally defined, maybe not measured last time
						if (!Double.isNaN(map_disparity[nTile])) { // re-measured
							boolean is_lma = (map_disparity_lma != null) && !Double.isNaN(map_disparity_lma[nTile]);
							if (is_lma) {
								combo_dsn_change[COMBO_DSN_INDX_CHANGE][nTile] = map_disparity_lma[nTile] * corr_nonlma;
							} else if (!Double.isNaN(map_disparity[nTile])) {
								combo_dsn_change[COMBO_DSN_INDX_CHANGE][nTile] = map_disparity[nTile] / clt_parameters.ofp.magic_scale * corr_lma;
							}
							if (!Double.isNaN(combo_dsn_change[COMBO_DSN_INDX_CHANGE][nTile])) {
								// will keep previous new_corr_scale value when no previous data or switching non-LMA/LMA. Should be initialized to 1.0 
								if (!Double.isNaN(disp_err[nTile]) && (is_lma == was_lma[nTile])) {
									// current target_disparity: combo_dsn_change[combo_dsn_indx_disp][nTile]
									double this_target_disparity = combo_dsn_change[COMBO_DSN_INDX_DISP][nTile];
									double previous_target_disparity = this_target_disparity - corr_scale[nTile] * disp_err[nTile];
									double prev_disp_err = disp_err[nTile];
									double this_disp_err = combo_dsn_change[COMBO_DSN_INDX_CHANGE][nTile];
									double new_corr_scale = (previous_target_disparity - this_target_disparity) / (this_disp_err - prev_disp_err);
									if ((new_corr_scale <= max_scale) && (new_corr_scale >= min_scale)) {
										corr_scale[nTile] = new_corr_scale;
									}
								}

								combo_dsn_change[COMBO_DSN_INDX_DISP][nTile] +=     combo_dsn_change[COMBO_DSN_INDX_CHANGE][nTile] * corr_scale[nTile];
								was_lma[nTile] = is_lma;

								combo_dsn_change[COMBO_DSN_INDX_STRENGTH][nTile]  = map_strength[nTile]; // combine CM/LMA
								if (bg_refine) {
									combo_dsn_final[COMBO_DSN_INDX_DISP_BG][nTile] =    combo_dsn_change[COMBO_DSN_INDX_DISP][nTile];
									combo_dsn_final[COMBO_DSN_INDX_STRENGTH_BG][nTile] = combo_dsn_change[COMBO_DSN_INDX_STRENGTH][nTile];
									combo_dsn_final[COMBO_DSN_INDX_LMA_BG][nTile] = combo_dsn_change[COMBO_DSN_INDX_DISP][nTile];
									if (map_disparity_lma != null) {
										combo_dsn_final[COMBO_DSN_INDX_LMA_BG][nTile] = Double.isNaN(map_disparity_lma[nTile])? Double.NaN : combo_dsn_final[COMBO_DSN_INDX_DISP_BG][nTile];
									}
									combo_dsn_final[COMBO_DSN_INDX_CHANGE_BG][nTile] = combo_dsn_change[COMBO_DSN_INDX_CHANGE][nTile];
								} else {
									combo_dsn_final[COMBO_DSN_INDX_DISP][nTile] =    combo_dsn_change[COMBO_DSN_INDX_DISP][nTile];
									combo_dsn_final[COMBO_DSN_INDX_STRENGTH][nTile] = combo_dsn_change[COMBO_DSN_INDX_STRENGTH][nTile];
									combo_dsn_final[COMBO_DSN_INDX_LMA][nTile] = combo_dsn_change[COMBO_DSN_INDX_DISP][nTile];
									if (map_disparity_lma != null) {
										combo_dsn_final[COMBO_DSN_INDX_LMA][nTile] = Double.isNaN(map_disparity_lma[nTile])? Double.NaN : combo_dsn_final[COMBO_DSN_INDX_DISP][nTile];
										// saving last LMA RMS to compare against split FG/BG to see if it improves (or significantly improves?)
										if (!Double.isNaN(map_disparity_lma[nTile])) {
											used_rms_lma[nTile] = map_rms_lma[nTile];
										}
									}
									combo_dsn_final[COMBO_DSN_INDX_VALID][nTile] = combo_dsn[COMBO_DSN_INDX_VALID][nTile]; // not much sense
									combo_dsn_final[COMBO_DSN_INDX_CHANGE][nTile] = combo_dsn_change[COMBO_DSN_INDX_CHANGE][nTile];
								}
							}
							disp_err[nTile] = combo_dsn_change[COMBO_DSN_INDX_CHANGE][nTile];
							if (Math.abs(combo_dsn_change[COMBO_DSN_INDX_CHANGE][nTile]) >= min_disp_change) {
								target_disparity[nTile] = combo_dsn_change[COMBO_DSN_INDX_DISP][nTile] ;
								selection[nTile] = true;
								num_tomeas ++;
							} else {
								num_tomeas+=0;
							}
						} else { // originally defined, but not re-measured
							num_tomeas+=0;
						}
					}
				}
				if (dbg_corr_scale != null) {
					dbg_corr_scale[nrefine] = corr_scale.clone();
				}
				// Copy disparity to disparity_lma and just mask out  tiles with no LMA data (keep all if LMA did not run at all)  
				System.arraycopy(combo_dsn_change[COMBO_DSN_INDX_DISP], 0, combo_dsn_change[COMBO_DSN_INDX_LMA], 0, combo_dsn_change[COMBO_DSN_INDX_DISP].length); // lma
				if (map_disparity_lma != null) { 
					for (int i = 0; i < map_disparity_lma.length; i++) {
						if (Double.isNaN(map_disparity_lma[i])) {
							combo_dsn_change[COMBO_DSN_INDX_LMA][i] = Double.NaN;		
						}
					}
				}

				for (int i = 0; i < iter_indices.length; i++) {
					refine_results[last_initial_slices + (i * max_refines) + nrefine] = combo_dsn_change[iter_indices[i]].clone();
				}
				//					clt_parameters.inp.show_final_2d, // final boolean        show_2d_corr,
//				if ((need_fix_sky && show_dbg_img) || (debug_level > 0)) { //-3) {
				if (debug_level > 0) { //-3) {
					ShowDoubleFloatArrays.showArrays(
							combo_dsn_change,
							tilesX,
							tilesY,
							true,
							ref_scene.getImageName()+"combo_dsn-"+nrefine+"-"+ref_scene.getImageName(),
							combo_dsn_titles); //	dsrbg_titles);
				}
				if (debug_level > -2) {
					System.out.println ("b. nrefine pass = "+nrefine+", clt_parameters.correlate_lma="+clt_parameters.correlate_lma+", remaining "+num_tomeas+" tiles to re-measure");
				}
				if (num_tomeas == 0) {
					break;
				}
			} //  else { //if (split_pass >= 0) {
		} //for (int nrefine = 0; nrefine < max_refines; nrefine++) {
		
		// Add duplicate of FG disparity and FG+BG disparity (FG where no BG) for visual comparison
		if (true) { // clt_parameters.rig.mll_max_refines_bg > 0) { 
			for (int nTile =0; nTile < combo_dsn_change[0].length; nTile++) {
				combo_dsn_final[COMBO_DSN_INDX_DISP_FG][nTile] = combo_dsn_final[COMBO_DSN_INDX_DISP][nTile];
				if (Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_DISP_BG][nTile])) {
					combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL][nTile] = combo_dsn_final[COMBO_DSN_INDX_DISP][nTile];
				} else {
					combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL][nTile] = combo_dsn_final[COMBO_DSN_INDX_DISP_BG][nTile];
				}
			}
		}
		// add far split if available
		boolean mod_cumul_disp = true;
		boolean mod_cumul_bg_disp = true;
		if ((far_fg_ds != null) && (far_fg_ds[0] != null)) {
			for (int nTile =0; nTile < combo_dsn_change[0].length; nTile++) {
				if (!Double.isNaN(far_fg_ds[0][nTile]) && !Double.isNaN(far_bg_ds[0][nTile])) {
					// filtering
					double fg_bg_diff = far_fg_ds[0][nTile] - far_bg_ds[0][nTile];
					if (fg_bg_diff < fsplit_min_diff) {
						continue;
					}
					
					double rms_gain = used_rms_lma[nTile] / split_rms_lma[nTile];
					if (rms_gain < fsplit_rms_gain) {
						continue;
					}
					
					double weak_strong_ratio = far_fg_ds[1][nTile] / far_bg_ds[1][nTile];
					if (weak_strong_ratio > 1) {
						weak_strong_ratio = 1.0 / weak_strong_ratio;
					}

					if (weak_strong_ratio < fsplit_min_ratio) {
						continue;
					}
					
					combo_dsn_final[COMBO_DSN_INDX_DISP_FG][nTile] =  far_fg_ds[0][nTile];
					combo_dsn_final[COMBO_DSN_INDX_LMA][nTile] =      far_fg_ds[0][nTile];
					if (mod_cumul_disp) {
						combo_dsn_final[COMBO_DSN_INDX_DISP][nTile] = far_fg_ds[0][nTile];
					}
					combo_dsn_final[COMBO_DSN_INDX_STRENGTH][nTile] = far_fg_ds[1][nTile] * scale_far_split_strength;

					combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL][nTile] = far_bg_ds[0][nTile];
					combo_dsn_final[COMBO_DSN_INDX_LMA_BG][nTile] =      far_bg_ds[0][nTile];
					if (mod_cumul_bg_disp) {
						combo_dsn_final[COMBO_DSN_INDX_DISP_BG][nTile] = far_bg_ds[0][nTile];
					}
					combo_dsn_final[COMBO_DSN_INDX_STRENGTH_BG][nTile] = far_bg_ds[1][nTile] * scale_far_split_strength;
				}
			}
		}
		
		// add blue sky slice
		/*
		boolean [] blue_sky = ref_scene.getBlueSky();
		double  [] payload_blue_sky = combo_dsn_final[combo_dsn_final.length-1]; // last slice, length by titles
		Arrays.fill(payload_blue_sky,Double.NaN);
		if (blue_sky != null) {
			for (int i = 0; i < tiles; i++) {
				payload_blue_sky[i] = blue_sky[i] ? 1.0 : 0.0;
			}
		}
		*/
		double  [] payload_blue_sky; //  = combo_dsn_final[combo_dsn_final.length-1]; // last slice, length by titles
		if (ref_scene.hasBlueSky()) {
			payload_blue_sky = ref_scene.getDoubleBlueSky().clone();
		} else {
			payload_blue_sky = new double[tiles];
			Arrays.fill(payload_blue_sky,Double.NaN);
		}
		combo_dsn_final[COMBO_DSN_INDX_BLUE_SKY] = payload_blue_sky;
		
		// restore modified parameters
		clt_parameters.img_dtt.bimax_combine_mode = save_bimax_combine_mode;
		clt_parameters.img_dtt.bimax_dual_only =    save_bimax_dual_only;
		if (dbg_corr_scale != null) {
			ShowDoubleFloatArrays.showArrays(
					dbg_corr_scale,
					tilesX,
					tilesY,
					true,
					"Correction scales"
					);
		}
// Do above twice: with 40 pairs, no-lma and then with all pairs+LMA
		
		if (debug_level > 1) {
			ShowDoubleFloatArrays.showArrays(
					combo_dsn_change,
					tilesX,
					tilesY,
					true,
					"combo_dsn_change-"+ref_scene.getImageName(),
					combo_dsn_titles); //	dsrbg_titles);
			ShowDoubleFloatArrays.showArrays(
					combo_dsn,
					tilesX,
					tilesY,
					true,
					"combo_dsn-"+ref_scene.getImageName(),
					combo_dsn_titles); //	dsrbg_titles);
			
			ShowDoubleFloatArrays.showArrays(
					combo_dsn_final,
					tilesX,
					tilesY,
					true,
					"combo_dsn-final-"+ref_scene.getImageName(),
					combo_dsn_titles_full); //	dsrbg_titles);
		}
		
		
		if (debug_level > 0) {
			ShowDoubleFloatArrays.showArrays(
					refine_results,
					tilesX,
					tilesY,
					true,
					"combo-"+max_refines+"-"+ref_scene.getImageName(),
					refine_titles); //	dsrbg_titles);
		}
		//noise_sigma_level
		String rslt_suffix = "-INTER-INTRA-HISTORIC";
		rslt_suffix += (clt_parameters.correlate_lma?"-LMA":"-NOLMA");
		// ref_index from the center is moved to the very end (indx_ref)
		// was bug - used ref_index Maybe that was intentional?
		if (compensate_dsi && !ref_scene.interDsiExists()) {
			System.out.println("Will compensate pose Z as average disparity may change");
			double z_corr = compensateDSI(
					clt_parameters,    // CLTParameters        clt_parameters,
					compensate_dsi,    // boolean              compensate_dsi, // false - do not apply, just calculate
					ref_scene,         // ref_index], // QuadCLT              ref_scene,
					combo_dsn_final[COMBO_DSN_INDX_LMA], // double []            disparity,      // new disparity          
					debug_level); // int                  debug_level
			System.out.println("Z correction = "+z_corr);
			

		}
		if (save_result) {
			ref_scene.saveDoubleArrayInModelDirectory(
					rslt_suffix,         // String      suffix,
					refine_titles,       // null,          // String []   labels, // or null
					refine_results,      // dbg_data,         // double [][] data,
					tilesX,              // int         width,
					tilesY);             // int         height)
		}
		rslt_suffix = "-INTER-INTRA";
		rslt_suffix += (clt_parameters.correlate_lma?"-LMA":"-NOLMA");
		// fixing NaN in strengths. It is uses to return RMS in Not needed - NaN was from Arrays.fill(combo_dsn_final[i], Double.NaN);
// ImageDtt.clt_process_tl_correlations( // convert to pixel domain and process correlations already prepared in fcorr_td and/or fcorr_combo_td
//		if (use_rms) nan_slices.add(DISPARITY_STRENGTH_INDEX); // will be used for RMS if LMA succeeded
// And NaN in strength cause (at least) NaN for double avg_z = quadCLTs[ref_index].getAverageZ(true);		
//		public static int COMBO_DSN_INDX_STRENGTH =    1; // strength, FG
//		public static int COMBO_DSN_INDX_STRENGTH_BG = 6; // background strength
		for (int slice: COMBO_DSN_NONNAN) { // new int[] {COMBO_DSN_INDX_STRENGTH,COMBO_DSN_INDX_STRENGTH_BG}) {
			if (combo_dsn_final[slice] != null) {
				for (int i = 0; i <combo_dsn_final[slice].length; i++) {
					if (Double.isNaN(combo_dsn_final[slice][i])) {
						combo_dsn_final[slice][i] = 0.0;
					}
				}
			}
		}
		if (save_result) {
		ref_scene.saveDoubleArrayInModelDirectory( // error
				rslt_suffix,           // String      suffix,
				combo_dsn_titles_full, // null,          // String []   labels, // or null
				combo_dsn_final,       // dbg_data,         // double [][] data,
				tilesX,                // int         width,
				tilesY);               // int         height)
		}
//		System.out.println("IntersceneAccumulate(), got previous scenes: "+sts.length);
		if (debug_level > 1) { // tested OK
			System.out.println("IntersceneAccumulate(): preparing image set...");
			int nscenes = scenes.length;
			//			int indx_ref = nscenes - 1; 
			double [][][] all_scenes_xyzatr = new double [scenes.length][][]; // includes reference (last)
			double [][][] all_scenes_ers_dt = new double [scenes.length][][]; // includes reference (last)
////			all_scenes_xyzatr[indx_ref] = new double [][] {ZERO3,ZERO3};
////			all_scenes_ers_dt[indx_ref] = new double [][] {
////				ers_reference.getErsXYZ_dt(),
////				ers_reference.getErsATR_dt()};

////				for (int i = 0; i < nscenes; i++) if (i != indx_ref) {
			for  (int i = 0; i < nscenes; i++) { // now no need to exclude ref_scene - should be in the list 
////					if (scenes[i] != ref_scene) {
				String ts = scenes[i].getImageName();
				all_scenes_xyzatr[i] = new double[][] {ers_reference.getSceneXYZ(ts),       ers_reference.getSceneATR(ts)}; 		
				all_scenes_ers_dt[i] = new double[][] {ers_reference.getSceneErsXYZ_dt(ts), ers_reference.getSceneErsATR_dt(ts)}; 		
////					}
			}
			compareRefSceneTiles( // null pointer
					"" ,               // String suffix,
					true, // false,    // boolean blur_reference,
					all_scenes_xyzatr, // double [][][] scene_xyzatr, // does not include reference
					all_scenes_ers_dt, // double [][][] scene_ers_dt, // does not include reference
					scenes,            // QuadCLT [] scenes,
					8);                // int iscale) // 8
		}
		// Moved to intersceneMlExport()
		return combo_dsn_final;
	}
	
	
	public static double [][] simpleBlueSky(
			final double [][] combo_dsn,
			final double      min_strength,
			final double      disp_above,
			final int []      slices,
			final int         tilesX,
			final int         tilesY,
			final int         skip_bottom) { // do not look at the very bottom
		double [] strength = combo_dsn[COMBO_DSN_INDX_STRENGTH];
		double [][] filtered_dsn = combo_dsn.clone();
		for (int slice:slices) {
			filtered_dsn[slice] = simpleBlueSky(
					combo_dsn[slice], // final double [] disparity,
					strength,         // final double [] strength,
					min_strength,     // final double    min_strength,
					disp_above,       // final double    disp_above,
					tilesX,           // final int       tilesX,
					tilesY,           // final int       tilesY,
					skip_bottom);     // final int         skip_bottom)
		}		
		return filtered_dsn;
	}
	
	public static double [] simpleBlueSky(
			final double [] disparity,
			final double [] strength,
			final double    min_strength,
			final double    disp_above,
			final int       tilesX,
			final int       tilesY,
			final int       skip_bottom) { // do not look at the very bottom
		if (disparity== null) {
			return null;
		}
		double [] filtered_disparity = disparity.clone();
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int tileX = ai.getAndIncrement(); tileX < tilesX; tileX = ai.getAndIncrement()) {
							double lim = Double.NaN;
							for (int tileY = tilesY-1-skip_bottom; tileY >= 0; tileY--) {
								int indx = tileX + tileY * tilesX;
								double d = filtered_disparity[indx];
								if (!Double.isNaN(d)) {
									if (strength[indx] > min_strength) { // NaN OK
										if (!(d >= lim)) { // NaN OK
											lim = d;
										}
									} else if (d > (lim + disp_above)) {
										filtered_disparity[indx] = lim + disp_above; 
									}
								}
							}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return filtered_disparity;
	}
	
	
	
	
	
	public static boolean [] calcFarFgBg(
			final int           split_src,       // target disparity is for 0 - avg, 1 - fg, 2 - bg
			final double        fsplit_str,      // minimal strength of the tile to split
			final int           fsplit_neibs,    // minimal number of neighbors to consider split
			final int           fsplit_neibs_side,// minimal number of neighbors from each side to consider thin FG
			final double        fsplit_neib_str, // minimal strength of the neighbor to use
			final double        fsplit_neib_diff,// maximal disparity difference to neighbor to use 
			final double        fsplit_disp,     // maximal tile disparity to consider split, also for neibs.
			final double        fsplit_adiff,    // minimal tile (combo) absolute disparity difference from neighbors
			final double        fsplit_rdiff,    // minimal tile (combo) relative disparity difference from neighbors
			final double        fsplit_kfg_min,  // minimal fraction of kfg and (1.0-kfg)
			final double [][]   far_fgbg,        // should be initialized to [nTiles][?]
			final double [][]   avg_ds,          // always defined, used for selection
			final double [][]   fg_ds,
			final double [][]   bg_ds,
			final boolean []    selection,    // may be null, does not aspply to neighbors
			final int           tilesX,
			final String        dbg_title){
//		final int dbg_tileX = -65, dbg_tileY=26, dbg_dX=4, dbg_dY=4; // 67,25
		final int dbg_tileX = 28, dbg_tileY=22, dbg_dX=4, dbg_dY=4; // 67,25
		final int nTiles = far_fgbg.length;
		final int tilesY = nTiles/tilesX;
		final TileNeibs tn = new TileNeibs(tilesX,tilesY);
		Arrays.fill(far_fgbg, null);
		final boolean [] sel_out = new boolean [nTiles];
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final boolean [] dbg_thin_fg = (dbg_title != null) ? new boolean[nTiles]: null;
		if (split_src == 0) { // other modes should have non-null selection[]
			//TODO: require no-BG
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						double [][] neib_ds = new double[24][];
						for (int tile = ai.getAndIncrement(); tile < nTiles; tile = ai.getAndIncrement()) {
							if (dbg_title != null) {
								int tileX = tile % tilesX;
								int tileY = tile / tilesX;
								if (    (tileX >= dbg_tileX) &&
										(tileY >= dbg_tileY) &&
										(tileX < (dbg_tileX + dbg_dX)) &&
										(tileY < (dbg_tileY + dbg_dY))){
									System.out.println("calcFarFgBg()-0: tile = "+tile+
											" (X="+tileX+", Y="+tileY+")");
								}
							}
							// TODO: maybe compare to avg_ds[0][tile] instead of fg_ds[0][tile]?
							if ((bg_ds != null) && !Double.isNaN(bg_ds[0][tile]) && (bg_ds[0][tile] != fg_ds[0][tile])){
								continue; // should not have BG
							}
							double center_disp = avg_ds[0][tile];
							double center_str =  avg_ds[1][tile];
							if (    (center_disp <= fsplit_disp) && // processes NaN
									(center_str >=  fsplit_str) &&
									((selection == null) || selection[tile])) {
								// get neibs
								Arrays.fill(neib_ds, null);
								int num_neibs = 0;
								for (int dir = 0; dir < 8; dir++) { // start withg 3x3
									int tile1 = tn.getNeibIndex2(tile, dir);
									if (    (tile1 >= 0) &&
											(avg_ds[0][tile1] <= fsplit_disp) &&
											(avg_ds[1][tile1] >= fsplit_neib_str) &&
											(Math.abs(avg_ds[0][tile1] - center_disp) <= fsplit_neib_diff)) {
										neib_ds[dir] = new double[] {avg_ds[0][tile1],avg_ds[1][tile1]};
										num_neibs++;
									}
								}
								if (num_neibs >= fsplit_neibs) {
									for (int dir = 8; dir < 24; dir++) { // copy outside tiles of 4x4 kernels
										int tile1 = tn.getNeibIndex2(tile, dir);
										if (    (tile1 >= 0) &&
												(avg_ds[0][tile1] <= fsplit_disp) &&
												(avg_ds[1][tile1] >= fsplit_neib_str) &&
												(Math.abs(avg_ds[0][tile1] - center_disp) <= fsplit_neib_diff)) {
											neib_ds[dir] = new double[] {avg_ds[0][tile1],avg_ds[1][tile1]};
										}
									}
									// detect thin FG over BG
									int [][] num_side = new int [KERN_FG.length][3];
									double [][] sw_side=new double[KERN_FG.length][3], swd_side=new double[KERN_FG.length][3], sww_side=new double[KERN_FG.length][3];
									double [][] sw_side2=new double[KERN_FG.length][3], swd_side2=new double[KERN_FG.length][3];
									for (int dir = 0; dir < 8; dir++) if (neib_ds[dir]!=null){
										for (int i = 0; i < KERN_FG.length; i++) if (KERN_FG[i][dir] > 0){
											int indx = KERN_FG[i][dir] - 1;
											num_side[i][indx]++;
											sww_side[i][indx] +=  KERN_FG_W[i][dir];
											double w = neib_ds[dir][1] * KERN_FG_W[i][dir];
											sw_side[i][indx] +=  w;
											swd_side[i][indx] += w * neib_ds[dir][0];
										}
									}
									// weights and indices for inner tiles are the same
									for (int i = 0; i < KERN_FG.length; i++ ) {
										sww_side[i][2] += 1;
										sw_side[i][2] +=  center_str;
										swd_side[i][2] += center_str*center_disp;
										
										for (int j = 0; j < 3; j++) {
											sw_side2[i][j] =  sw_side[i][j];
											swd_side2[i][j] = swd_side[i][j];
										}
									}
									for (int dir = 8; dir < 24; dir++) if (neib_ds[dir]!=null){
										for (int i = 0; i < KERN_FG.length; i++) if (KERN_FG2[i][dir] > 0){
											int indx = KERN_FG2[i][dir] - 1;
											double w = neib_ds[dir][1] * KERN_FG_W2[i][dir];
											sw_side2[i][indx] +=  w;
											swd_side2[i][indx] += w * neib_ds[dir][0];
										}
									}
									double avg_disparity = Double.NaN;
									double avg_strength = Double.NaN;
									double avg_center = Double.NaN; // center disparity in the "best" direction
									// Maybe replace center_str too?
									int best_dir = -1;
//									double max_neib_avg = Math.min(center_disp - fsplit_adiff, center_disp * (1.0 - fsplit_rdiff));
									for (int i = 0; i < KERN_FG.length; i++) {
										if ((num_side[i][0] >= fsplit_neibs_side) && (num_side[i][0] >= fsplit_neibs_side)){
											double center_dispm = swd_side[i][2]/sw_side[i][2]; // averaged center disparity
											double max_neib_avg = Math.min(center_dispm - fsplit_adiff, center_dispm * (1.0 - fsplit_rdiff));
											// center should be above overall average
											double avg_all = (swd_side[i][0]+swd_side[i][1]) / (sw_side[i][0] + sw_side[i][1]);
//											double diff = center_disp - avg_all;
											double [] avg_side = new double[] {swd_side[i][0]/sw_side[i][0], swd_side[i][1]/sw_side[i][1]};
											double lowest_near = Math.min(avg_side[0],avg_side[1]);
											if (   (lowest_near < max_neib_avg) &&
													!(avg_disparity <= lowest_near) && // lower than current best or current is undefined
													(avg_all < max_neib_avg)){// overall average is low enough
												// Verify that each half (or extended half) is be lower than the center (thin FG is not on a slope)
												if (    ((center_dispm > (swd_side[i][0]/sw_side[i][0])) || (center_dispm > (swd_side2[i][0]/sw_side2[i][0]))) &&
														((center_dispm > (swd_side[i][1]/sw_side[i][1])) || (center_dispm > (swd_side2[i][1]/sw_side2[i][1])))) {
													best_dir = i;
													avg_center = center_dispm;
													avg_disparity = lowest_near;
													avg_strength = (avg_side[1] > avg_side[0])?
															(    sw_side[i][0]/sww_side[i][0]) :
																(sw_side[i][1]/sww_side[i][1]);
												}
											}
										}
									}
									double disp_diff, kfg;
									if (best_dir >= 0) { // found at least one good direction
										// for BG use weighted average among near tiles (of 3x3) from the side which is lower
										// find BG weighted-average strength
										double disp_fg = avg_center + (avg_center - avg_disparity) * avg_strength / center_str;
										disp_diff = disp_fg - avg_disparity;
										kfg = (avg_center - avg_disparity) / disp_diff;
										// limit kfg
										if ((kfg < fsplit_kfg_min) || (kfg > (1.0 - fsplit_kfg_min))) {
											kfg = (kfg < fsplit_kfg_min) ? fsplit_kfg_min : (1.0 - fsplit_kfg_min);
											disp_fg =  (avg_center - avg_disparity)/kfg + avg_disparity;
											disp_diff = disp_fg - avg_disparity;
										}
										sel_out[tile] = true;
										if (dbg_thin_fg != null) {
											dbg_thin_fg[tile] = true;
										}
									} else { // not a thin FG over BG, just improve edge
										// Keep center_disp at least for now
										double sw = 0.0, swd = 0.0;
										double n_min = center_disp, n_max = center_disp; // min/max includes center
										for (int dir = 0; dir < 8; dir++)  if (neib_ds[dir] != null){
											sw +=  neib_ds[dir][1];
											swd += neib_ds[dir][1] * neib_ds[dir][0];
											num_neibs++;
											if (!(n_min <= neib_ds[dir][0])) {
												n_min = neib_ds[dir][0];
											}
											if (!(n_max >= neib_ds[dir][0])) {
												n_max = neib_ds[dir][0];
											}
										}
										double davg = swd / sw;
										double adiff = Math.abs(center_disp - davg);
										// see if it is concave/convex enough
										if ((adiff >= fsplit_adiff) && (adiff/davg >= fsplit_rdiff)) {
											sel_out[tile] = true;
											disp_diff = n_max - n_min;
											kfg = (center_disp - n_min) / disp_diff;
											if ((kfg < fsplit_kfg_min) || (kfg > (1.0 - fsplit_kfg_min))) {
												if (kfg < fsplit_kfg_min) {
													n_min = (center_disp - fsplit_kfg_min * n_max) / (1.0 - fsplit_kfg_min);
													kfg = fsplit_kfg_min;
												} else {
													n_max = (center_disp - fsplit_kfg_min * n_min) / (1.0 - fsplit_kfg_min);
													kfg = 1.0 - fsplit_kfg_min;
												}
												disp_diff = n_max - n_min;
											}
										} else {
											continue; // not a thin FG and not concave/convex enough
										}
									}
									// assuming avg_ds[0] will be used as target disparity
									far_fgbg[tile]  = new double[] {
											disp_diff * (1.0 - kfg),  // FD disparity - target disparity
											-disp_diff * kfg,         // BG disparity - target disparity
											kfg};
									sel_out[tile] = true;
								}								
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		} else if (split_src == 1) { // target_disparity is FG, selection[] is defined, avg_ds, fg_ds, bg_ds defined
			// assuming fg_ds[0] will be used as target disparity
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						for (int tile = ai.getAndIncrement(); tile < nTiles; tile = ai.getAndIncrement()) {
							if (selection[tile] && !Double.isNaN(fg_ds[0][tile]) && !Double.isNaN(bg_ds[0][tile])) { // selection should be defined as well as avg_ds and fg_ds
								double kfg = fg_ds[1][tile]/ (fg_ds[1][tile] + bg_ds[1][tile]);
								if ((kfg >= fsplit_kfg_min) && (kfg <= (1.0 - fsplit_kfg_min))) {
									far_fgbg[tile]  = new double[] {
											0.0,                              // FD disparity - target disparity
											bg_ds[0][tile] - fg_ds[0][tile],  // BG disparity - target disparity
											kfg};
									sel_out[tile] = true;
								}
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		} else if (split_src == 2) { // target_disparity is BG, selection[] is defined, avg_ds, fg_ds, bg_ds defined
			// assuming fg_ds[0] will be used as target disparity
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						for (int tile = ai.getAndIncrement(); tile < nTiles; tile = ai.getAndIncrement()) {
							if (selection[tile] && !Double.isNaN(fg_ds[0][tile]) && !Double.isNaN(bg_ds[0][tile])) { // selection should be defined as well as avg_ds and fg_ds
								double kfg = fg_ds[1][tile]/ (fg_ds[1][tile] + bg_ds[1][tile]);
								if ((kfg >= fsplit_kfg_min) && (kfg <= (1.0 - fsplit_kfg_min))) {
									far_fgbg[tile]  = new double[] {
											fg_ds[0][tile] - bg_ds[0][tile],  // FD disparity - target disparity
											0.0,                              // BG disparity - target disparity
											kfg};
									sel_out[tile] = true;
								}
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		}
		// TODO: add debug avg_ds[0], fg_ds[0], bg_ds[0], predicted fg_disp, bg_disp, far_fgbg (both), avg_ds[1], fg_ds[1], bg_ds[1],
		if (dbg_title != null) {
			String [] dbg_titles = {
					"avg_disp",   // 0
					"fg_disp",    // 1
					"bg_disp",    // 2
					"fg_pred",    // 3
					"bg_pred",    // 4
					"fg-targ",    // 5
					"bg-targ",    // 6
					"kfg",        // 7
					"avg_str",    // 8
					"fg_str",     // 9
					"bg_str",     //10
					"fg_pred_str",//11
					"bg_pred_str",//12
					"sel_thin"};  //13
			double [][] dbg_img = new double [dbg_titles.length][nTiles];
			for (int i = 0; i < dbg_img.length; i++) {
				Arrays.fill(dbg_img[i], Double.NaN);
			}
			dbg_img[0] = avg_ds[0];
			if (fg_ds != null) dbg_img[1] =  fg_ds[0];
			if (bg_ds != null) dbg_img[2] =  bg_ds[0];
			
			dbg_img[8] = avg_ds[1];
			if (fg_ds != null) dbg_img[9] =  fg_ds[1];
			if (bg_ds != null) dbg_img[10] =  bg_ds[1];
			
			ai.set(0);
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						for (int tile = ai.getAndIncrement(); tile < nTiles; tile = ai.getAndIncrement()) {
							dbg_img[13][tile] = (sel_out[tile] ? 0.5 : 0.0) +
							(dbg_thin_fg[tile] ? 0.6 : 0.0);
							if (far_fgbg[tile] != null) {
								double fg_targ =  far_fgbg[tile][0];
								double bg_targ =  far_fgbg[tile][1];
								double kfg =      far_fgbg[tile][2];
								dbg_img[5][tile] = fg_targ;
								dbg_img[6][tile] = bg_targ;
								dbg_img[7][tile] = kfg;
								if (fg_targ == 0) { // target is FG
									dbg_img[ 3][tile] = 0.0 +     fg_ds[0][tile];      // disparity(FG)
									dbg_img[ 4][tile] = bg_targ + fg_ds[0][tile];      // disparity(BG)
									dbg_img[11][tile] = avg_ds[1][tile] * kfg;         // strength(FG)
									dbg_img[12][tile] = avg_ds[1][tile] * (1.0 - kfg); // strength(BG)
								} else if (bg_targ == 0) { // target is BG
									dbg_img[ 3][tile] = fg_targ + bg_ds[0][tile];      // disparity(FG)
									dbg_img[ 4][tile] = 0.0 +     bg_ds[0][tile];      // disparity(BG)
									dbg_img[11][tile] = avg_ds[1][tile] * kfg;         // strength(FG)
									dbg_img[12][tile] = avg_ds[1][tile] * (1.0 - kfg); // strength(BG)
								} else { // target is unresolved merged maximums
									dbg_img[ 3][tile] = fg_targ + avg_ds[0][tile];     // disparity(FG)
									dbg_img[ 4][tile] = bg_targ + avg_ds[0][tile];     // disparity(BG)
									dbg_img[11][tile] = avg_ds[1][tile] * kfg;         // strength(FG)
									dbg_img[12][tile] = avg_ds[1][tile] * (1.0 - kfg); // strength(BG)
								}
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
			
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					tilesX,
					tilesY,
					true,
					dbg_title,
					dbg_titles); //	dsrbg_titles);
		}
		return sel_out;
	}
	
	
	public void intersceneMlExport(
			CLTParameters        clt_parameters,
			ErsCorrection        ers_reference,
			QuadCLT []           scenes,
			ColorProcParameters  colorProcParameters,
			double [][]          combo_dsn_final,
			int                  debug_level
			) {
		
		final int margin = 8;
		boolean add_combo =         clt_parameters.rig.mll_add_combo;         //true; add 121-st slice with combined pairs correlation
		boolean save_accum =        clt_parameters.rig.mll_save_accum;        //true;  // save accumulated 0-offset correlation
		boolean randomize_offsets = clt_parameters.rig.mll_randomize_offsets; // true; 
		double  disparity_low =     clt_parameters.rig.mll_disparity_low;     // -5.0;
		double  disparity_high =    clt_parameters.rig.mll_disparity_high;    // 5.0;
		double  disparity_pwr =     clt_parameters.rig.mll_disparity_pwr;     // 2.0;
		int     disparity_steps =   clt_parameters.rig.mll_disparity_steps;   // 20;
		double  tileMetaScale =     clt_parameters.rig.mll_tileMetaScale;     // 0.001;
		int     tileMetaSlice =     clt_parameters.rig.mll_tileMetaSlice;     // -1; // all slices
		int     tileStepX =         clt_parameters.rig.mll_tileStepX;         // 16;
		int     tileStepY =         clt_parameters.rig.mll_tileStepY;         // 16;
		String  suffix =            clt_parameters.rig.mll_suffix;            // "-ML";
		boolean show_input =        clt_parameters.img_dtt.lmamask_dbg; // true; // debug_level = -1;
		

		double  disp_ampl = Math.max(Math.abs(disparity_low),Math.abs(disparity_high));
		int      indx_ref = scenes.length - 1; // Always added to the end even if out-of order
		QuadCLT  ref_scene = scenes[indx_ref]; // ordered by increasing timestamps
		final int num_scenes =  indx_ref - ref_scene.getEarliestScene(scenes) + 1;
		final int tilesX = ref_scene.getTileProcessor().getTilesX();
		final int tilesY = ref_scene.getTileProcessor().getTilesY();
		final int tiles = tilesX * tilesY;

		// COMBO_DSN_TITLES
		// fix for old files
		if (combo_dsn_final.length < COMBO_DSN_TITLES.length) {
			System.out.println("=== Old data format, rebuilding blue sky ===");
			double[][] combo_dsn_final_was = combo_dsn_final;
			combo_dsn_final = new double[COMBO_DSN_TITLES.length][]; 
			for (int i = 0; i < combo_dsn_final_was.length; i++) {
				combo_dsn_final[i] = combo_dsn_final_was[i];
			}
			for (int i = combo_dsn_final_was.length; i < combo_dsn_final.length; i++) {
				combo_dsn_final[i] = new double[tiles];
				Arrays.fill(combo_dsn_final[i],Double.NaN);
			}
			/*
			boolean [] blue_sky = ref_scene.getBlueSky();
			if (blue_sky != null) {
				for (int i = 0; i < tiles; i++) {
					combo_dsn_final[COMBO_DSN_INDX_BLUE_SKY][i] = blue_sky[i] ? 1.0 : 0.0;
				}
			}
			*/
			if (ref_scene.hasBlueSky()) {
				combo_dsn_final[COMBO_DSN_INDX_BLUE_SKY] = ref_scene.getDoubleBlueSky();
			}
			
			
		}
		double  fat_zero_single = clt_parameters.getGpuFatZero(ref_scene.isMonochrome()); // for single scene
		ImageDtt image_dtt;
		image_dtt = new ImageDtt(
				numSens,
				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());
		image_dtt.getCorrelation2d(); // initiate image_dtt.correlation2d, needed if disparity_map != null  
		
		if (show_input) {
			//COMBO_DSN_TITLES
			ShowDoubleFloatArrays.showArrays(
					combo_dsn_final,
					tilesX,
					tilesY,
					true,
					"combo_dsn_final-"+ref_scene.getImageName(),
					COMBO_DSN_TITLES); //	dsrbg_titles);

		}
		
		if (save_accum) {
			int mcorr_sel = ImageDttParameters.corrSelEncodeAll(0); // all sensors
			float [][][] facc_2d_img = new float [1][][];
			// FIXME?: should work with non-matched
			boolean        no_map = true;
			boolean        show_2d_corr = false;
			if (clt_parameters.img_dtt.lmamask_dbg) {
				no_map = false;
				show_2d_corr = true;
			}
			
			double [][] disparity_map = correlateInterscene(
					clt_parameters, // final CLTParameters  clt_parameters,
					scenes,         // final QuadCLT []     scenes,
////				indx_ref,       // final int            indx_ref,
					scenes[indx_ref],// final QuadCLT        ref_scene,					
					combo_dsn_final[COMBO_DSN_INDX_DISP],   // final double []      disparity_ref,  // disparity in the reference view tiles (Double.NaN - invalid)
					null,           // final boolean []     selection, // may be null, if not null do not  process unselected tiles
					null,           // final double [][]    far_fgbg,        // null, or [nTile]{disp(fg)-disp(bg), str(fg)-str(bg)} hints for LMA FG/BG split 
					margin,         // final int            margin,
					-1,             // final int            nrefine, // just for debug title
					show_2d_corr,   // final boolean        show_2d_corr,
					mcorr_sel,      // final int            mcorr_sel, //  = 							
					facc_2d_img,    // final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)
					no_map,         // final boolean        no_map, // do not generate disparity_map (time-consuming LMA)
		            false,          // final boolean        use_rms, // DISPARITY_STRENGTH_INDEX means LMA RMS (18/04/2023)
					clt_parameters.img_dtt.lmamask_dbg? 1:(debug_level-8)); // final int            debug_level)
			if (disparity_map != null) {
				ShowDoubleFloatArrays.showArrays(
						disparity_map,
						tilesX,
						tilesY,
						true,
						"disparity_map_debug-"+ref_scene.getImageName(),
						image_dtt.getDisparityTitles()); //	dsrbg_titles);

			}
			float [][] corr_2d_img = facc_2d_img[0];					
			double [] target_disparity = combo_dsn_final[COMBO_DSN_INDX_DISP].clone();
			double [][] payload = {
					target_disparity,
					combo_dsn_final[COMBO_DSN_INDX_DISP],       // GT disparity
					combo_dsn_final[COMBO_DSN_INDX_STRENGTH],   // GT confidence
					combo_dsn_final[COMBO_DSN_INDX_LMA],        // disparity_lma
					combo_dsn_final[COMBO_DSN_INDX_VALID],      // frac_valid
					combo_dsn_final[COMBO_DSN_INDX_CHANGE],     // last_diff
					combo_dsn_final[COMBO_DSN_INDX_DISP_BG],    // cumulative BG disparity (from CM or POLY)
					combo_dsn_final[COMBO_DSN_INDX_STRENGTH_BG],// background strength
					combo_dsn_final[COMBO_DSN_INDX_LMA_BG],     // masked copy from BG disparity 
					combo_dsn_final[COMBO_DSN_INDX_CHANGE_BG],  // increment, BG
					combo_dsn_final[COMBO_DSN_INDX_DISP_FG],    //cumulative disparity (from CM or POLY), FG == COMBO_DSN_INDX_DISP
					combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL],// cumulative BG disparity (Use FG where no BG is available)
					combo_dsn_final[COMBO_DSN_INDX_BLUE_SKY]    // detected featureless sky - 1.0, reliable - 0.0, no data - NaN
					
			};
			for (int i = 0; i < payload.length; i++) {
				add_tile_meta(
						corr_2d_img,   // final float [][] fimg,
						tilesX,        // final int tilesX,
						tilesY,        // final int tilesY,
						tileStepX,     // final int stepX,
						tileStepY,     // final int stepY,
						tileMetaScale, //final double payload_scale,
						payload[i],    // final double [] payload,
						tileMetaSlice, // final int slice,
						i,             // final int offsX,
						tileStepY-1);  // final int offsY)
			}
			String [] titles = new String [corr_2d_img.length]; // dcorr_tiles[0].length];
			int ind_length = image_dtt.getCorrelation2d().getCorrTitles().length;

			System.arraycopy(image_dtt.getCorrelation2d().getCorrTitles(), 0, titles, 0, ind_length);
			for (int i = ind_length; i < titles.length; i++) {
				titles[i] = "combo-"+(i - ind_length);
			}
			ImageStack ml_stack = ShowDoubleFloatArrays.makeStack(
					corr_2d_img,                         // float[][] pixels,
					tilesX*(2*image_dtt.transform_size), // int width,
					tilesY*(2*image_dtt.transform_size), // int height,
					titles, // String [] titles,
					false); // boolean noNaN)
			String x3d_path = ref_scene.getX3dDirectory(true);
			String title = ref_scene.getImageName() + suffix+
					(ref_scene.isAux()?"-AUX":"-MAIN")+"-ACCUM";
			String mldir=ref_scene.correctionsParameters.mlDirectory;
			String aMldir=x3d_path + Prefs.getFileSeparator() + mldir;
			String file_path = aMldir + Prefs.getFileSeparator() + title + ".tiff";
			File dir = (new File(file_path)).getParentFile();
			if (!dir.exists()){
				dir.mkdirs();
			}
			ImagePlus imp_ml = new ImagePlus(title, ml_stack);
			imp_ml.setProperty("VERSION",             "2.0");
			//					imp_ml.setProperty("tileWidth",    ""+ml_width);
			imp_ml.setProperty("numScenes",           ""+num_scenes);
			imp_ml.setProperty("indexReference",      ""+indx_ref);
			imp_ml.setProperty("fatZero",             ""+fat_zero_single);
			imp_ml.setProperty("dispOffset",          ""+0);
			imp_ml.setProperty("tileMetaScale",       ""+tileMetaScale);
			imp_ml.setProperty("tileMetaSlice",       ""+tileMetaSlice);
			imp_ml.setProperty("tileStepX",           ""+tileStepX);
			imp_ml.setProperty("tileStepY",           ""+tileStepY);
			imp_ml.setProperty("metaTargetDisparity", ""+0);
			imp_ml.setProperty("metaGTDisparity",     ""+1);
			imp_ml.setProperty("metaGTConfidence",    ""+2);
			imp_ml.setProperty("metaGTDisparityLMA",  ""+3);
			imp_ml.setProperty("metaFracValid",       ""+4);
			imp_ml.setProperty("metaLastDiff",        ""+5);
			imp_ml.setProperty("metaBGDisparity",     ""+6);
			imp_ml.setProperty("metaBGConfidence",    ""+7);
			imp_ml.setProperty("metaBGDisparityLMA",  ""+8);
			imp_ml.setProperty("metaBGLastDiff",      ""+9);
			imp_ml.setProperty("metaFGDisparity",     ""+10); //=="metaGTDisparity"
			imp_ml.setProperty("metaBGDisparityAll",  ""+11);
			imp_ml.setProperty("metaBlueSky",         ""+12);
			JP46_Reader_camera.encodeProperiesToInfo(imp_ml);			
			FileSaver fs=new FileSaver(imp_ml);
			fs.saveAsTiff(file_path);
			System.out.println("intersceneExport(): saved "+file_path);
		}
		if ( clt_parameters.ofp.pattern_mode) {
			return; //  combo_dsn_final;
		}
		double [][] all_offsets = new double [disparity_steps][];
		String [] soffset_centers = new String [disparity_steps];
		for (int nstep = 0; nstep < disparity_steps; nstep++) {
			double [] disparity_offsets_rel = { // below, center, above
					(disparity_low + (disparity_high - disparity_low) * (nstep - 1) / (disparity_steps-1))/disp_ampl,
					(disparity_low + (disparity_high - disparity_low) * (nstep + 0) / (disparity_steps-1))/disp_ampl,
					(disparity_low + (disparity_high - disparity_low) * (nstep + 1) / (disparity_steps-1))/disp_ampl
			};
			double [] disparity_offset3 = new double [disparity_offsets_rel.length];
			for (int i = 0; i < disparity_offsets_rel.length; i++) {
				double dsgn = (disparity_offsets_rel[i] > 0.0) ? 1.0 : ((disparity_offsets_rel[i] < 0.0)? -1.0 : 0.0);
				disparity_offset3[i] = Math.pow(Math.abs(disparity_offsets_rel[i]), disparity_pwr) * dsgn * disp_ampl ;
			}
			if (disparity_offset3[0] > disparity_offset3[2]) { // can that happen?
				double d = disparity_offset3[0];
				disparity_offset3[0] =disparity_offset3[2];
				disparity_offset3[2] = d; 
			}
			double disparity_offset = disparity_offset3[1];
			if (debug_level > -2) {
				System.out.println("dispatity offset #"+(nstep + 1)+" (of "+disparity_steps+") = "+disparity_offset);
			}
			double [] disparity_offsets;
			if (randomize_offsets) {
				disparity_offsets = getLappedRandom(
						disparity_offset3[0], // double min_exclusive,
						disparity_offset3[2], // double max_exclusive,
						tiles); // int    nSamples);
			} else {
				disparity_offsets = new double[tiles];
				Arrays.fill(disparity_offsets, disparity_offset3[0]);
			}
			all_offsets[nstep] = disparity_offsets;
			
			float [][] corr_2d_img = generateOffset2DCorrelations(
					clt_parameters, // final CLTParameters  clt_parameters,
					scenes[indx_ref], // final QuadCLT        ref_scene,
					combo_dsn_final[COMBO_DSN_INDX_DISP], // final double []      disparity_ref_in,  // disparity in the reference view tiles (Double.NaN - invalid)
					disparity_offsets, // disparity_offset, // final double         disparity_offset,
					margin, // final int            margin,
					add_combo, // final boolean        add_combo,
					debug_level-9); // final int            debug_level);
			double [] target_disparity = combo_dsn_final[COMBO_DSN_INDX_DISP].clone();
			for (int i = 0; i < target_disparity.length; i++) {
				target_disparity[i]+= disparity_offsets[i];
			}
			double [][] payload = {
					target_disparity,
					combo_dsn_final[COMBO_DSN_INDX_DISP],       // GT disparity
					combo_dsn_final[COMBO_DSN_INDX_STRENGTH],   // GT confidence
					combo_dsn_final[COMBO_DSN_INDX_LMA],        // disparity_lma
					combo_dsn_final[COMBO_DSN_INDX_VALID],      // frac_valid
					combo_dsn_final[COMBO_DSN_INDX_CHANGE],     // last_diff
					combo_dsn_final[COMBO_DSN_INDX_DISP_BG],    // cumulative BG disparity (from CM or POLY)
					combo_dsn_final[COMBO_DSN_INDX_STRENGTH_BG],// background strength
					combo_dsn_final[COMBO_DSN_INDX_LMA_BG],     // masked copy from BG disparity 
					combo_dsn_final[COMBO_DSN_INDX_CHANGE_BG],  // increment, BG
					combo_dsn_final[COMBO_DSN_INDX_DISP_FG],    // cumulative disparity (from CM or POLY), FG == COMBO_DSN_INDX_DISP
					combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL],// cumulative BG disparity (Use FG where no BG is available)
					combo_dsn_final[COMBO_DSN_INDX_BLUE_SKY]    // detected featureless sky - 1.0, reliable - 0.0, no data - NaN
			};
			for (int i = 0; i < payload.length; i++) {
				add_tile_meta(
						corr_2d_img,   // final float [][] fimg,
						tilesX,        // final int tilesX,
						tilesY,        // final int tilesY,
						tileStepX,     // final int stepX,
						tileStepY,     // final int stepY,
						tileMetaScale, // final double payload_scale,
						payload[i],    // final double [] payload,
						tileMetaSlice, // final int slice,
						i,             // final int offsX,
						tileStepY-1);  // final int offsY)
			}
			
			String [] titles = new String [corr_2d_img.length]; // dcorr_tiles[0].length];
			int ind_length = image_dtt.getCorrelation2d().getCorrTitles().length;

			System.arraycopy(image_dtt.getCorrelation2d().getCorrTitles(), 0, titles, 0, ind_length);
			for (int i = ind_length; i < titles.length; i++) {
				titles[i] = "combo-"+(i - ind_length);
			}
			ImageStack ml_stack = ShowDoubleFloatArrays.makeStack(
					corr_2d_img,                         // float[][] pixels,
					tilesX*(2*image_dtt.transform_size), // int width,
					tilesY*(2*image_dtt.transform_size), // int height,
					titles, // String [] titles,
					false); // boolean noNaN)
			String x3d_path = ref_scene.getX3dDirectory(true);
			String sdisparity_offset = String.format("%8.3f", disparity_offset).trim();
			soffset_centers[nstep] = sdisparity_offset;

			String title = ref_scene.getImageName() + suffix+
					(ref_scene.isAux()?"-AUX":"-MAIN");
			if (randomize_offsets) {
				title+="-RND";
			}			
			title+="-DOFFS"+ sdisparity_offset;
			String mldir=ref_scene.correctionsParameters.mlDirectory;
			String aMldir=x3d_path + Prefs.getFileSeparator() + mldir;
			String file_path = aMldir + Prefs.getFileSeparator() + title + ".tiff";
			File dir = (new File(file_path)).getParentFile();
			if (!dir.exists()){
				dir.mkdirs();
			}
			ImagePlus imp_ml = new ImagePlus(title, ml_stack);
			imp_ml.setProperty("VERSION",             "2.0");
			imp_ml.setProperty("numScenes",           ""+num_scenes);
			imp_ml.setProperty("indexReference",      ""+indx_ref);
			imp_ml.setProperty("fatZero",             ""+fat_zero_single);
			imp_ml.setProperty("dispOffset",          ""+disparity_offset3[1]);
			imp_ml.setProperty("randomize_offsets",   ""+randomize_offsets);
			if (randomize_offsets) {
				imp_ml.setProperty("dispOffsetLow",   ""+disparity_offset3[0]);
				imp_ml.setProperty("dispOffsetHigh",  ""+disparity_offset3[2]);
			}
			imp_ml.setProperty("disparity_low",       ""+disparity_low);
			imp_ml.setProperty("disparity_high",      ""+disparity_high);
			imp_ml.setProperty("disparity_pwr",       ""+disparity_pwr);
			imp_ml.setProperty("disparity_steps",     ""+disparity_steps);
			imp_ml.setProperty("tileMetaScale",       ""+tileMetaScale);
			imp_ml.setProperty("tileMetaSlice",       ""+tileMetaSlice);
			imp_ml.setProperty("tileStepX",           ""+tileStepX);
			imp_ml.setProperty("tileStepY",           ""+tileStepY);
			imp_ml.setProperty("metaTargetDisparity", ""+0);
			imp_ml.setProperty("metaGTDisparity",     ""+1);
			imp_ml.setProperty("metaGTConfidence",    ""+2);
			imp_ml.setProperty("metaGTDisparityLMA",  ""+3);
			imp_ml.setProperty("metaFracValid",       ""+4);
			imp_ml.setProperty("metaLastDiff",        ""+5);
			imp_ml.setProperty("metaBGDisparity",     ""+6);
			imp_ml.setProperty("metaBGConfidence",    ""+7);
			imp_ml.setProperty("metaBGDisparityLMA",  ""+8);
			imp_ml.setProperty("metaBGLastDiff",      ""+9);
			imp_ml.setProperty("metaFGDisparity",     ""+10); //=="metaGTDisparity"
			imp_ml.setProperty("metaBGDisparityAll",  ""+11);
			imp_ml.setProperty("metaBlueSky",         ""+12);
			JP46_Reader_camera.encodeProperiesToInfo(imp_ml);			
			FileSaver fs=new FileSaver(imp_ml);
			fs.saveAsTiff(file_path);
			System.out.println("intersceneExport(): saved "+file_path);
		}
		if (disparity_steps > 0) {
			String offsets_suffix = "-DISP_OFFSETS";
			if (randomize_offsets) {
				offsets_suffix+="-RND";
			}			

			ref_scene.saveDoubleArrayInModelDirectory(
					offsets_suffix,      // String      suffix,
					soffset_centers,     // null,          // String []   labels, // or null
					all_offsets,         // dbg_data,         // double [][] data,
					tilesX,              // int         width,
					tilesY);             // int         height)
		}
		
	}
	
	/**
	 * Generate a pseudo-random values in the range (min_exclusive, max_exclusive) - both exclusive
	 * with a histogram being a shifted cosine, so an overlap of such distributions shifted by half-range
	 * will result in even distribution over the full range
	 * @param min_exclusive minimal value (exclusive)
	 * @param max_exclusive maximal value (exclusive)
	 * @param nSamples number of samples to generate
	 * @return nSamples-long array of values
	 */
	public static double [] getLappedRandom(
			double min_exclusive,
			double max_exclusive,
			int    nSamples) {
		return getLappedRandom(
				min_exclusive,
				max_exclusive,
				nSamples,
				1E-8,
				100);
	}

	public static double [] getLappedRandom(
			double min_exclusive,
			double max_exclusive,
			int    nSamples,
			double e,
			int    num_iter)
	{
		final double [] rslt = new double [nSamples];
		final double offset =    0.5 * (max_exclusive + min_exclusive);
		final double amplitude = 0.5 * (max_exclusive - min_exclusive);
		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 < nSamples; nTile = ai.getAndIncrement()) {
						double x = ThreadLocalRandom.current().nextDouble(); // only positives, will mirror later
						// use Newton method to invert x=y+sin(pi*y)/pi
						double y = 1 - Math.sqrt(1 - x); // initial approximation
						for (int n = 0; n < num_iter; n++) {
							double xi = y + Math.sin(Math.PI * y)/Math.PI;
							if (Math.abs (x - xi) < e) {
								break;
							}
							y += (x - xi) / (1.0 + Math.cos(Math.PI * y));
						}
						if (ThreadLocalRandom.current().nextBoolean()) { // negative values, now -1.0<y<1.0 
							y = -y; 
						}
						rslt[nTile] = offset + amplitude * y;
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return rslt;
	}
	
	
	/**
	 * Add per-tile metada into the gaps between the image tiles
	 * @param fimg    image to add metadata [slices][(tilesY*stepY)*(tilesX*stepX)
	 * @param tilesX  number of image tiles in X direction
	 * @param tilesY  number of image tiles in Y direction
	 * @param stepX   width of the image tile in pixels
	 * @param stepY   height of the image tile in pixels 
	 * @param payload_scale Multiply payload data to reduce its contrast (0.001) 
	 * @param payload metadata [tilesY*tilesX] to be embedded, one double per tile
	 * @param slice   image slice number to embedd (first index in fimg). Negative - embed into all slices
	 * @param offsX   pixel horizontal (righth) offset to place metadata inside each tile. Typical 0,1,2,3 ...
	 * @param offsY   pixel vertical (down) offset to place metadata inside each tile Typical 15
	 */
	public static void add_tile_meta(
			final float [][] fimg,
			final int tilesX,
			final int tilesY,
			final int stepX,
			final int stepY,
			final double payload_scale,
			final double [] payload,
			final int slice,
			final int offsX,
			final int offsY) {
		final int width = tilesX * stepX;
		final int offs = offsX + width*offsY;
		final int tiles = tilesX * tilesY; 
		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 < tiles; nTile = ai.getAndIncrement()) {
						int tileY = nTile / tilesX;  
						int tileX = nTile % tilesX;
						float d = (float) (payload_scale* payload[nTile]);
						if (slice >=0) {
							fimg[slice][offs + tileY * width * stepY + tileX * stepX]  = d;
						} else {
							for (int s = 0; s < fimg.length; s ++) {
								fimg[s][offs + tileY * width * stepY + tileX * stepX]  = d;
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
	}
	
	
	
	
	
	
	public void intersceneNoise(
			CLTParameters        clt_parameters,
			boolean              ref_only, // process only reference frame (false - inter-scene)
			ColorProcParameters  colorProcParameters,
			QuadCLT              ref_scene, // ordered by increasing timestamps
//			double []
			NoiseParameters		 noise_sigma_level,
			int                  noise_variant, // <0 - no-variants, compatible with old code			
			int                  debug_level
			)
	{
		System.out.println("IntersceneNoise(), scene timestamp="+ref_scene.getImageName());
		ErsCorrection ers_reference = ref_scene.getErsCorrection();
		String [] sts = ref_only ? (new String [0]) : ers_reference.getScenes();
		// get list of all other scenes
		int num_scenes = sts.length + 1;
		int indx_ref = num_scenes - 1; 
		QuadCLT [] scenes = new QuadCLT [num_scenes];
		scenes[indx_ref] = ref_scene;
		
		for (int i = 0; i < sts.length; i++) {
			scenes[i] = ref_scene.spawnQuadCLTWithNoise( // spawnQuadCLT(
					sts[i],
					clt_parameters,
					colorProcParameters, //
					noise_sigma_level,   // double []            noise_sigma_level,
					noise_variant,       // int                  noise_variant, // <0 - no-variants, compatible with old code
					ref_scene,          // QuadCLTCPU           ref_scene, // may be null if scale_fpn <= 0
					THREADS_MAX,
					-1); // debug_level);
			scenes[i].setDSRBG(
					clt_parameters, // CLTParameters  clt_parameters,
					THREADS_MAX,     // int            threadsMax,  // maximal number of threads to launch
					updateStatus,   // boolean        updateStatus,
					-1); // debug_level);    // int            debugLevel)
		}
//		String [] combo_dsn_titles = {"disp", "strength", "num_valid","change"};
		String [] combo_dsn_titles = {"disp", "strength","disp_lma","num_valid","change"};
		int combo_dsn_indx_disp =     0; // cumulative disparity (from CM or POLY)
		int combo_dsn_indx_strength = 1;
		int combo_dsn_indx_lma =      2; // masked copy from 0 - cumulative disparity
		int combo_dsn_indx_valid =    3; // initial only
		int combo_dsn_indx_change =   4; // increment
		boolean read_nonoise_lma =clt_parameters.correlate_lma || true; // read always
//		final String [] iter_titles = {"disp", "diff", "strength","disp_lma"};
		final int [] iter_indices = {
				combo_dsn_indx_disp,
				combo_dsn_indx_strength,
				combo_dsn_indx_lma,
				combo_dsn_indx_change}; // which to save for each iteration: {"disp", "strength","disp_lma","change"};
		final int [] initial_indices = {
				combo_dsn_indx_disp,
				combo_dsn_indx_strength,
				combo_dsn_indx_valid}; // initial: "disp", "strength","num_valid"
		double [][] combo_dsn =  null;
		if (noise_sigma_level == null) {
			double[][] combo_dsn0 = prepareInitialComboDS( // 3
					clt_parameters,   // final CLTParameters       clt_parameters,
					scenes,           // final QuadCLT []          scenes,
////					indx_ref,         // final int                 indx_ref,
        			scenes[indx_ref], // final QuadCLT             ref_scene,
					debug_level-2);     // final int                 debug_level);
			combo_dsn = new double[combo_dsn_titles.length - 1][];
			for (int i = 0; i < combo_dsn0.length; i++) {
				combo_dsn[initial_indices[i]] = combo_dsn0[i]; // "disp", "strength", <null>, "num_valid"
			}
			
		} else {
			combo_dsn = ref_scene.readDoubleArrayFromModelDirectory( //"disp", "strength","disp_lma","num_valid"
					"-results-nonoise" + (read_nonoise_lma?"-lma":"-nolma"), // String      suffix,
					combo_dsn_titles.length - 1, // 4
					null); // int []      wh);
		}
		
		
		
//		final double [][] combo_dsn_change = new double [combo_dsn.length+1][];
		final int margin = 8;
		final int tilesX = ref_scene.getTileProcessor().getTilesX();
		final int tilesY = ref_scene.getTileProcessor().getTilesY();
		if (debug_level > 0) {
			int        extra = 10; // pixels around largest outline
			int        scale = 4;

			int        line_width_outline = 1;
			int        line_width_corners = 3;
			Color      line_color_outline = new Color(0, 255, 0);   // green
			Color      line_color_corners = new Color(0, 255, 255); // cyan

			// generating scene outlines for results documentation
			ImagePlus imp_outlines = generateSceneOutlines(
					ref_scene,          // QuadCLT    ref_scene, // ordered by increasing timestamps
					scenes,             // QuadCLT [] scenes
					extra,              // int        extra // add around largest outline
					scale,              // int        scale,
					line_width_outline, // int        line_width_outline,
					line_width_corners, // int        line_width_corners,
					line_color_outline, // Color      line_color_outline,
					line_color_corners  // Color      line_color_corners
					);		
			imp_outlines.show();
		}
		final double [][] combo_dsn_change = new double [combo_dsn_titles.length] [tilesX*tilesY];
		for (int i = 0; i < combo_dsn.length; i++) { // 4 elements: "disp", "strength","disp_lma","num_valid"
			if (combo_dsn[i] != null) combo_dsn_change[i] = combo_dsn[i]; // all but change
		}
		if (noise_sigma_level != null) { // add initial offset to the expected disparity
			for (int i = 0; i < combo_dsn_change[0].length; i++) {
				combo_dsn_change[combo_dsn_indx_disp][i] += noise_sigma_level.initial_offset; //initial offset
			}
		}
//		combo_dsn_change[combo_dsn_change.length - 1] = new double [tilesX*tilesY];
		if (debug_level > 0) {
			ShowDoubleFloatArrays.showArrays(
					combo_dsn_change,
					tilesX,
					tilesY,
					true,
					"combo_dsn-initial"+ref_scene.getImageName(),
					combo_dsn_titles); //	dsrbg_titles);
		}
		
		final int max_refines = 10;
//		final String [] iter_titles = {"disp", "diff", "strength"};
		final int last_slices = combo_dsn_titles.length;
		final int last_initial_slices = last_slices + initial_indices.length;
		
//		final double [][] refine_results = new double [last_slices + 3 * (max_refines + 1)][];
		final double [][] refine_results = new double [last_slices + 4 * (max_refines + 1)][];
		String [] refine_titles = new String [refine_results.length];
		for (int i = 0; i < combo_dsn_titles.length; i++) {
			refine_results[i] = combo_dsn_change[i]; // first 5 - references to 5-element combo_dsn_change
			refine_titles[i] = combo_dsn_titles[i]+"-last"; // "disp", "strength","disp_lma","num_valid","change"
		}
		for (int i = 0; i < initial_indices.length; i++) {
			refine_titles[last_slices + i] = combo_dsn_titles[initial_indices[i]]+"-initial"; // "disp", "strength","num_valid"
			if (combo_dsn_change[initial_indices[i]] != null) {
				refine_results[last_slices + i] = combo_dsn_change[initial_indices[i]].clone();
			} else {
				refine_results[last_slices + i] = new double [tilesX * tilesY];
			}
		}
		for (int nrefine = 0; nrefine < max_refines; nrefine++) {
			for (int i = 0; i < iter_indices.length; i++) {
				refine_titles[last_initial_slices + i * max_refines + nrefine ] = combo_dsn_titles[iter_indices[i]]+"-"+nrefine;
			}
		}		
		for (int nrefine = 0; nrefine < max_refines; nrefine++) {
			int mcorr_sel = ImageDttParameters.corrSelEncode(clt_parameters.img_dtt,scenes[indx_ref].getNumSensors());
			// FIXME: 							null,           // final boolean []     selection, // may be null, if not null do not  process unselected tiles
			double [][] disparity_map = 
					correlateInterscene(
							clt_parameters, // final CLTParameters  clt_parameters,
							scenes,         // final QuadCLT []     scenes,
////						indx_ref,       // final int            indx_ref,
							scenes[indx_ref],// final QuadCLT        ref_scene,					
							combo_dsn_change[0],   // final double []      disparity_ref,  // disparity in the reference view tiles (Double.NaN - invalid)
							null,           // final boolean []     selection, // may be null, if not null do not  process unselected tiles
							null,           // final double [][]    far_fgbg,        // null, or [nTile]{disp(fg)-disp(bg), str(fg)-str(bg)} hints for LMA FG/BG split 
							margin,         // final int            margin,
							nrefine,        // final int            nrefine, // just for debug title
							( nrefine == (max_refines - 1)) && clt_parameters.inp.show_final_2d, // final boolean        show_2d_corr,
					        mcorr_sel,      // final int            mcorr_sel, //  = 							
							null,           // final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)
							false,          // final boolean        no_map, // do not generate disparity_map (time-consuming LMA)
				            false,          // final boolean        use_rms, // DISPARITY_STRENGTH_INDEX means LMA RMS (18/04/2023)
							debug_level-5);   // final int            debug_level)

			if (debug_level > 0) {
				ShowDoubleFloatArrays.showArrays(
						disparity_map,
						tilesX,
						tilesY,
						true,
						"accumulated_disparity_map-"+nrefine,
						ImageDtt.getDisparityTitles(ref_scene.getNumSensors(),ref_scene.isMonochrome()) // ImageDtt.DISPARITY_TITLES
						);
			}
			// update disparities
			double [] map_disparity =     disparity_map[ImageDtt.DISPARITY_INDEX_CM]; // 2
			double [] map_strength =      disparity_map[ImageDtt.DISPARITY_STRENGTH_INDEX]; // 10
			double [] map_disparity_lma = disparity_map[ImageDtt.DISPARITY_INDEX_POLY]; // 8
			for (int nTile =0; nTile < combo_dsn_change[0].length; nTile++) {
				if (!Double.isNaN(combo_dsn_change[0][nTile])) {
					if ((map_disparity_lma != null) && !Double.isNaN(map_disparity_lma[nTile])) {
						combo_dsn_change[combo_dsn_indx_change][nTile] = map_disparity_lma[nTile];
					} else if (!Double.isNaN(map_disparity[nTile])) {
						combo_dsn_change[combo_dsn_indx_change][nTile] = map_disparity[nTile] / clt_parameters.ofp.magic_scale;
					}
					if (!Double.isNaN(combo_dsn_change[combo_dsn_indx_change][nTile])) {
						combo_dsn_change[combo_dsn_indx_disp][nTile] +=     combo_dsn_change[combo_dsn_indx_change][nTile]; 
						combo_dsn_change[combo_dsn_indx_strength][nTile]  = map_strength[nTile]; // combine CM/LMA
					}
				}
			}
			// Copy disparity to sisparity_lma and just mask out  tiles with no DMA data (keep all if LMA did not run at all)  
			System.arraycopy(combo_dsn_change[combo_dsn_indx_disp], 0, combo_dsn_change[combo_dsn_indx_lma], 0, combo_dsn_change[combo_dsn_indx_disp].length); // lma
			if (map_disparity_lma != null) { 
				for (int i = 0; i < map_disparity_lma.length; i++) {
					if (Double.isNaN(map_disparity_lma[i])) {
						combo_dsn_change[combo_dsn_indx_lma][i] = Double.NaN;		
					}
				}
			}
			
			for (int i = 0; i < iter_indices.length; i++) {
				refine_results[last_initial_slices + (i * max_refines) + nrefine] = combo_dsn_change[iter_indices[i]].clone();
			}
			//					clt_parameters.inp.show_final_2d, // final boolean        show_2d_corr,
			if (debug_level >0) {
				ShowDoubleFloatArrays.showArrays(
						combo_dsn_change,
						tilesX,
						tilesY,
						true,
						"combo_dsn-"+nrefine+"-"+ref_scene.getImageName(),
						combo_dsn_titles); //	dsrbg_titles);
			}
		}
		
		if (debug_level > 0) {
			ShowDoubleFloatArrays.showArrays(
					refine_results,
					tilesX,
					tilesY,
					true,
					"combo-"+max_refines+"-"+ref_scene.getImageName(),
					refine_titles); //	dsrbg_titles);
		}
		//noise_sigma_level
		String rslt_suffix = "-results-nonoise";
		if (noise_sigma_level != null) {
			rslt_suffix =
//					"-results-lev_"+noise_sigma_level[0]+
					"-results-rnd_"+noise_sigma_level.scale_random+
					"-fpn_"+        noise_sigma_level.scale_fpn+
					"-sigma_"+      noise_sigma_level.sigma+ // [1]+
					"-offset"+      noise_sigma_level.initial_offset+ // [2];
					"-sensors"+     noise_sigma_level.used_sensors;
			
			if (ref_only) {
				rslt_suffix +="-nointer";
			} else {
				rslt_suffix +="-inter";
			}
			//rslt_suffix +="-mask"+clt_parameters.img_dtt.dbg_pair_mask;
		}
		rslt_suffix += (clt_parameters.correlate_lma?"-lma":"-nolma");
		if (noise_variant >= 0) {
			rslt_suffix +="-variant"+noise_variant;
		}
		
		//			int                  noise_variant, // <0 - no-variants, compatible with old code			

		ref_scene.saveDoubleArrayInModelDirectory(
				rslt_suffix,         // String      suffix,
				refine_titles,       // null,          // String []   labels, // or null
				refine_results,      // dbg_data,         // double [][] data,
				tilesX,              // int         width,
				tilesY);             // int         height)
		// save combo_dsn_change to model directory
		if (debug_level >-100) {
			return;
		}
		
		System.out.println("IntersceneAccumulate(), got previous scenes: "+sts.length);
		if (debug_level > 1) { // tested OK
			System.out.println("IntersceneAccumulate(): preparing image set...");
			int nscenes = scenes.length;
			//			int indx_ref = nscenes - 1; 
			double [][][] all_scenes_xyzatr = new double [scenes.length][][]; // includes reference (last)
			double [][][] all_scenes_ers_dt = new double [scenes.length][][]; // includes reference (last)
			all_scenes_xyzatr[indx_ref] = new double [][] {ZERO3,ZERO3};
			all_scenes_ers_dt[indx_ref] = new double [][] {
				ers_reference.getErsXYZ_dt(),
				ers_reference.getErsATR_dt()};
				
				for (int i = 0; i < nscenes; i++) if (i != indx_ref) {
					String ts = scenes[i].getImageName();
					all_scenes_xyzatr[i] = new double[][] {ers_reference.getSceneXYZ(ts),       ers_reference.getSceneATR(ts)}; 		
					all_scenes_ers_dt[i] = new double[][] {ers_reference.getSceneErsXYZ_dt(ts), ers_reference.getSceneErsATR_dt(ts)}; 		
				}
				compareRefSceneTiles(
						"" ,               // String suffix,
						true, // false,             // boolean blur_reference,
						all_scenes_xyzatr, // double [][][] scene_xyzatr, // does not include reference
						all_scenes_ers_dt, // double [][][] scene_ers_dt, // does not include reference
						scenes,            // QuadCLT [] scenes,
						8);                // int iscale) // 8
		}
		// create initial disparity map for the reference scene
		
	}
	
	@Deprecated
	public static double [][] prepareInitialComboDS(
			final CLTParameters       clt_parameters,
			final QuadCLT []          scenes,
			final int                 indx_ref,
			final int                 debug_level)
	{
		final QuadCLT ref_scene = scenes[indx_ref];
		final int num_scenes = scenes.length+0;
		final double [][][] initial_ds = new double[num_scenes][][];
		final ErsCorrection ers_reference = ref_scene.getErsCorrection();
		for (int i = 0; i < num_scenes; i++) if (scenes[i] != null) {
			initial_ds[i] = conditionInitialDS(
					clt_parameters, // CLTParameters  clt_parameters,
					scenes[i],      // QuadCLT        scene,
					-1);            // int debug_level);
		}
		if (debug_level > -1) {
			String []   dbg_titles = new String [2*num_scenes];
			double [][] dbg_img =    new double [2*num_scenes][];
			for (int i = 0; i < num_scenes; i++) if (scenes[i] != null) {
				dbg_titles[i + 0] =          "d-"+scenes[i].getImageName();
				dbg_titles[i + num_scenes] = "s-"+scenes[i].getImageName();
				if (initial_ds[i] != null) {
					dbg_img   [i + 0] =          initial_ds[i][0];
					dbg_img   [i + num_scenes] = initial_ds[i][1];
				}
			}
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					ref_scene.getTileProcessor().getTilesX(),
					ref_scene.getTileProcessor().getTilesY(),
					true,
					"all_set_fill_dispatity_gaps"+ref_scene.getImageName(),
					dbg_titles); //	dsrbg_titles);
		}

		final int         margin = 8;
		final double      tolerance_ref_absolute = 0.2; // how much scene disparity may diverge from predicted from reference
		final double      tolerance_ref_relative = 0.02;
		final double      tolerance_scene_absolute = 0.2;   // how much 4 bi-linear interpolation corners may differ (remove extremes while not)
		final double      tolerance_scene_relative = 0.02;
		double [][][] initial_toref_ds = new double[num_scenes][][];
		initial_toref_ds[indx_ref] = initial_ds[indx_ref];
		// reference camera ERS should be already set
		for (int i = 0; i < num_scenes; i++) if ((i != indx_ref) && (initial_ds[i] != null) && (scenes[i] != null)) {
			String ts = scenes[i].getImageName();
			double []   scene_xyz = ers_reference.getSceneXYZ(ts);
			double []   scene_atr = ers_reference.getSceneATR(ts);
			if ((scene_xyz != null) && (scene_atr != null)) { // skip mission scenes (now all but reference)
				double []   scene_ers_xyz_dt = ers_reference.getSceneErsXYZ_dt(ts);
				double []   scene_ers_atr_dt = ers_reference.getSceneErsATR_dt(ts);
				initial_toref_ds[i] = getSceneDisparityStrength(
						clt_parameters, // final CLTParameters       clt_parameters,
						true,                      // final boolean     to_ref_disparity, // false - return scene disparity, true - convert disparity back to the reference scene
						initial_ds[indx_ref][0],   // final double []   disparity_ref,   // invalid tiles - NaN in disparity
						initial_ds[i][0],          // final double []   disparity_scene, // invalid tiles - NaN in disparity (just for masking out invalid scene tiles)
						initial_ds[i][1],          // initial_ds[i][1],          // final double []   strength_scene,  // to calculate interpolated strength
						scene_xyz,                 // final double []   scene_xyz, // camera center in world coordinates
						scene_atr,                 // final double []   scene_atr, // camera orientation relative to world frame
						scene_ers_xyz_dt,          // final double []   scene_ers_xyz_dt, // camera ERS linear
						scene_ers_atr_dt,          // final double []   scene_ers_atr_dt, // camera ERS linear
						scenes[i],                 // final QuadCLT     scene_QuadClt,
						scenes[indx_ref],          // final QuadCLT     reference_QuadClt,
						margin, // final int         margin,
						tolerance_ref_absolute,    // final double      tolerance_ref_absolute,
						tolerance_ref_relative,    // final double      tolerance_ref_relative,
						tolerance_scene_absolute,  // final double      tolerance_scene_absolute, // of 4 bi-linear interpolation corners
						tolerance_scene_relative); // final double      tolerance_scene_relative)  // of 4 bi-linear interpolation corners
			}
		}		
		if (debug_level > -1) { // **** Used to create images for report !
			String []   dbg_titles = new String [2*num_scenes];
			double [][] dbg_img =    new double [2*num_scenes][];
			for (int i = 0; i < num_scenes; i++) if (scenes[i] != null) {
				dbg_titles[i + 0] =          "d-"+scenes[i].getImageName();
				dbg_titles[i + num_scenes] = "s-"+scenes[i].getImageName();
				if (initial_toref_ds[i] != null) {
					dbg_img   [i + 0] =          initial_toref_ds[i][0];
					dbg_img   [i + num_scenes] = initial_toref_ds[i][1];
				}
			}
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					ref_scene.getTileProcessor().getTilesX(),
					ref_scene.getTileProcessor().getTilesY(),
					true,
					"mapped_scenes_disparity_strength"+ref_scene.getImageName(),
					dbg_titles); //	dsrbg_titles);
		}
		//ref_scene
		final int tilesX = ref_scene.getTileProcessor().getTilesX();
		final int tilesY = ref_scene.getTileProcessor().getTilesY();
		final int tiles =tilesX * tilesY;

		final double zero_strength = 0.05; // add where disparity exists and strength is 0.0
		final double [][] combo_dsn = new double[3][tiles];
		Arrays.fill(combo_dsn[0], Double.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 < tiles; nTile = ai.getAndIncrement()) {
						int ndef = 0;
						double sw = 0.0, swd = 0.0;
						for (int i = 0; i < initial_toref_ds.length; i++) if (initial_toref_ds[i] != null){
							double d = initial_toref_ds[i][0][nTile];
							double w = initial_toref_ds[i][1][nTile]+zero_strength;
							if (!Double.isNaN(d)) {
								sw += w;
								swd += w * d;
								ndef++;
							}
							if (ndef > 0) {
								combo_dsn[0][nTile] = swd/sw;
								combo_dsn[1][nTile] = sw/ndef;
								combo_dsn[2][nTile] = 1.0*ndef/initial_toref_ds.length;
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return combo_dsn;
	}
	
	public static double [][] prepareInitialComboDS(
			final CLTParameters       clt_parameters,
			final QuadCLT []          scenes,
			final QuadCLT             ref_scene,
			final int                 debug_level)
	{
//		final QuadCLT ref_scene = scenes[indx_ref];
		final int num_scenes = scenes.length+0;
		final double [][][] initial_ds = new double[num_scenes][][];
		double [][] initial_ds_ref = null; //  = new double[num_scenes][][];
		
		final ErsCorrection ers_reference = ref_scene.getErsCorrection();
		for (int i = 0; i < num_scenes; i++) if (scenes[i] != null) { // only ref has dsi here!
			initial_ds[i] = conditionInitialDS(
					clt_parameters, // CLTParameters  clt_parameters,
					scenes[i],      // QuadCLT        scene,
					-1);            // int debug_level);
			if (scenes[i] == ref_scene) {
				initial_ds_ref = initial_ds[i];
			}
		}
		if (initial_ds_ref == null) { // ref scene is not in scenes
			initial_ds_ref = conditionInitialDS(
					clt_parameters, // CLTParameters  clt_parameters,
					ref_scene,      // QuadCLT        scene,
					-1);            // int debug_level);
		}
		if (debug_level > -1) {
			String []   dbg_titles = new String [2*(num_scenes+1)];
			double [][] dbg_img =    new double [2*(num_scenes+1)][];
			for (int i = 0; i < num_scenes; i++) if (scenes[i] != null) {
				dbg_titles[i + 0] =          "d-"+scenes[i].getImageName();
				dbg_titles[i + (num_scenes+1)] = "s-"+scenes[i].getImageName();
				if (initial_ds[i] != null) {
					dbg_img   [i + 0] =          initial_ds[i][0];
					dbg_img   [i + (num_scenes+1)] = initial_ds[i][1];
				}
			}
			dbg_titles[num_scenes] =          "d-ref-"+ref_scene.getImageName();
			dbg_titles[num_scenes + (num_scenes+1)] = "s-ref-"+ref_scene.getImageName();
			if (initial_ds_ref != null) {
				dbg_img   [num_scenes + 0] =          initial_ds_ref[0];
				dbg_img   [num_scenes + (num_scenes+1)] = initial_ds_ref[1];
			}
			
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					ref_scene.getTileProcessor().getTilesX(),
					ref_scene.getTileProcessor().getTilesY(),
					true,
					"all_set_fill_dispatity_gaps"+ref_scene.getImageName(),
					dbg_titles); //	dsrbg_titles);
		}

		final int         margin = 8;
		final double      tolerance_ref_absolute = 0.2; // how much scene disparity may diverge from predicted from reference
		final double      tolerance_ref_relative = 0.02;
		final double      tolerance_scene_absolute = 0.2;   // how much 4 bi-linear interpolation corners may differ (remove extremes while not)
		final double      tolerance_scene_relative = 0.02;
		double [][][] initial_toref_ds = new double[num_scenes][][];
		
///		initial_toref_ds[indx_ref] = initial_ds[indx_ref];
		// reference camera ERS should be already set
///		for (int i = 0; i < num_scenes; i++) if ((i != indx_ref) && (initial_ds[i] != null) && (scenes[i] != null)) {
		for (int i = 0; i < num_scenes; i++) if ((scenes[i] != ref_scene) && (initial_ds[i] != null) && (scenes[i] != null)) { // never
			String ts = scenes[i].getImageName();
			double []   scene_xyz = ers_reference.getSceneXYZ(ts);
			double []   scene_atr = ers_reference.getSceneATR(ts);
			if ((scene_xyz != null) && (scene_atr != null)) { // skip mission scenes (now all but reference)
				double []   scene_ers_xyz_dt = ers_reference.getSceneErsXYZ_dt(ts);
				double []   scene_ers_atr_dt = ers_reference.getSceneErsATR_dt(ts);
				initial_toref_ds[i] = getSceneDisparityStrength(
						clt_parameters, // final CLTParameters       clt_parameters,
						true,                      // final boolean     to_ref_disparity, // false - return scene disparity, true - convert disparity back to the reference scene
//						initial_ds[indx_ref][0],   // final double []   disparity_ref,   // invalid tiles - NaN in disparity
						initial_ds_ref[0],         // final double []   disparity_ref,   // invalid tiles - NaN in disparity
						initial_ds[i][0],          // final double []   disparity_scene, // invalid tiles - NaN in disparity (just for masking out invalid scene tiles)
						initial_ds[i][1],          // initial_ds[i][1],          // final double []   strength_scene,  // to calculate interpolated strength
						scene_xyz,                 // final double []   scene_xyz, // camera center in world coordinates
						scene_atr,                 // final double []   scene_atr, // camera orientation relative to world frame
						scene_ers_xyz_dt,          // final double []   scene_ers_xyz_dt, // camera ERS linear
						scene_ers_atr_dt,          // final double []   scene_ers_atr_dt, // camera ERS linear
						scenes[i],                 // final QuadCLT     scene_QuadClt,
//						scenes[indx_ref],          // final QuadCLT     reference_QuadClt,
						ref_scene,                 // final QuadCLT     reference_QuadClt,
						margin, // final int         margin,
						tolerance_ref_absolute,    // final double      tolerance_ref_absolute,
						tolerance_ref_relative,    // final double      tolerance_ref_relative,
						tolerance_scene_absolute,  // final double      tolerance_scene_absolute, // of 4 bi-linear interpolation corners
						tolerance_scene_relative); // final double      tolerance_scene_relative)  // of 4 bi-linear interpolation corners
			}
		}		
		if (debug_level > -1) { // **** Used to create images for report !
			String []   dbg_titles = new String [2*(num_scenes+1)];
			double [][] dbg_img =    new double [2*(num_scenes+1)][];
			for (int i = 0; i < num_scenes; i++) if (scenes[i] != null) {
				dbg_titles[i + 0] =              "d-"+scenes[i].getImageName();
				dbg_titles[i + num_scenes + 1] = "s-"+scenes[i].getImageName();
				if (initial_toref_ds[i] != null) {
					dbg_img   [i + 0] =            initial_toref_ds[i][0];
					dbg_img   [i + num_scenes+1] = initial_toref_ds[i][1];
				}
			}
			dbg_titles[num_scenes] =                  "d-ref-"+ref_scene.getImageName();
			dbg_titles[num_scenes + (num_scenes+1)] = "s-ref-"+ref_scene.getImageName();
			if (initial_ds_ref != null) {
				dbg_img   [num_scenes + 0] =              initial_ds_ref[0];
				dbg_img   [num_scenes + (num_scenes+1)] = initial_ds_ref[1];
			}
			
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					ref_scene.getTileProcessor().getTilesX(),
					ref_scene.getTileProcessor().getTilesY(),
					true,
					"mapped_scenes_disparity_strength"+ref_scene.getImageName(),
					dbg_titles); //	dsrbg_titles);
		}
		//ref_scene
		final int tilesX = ref_scene.getTileProcessor().getTilesX();
		final int tilesY = ref_scene.getTileProcessor().getTilesY();
		final int tiles =tilesX * tilesY;

		final double zero_strength = 0.05; // add where disparity exists and strength is 0.0
		final double [][] combo_dsn = new double[3][tiles];
		Arrays.fill(combo_dsn[0], Double.NaN);
		final Thread[] threads = ImageDtt.newThreadArray(THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final double [][] initial_ds_ref_final = initial_ds_ref; 
		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()) {
						int ndef = 0;
						double sw = 0.0, swd = 0.0;
						for (int i = 0; i < initial_toref_ds.length; i++) if (initial_toref_ds[i] != null){
							double d = initial_toref_ds[i][0][nTile];
							double w = initial_toref_ds[i][1][nTile]+zero_strength;
							if (!Double.isNaN(d)) {
								sw += w;
								swd += w * d;
								ndef++;
							}
						}
						if (ndef > 0) {
							combo_dsn[0][nTile] = swd/sw;
							combo_dsn[1][nTile] = sw/ndef;
							combo_dsn[2][nTile] = 1.0*ndef/initial_toref_ds.length;
						} else {
							// 11/23/2025 - did not understand how it was original, just copying
							combo_dsn[0][nTile] = initial_ds_ref_final[0][nTile];
							combo_dsn[1][nTile] = initial_ds_ref_final[1][nTile];;
							combo_dsn[2][nTile] = 1.0;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		if (debug_level > -1) {
			ShowDoubleFloatArrays.showArrays(
					combo_dsn,
					ref_scene.getTileProcessor().getTilesX(),
					ref_scene.getTileProcessor().getTilesY(),
					true,
					"combo_dsn-"+ref_scene.getImageName(),
					new String[] {"swd/sw","sw/ndef","1.0*ndef/initial_toref_ds.length"}); //	dsrbg_titles);
		}		
		
		return combo_dsn;
	}
	
	/**
	 * Limit scenes list size for LY adjustment, preserve reference scene, throw away random 
	 * @param max_num_scenes maximal number of scenes
	 * @param scenes     array of QuadCLT scenes
	 * @param indx_ref   int[1] - will return index of the reference scene in the new list
	 * @return ordered array of QuadCLT scenes, including reference scene, limited by max_num_scenes. contains reference scene
	 */
	public static QuadCLT [] limitScenes(
			final int            max_num_scenes,
			final QuadCLT []     scenes,
			final QuadCLT        ref_scene) {
		if (scenes.length <= max_num_scenes) {
			return scenes;
		}
//		final QuadCLT ref_scene = scenes[indx_ref];
		final ErsCorrection ers_reference = ref_scene.getErsCorrection();
		final String ref_name = ref_scene.getImageName();
		ArrayList<Integer> scene_list = new ArrayList<Integer>();
		int indx_ref = -1;
		for (int nscene = 0; nscene < scenes.length; nscene++) { // if (nscene != indx_ref){ // should be for ref_index
			String ts = scenes[nscene].getImageName();
			if (ref_name.equals(ts)) {
				indx_ref  = nscene; // later add reference scene
			} else {
				if ((ers_reference.getSceneXYZ(ts) != null) && (ers_reference.getSceneATR(ts) != null)){
					scene_list.add(nscene); // scene is not matched
				}
			}
		}
		Random r= new Random();
		int add_ref = (indx_ref >= 0) ? 1 : 0;
		while ((scene_list.size() + add_ref) > max_num_scenes) {
			scene_list.remove(r.nextInt(scene_list.size()));
		}
		if (indx_ref >= 0) {
			scene_list.add(indx_ref);
		}
		Collections.sort(scene_list);
		QuadCLT [] filtered_scenes = new QuadCLT[scene_list.size()];
		for (int indx = 0; indx < filtered_scenes.length; indx++) {
			filtered_scenes[indx] = scenes[scene_list.get(indx)];
		}
		return filtered_scenes;
	}
	
	public static double [][] intepolateSceneDisparity(
			final CLTParameters  clt_parameters,
			final QuadCLT []     scenes,
			final int            indx_ref,
			final double []      disparity_ref,  // disparity in the reference view tiles (Double.NaN - invalid)
			final int            debug_level
			)
	{
		final int scene_extrap_irad = 1;
		final double scene_extrap_rad = scene_extrap_irad + 0.5;
		final QuadCLT ref_scene = scenes[indx_ref];
		final ErsCorrection ers_reference = ref_scene.getErsCorrection();
		final int tilesX = ref_scene.getTileProcessor().getTilesX();
		final int tilesY = ref_scene.getTileProcessor().getTilesY();
		final int tileSize = ref_scene.getTileProcessor().getTileSize();
		double max_rad2 = scene_extrap_rad * scene_extrap_rad * tileSize * tileSize; // in pixels
		final double [][] disparity_scenes = new double [scenes.length][]; // [tilesX*tilesY];
//		final double [][] ref_pXpYD = new double[3][];
		final int dbg_tileX=-70;
		final int dbg_tileY=-19;
		final int dbg_tile=dbg_tileY * tilesX + dbg_tileX;
		if (debug_level > -1) {
			System.out.print("Correlating scene ");
		}
		for (int nscene = scenes.length-1; nscene >= 0; nscene--) {
			String ts = scenes[nscene].getImageName();
			double [][] scene_pXpYD;
			if (nscene == indx_ref) {
				// transform to self - maybe use a method that sets central points
				scene_pXpYD = transformToScenePxPyD( // check it is all 0.5
						null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
						disparity_ref,      // 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)
				disparity_scenes[indx_ref] = disparity_ref; // .clone();
//				for (int i = 0; i < ref_pXpYD.length; i++) {
//					ref_pXpYD[i] = scene_pXpYD[i];
//				}

			} else {
				final Matrix [][] scene_approx = new Matrix[disparity_ref.length][];
				double []   scene_xyz = ers_reference.getSceneXYZ(ts);
				double []   scene_atr = ers_reference.getSceneATR(ts);
				if ((scene_xyz == null) || (scene_atr == null)){
					continue; // scene is not matched
				}
				disparity_scenes[nscene] = new double [tilesX*tilesY];
				Arrays.fill(disparity_scenes[nscene], Double.NaN);
				double []   scene_ers_xyz_dt = ers_reference.getSceneErsXYZ_dt(ts);
				double []   scene_ers_atr_dt = ers_reference.getSceneErsATR_dt(ts);
				scenes[nscene].getErsCorrection().setErsDt(
						scene_ers_xyz_dt, // double []    ers_xyz_dt,
						scene_ers_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
				//setupERS() will be inside transformToScenePxPyD()
				double [][] scene_pXpYD_prefilter = transformToScenePxPyD( // will be null for disparity == NaN, total size - tilesX*tilesY
						null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
						disparity_ref,      // 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
						scenes[nscene],      // final QuadCLT     scene_QuadClt,
						ref_scene); // final QuadCLT     reference_QuadClt)
				
				double max_overlap = 0.6; 
				double min_adisp_cam =  0.2;
				double min_rdisp_cam =  0.03;
				double [][] scene_ds =conditionInitialDS(
						clt_parameters, // CLTParameters  clt_parameters,
						scenes[nscene], // QuadCLT        scene,
						-1); // int debug_level);
				if (scene_ds != null) {
					double [] disparity_cam = scene_ds[0]; // null; // for now
					scene_pXpYD = filterBG (
							scenes[indx_ref].getTileProcessor(), // final TileProcessor tp,
							scene_pXpYD_prefilter, // final double [][] pXpYD,
							max_overlap,           // final double max_overlap,
							null, // disparity_cam,         // final double [] disparity_cam,
							min_adisp_cam,         // final double min_adisp_cam,
							min_rdisp_cam,         // final double min_rdisp_cam,
							clt_parameters.tileX,  // final int    dbg_tileX,
							clt_parameters.tileY,  // final int    dbg_tileY,
							0); // 1); //debug_level);          // final int    debug_level);
				} else {
					scene_pXpYD = scene_pXpYD_prefilter;
				}
				// acummulate single-thread
// tileSize				
				for (int nTile = 0; nTile < scene_pXpYD.length; nTile++) if ((scene_pXpYD[nTile] != null) && !Double.isNaN(scene_pXpYD[nTile][2])) {
//		final int scene_extrap_irad = 1;
//					double max_rad2 = scene_extrap_irad * scene_extrap_irad; 
//					int tileX0=nTile % tilesX;
//					int tileY0=nTile / tilesX;
					int tileX0=(int) Math.floor(scene_pXpYD[nTile][0]/tileSize);
					int tileY0=(int) Math.floor(scene_pXpYD[nTile][1]/tileSize);
					if ((debug_level > -1) && (Math.abs(tileY0-dbg_tileY) < 2) && (Math.abs(tileX0-dbg_tileX) < 2)) {
						System.out.println("nTile="+nTile+", tilX0="+tileX0+", tileY0="+tileY0);
					}
					for (int dTy = -scene_extrap_irad; dTy <= scene_extrap_irad; dTy++) {
						int tileY = tileY0 + dTy;
						double dy = tileY * tileSize + tileSize/2 - scene_pXpYD[nTile][1]; 
						if ((tileY >=0) && (tileY < tilesY)) {
							for (int dTx = -scene_extrap_irad; dTx <= scene_extrap_irad; dTx++) {
								int tileX = tileX0 + dTx;
								if ((tileX >=0) && (tileX < tilesX)) {
									if ((debug_level > -1) && (tileY == dbg_tileY) && (tileX == dbg_tileX)) {
										System.out.println("tileX="+tileX+", tileY="+tileY);
									}
									double dx = tileX * tileSize + tileSize/2 - scene_pXpYD[nTile][0];
									double rad2 = dy*dy+dx*dx;
									if (rad2 < max_rad2) {
										int tile = tileY * tilesX + tileX;
										double w = 1 - (rad2/max_rad2);
										if (scene_approx[tile] == null)	{
											scene_approx[tile] =    new Matrix[2];
											scene_approx[tile][0] = new Matrix(3,3); // A
											scene_approx[tile][1] = new Matrix(3,1); // B
										}
										double d = scene_pXpYD[nTile][2];
										double dsx = w *dx;
										double dsy = w *dy;
										double dsx2 = dsx*dx;
										double dsy2 = dsy*dy;
										double dsxy = dsx*dy;
										double ds0 = w;
										double dsxd = dsx * d;
										double dsyd = dsy * d;
										double dsd =  ds0 * d;
										double [][] A = scene_approx[tile][0].getArray();
										A[0][0] += dsx2;
										A[0][1] += dsxy;
										A[0][2] += dsx;
										A[1][1] += dsy2;
										A[1][2] += dsy;
										A[2][2] += ds0;
										double [][] B = scene_approx[tile][1].getArray();
										B[0][0] += dsxd;
										B[1][0] += dsyd;
										B[2][0] += dsd;
/*  ax + by + c ~= d
    a * sx2 + b * sxy + c * sx - sxd = 0
    a * sxy + b * sy2 + c * sy - syd = 0
    a * sx  + b * sy  + c * s0 - sd  = 0
   | sx2 sxy sx |   | a |   | sxd |
   | sxy sy2 sy | * | b | = | syd |
   | sx  sy  s0 |   | c |   | sd  | */
									}
								}
							}
						}
					}
				}
				for (int nTile = 0; nTile < scene_pXpYD.length; nTile++) if (scene_approx[nTile] != null) {
					if (debug_level > -1) {
						int tileY = nTile / tilesX;
						int tileX = nTile % tilesX;
						if ((tileY == dbg_tileY) && (tileX == dbg_tileX)) {
							System.out.println("tileX="+tileX+", tileY="+tileY);
						}
					}
					double [][] A = scene_approx[nTile][0].getArray();
					if (A[2][2] > 0) {
						A[1][0] = A[0][1];
						A[2][0] = A[0][2];
						A[2][1] = A[1][2];
						try {
							Matrix abc = scene_approx[nTile][0].solve(scene_approx[nTile][1]);
							disparity_scenes[nscene][nTile] = abc.get(2, 0) + abc.get(0, 0)*tileSize/2 + abc.get(1, 0)*tileSize/2;
						} catch (RuntimeException e){
							// Use just average of disparities
							disparity_scenes[nscene][nTile] = scene_approx[nTile][1].get(2,0) / A[2][2]; // Double.NaN;
						}
					}

				}			
			}
			if (debug_level > -1) {
				if (nscene == indx_ref) {
					System.out.print("reference ");
//					System.out.println("Correlating reference scene"); // , nrefine = "+nrefine);
				} else {
					System.out.print(nscene+ " ");
//					System.out.println("Correlating scene "+nscene); // +nrefine+":"+nscene);
				}
			}
		} // for (int nscene = scenes.length-1; nscene >= 0; nscene--) {
		if (debug_level > -1) {
			System.out.println();
		}
		if (clt_parameters.lyms_show_images) {
			ShowDoubleFloatArrays.showArrays(
					disparity_scenes,
					tilesX,
					tilesY,
					true,
					ref_scene.getImageName()+"-disparity_scenes");
		}		
		return disparity_scenes;
	}

	public static double [][] intepolateSceneDisparity(
			final CLTParameters  clt_parameters,
			final QuadCLT []     scenes,
			final QuadCLT        ref_scene,
			final double []      disparity_ref,  // disparity in the reference view tiles (Double.NaN - invalid)
			final int            debug_level){
		final int scene_extrap_irad = 1;
		final double scene_extrap_rad = scene_extrap_irad + 0.5;
		final ErsCorrection ers_reference = ref_scene.getErsCorrection();
		final int tilesX = ref_scene.getTileProcessor().getTilesX();
		final int tilesY = ref_scene.getTileProcessor().getTilesY();
		final int tileSize = ref_scene.getTileProcessor().getTileSize();
		double max_rad2 = scene_extrap_rad * scene_extrap_rad * tileSize * tileSize; // in pixels
		final double [][] disparity_scenes = new double [scenes.length][]; // [tilesX*tilesY];
		final int dbg_tileX=-70;
		final int dbg_tileY=-19;
		final int dbg_tile=dbg_tileY * tilesX + dbg_tileX;
		if (debug_level > -1) {
			System.out.print("Correlating scene ");
		}
		final String ref_name = ref_scene.getImageName();
		for (int nscene = scenes.length-1; nscene >= 0; nscene--) {
			String ts = scenes[nscene].getImageName();
			double [][] scene_pXpYD;
			if (ref_name.equals(ts)){ // never for (virtual) center
				// transform to self - maybe use a method that sets central points
				scene_pXpYD = transformToScenePxPyD( // check it is all 0.5
						null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
						disparity_ref,      // 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)
				disparity_scenes[nscene] = disparity_ref; // .clone();
//				for (int i = 0; i < ref_pXpYD.length; i++) {
//					ref_pXpYD[i] = scene_pXpYD[i];
//				}

			} else {
				final Matrix [][] scene_approx = new Matrix[disparity_ref.length][];
				double []   scene_xyz = ers_reference.getSceneXYZ(ts);
				double []   scene_atr = ers_reference.getSceneATR(ts);
				if ((scene_xyz == null) || (scene_atr == null)){
					continue; // scene is not matched
				}
				disparity_scenes[nscene] = new double [tilesX*tilesY];
				Arrays.fill(disparity_scenes[nscene], Double.NaN);
				double []   scene_ers_xyz_dt = ers_reference.getSceneErsXYZ_dt(ts);
				double []   scene_ers_atr_dt = ers_reference.getSceneErsATR_dt(ts);
				scenes[nscene].getErsCorrection().setErsDt(
						scene_ers_xyz_dt, // double []    ers_xyz_dt,
						scene_ers_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
				//setupERS() will be inside transformToScenePxPyD()
				double [][] scene_pXpYD_prefilter = transformToScenePxPyD( // will be null for disparity == NaN, total size - tilesX*tilesY
						null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
						disparity_ref,      // 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
						scenes[nscene],      // final QuadCLT     scene_QuadClt,
						ref_scene); // final QuadCLT     reference_QuadClt)
				
				double max_overlap = 0.6; 
				double min_adisp_cam =  0.2;
				double min_rdisp_cam =  0.03;
				double [][] scene_ds =conditionInitialDS(
						clt_parameters, // CLTParameters  clt_parameters,
						scenes[nscene], // QuadCLT        scene,
						-1); // int debug_level);
				if (scene_ds != null) {
					double [] disparity_cam = scene_ds[0]; // null; // for now
					scene_pXpYD = filterBG (
							ref_scene.getTileProcessor(), // final TileProcessor tp,
							scene_pXpYD_prefilter, // final double [][] pXpYD,
							max_overlap,           // final double max_overlap,
							null, // disparity_cam,         // final double [] disparity_cam,
							min_adisp_cam,         // final double min_adisp_cam,
							min_rdisp_cam,         // final double min_rdisp_cam,
							clt_parameters.tileX,  // final int    dbg_tileX,
							clt_parameters.tileY,  // final int    dbg_tileY,
							0); // 1); //debug_level);          // final int    debug_level);
				} else {
					scene_pXpYD = scene_pXpYD_prefilter;
				}
				// acummulate single-thread
// tileSize				
				for (int nTile = 0; nTile < scene_pXpYD.length; nTile++) if ((scene_pXpYD[nTile] != null) && !Double.isNaN(scene_pXpYD[nTile][2])) {
//		final int scene_extrap_irad = 1;
//					double max_rad2 = scene_extrap_irad * scene_extrap_irad; 
//					int tileX0=nTile % tilesX;
//					int tileY0=nTile / tilesX;
					int tileX0=(int) Math.floor(scene_pXpYD[nTile][0]/tileSize);
					int tileY0=(int) Math.floor(scene_pXpYD[nTile][1]/tileSize);
					if ((debug_level > -1) && (Math.abs(tileY0-dbg_tileY) < 2) && (Math.abs(tileX0-dbg_tileX) < 2)) {
						System.out.println("nTile="+nTile+", tilX0="+tileX0+", tileY0="+tileY0);
					}
					for (int dTy = -scene_extrap_irad; dTy <= scene_extrap_irad; dTy++) {
						int tileY = tileY0 + dTy;
						double dy = tileY * tileSize + tileSize/2 - scene_pXpYD[nTile][1]; 
						if ((tileY >=0) && (tileY < tilesY)) {
							for (int dTx = -scene_extrap_irad; dTx <= scene_extrap_irad; dTx++) {
								int tileX = tileX0 + dTx;
								if ((tileX >=0) && (tileX < tilesX)) {
									if ((debug_level > -1) && (tileY == dbg_tileY) && (tileX == dbg_tileX)) {
										System.out.println("tileX="+tileX+", tileY="+tileY);
									}
									double dx = tileX * tileSize + tileSize/2 - scene_pXpYD[nTile][0];
									double rad2 = dy*dy+dx*dx;
									if (rad2 < max_rad2) {
										int tile = tileY * tilesX + tileX;
										double w = 1 - (rad2/max_rad2);
										if (scene_approx[tile] == null)	{
											scene_approx[tile] =    new Matrix[2];
											scene_approx[tile][0] = new Matrix(3,3); // A
											scene_approx[tile][1] = new Matrix(3,1); // B
										}
										double d = scene_pXpYD[nTile][2];
										double dsx = w *dx;
										double dsy = w *dy;
										double dsx2 = dsx*dx;
										double dsy2 = dsy*dy;
										double dsxy = dsx*dy;
										double ds0 = w;
										double dsxd = dsx * d;
										double dsyd = dsy * d;
										double dsd =  ds0 * d;
										double [][] A = scene_approx[tile][0].getArray();
										A[0][0] += dsx2;
										A[0][1] += dsxy;
										A[0][2] += dsx;
										A[1][1] += dsy2;
										A[1][2] += dsy;
										A[2][2] += ds0;
										double [][] B = scene_approx[tile][1].getArray();
										B[0][0] += dsxd;
										B[1][0] += dsyd;
										B[2][0] += dsd;
/*  ax + by + c ~= d
    a * sx2 + b * sxy + c * sx - sxd = 0
    a * sxy + b * sy2 + c * sy - syd = 0
    a * sx  + b * sy  + c * s0 - sd  = 0
   | sx2 sxy sx |   | a |   | sxd |
   | sxy sy2 sy | * | b | = | syd |
   | sx  sy  s0 |   | c |   | sd  | */
									}
								}
							}
						}
					}
				}
				for (int nTile = 0; nTile < scene_pXpYD.length; nTile++) if (scene_approx[nTile] != null) {
					if (debug_level > -1) {
						int tileY = nTile / tilesX;
						int tileX = nTile % tilesX;
						if ((tileY == dbg_tileY) && (tileX == dbg_tileX)) {
							System.out.println("tileX="+tileX+", tileY="+tileY);
						}
					}
					double [][] A = scene_approx[nTile][0].getArray();
					if (A[2][2] > 0) {
						A[1][0] = A[0][1];
						A[2][0] = A[0][2];
						A[2][1] = A[1][2];
						try {
							Matrix abc = scene_approx[nTile][0].solve(scene_approx[nTile][1]);
							disparity_scenes[nscene][nTile] = abc.get(2, 0) + abc.get(0, 0)*tileSize/2 + abc.get(1, 0)*tileSize/2;
						} catch (RuntimeException e){
							// Use just average of disparities
							disparity_scenes[nscene][nTile] = scene_approx[nTile][1].get(2,0) / A[2][2]; // Double.NaN;
						}
					}

				}			
			}
			if (debug_level > -1) {
				if (ref_name.equals(ts)){ // never for (virtual) center
//				if (nscene == indx_ref) {
					System.out.print("reference ");
//					System.out.println("Correlating reference scene"); // , nrefine = "+nrefine);
				} else {
					System.out.print(nscene+ " ");
//					System.out.println("Correlating scene "+nscene); // +nrefine+":"+nscene);
				}
			}
		} // for (int nscene = scenes.length-1; nscene >= 0; nscene--) {
		if (debug_level > -1) {
			System.out.println();
		}
		if (clt_parameters.lyms_show_images) {
			ShowDoubleFloatArrays.showArrays(
					disparity_scenes,
					tilesX,
					tilesY,
					true,
					ref_scene.getImageName()+"-disparity_scenes");
		}		
		return disparity_scenes;
	}
	
	
	
// Cleaned up and optimized version to reduce memory usage (on-the-fly integration, not saving full correlation data)
	
	public static double[][] correlateInterscene(
			final CLTParameters  clt_parameters,
			final QuadCLT []     scenes,
			final QuadCLT        ref_scene,
			final double []      disparity_ref,  // disparity in the reference view tiles (Double.NaN - invalid)
			final boolean []     selection, // may be null, if not null do not  process unselected tiles
			final double [][]    far_fgbg,        // null, or [nTile]{disp(fg)-disp(bg), str(fg)-str(bg)} hints for LMA FG/BG split 
			final int            margin,
			final int            nrefine, // just for debug title
			final boolean        show_2d_corr,
			final int            mcorr_sel, //  = Correlation2d.corrSelEncode(clt_parameters.img_dtt,scenes[nscene].getNumSensors());
			final float [][][]   accum_2d_corr, // if [1][][] - return accumulated 2d correlations (all pairs)
			final boolean        no_map, // do not generate disparity_map (time-consuming LMA)
            final boolean        use_rms, // DISPARITY_STRENGTH_INDEX means LMA RMS (18/04/2023)
			final int            debug_level)
	{
////		final QuadCLT ref_scene = scenes[indx_ref];
////		final int num_scenes = indx_ref - ref_scene.getEarliestScene(scenes) + 1;
		final int num_scenes = scenes.length;
		final ErsCorrection ers_reference = ref_scene.getErsCorrection();
		final int tilesX = ref_scene.getTileProcessor().getTilesX();
		final int tilesY = ref_scene.getTileProcessor().getTilesY();
		final int num_pairs = Correlation2d.getNumPairs(ref_scene.getNumSensors());
		final double [][][][][] dcorr_td_acc  = new double[num_pairs][][][][];
		final float  [][][][]   fcorr_td_acc  = new float [tilesY][tilesX][][];
		final float  [][][]     accum_weights = new float [tilesY][tilesX][num_pairs];
		boolean show_accumulated_correlations = show_2d_corr || debug_level > -5;
		boolean show_reference_correlations =  show_2d_corr || debug_level > -5;
		final float  [][][]       fclt_corr = ((accum_2d_corr != null) || show_accumulated_correlations || show_reference_correlations) ?
				(new float [tilesX * tilesY][][]) : null;
		ImageDtt image_dtt;
		image_dtt = new ImageDtt(
				ref_scene.getNumSensors(), // numSens,
				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());
		if (ref_scene.getGPU() != null) {
			ref_scene.getGPU().setGpu_debug_level(debug_level);
		}
		image_dtt.getCorrelation2d(); // initiate image_dtt.correlation2d, needed if disparity_map != null  
		double[][] disparity_map = no_map ? null : new double [image_dtt.getDisparityTitles().length][];
//		final double disparity_corr = clt_parameters.imp.disparity_corr; // 04/07/2023 // 0.00; // (z_correction == 0) ? 0.0 : geometryCorrection.getDisparityFromZ(1.0/z_correction);
		final double disparity_corr = clt_parameters.imp.disparity_corr + ref_scene.getDispInfinityRef() ; // 12/11/2025 - added ref_scene.getDispInfinityRef()
		
		double [][] scene_pXpYD = transformToScenePxPyD(
				null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
				disparity_ref,      // 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)
		
		TpTask[] tp_tasks_ref = GpuQuad.setInterTasks(
				ref_scene.getNumSensors(),
				ref_scene.getErsCorrection().getSensorWH()[0],
				!ref_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
				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,            
				THREADS_MAX);              // final int                 threadsMax)  // maximal number of threads to launch
		if (ref_scene.hasGPU()){
			image_dtt.updateTasksGPU (tp_tasks_ref);
			if (debug_level > -3) {
				System.out.println("correlateInterscene(): Updated tp_tasks_ref through GPU");
			}
		}
		//TODO: Need to set scenes[indx_ref].getErsCorrection().setErsDt() before processing scenes ?
		
		
		boolean got_ref = false;
		for (int nscene = 0; nscene < scenes.length; nscene++) if (scenes[nscene] != null){
			String ts = scenes[nscene].getImageName();
			if (scenes[nscene] == ref_scene) {
				// transform to self - maybe use a method that sets central points
				scene_pXpYD = transformToScenePxPyD(
						null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
						disparity_ref,      // 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)
				got_ref=true;
			} else {
				double []   scene_xyz = ers_reference.getSceneXYZ(ts);
				double []   scene_atr = ers_reference.getSceneATR(ts);
				if ((scene_xyz == null) || (scene_atr == null)){
					continue; // scene is not matched
				}
				double []   scene_ers_xyz_dt = ers_reference.getSceneErsXYZ_dt(ts);
				double []   scene_ers_atr_dt = ers_reference.getSceneErsATR_dt(ts);
				scenes[nscene].getErsCorrection().setErsDt(
						scene_ers_xyz_dt, // double []    ers_xyz_dt,
						scene_ers_atr_dt); // double []    ers_atr_dt)(ers_scene_original_xyz_dt);
				//setupERS() will be inside transformToScenePxPyD()
				double [][] scene_pXpYD_prefilter = transformToScenePxPyD( // will be null for disparity == NaN, total size - tilesX*tilesY
						null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
						disparity_ref,      // 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
						scenes[nscene],      // final QuadCLT     scene_QuadClt,
						ref_scene); // final QuadCLT     reference_QuadClt)
				
				double max_overlap = 0.6; 
				double min_adisp_cam =  0.2;
				double min_rdisp_cam =  0.03;
				double [][] scene_ds =conditionInitialDS(
						clt_parameters, // CLTParameters  clt_parameters,
						scenes[nscene], // QuadCLT        scene,
						-1); // int debug_level);
				if (scene_ds != null) {
					double [] disparity_cam = scene_ds[0]; // null; // for now
					scene_pXpYD = filterBG (
							ref_scene.getTileProcessor(), // final TileProcessor tp,
							scene_pXpYD_prefilter, // final double [][] pXpYD,
							max_overlap,           // final double max_overlap,
							null, // disparity_cam,         // final double [] disparity_cam,
							min_adisp_cam,         // final double min_adisp_cam,
							min_rdisp_cam,         // final double min_rdisp_cam,
							clt_parameters.tileX,  // final int    dbg_tileX,
							clt_parameters.tileY,  // final int    dbg_tileY,
							0); // 1); //debug_level);          // final int    debug_level);
				} else {
					scene_pXpYD = scene_pXpYD_prefilter;
				}
			}
			if (debug_level > -1) {
				if (scenes[nscene] == ref_scene) {
					System.out.println("Correlating reference scene, nrefine = "+nrefine);
				} else {
					System.out.println("Correlating scene "+nrefine+":"+nscene);
				}
			}
			scenes[nscene].saveQuadClt(); // to re-load new set of Bayer images to the GPU (do nothing for CPU)
			final double gpu_sigma_corr =     clt_parameters.getGpuCorrSigma(scenes[nscene].isMonochrome());
			final double gpu_sigma_rb_corr =  scenes[nscene].isMonochrome()? 1.0 : clt_parameters.gpu_sigma_rb_corr;
			final double gpu_sigma_log_corr = clt_parameters.getGpuCorrLoGSigma(scenes[nscene].isMonochrome());
			//			final float  [][][]       fclt_corr = new float [tilesX * tilesY][][];
			TpTask[] tp_tasks;
			if (scenes[nscene] == ref_scene) {
				tp_tasks = tp_tasks_ref; // will use coordinates data for LMA ? disp_dist
			} else {
				tp_tasks =  GpuQuad.setInterTasks(
						scenes[nscene].getNumSensors(),
						scenes[nscene].getErsCorrection().getSensorWH()[0],
						!scenes[nscene].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
						scenes[nscene].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
			}
			if (scenes[nscene].hasGPU()) {
				float  [][][][]     fcorr_td =       new float[tilesY][tilesX][][];
				image_dtt.quadCorrTD(
						clt_parameters.img_dtt,            // final ImageDttParameters imgdtt_params,    // Now just extra correlation parameters, later will include, most others
						tp_tasks, // *** will be updated inside from GPU-calculated geometry
						fcorr_td, // fcorrs_td[nscene],                 // [tilesY][tilesX][pair][4*64] transform domain representation of 6 corr pairs
						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);
				if (image_dtt.getGPU().getGpu_debug_level() > -1) {
					System.out.println("==ooo=after image_dtt.quadCorrTD()");
				}
// Verify tasks are now updated
				accumulateCorrelations(
						1.0,           // final double         weight,
						accum_weights, // final int [][][]     num_acc,          // number of accumulated tiles [tilesY][tilesX][pair]
						fcorr_td,      // final float [][][][] fcorr_td,         // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs 
						fcorr_td_acc); // final float [][][][] fcorr_td_acc      // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs
				if (image_dtt.getGPU().getGpu_debug_level() > -1) {
					System.out.println("==ooo=accumulateCorrelations()");
				}
				
				//// show after all
				if ((scenes[nscene] == ref_scene) && show_reference_correlations) { // prepare 2d correlations for visualization, double/CPU mode
					//clt_parameters.getGpuFatZero(ref_scene.isMonochrome()),   // final double     gpu_fat_zero,
					//Use same for both CPU/GPU
					double [][][] dcorr_tiles = (fclt_corr != null)? (new double [tp_tasks_ref.length][][]):null;
					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,              // int [][][]                num_acc,         // number of accumulated tiles [tilesY][tilesX][pair] (or null)       
							null, // 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(ref_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_ref,                  // final TpTask []           tp_tasks,        // data from the reference frame - will be applied to LMW for the integrated correlations
							far_fgbg, // final double [][][]       far_fgbg,        // null, or [tilesY][tilesX]{disp(fg)-disp(bg), str(fg)-str(bg)} hints for LMA FG/BG split 
							ref_scene.getErsCorrection().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
							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
							use_rms,                         // 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][]
							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
							"reference",                   // final String              debug_suffix,
							debug_level + 2); // -1 );              // final int                 globalDebugLevel)
					ImageDtt.convertFcltCorr(
							dcorr_tiles, // double [][][] dcorr_tiles,// [tile][sparse, correlation pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
							fclt_corr);  // float  [][][] fclt_corr) //  new float [tilesX * tilesY][][] or null
				}
				
			} else { // CPU version
				//Correlation2d correlation2d = 
				image_dtt.getCorrelation2d();
				double [][][][]   dcorr_td = new double[tp_tasks.length][][][]; // [tile][pair][4][64] sparse by pair transform domain representation of corr pairs
				image_dtt.quadCorrTD(
						scenes[nscene].getResetImageData(),                      // final double [][][]       image_data,      // first index - number of image in a quad
						scenes[nscene].getErsCorrection().getSensorWH()[0], // final int                 width,
						tp_tasks,                                           // final TpTask []           tp_tasks,
						clt_parameters.img_dtt,                             // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
						dcorr_td,                                           // final double [][][][][]   dcorr_td,        // [pair][tilesY][tilesX][4][64] sparse transform domain representation of corr pairs
						// no combo here - rotate, combine in pixel domain after interframe
						scenes[nscene].getCltKernels(),                     // final double [][][][][][] clt_kernels,     // [channel_in_quad][color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
						clt_parameters.clt_window,                          // final int                 window_type,
						clt_parameters.corr_red,                            // final double              corr_red,
						clt_parameters.corr_blue,                           // final double              corr_blue,
						mcorr_sel,                                          // final int                 mcorr_sel,    // Which pairs to correlate // +1 - all, +2 - dia, +4 - sq, +8 - neibs, +16 - hor + 32 - vert
						clt_parameters.tileX,                               // final int                 debug_tileX,
						clt_parameters.tileY,                               // final int                 debug_tileY,
						THREADS_MAX,                                         // final int                 threadsMax,       // maximal number of threads to launch
						debug_level);                                       // final int                 globalDebugLevel)
				
				accumulateCorrelations(
						tp_tasks,      // final TpTask []         tp_tasks,
						accum_weights,       // final int [][][]        num_acc,     // number of accumulated tiles [tilesY][tilesX][pair]
						dcorr_td,      // final double [][][][][] dcorr_td,    // [tile][pair][4][64] sparse transform domain representation of corr pairs 
						dcorr_td_acc); // final double [][][][][] dcorr_td_acc // [pair][tilesY][tilesX][4][64] sparse transform domain representation of corr pairs 

				if ((scenes[nscene] == ref_scene) && show_reference_correlations) { // prepare 2d correlations for visualization, double/CPU mode
					double [][][] dcorr_tiles = (fclt_corr != null)? (new double [tp_tasks_ref.length][][]):null;
					// non-GPU (broken)
					image_dtt.clt_process_tl_correlations( // convert to pixel domain and process correlations already prepared in fcorr_td and/or fcorr_combo_td
							clt_parameters.img_dtt,		   // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
							tp_tasks_ref,                  // final TpTask []           tp_tasks,        // data from the reference frame - will be applied to LMW for the integrated correlations
							
							// only listed tiles will be processed
							ref_scene.getErsCorrection().getRXY(false), // final double [][]         rXY,             // from geometryCorrection
							tilesX,                        // final int                 tilesX,          // tp_tasks may lack maximal tileX, tileY  
							tilesY,                        // final int                 tilesY,
							// no fcorr_combo_td here both arrays should have same non-null tiles
							dcorr_td,                      // final double [][][][]     dcorr_td,        // [tile][pair][4][64] sparse by pair transform domain representation of corr pairs
							null,                          // final double []           dcorr_weight,    // [tile] weighted number of tiles averaged (divide squared fat zero by this)
							// 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
							// to be converted to float
							dcorr_tiles,                   // final double  [][][]      dcorr_tiles,     // [tile][pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
							// When clt_mismatch is non-zero, no far objects extraction will be attempted
							//optional, may be null
							disparity_map,                 // final double [][]         disparity_map,   // [8][tilesY][tilesX], only [6][] is needed on input or null - do not calculate
							null,                          // final double [][]         ddnd,            // data for LY. SHould be either null or [num_sensors][]
							clt_parameters.correlate_lma,  // final boolean             run_lma,         // calculate LMA, false - CM only
							// last 2 - contrast, avg/ "geometric average)
							clt_parameters.getGpuFatZero(ref_scene.isMonochrome()),   // clt_parameters.getGpuFatZero(ref_scene.isMonochrome()), // final double              afat_zero2,      // gpu_fat_zero ==30? clt_parameters.getGpuFatZero(is_mono); absolute fat zero, same units as components squared values
							clt_parameters.gpu_sigma_m,    // final double              corr_sigma,      //
							// define combining of all 2D correlation pairs for CM (LMA does not use them)

							clt_parameters.img_dtt.mcorr_comb_width, // final int                 mcorr_comb_width,  // combined correlation tile width
							clt_parameters.img_dtt.mcorr_comb_height,// final int                 mcorr_comb_height, // combined correlation tile full height
							clt_parameters.img_dtt.mcorr_comb_offset,// final int                 mcorr_comb_offset, // combined correlation tile height offset: 0 - centered (-height/2 to 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 );              // final int                 globalDebugLevel)
					ImageDtt.convertFcltCorr(
							dcorr_tiles, // double [][][] dcorr_tiles,// [tile][sparse, correlation pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
							fclt_corr);  // float  [][][] fclt_corr) //  new float [tilesX * tilesY][][] or null
				}
			} // GPU-version, CPU-version
            // render reference scene when processing it (one of the sequence). Never in cuas mode.
			if ((scenes[nscene] == ref_scene) && show_reference_correlations) { // visualize prepare ref_scene correlation data
				float [][] dbg_corr_rslt_partial = ImageDtt.corr_partial_dbg( // not used in lwir
						fclt_corr, // final float  [][][]     fcorr_data,       // [tile][pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
						tp_tasks_ref, // 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];
				int ind_length = image_dtt.getCorrelation2d().getCorrTitles().length;
				
				System.arraycopy(image_dtt.getCorrelation2d().getCorrTitles(), 0, titles, 0, ind_length);
				for (int i = ind_length; i < titles.length; i++) {
					titles[i] = "combo-"+(i - ind_length);
				}
				
				// 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,
						ref_scene.getImageName()+"-CORR-REFSCENE-"+nrefine,
						titles); // image_dtt.getCorrelation2d().getCorrTitles()); //CORR_TITLES);

			}
		} // for (int nscene = 0; nscene < num_scenes; nscene++) {
		// Normalize accumulated correlations
		if (ref_scene.hasGPU()) {
			 accumulateCorrelationsAcOnly(
					 accum_weights,       // final float [][][]     num_acc,          // number of accumulated tiles [tilesY][tilesX][pair]
					 fcorr_td_acc); // final float [][][][] fcorr_td_acc      // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs 
		} else {
			accumulateCorrelations(
					accum_weights,       // final float [][][]        num_acc,     // number of accumulated tiles [tilesY][tilesX][pair]
					dcorr_td_acc); // final double [][][][][] dcorr_td_acc // [pair][tilesY][tilesX][4][64] sparse transform domain representation of corr pairs 
		}
		
		if (ref_scene.hasGPU()) {
			double [][][]     dcorr_tiles = (fclt_corr != null)? (new double [tp_tasks_ref.length][][]):null;
			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_acc,		 	     // final float  [][][][]     fcorr_td,        // [tilesY][tilesX][pair][4*64] transform domain representation of all selected corr pairs
					accum_weights,                       // float [][][]                num_acc,         // number of accumulated tiles [tilesY][tilesX][pair] (or null)       
					null, // 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(ref_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_ref,                  // final TpTask []           tp_tasks,        // data from the reference frame - will be applied to LMW for the integrated correlations
					far_fgbg, // 			final double [][]         far_fgbg,        // null, or [nTile]{disp(fg)-disp(bg), str(fg)-str(bg)} hints for LMA FG/BG split 
 					ref_scene.getErsCorrection().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
					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
					use_rms,                       // 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][]
					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
					"accumulated",                 // final String              debug_suffix,
					debug_level + 2); // -1 );     // final int                 globalDebugLevel)
			ImageDtt.convertFcltCorr(
					dcorr_tiles, // double [][][] dcorr_tiles,// [tile][sparse, correlation pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
					fclt_corr);  // float  [][][] fclt_corr) //  new float [tilesX * tilesY][][] or null
		} else { // no-GPU version
			double [][][][]   dcorr_td = new double[tp_tasks_ref.length][][][]; // [tile][pair][4][64] sparse transform domain representation of corr pairs
			double []         dcorr_weight = new double[tp_tasks_ref.length];
			for (int iTile = 0; iTile < dcorr_td.length; iTile++) {
				TpTask task = tp_tasks_ref[iTile];
				int tileY = task.getTileY(); // tilesX;  
				int tileX = task.getTileX(); // nTile % tilesX;
				dcorr_td[iTile] = new double [num_pairs][][];
				for (int npair = 0; npair < num_pairs; npair++) if (dcorr_td_acc[npair] != null){
					dcorr_td[iTile][npair] = dcorr_td_acc[npair][tileY][tileX];
					dcorr_weight[iTile] = accum_weights[tileY][tileX][npair]; // number of accumulated tiles [tilesY][tilesX][pair]
				}
			}
			double [][][]     dcorr_tiles = (fclt_corr != null)? (new double [tp_tasks_ref.length][][]):null;
			image_dtt.clt_process_tl_correlations( // convert to pixel domain and process correlations already prepared in fcorr_td and/or fcorr_combo_td
					clt_parameters.img_dtt,		   // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
					tp_tasks_ref,                  // final TpTask []           tp_tasks,        // data from the reference frame - will be applied to LMW for the integrated correlations
					// only listed tiles will be processed
					ref_scene.getErsCorrection().getRXY(false), // final double [][]         rXY,             // from geometryCorrection
					tilesX,                        // final int                 tilesX,          // tp_tasks may lack maximal tileX, tileY  
					tilesY,                        // final int                 tilesY,
					// no fcorr_combo_td here both arrays should have same non-null tiles
					dcorr_td,                      // final double [][][][]     dcorr_td,        // [tile][pair][4][64] sparse by pair transform domain representation of corr pairs
					dcorr_weight,                   // final double []           dcorr_weight,    // [tile] weighted number of tiles averaged (divide squared fat zero by this)

					// 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
					// to be converted to float
					dcorr_tiles,                   // final double  [][][]      dcorr_tiles,     // [tile][pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
					// When clt_mismatch is non-zero, no far objects extraction will be attempted
					//optional, may be null
					disparity_map,                 // final double [][]         disparity_map,   // [8][tilesY][tilesX], only [6][] is needed on input or null - do not calculate
					null,                          // final double [][]         ddnd,            // data for LY. SHould be either null or [num_sensors][]
					clt_parameters.correlate_lma, // true,                          // final boolean             run_lma,         // calculate LMA, false - CM only
					// last 2 - contrast, avg/ "geometric average)
					clt_parameters.getGpuFatZero(ref_scene.isMonochrome()),   // clt_parameters.getGpuFatZero(ref_scene.isMonochrome()), // final double              afat_zero2,      // gpu_fat_zero ==30? clt_parameters.getGpuFatZero(is_mono); absolute fat zero, same units as components squared values
					clt_parameters.gpu_sigma_m,    // final double              corr_sigma,      //
					// define combining of all 2D correlation pairs for CM (LMA does not use them)

					clt_parameters.img_dtt.mcorr_comb_width, // final int                 mcorr_comb_width,  // combined correlation tile width
					clt_parameters.img_dtt.mcorr_comb_height,// final int                 mcorr_comb_height, // combined correlation tile full height
					clt_parameters.img_dtt.mcorr_comb_offset,// final int                 mcorr_comb_offset, // combined correlation tile height offset: 0 - centered (-height/2 to 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 + (show_reference_correlations? 2 : -1));              // final int                 globalDebugLevel)
			ImageDttCPU.convertFcltCorr(
					dcorr_tiles, // double [][][] dcorr_tiles,// [tile][sparse, correlation pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
					fclt_corr);  // float  [][][] fclt_corr) //  new float [tilesX * tilesY][][] or null
			
		}
		if (show_accumulated_correlations || (accum_2d_corr != null)){ // -1
			float [][] accum_2d_img = ImageDtt.corr_partial_dbg( // not used in lwir
					fclt_corr, // final float  [][][]     fcorr_data,       // [tile][pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
					tp_tasks_ref, // 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 [accum_2d_img.length]; // dcorr_tiles[0].length];
			int ind_length = image_dtt.getCorrelation2d().getCorrTitles().length;
			
			System.arraycopy(image_dtt.getCorrelation2d().getCorrTitles(), 0, titles, 0, ind_length);
			for (int i = ind_length; i < titles.length; i++) {
				titles[i] = "combo-"+(i - ind_length);
			}
			if ((accum_2d_corr != null)) {
				accum_2d_corr[0] = accum_2d_img;
			}
			if (show_accumulated_correlations) {
				ShowDoubleFloatArrays.showArrays( // out of boundary 15
						accum_2d_img,
						tilesX*(2*image_dtt.transform_size),
						tilesY*(2*image_dtt.transform_size),
						true,
						ref_scene.getImageName()+"-CORR-ACCUM"+num_scenes+"-"+nrefine,
						titles);      // image_dtt.getCorrelation2d().getCorrTitles()); //CORR_TITLES);
			}
		}
		return disparity_map; // disparity_map
	}
	/**
	 * Generate (randomly) offset 2D correlations for DNN training
	 * @param clt_parameters
	 * @param ref_scene
	 * @param disparity_ref_in
	 * @param disparity_offsets
	 * @param margin
	 * @param add_combo
	 * @param debug_level
	 * @return
	 */
	public float[][] generateOffset2DCorrelations(
			final CLTParameters  clt_parameters,
			final QuadCLT        ref_scene,
			final double []      disparity_ref_in,  // disparity in the reference view tiles (Double.NaN - invalid)
			final double []      disparity_offsets,
			final int            margin,
			final boolean        add_combo,
			final int            debug_level)
	{
		final double [] disparity_ref = disparity_ref_in.clone();
		///		final ErsCorrection ers_reference = ref_scene.getErsCorrection();
		final int tilesX = ref_scene.getTileProcessor().getTilesX();
		final int tilesY = ref_scene.getTileProcessor().getTilesY();
		int mcorr_sel = ImageDttParameters.corrSelEncodeAll(0); // all sensors
		
		final float  [][][]  fclt_corr =  new float [tilesX * tilesY][][];
		ImageDtt image_dtt;
		image_dtt = new ImageDtt(
				numSens,
				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());

		image_dtt.getCorrelation2d(); // initiate image_dtt.correlation2d, needed if disparity_map != null  

//		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);
		final double disparity_corr = clt_parameters.imp.disparity_corr+ ref_scene.getDispInfinityRef() ; // 12/11/2025 - added ref_scene.getDispInfinityRef()
		TpTask[] tp_tasks_ref = null;
		double [][] scene_pXpYD;
		// transform to self - maybe use a method that sets central points
		scene_pXpYD = transformToScenePxPyD(
				null, // final Rectangle [] extra_woi,    // show larger than sensor WOI (or null)
				disparity_ref,      // 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)
		for (int i = 0; i < scene_pXpYD.length; i++) {
			if ((scene_pXpYD[i] != null) && !Double.isNaN(scene_pXpYD[i][2])) {
				scene_pXpYD[i][2] += disparity_offsets[i];
			}
		}

		ref_scene.saveQuadClt(); // to re-load new set of Bayer images to the GPU (do nothing for CPU)
		final double gpu_sigma_corr =     clt_parameters.getGpuCorrSigma(ref_scene.isMonochrome());
		final double gpu_sigma_rb_corr =  ref_scene.isMonochrome()? 1.0 : clt_parameters.gpu_sigma_rb_corr;
		final double gpu_sigma_log_corr = clt_parameters.getGpuCorrLoGSigma(ref_scene.isMonochrome());

		TpTask[] tp_tasks =  GpuQuad.setInterTasks(
				ref_scene.getNumSensors(),
				ref_scene.getErsCorrection().getSensorWH()[0],
				!ref_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
				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,            
				THREADS_MAX);        // final int                 threadsMax)  // maximal number of threads to launch
		tp_tasks_ref = tp_tasks; // will use coordinates data for LMA ? disp_dist
		if (ref_scene.hasGPU()) {
			float  [][][][]     fcorr_td =       new float[tilesY][tilesX][][];
			image_dtt.quadCorrTD(
					clt_parameters.img_dtt,            // final ImageDttParameters imgdtt_params,    // Now just extra correlation parameters, later will include, most others
					tp_tasks, // *** will be updated inside from GPU-calculated geometry
					fcorr_td, // fcorrs_td[nscene],                 // [tilesY][tilesX][pair][4*64] transform domain representation of 6 corr pairs
//					ref_scene.getErsCorrection(), //
					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);
			if (image_dtt.getGPU().getGpu_debug_level() > -1) {
				System.out.println("==ooo=after image_dtt.quadCorrTD()");
			}

			//Use same for both CPU/GPU
			double [][][] dcorr_tiles = (fclt_corr != null)? (new double [tp_tasks_ref.length][][]):null;
			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,              // int [][][]                num_acc,         // number of accumulated tiles [tilesY][tilesX][pair] (or null)       
					null, // 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(ref_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_ref,                  // 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 
					ref_scene.getErsCorrection().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
					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
					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][]
					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)
					(add_combo ? clt_parameters.img_dtt.mcorr_comb_width : 0), //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 );              // final int                 globalDebugLevel)
			ImageDtt.convertFcltCorr(
					dcorr_tiles, // double [][][] dcorr_tiles,// [tile][sparse, correlation pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
					fclt_corr);  // float  [][][] fclt_corr) //  new float [tilesX * tilesY][][] or null

		} else { // CPU version
			//Correlation2d correlation2d = 
			image_dtt.getCorrelation2d();
			double [][][][]   dcorr_td = new double[tp_tasks.length][][][]; // [tile][pair][4][64] sparse by pair transform domain representation of corr pairs
			image_dtt.quadCorrTD(
					ref_scene.getResetImageData(),                      // final double [][][]       image_data,      // first index - number of image in a quad
					ref_scene.getErsCorrection().getSensorWH()[0], // final int                 width,
					tp_tasks,                                           // final TpTask []           tp_tasks,
					clt_parameters.img_dtt,                             // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
					dcorr_td,                                           // final double [][][][][]   dcorr_td,        // [pair][tilesY][tilesX][4][64] sparse transform domain representation of corr pairs
					// no combo here - rotate, combine in pixel domain after interframe
					ref_scene.getCltKernels(),                     // final double [][][][][][] clt_kernels,     // [channel_in_quad][color][tileY][tileX][band][pixel] , size should match image (have 1 tile around)
//					clt_parameters.kernel_step,                         // final int                 kernel_step,
					clt_parameters.clt_window,                          // final int                 window_type,
					clt_parameters.corr_red,                            // final double              corr_red,
					clt_parameters.corr_blue,                           // final double              corr_blue,
					mcorr_sel,                                          // final int                 mcorr_sel,    // Which pairs to correlate // +1 - all, +2 - dia, +4 - sq, +8 - neibs, +16 - hor + 32 - vert
					clt_parameters.tileX,                               // final int                 debug_tileX,
					clt_parameters.tileY,                               // final int                 debug_tileY,
					THREADS_MAX,                                         // final int                 threadsMax,       // maximal number of threads to launch
					debug_level);                                       // final int                 globalDebugLevel)

			double [][][] dcorr_tiles = (fclt_corr != null)? (new double [tp_tasks_ref.length][][]):null;
			image_dtt.clt_process_tl_correlations( // convert to pixel domain and process correlations already prepared in fcorr_td and/or fcorr_combo_td
					clt_parameters.img_dtt,		   // final ImageDttParameters  imgdtt_params,   // Now just extra correlation parameters, later will include, most others
					tp_tasks_ref,                  // final TpTask []           tp_tasks,        // data from the reference frame - will be applied to LMW for the integrated correlations
					// only listed tiles will be processed
					ref_scene.getErsCorrection().getRXY(false), // final double [][]         rXY,             // from geometryCorrection
					tilesX,                        // final int                 tilesX,          // tp_tasks may lack maximal tileX, tileY  
					tilesY,                        // final int                 tilesY,
					// no fcorr_combo_td here both arrays should have same non-null tiles
					dcorr_td,                      // final double [][][][]     dcorr_td,        // [tile][pair][4][64] sparse by pair transform domain representation of corr pairs
					null,                          // final double []           dcorr_weight,    // [tile] weighted number of tiles averaged (divide squared fat zero by this)
					// 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
					// to be converted to float
					dcorr_tiles,                   // final double  [][][]      dcorr_tiles,     // [tile][pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
					// When clt_mismatch is non-zero, no far objects extraction will be attempted
					//optional, may be null
					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][]
					clt_parameters.correlate_lma,  // final boolean             run_lma,         // calculate LMA, false - CM only
					// last 2 - contrast, avg/ "geometric average)
					clt_parameters.getGpuFatZero(ref_scene.isMonochrome()),   // clt_parameters.getGpuFatZero(ref_scene.isMonochrome()), // final double              afat_zero2,      // gpu_fat_zero ==30? clt_parameters.getGpuFatZero(is_mono); absolute fat zero, same units as components squared values
					clt_parameters.gpu_sigma_m,    // final double              corr_sigma,      //
					// define combining of all 2D correlation pairs for CM (LMA does not use them)

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

					clt_parameters.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 );              // final int                 globalDebugLevel)
			ImageDtt.convertFcltCorr(
					dcorr_tiles, // double [][][] dcorr_tiles,// [tile][sparse, correlation pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
					fclt_corr);  // float  [][][] fclt_corr) //  new float [tilesX * tilesY][][] or null
		} // GPU-version, CPU-version
		
		float [][] dbg_corr_rslt_partial = ImageDtt.corr_partial_dbg( // not used in lwir
				fclt_corr, // final float  [][][]     fcorr_data,       // [tile][pair][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
				tp_tasks_ref, // 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)

		return dbg_corr_rslt_partial;
	}
	
	public static void accumulateCorrelations(
			final TpTask []         tp_tasks,
			final float [][][]      num_acc,     // number of accumulated tiles [tilesY][tilesX][pair] 
			final double [][][][]   dcorr_td,    // [tile][pair][4][64] sparse transform domain representation of corr pairs 
			final double [][][][][] dcorr_td_acc // [pair][tilesY][tilesX][4][64] sparse transform domain representation of corr pairs 
			) {
		accumulateCorrelations(
				tp_tasks,
				num_acc,     // number of accumulated tiles [tilesY][tilesX][pair] 
				dcorr_td,    // [tile][pair][4][64] sparse transform domain representation of corr pairs 
				dcorr_td_acc, // [pair][tilesY][tilesX][4][64] sparse transform domain representation of corr pairs
				THREADS_MAX);
	}
	
	
	public static void accumulateCorrelations(
			final TpTask []         tp_tasks,
			final float [][][]      num_acc,     // number of accumulated tiles [tilesY][tilesX][pair] 
			final double [][][][]   dcorr_td,    // [tile][pair][4][64] sparse transform domain representation of corr pairs 
			final double [][][][][] dcorr_td_acc, // [pair][tilesY][tilesX][4][64] sparse transform domain representation of corr pairs
			final int               threadsMax
			) {
		final int tilesY = num_acc.length;
		final int tilesX = num_acc[0].length;
		final int num_pairs = num_acc[0][0].length;
		final AtomicBoolean [] acorrs = new AtomicBoolean[num_pairs];
		for (int i = 0; i < acorrs.length; i++) {
			acorrs[i] = new AtomicBoolean();
		}
		final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int iTile = ai.getAndIncrement(); iTile < tp_tasks.length; iTile = ai.getAndIncrement()) if (dcorr_td[iTile] != null) {
						for (int pair = 0; pair < num_pairs; pair++) if (dcorr_td[iTile][pair] != null){
							acorrs[pair].set(true);
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		for (int pair = 0; pair < acorrs.length; pair++) if ((dcorr_td_acc[pair] == null) && acorrs[pair].get()) {
			dcorr_td_acc[pair] = new double[tilesY][tilesX][][];
		}
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int iTile = ai.getAndIncrement(); iTile < tp_tasks.length; iTile = ai.getAndIncrement()) if ((tp_tasks[iTile] != null) && (tp_tasks[iTile].getTask() != 0)) {
						int tileY = tp_tasks[iTile].getTileY(); // tilesX;  
						int tileX = tp_tasks[iTile].getTileX(); // nTile % tilesX;
						for (int pair = 0; pair < num_pairs; pair++) if ((dcorr_td[iTile] != null) && (dcorr_td[iTile][pair] != null)){
							if (dcorr_td_acc[pair][tileY][tileX] == null) {
								dcorr_td_acc[pair][tileY][tileX] = new double [dcorr_td[iTile][pair].length][]; // 4
								for (int q = 0; q < dcorr_td_acc[pair][tileY][tileX].length; q++) {
									dcorr_td_acc[pair][tileY][tileX][q] = dcorr_td[iTile][pair][q].clone();
								}
							} else {
								for (int q = 0; q < dcorr_td_acc[pair][tileY][tileX].length; q++) {
									for (int i = 0; i < dcorr_td_acc[pair][tileY][tileX][q].length; i++) {
										dcorr_td_acc[pair][tileY][tileX][q][i] += dcorr_td[iTile][pair][q][i];
									}
								}
							}
							num_acc[tileY][tileX][pair]++;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
	}
	
	
// GPU (float) version
	public static void accumulateCorrelations(
			final double         weight,
			final float [][][]   num_acc,          // number of accumulated tiles [tilesY][tilesX][pair]
			final float [][][][] fcorr_td,         // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs 
			final float [][][][] fcorr_td_acc      // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs
			) {
		accumulateCorrelations(
				weight,           // final double         weight,
				num_acc,          // number of accumulated tiles [tilesY][tilesX][pair]
				fcorr_td,         // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs 
				fcorr_td_acc,     // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs
				THREADS_MAX);
	}

	
	public static void accumulateCorrelations(
			final double         weight,
			final float [][][]   num_acc,          // number of accumulated tiles [tilesY][tilesX][pair]
			final float [][][][] fcorr_td,         // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs 
			final float [][][][] fcorr_td_acc,      // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs
			final int threadsMax
			) {
		int tX=-1; 
		for (int ity = 0; ity < fcorr_td.length; ity++) if (fcorr_td[ity] != null){
			tX = fcorr_td[ity].length;
			break;
		}
		final int tilesY = fcorr_td.length;
		final int tilesX = tX;
		final int tiles =  tilesY * tilesX;

		final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < tiles; nTile = ai.getAndIncrement()) {
						int tileY = nTile / tilesX;  
						int tileX = nTile % tilesX;
						if (fcorr_td [tileY][tileX] != null) {
							if (fcorr_td_acc[tileY][tileX] == null) {
								fcorr_td_acc[tileY][tileX] = new float [fcorr_td [tileY][tileX].length][];
							}
							for (int pair = 0; pair < fcorr_td [tileY][tileX].length; pair++) if (fcorr_td [tileY][tileX][pair] != null){
								if (fcorr_td_acc[tileY][tileX][pair] == null) {
									fcorr_td_acc[tileY][tileX][pair] = fcorr_td[tileY][tileX][pair].clone();
								} else {
									for (int i = 0; i < fcorr_td [tileY][tileX][pair].length; i++) {
										fcorr_td_acc[tileY][tileX][pair][i] += fcorr_td[tileY][tileX][pair][i];
									}
								}
								num_acc[tileY][tileX][pair] += weight;
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
	}

	
	public static void accumulateCorrelations(
			double     weight,
			float []   num_acc,          // number of accumulated tiles [pair]
			float [][] fcorr_td,         // [pair][256] sparse transform domain representation of corr pairs 
			float [][] fcorr_td_acc      // [pair][256] sparse transform domain representation of corr pairs // should not be null, same length as fcorr_td
			) {
		int tX=-1; 
		for (int ity = 0; ity < fcorr_td.length; ity++) if (fcorr_td[ity] != null){
			tX = fcorr_td[ity].length;
			break;
		}
		final int tilesY = fcorr_td.length;
		final int tilesX = tX;
		final int tiles =  tilesY * tilesX;

		if (fcorr_td != null) {
			/*
			if (fcorr_td_acc == null) {
				fcorr_td_acc = new float [fcorr_td.length][];
			}
			*/
			for (int pair = 0; pair < fcorr_td.length; pair++) if (fcorr_td [pair] != null){
				if (fcorr_td_acc[pair] == null) {
					fcorr_td_acc[pair] = fcorr_td[pair].clone();
				} else {
					for (int i = 0; i < fcorr_td[pair].length; i++) {
						fcorr_td_acc[pair][i] += fcorr_td[pair][i];
					}
				}
				num_acc[pair] += weight;
			}
		}
		return;
	}
	
	
	
	
	
	
	public static void accumulateCorrelations(  // normalize
			final float  [][][]     num_acc,      // number of accumulated tiles [tilesY][tilesX][pair]
			final double [][][][][] dcorr_td_acc) { // [pair][tilesY][tilesX][4][64] sparse transform domain representation of corr pairs
		accumulateCorrelations(  // normalize
				num_acc,     // number of accumulated tiles [tilesY][tilesX][pair]
				dcorr_td_acc, // [pair][tilesY][tilesX][4][64] sparse transform domain representation of corr pairs
				THREADS_MAX);
	}
	
	public static void accumulateCorrelations(  // normalize
			final float  [][][]     num_acc,     // number of accumulated tiles [tilesY][tilesX][pair]
			final double [][][][][] dcorr_td_acc, // [pair][tilesY][tilesX][4][64] sparse transform domain representation of corr pairs
			final int               threadsMax
			) {
		int tX=-1, tY=-1; 
		for (int np = 0; np < dcorr_td_acc.length; np++) if (dcorr_td_acc[np] != null){
			for (int ity = 0; ity < dcorr_td_acc[np].length; ity++) if (dcorr_td_acc[np][ity] != null){
				tY = dcorr_td_acc[np].length;
				tX = dcorr_td_acc[np][ity].length;
				np = dcorr_td_acc.length;
				break;
			}
		}
		final int tilesY = tY;
		final int tilesX = tX;
		final int tiles =  tilesY * tilesX;

		final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < tiles; nTile = ai.getAndIncrement()) {
						int tileY = nTile / tilesX;  
						int tileX = nTile % tilesX;
						for (int pair = 0; pair < dcorr_td_acc.length; pair++) if (num_acc [tileY][tileX][pair] > 0){
							double k = 1.0 / num_acc [tileY][tileX][pair];
							for (int q = 0; q < dcorr_td_acc[pair][tileY][tileX].length; q++) {
								for (int i = 0; i < dcorr_td_acc[pair][tileY][tileX][q].length; i++) {
									dcorr_td_acc[pair][tileY][tileX][q][i] *= k; 
								}
							}							
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
	}

	public static void accumulateCorrelationsAcOnly( // normalize
			final float [][][]   num_acc,          // number of accumulated tiles [tilesY][tilesX][pair]
			final float [][][][] fcorr_td_acc      // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs 
			) {
		accumulateCorrelationsAcOnly( // normalize
				num_acc,          // number of accumulated tiles [tilesY][tilesX][pair]
				fcorr_td_acc,     // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs
				THREADS_MAX);
	}

	public static void accumulateCorrelationsAcOnly( // normalize
			final float [][][]   num_acc,          // number of accumulated tiles [tilesY][tilesX][pair]
			final float [][][][] fcorr_td_acc,      // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs
			final int            threadsMax
			) {
		int tX=-1; 
		for (int ity = 0; ity < fcorr_td_acc.length; ity++) if (fcorr_td_acc[ity] != null){
			tX = fcorr_td_acc[ity].length;
			break;
		}
		final int tilesY = fcorr_td_acc.length;
		final int tilesX = tX;
		final int tiles =  tilesY * tilesX;

		final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < tiles; nTile = ai.getAndIncrement()) {
						int tileY = nTile / tilesX;  
						int tileX = nTile % tilesX;
						if (fcorr_td_acc [tileY][tileX] != null) {
							for (int pair = 0; pair < fcorr_td_acc [tileY][tileX].length; pair++) if (num_acc [tileY][tileX][pair] >0){
								double k = 1.0 / num_acc [tileY][tileX][pair];
								for (int i = 0; i < fcorr_td_acc[tileY][tileX][pair].length; i++) {
									fcorr_td_acc[tileY][tileX][pair][i] *= k; 
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
	}

	/**
	 * Merge FD correlations and weights for tile clusters, normalize, modify fcorr_td_acc and
	 * num_acc (to be used for fat zero). Put merged data to top left tile of each cluster, remove
	 * other ones.
	 * @param clust_size Cluster size (side of a square), rightmost and bottom may be partial (used ceil())
	 * @param num_acc weighs (currently just number) of accumulated pairs (individually for each pair). WILL BE MODIFIED 
	 * @param fcorr_td_acc Frequency-domain correlation per tile, per pair. . WILL BE MODIFIED 
	 * @param tile_clust_weights Per-tile weights to calculate per-cluster average coordinates. Sum for each cluster is 1.0
	 * @param threadsMax
	 */
	public static void  accumulateCorrelationsAcOnly( // normalize
			final int            clust_size,
			final float [][][]   num_acc,        // number of accumulated tiles [tilesY][tilesX][pair]
			final float [][][][] fcorr_td_acc,   // [tilesY][tilesX][pair][256] sparse transform domain representation of corr pairs
			final double [][]    tile_clust_weights,  // null or  [tilesY][tilesX]
			final int            threadsMax
			) {
		int tX=-1; 
		for (int ity = 0; ity < fcorr_td_acc.length; ity++) if (fcorr_td_acc[ity] != null){
			tX = fcorr_td_acc[ity].length;
			break;
		}
		final int tilesY =    fcorr_td_acc.length;
		final int tilesX =    tX;
		final int clustersX = (int) Math.ceil(1.0 * tilesX / clust_size);
		final int clustersY = (int) Math.ceil(1.0 * tilesY / clust_size);
		final int clusters =  clustersX * clustersY;
		final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nClust = ai.getAndIncrement(); nClust < clusters; nClust = ai.getAndIncrement()) {
						int clustX = nClust % clustersX;
						int clustY = nClust / clustersX;
						double [] total_weights = null; // for this cluster, per-pair
						double cluster_weight = 0.0;
						for (int ctY = 0; ctY < clust_size; ctY++) {
							int tileY = clustY * clust_size + ctY;
							if (tileY < tilesY) {
								for (int ctX = 0; ctX < clust_size; ctX++) {
									int tileX = clustX * clust_size + ctX;
									if (tileX < tilesX) {
										if (num_acc[tileY][tileX] != null) {
//											if (total_weights == null) {
//												total_weights = new double [num_acc[tileY][tileX].length];
//											}
											for (int pair = 0; pair < num_acc[tileY][tileX].length; pair++) {
												double w = num_acc[tileY][tileX][pair];
												if (w > 0.0) {
													if (total_weights == null) {
														total_weights = new double [num_acc[tileY][tileX].length];
													}
													total_weights[pair] += w; // for this cluster, per-pair
													tile_clust_weights[tileY][tileX] += w; // for this tile
													cluster_weight += w; // all pairs, this cluster
												}
											}
										}
									}
								}
							}
						}
						if (total_weights != null) {
							// normalize per-tile weights, so for each cluster sum is 1.0
							for (int ctY = 0; ctY < clust_size; ctY++) {
								int tileY = clustY * clust_size + ctY;
								if (tileY < tilesY) {
									for (int ctX = 0; ctX < clust_size; ctX++) {
										int tileX = clustX * clust_size + ctX;
										if (tileX < tilesX) {
//											for (int pair = 0; pair < total_weights.length; pair++) {
												tile_clust_weights[tileY][tileX] /= cluster_weight; // for this tile
//											}
										}
									}
								}
							}
							
							// add/normalize correlations from each tile in a cluster to the top-left tile
							int tileX0 = clustX * clust_size;
							int tileY0 = clustY * clust_size;
							if (num_acc[tileY0][tileX0] == null) {
								num_acc[tileY0][tileX0] = new float [total_weights.length];
							}
							if (fcorr_td_acc[tileY0][tileX0] == null) {
								fcorr_td_acc[tileY0][tileX0] = new float [total_weights.length][];
							}
							for (int pair = 0; pair < total_weights.length; pair++) { // if (total_weights[pair] > 0) {
								if (total_weights[pair] > 0) {
									num_acc[tileY0][tileX0][pair] = (float) total_weights[pair];
									for (int ctY = 0; ctY < clust_size; ctY++) {
										int tileY = clustY * clust_size + ctY;
										if (tileY < tilesY) {
											for (int ctX = 0; ctX < clust_size; ctX++) {
												int tileX = clustX * clust_size + ctX;
												if (tileX < tilesX) {
													if ((fcorr_td_acc[tileY][tileX] != null ) &&
															(fcorr_td_acc[tileY][tileX][pair] != null )) {
														if (fcorr_td_acc[tileY0][tileX0][pair] == null) {
															fcorr_td_acc[tileY0][tileX0][pair] = new float [fcorr_td_acc[tileY][tileX][pair].length];
														}
														
														for (int i = 0; i < fcorr_td_acc[tileY0][tileX0][pair].length; i++) {
															fcorr_td_acc[tileY0][tileX0][pair][i] += fcorr_td_acc[tileY][tileX][pair][i];
														}
														/*
													} else {
														if (num_acc[tileY][tileX] != null) { 
															num_acc[tileY][tileX][pair] =    0;
														}
														if (fcorr_td_acc[tileY][tileX] != null) {
															fcorr_td_acc[tileY][tileX][pair] = null;
														}
													}
													if ((ctY != 0) || (ctX != 0)){
														if (num_acc[tileY][tileX] != null) { 
															num_acc[tileY][tileX][pair] =    0;
														}
														if (fcorr_td_acc[tileY][tileX] != null) {
															fcorr_td_acc[tileY][tileX][pair] = null;
														}
														*/
													}
												}
											}
										}
									}
									double k = 1.0 / total_weights[pair];
									for (int i = 0; i < fcorr_td_acc[tileY0][tileX0][pair].length; i++) {
										fcorr_td_acc[tileY0][tileX0][pair][i] *= k;
									}
								}
							}
						}
						// Make null for all pairs (all tiles of empty clusters, all but top left for used ones)
						int aa=0;
						for (int ctY = 0; ctY < clust_size; ctY++) {
							int tileY = clustY * clust_size + ctY;
							if (tileY < tilesY) {
								for (int ctX = 0; ctX < clust_size; ctX++) {
									int tileX = clustX * clust_size + ctX;
									if (tileX < tilesX) {
										if ((ctY != 0) || (ctX != 0) || (total_weights == null)) {
											num_acc[tileY][tileX] =      null; // OK, if that tileX, tileY is not used in tp_tasks
											fcorr_td_acc[tileY][tileX] = null;
										}
									}
								}
							}
						}
						aa++;
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return;
	}
	
	 // what does it do?
	static double [][] conditionInitialDS(
			CLTParameters  clt_parameters,
			QuadCLT        scene,
			int debug_level){
		double [][] dls = scene.getDLS();
		if (dls == null) {
			return null;
		}
		return conditionInitialDS(
				true, // 04.07.2023 //false, // boolean        use_conf,       // use configuration parameters, false - use following  
				clt_parameters,
				dls,
				scene,
				debug_level);
	}
	
	/**
	 * Separately filters foreground and background of the combo_dsn_final_in (see COMBO_DSN_TITLES)
	 * modifies outputs (source untouched) :COMBO_DSN_INDX_DISP, COMBO_DSN_INDX_STRENGTH, COMBO_DSN_INDX_LMA,
	 * COMBO_DSN_INDX_DISP_FG
	 * COMBO_DSN_INDX_DISP_BG, COMBO_DSN_INDX_STRENGTH_BG, COMBO_DSN_INDX_LMA_BG, COMBO_DSN_INDX_DISP_BG_ALL
	 * COMBO_DSN_INDX_STRENGTH_BG corresponds to COMBO_DSN_INDX_DISP_BG_ALL
	 * 
	 * @param use_conf
	 * @param clt_parameters
	 * @param combo_dsn_final_in
	 * @param scene
	 * @param debug_level
	 * @return
	 */
	
	private static double [][] conditionComboDsnFinal(
			boolean        use_conf,       // use configuration parameters, false - use following  
			CLTParameters  clt_parameters,
			double [][]    combo_dsn_final, // dls,
			QuadCLT        scene,
			int            debugLevel) {
		double [][]    combo_dsn_final_out = combo_dsn_final.clone();
		for (int i = 0; i < combo_dsn_final.length; i++) {
			if (combo_dsn_final[i] != null) {
				combo_dsn_final_out[i] = combo_dsn_final[i].clone();
			}
		}
		
        double [][] dls = {
        		combo_dsn_final[COMBO_DSN_INDX_DISP],
        		combo_dsn_final[COMBO_DSN_INDX_LMA],
        		combo_dsn_final[COMBO_DSN_INDX_STRENGTH],
        		combo_dsn_final[COMBO_DSN_INDX_BLUE_SKY]
        };

        double [][] ds = conditionInitialDS(
        		true,                // boolean        use_conf,       // use configuration parameters, false - use following  
        		clt_parameters,      // CLTParameters  clt_parameters,
        		dls,                 // double [][]    dls
        		scene,               // QuadCLT        scene,
        		debugLevel);			
        combo_dsn_final_out[COMBO_DSN_INDX_DISP] =     ds[0];
        combo_dsn_final_out[COMBO_DSN_INDX_STRENGTH] = ds[1];
        combo_dsn_final_out[COMBO_DSN_INDX_DISP_FG] =  ds[0].clone();
        combo_dsn_final_out[COMBO_DSN_INDX_LMA] =      ds[0].clone();
        for (int i = 0; i < combo_dsn_final_out[COMBO_DSN_INDX_LMA].length; i++) {
        	if (Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_LMA][i])) {
        		combo_dsn_final_out[COMBO_DSN_INDX_LMA][i] = Double.NaN; // mark them NaN if original LMA was NaN
        	}
        }
        // BG mode
        double [] bg_lma = (combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL] !=  null) ? combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL].clone(): null;
        double [] bg_str = (combo_dsn_final[COMBO_DSN_INDX_STRENGTH] != null) ? combo_dsn_final[COMBO_DSN_INDX_STRENGTH].clone(): null;
        if ((bg_lma != null) && (bg_str != null)) {
        	for (int i = 0; i < bg_lma.length; i++) {
        		if (Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_LMA][i])){
        			bg_lma[i] = Double.NaN;
        		}
        		if (!Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_DISP_BG][i])){
        			bg_lma[i] = combo_dsn_final[COMBO_DSN_INDX_LMA_BG][i];
        		}
        		if (!Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_STRENGTH_BG][i])){
        			bg_str[i] = combo_dsn_final[COMBO_DSN_INDX_STRENGTH_BG][i];
        		}
        	}
        	double [][] dls_bg = {
        			combo_dsn_final[COMBO_DSN_INDX_DISP_BG_ALL],
        			bg_lma,
        			bg_str};
        	double [][] ds_bg = conditionInitialDS(
        			true, // boolean        use_conf,       // use configuration parameters, false - use following  
        			clt_parameters,         // CLTParameters  clt_parameters,
        			dls_bg,                 // double [][]    dls
        			scene,                  // QuadCLT        scene,
        			debugLevel);
        	combo_dsn_final_out[COMBO_DSN_INDX_DISP_BG_ALL] = ds_bg[0];
        	combo_dsn_final_out[COMBO_DSN_INDX_STRENGTH_BG] = ds_bg[1];
        	combo_dsn_final_out[COMBO_DSN_INDX_DISP_BG] =     ds_bg[0].clone();
        	combo_dsn_final_out[COMBO_DSN_INDX_LMA_BG] =      ds_bg[0].clone();
        	for (int i = 0; i < combo_dsn_final_out[COMBO_DSN_INDX_LMA].length; i++) {
        		if (Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_DISP_BG][i])) {
        			combo_dsn_final_out[COMBO_DSN_INDX_DISP_BG][i] = Double.NaN; // mark them NaN if original LMA was NaN
        			combo_dsn_final_out[COMBO_DSN_INDX_LMA_BG][i] =  Double.NaN; // mark them NaN if original LMA was NaN
        		} 
        		if (Double.isNaN(combo_dsn_final[COMBO_DSN_INDX_LMA_BG][i])) {
        			combo_dsn_final_out[COMBO_DSN_INDX_LMA_BG][i] = Double.NaN; // mark them NaN if original LMA was NaN
        		}
        	}
        } else {
        	if (debugLevel > -3) {
        		System.out.println (" conditionComboDsnFinal(): no BG processing");
        	}
        }
		return combo_dsn_final_out;
	}
	
	
	public static double [][] conditionInitialDS(
			boolean        use_conf,       // use configuration parameters, false - use following  
			CLTParameters  clt_parameters,
			double [][]    dls,
			QuadCLT        scene, // for correction assuming it is a reference scene
			int debug_level)
	{
		int         num_bottom =                      6; // average this number of lowest disparity neighbors (of 8)
		int         num_passes =                    100;
		double      max_change =                      1.0e-3;
		double      min_disparity =                  -0.2;
		double      max_sym_disparity =               0.2;
		double      min_strength_lma =                0.0; // 7; // weaker - treat as non-lma
		double      min_strength_replace =            0.05; ///  0.14; /// Before /// - LWIR, after - RGB
		double      min_strength_blur =               0.06; ///  0.2;
		double      sigma =                           2; /// 5;
		int         num_blur =                        1; // 3;
		double      disparity_corr =                  0.0;
		int         outliers_nth_fromextrem =         1; // second from min/max - removes dual-tile max/mins
		double      outliers_tolerance_absolute =     0.2;
		double      outliers_tolerance_relative =     0.02;
		int         outliers_max_iter =             100;
		double      outliers_max_strength2 =          1.0; // 0.5; any
		int         outliers_nth_fromextrem2 =        0; // second from min/max - removes dual-tile max/mins
		double      outliers_tolerance_absolute2 =    0.5;
		double      outliers_tolerance_relative2 =    0.1;
		double      outliers_lma_max_strength =       0.4; // 0.5;
		double      outliers_max_strength =           0.1; ///  0.25;
		double      outliers_from_lma_max_strength =  0.8;
		int         search_radius =                   1;       // Search farther if no LMA neighbor is found closer. Original value - 1 (8 neighbors)
		boolean     remove_no_lma_neib =              false;
		double      diff_from_lma_pos =             100.0;
		double      diff_from_lma_neg =               2.0;
		int         outliers_lma_nth_fromextrem =     0; // 1; 
		int         margin =                          8; // pixels
		double      weak_tolerance_absolute=          0.25;
		double      weak_tolerance_relative=          0.025;
		int         weak_min_neibs =                  5;
		double      strong_strength=                  0.5;
		double      weak_strength=                    0.2; // none is below 
		
		
		if (use_conf) {
			num_bottom                    = clt_parameters.imp.num_bottom;
			num_passes                    = clt_parameters.imp.num_passes;
			max_change                    = clt_parameters.imp.max_change;
			min_disparity                 = clt_parameters.imp.min_disparity;
			max_sym_disparity             = clt_parameters.imp.max_sym_disparity;
			min_strength_lma              = clt_parameters.imp.min_strength_lma;
			min_strength_replace          = clt_parameters.imp.min_strength_replace;
			min_strength_blur             = clt_parameters.imp.min_strength_blur;
			sigma                         = clt_parameters.imp.sigma;
			num_blur                      = clt_parameters.imp.num_blur;
//			disparity_corr                = clt_parameters.imp.disparity_corr;
			// assuming scene is a referencve scene (others should have scene.getDispInfinityRef() == 0) 
			disparity_corr                = clt_parameters.imp.disparity_corr + scene.getDispInfinityRef() ; // 12/11/2025 - added ref_scene.getDispInfinityRef()
			
			outliers_nth_fromextrem       = clt_parameters.imp.outliers_nth_fromextrem;
			outliers_tolerance_absolute   = clt_parameters.imp.outliers_tolerance_absolute;
			outliers_tolerance_relative   = clt_parameters.imp.outliers_tolerance_relative;
			outliers_max_iter             = clt_parameters.imp.outliers_max_iter;
			outliers_max_strength2        = clt_parameters.imp.outliers_max_strength2;
			outliers_nth_fromextrem2      = clt_parameters.imp.outliers_nth_fromextrem2;
			outliers_tolerance_absolute2  = clt_parameters.imp.outliers_tolerance_absolute2;
			outliers_tolerance_relative2  = clt_parameters.imp.outliers_tolerance_relative2;
			outliers_lma_max_strength     = clt_parameters.imp.outliers_lma_max_strength; //.4
			outliers_max_strength         = clt_parameters.imp.outliers_max_strength;
			outliers_from_lma_max_strength= clt_parameters.imp.outliers_from_lma_max_strength;
			search_radius                 = clt_parameters.imp.search_radius;
			remove_no_lma_neib            = clt_parameters.imp.remove_no_lma_neib;
			diff_from_lma_pos             = clt_parameters.imp.diff_from_lma_pos;
			diff_from_lma_neg             = clt_parameters.imp.diff_from_lma_neg;
			outliers_lma_nth_fromextrem   = clt_parameters.imp.outliers_lma_nth_fromextrem; //0
			margin                        = clt_parameters.imp.filter_margin;
			weak_tolerance_absolute       = clt_parameters.imp.weak_tolerance_absolute; 
			weak_tolerance_relative       = clt_parameters.imp.weak_tolerance_relative; 
			weak_min_neibs                = clt_parameters.imp.weak_min_neibs;          
			strong_strength               = clt_parameters.imp.strong_strength;         
			weak_strength                 = clt_parameters.imp.weak_strength;           

		}
		
		return conditionInitialDS(
				clt_parameters,                 // CLTParameters  clt_parameters,
				dls,                            // double [][]    dls,
				scene,                          // QuadCLT        scene,
				debug_level,                    // int            debug_level,
				num_bottom,                     // final int         num_bottom,
				num_passes,                     // final int         num_passes,
				max_change,                     // final double      max_change,
				min_disparity,                  // final double      min_disparity,
				max_sym_disparity,              // final double      max_sym_disparity,
				min_strength_lma,               // final double      min_strength_lma,
				min_strength_replace,           // final double      min_strength_replace,
				min_strength_blur,              // final double      min_strength_blur,
				sigma,                          // final double      sigma,
				num_blur,                       // final int         num_blur,
				disparity_corr,                 // final double      disparity_corr,
				outliers_nth_fromextrem,        // final int         outliers_nth_fromextrem,
				outliers_tolerance_absolute,    // final double      outliers_tolerance_absolute,
				outliers_tolerance_relative,    // final double      outliers_tolerance_relative,
				outliers_max_iter,              // final int         outliers_max_iter,
				outliers_max_strength2,         // final double      outliers_max_strength2,
				outliers_nth_fromextrem2,       // final int         outliers_nth_fromextrem2,
				outliers_tolerance_absolute2,   // final double      outliers_tolerance_absolute2,
				outliers_tolerance_relative2,   // final double      outliers_tolerance_relative2,
				outliers_lma_max_strength,      // final double      outliers_lma_max_strength,
				outliers_max_strength,          // final double      outliers_max_strength,
				outliers_from_lma_max_strength, // final double      outliers_from_lma_max_strength,
				search_radius,       // final int         search_radius,       // Search farther if no LMA neighbor is found closer. Original value - 1 (8 neighbors)
				remove_no_lma_neib,  // final boolean     remove_no_lma_neib,  // remove without LMA neighbors
				diff_from_lma_pos,              // final double      diff_from_lma_pos,
				diff_from_lma_neg,              // final double      diff_from_lma_neg,
				outliers_lma_nth_fromextrem,    // final int         outliers_lma_nth_fromextrem, 
				margin,                         // final int         margin)
				weak_tolerance_absolute,	
				weak_tolerance_relative,
				weak_min_neibs,
				strong_strength,
				weak_strength);
    }		
	
	
	
	private static double [][] conditionInitialDS(
			CLTParameters  clt_parameters,
			double [][]    dls,
			QuadCLT        scene,
			int            debug_level,
			final int         num_bottom,
			final int         num_passes,
			final double      max_change,
			final double      min_disparity,
			final double      max_sym_disparity,		
			final double      min_strength_lma, // remove weak LMA
			final double      min_strength_replace,
			final double      min_strength_blur,
			final double      sigma,
			final int         num_blur,
			final double      disparity_corr,
			final int         outliers_nth_fromextrem,
			final double      outliers_tolerance_absolute,
			final double      outliers_tolerance_relative,
			final int         outliers_max_iter,
			final double      outliers_max_strength2,
			final int         outliers_nth_fromextrem2,
			final double      outliers_tolerance_absolute2,
			final double      outliers_tolerance_relative2,
			final double      outliers_lma_max_strength,
			final double      outliers_max_strength,
			final double      outliers_from_lma_max_strength,
			final int         search_radius,       // Search farther if no LMA neighbor is found closer. Original value - 1 (8 neighbors)
			final boolean     remove_no_lma_neib,  // remove without LMA neighbors
			final double      diff_from_lma_pos,
			final double      diff_from_lma_neg,
			final int         outliers_lma_nth_fromextrem, 
			final int         margin,
			final double      weak_tolerance_absolute,
			final double      weak_tolerance_relative,
			final int         weak_min_neibs,
			final double      strong_strength,
			final double      weak_strength)
	{
		// TODO:dls[3] if not null - blue sky -	 use for filtering.
		final int tilesX = scene.getTileProcessor().getTilesX();
		final int tilesY = scene.getTileProcessor().getTilesY();
		final int transform_size = scene.getTileProcessor().getTileSize();
		final int tiles =tilesX * tilesY;
		boolean debug_images = (debug_level > 0) && clt_parameters.ofp.enable_debug_images;
		String [] dbg_titles = {"str", "lma", "clean-lma", "disp","-sky","-lma","by-lma","-nonlma", "few_weak", "old-disp","old-sngl","weak","filled"}; 
		double [][] dbg_img = new double [dbg_titles.length][];
		double [] clean_lma = dls[1].clone();
		double [] clean_disparity = dls[0].clone();
		for (int i = 00; i <clean_lma.length; i++) {
			if (dls[2][i] < min_strength_lma) {
				clean_lma[i] = Double.NaN;
			}
		}
		boolean [] blue_sky = scene.getBooleanBlueSky();
		if (blue_sky != null) {
			for (int i = 0; i < clean_lma.length; i++) if (blue_sky[i]){
				clean_lma[i] = Double.NaN;
				clean_disparity[i] = 0.0;
			}
		}
		
		//Remove crazy LMA high-disparity tiles
		dbg_img[0] = dls[2].clone();
		dbg_img[1] = dls[1].clone();
		dbg_img[2] = clean_lma.clone();
		dbg_img[3] = dls[0].clone();
		dbg_img[4] = clean_disparity.clone();
		double [] disp_outliers = QuadCLT.removeDisparityLMAOutliers( // nothing removed (trying to remove bad LMA)
				false,                       // final boolean     non_ma,
				new double[][] {clean_disparity,  clean_lma, dls[2]}, //final double [][] dls,
				outliers_lma_max_strength,   // final double      max_strength,  // do not touch stronger
				outliers_lma_nth_fromextrem, // final int         nth_fromextrem, // 0 - compare to max/min. 1 - second max/min, ... 
				outliers_tolerance_absolute, // final double      tolerance_absolute,
				outliers_tolerance_relative, // final double      tolerance_relative,
				tilesX,                      //final int         width,               //tilesX
				outliers_max_iter,           // final int         max_iter,
				THREADS_MAX,                  // final int         threadsMax,
				debug_level);                // final int         debug_level)
		dbg_img[5] = disp_outliers.clone();
		disp_outliers = QuadCLT.removeDisparityOutliersByLMA( // removed sky, keeps sky edge near strong objects
				new double[][] {disp_outliers, clean_lma, dls[2]}, //final double [][] dls,
				outliers_from_lma_max_strength,  // final double      max_strength,  // do not touch stronger
				diff_from_lma_pos,           // final double      diff_from_lma_pos,   // Difference from farthest FG objects (OK to have large, e.g. 100)
				diff_from_lma_neg,           // final double      diff_from_lma_neg,   // Difference from nearest BG objects (small, as FG are usually more visible)
				search_radius,               // int               search_radius,       // Search farther if no LMA neighbor is found closer. Original value - 1 (8				
				remove_no_lma_neib,          // final boolean     remove_no_lma_neib,  // remove without LMA neighbors
				tilesX,                      // final int         width,               //tilesX
				THREADS_MAX,                  // final int         threadsMax,
				debug_level);                // final int         debug_level)
		dbg_img[6] = disp_outliers.clone();
		// mostly filter infinity, clouds, sky
		disp_outliers = QuadCLT.removeDisparityLMAOutliers( // filter non-lma tiles // removed too few !!!
				true,                       // final boolean     non_ma,
				new double[][] {disp_outliers, clean_lma, dls[2]}, //final double [][] dls,
				outliers_max_strength,   // final double      max_strength,  // do not touch stronger
				outliers_nth_fromextrem, // final int         nth_fromextrem, // 0 - compare to max/min. 1 - second max/min, ... 
				outliers_tolerance_absolute, // final double      tolerance_absolute,
				outliers_tolerance_relative, // final double      tolerance_relative,
				tilesX,                      //final int         width,               //tilesX
				outliers_max_iter,           // final int         max_iter,
				THREADS_MAX,                  // final int         threadsMax,
				debug_level);                // final int         debug_level)
		dbg_img[7] = disp_outliers.clone();
		
		disp_outliers = QuadCLT.removeFewWeak( // filter non-lma tiles // removed too few !!!
				new double[][] {disp_outliers, clean_lma, dls[2]}, //final double [][] dls,
				strong_strength,             // final double      strong,
				weak_strength,               // final double      weak,
				weak_min_neibs,              // final int         min_neibs, 
				weak_tolerance_absolute,     // final double      tolerance_absolute,
				weak_tolerance_relative,     // final double      tolerance_relative,
				tilesX,                      //final int         width,               //tilesX
				outliers_max_iter,           // final int         max_iter,
				THREADS_MAX,                  // final int         threadsMax,
				debug_level);                // final int         debug_level)
		
		dbg_img[8] = disp_outliers.clone();
		// Pre- 2022 filters, some may be obsolete 
		disp_outliers = QuadCLT.removeDisparityOutliers(
				new double[][] {disp_outliers, dls[2]}, //final double [][] dls,
				outliers_max_strength,       // final double      max_strength,  // do not touch stronger
				outliers_nth_fromextrem,     // final int         nth_fromextrem, // 0 - compare to max/min. 1 - second max/min, ... 
				outliers_tolerance_absolute, // final double      tolerance_absolute,
				outliers_tolerance_relative, // final double      tolerance_relative,
				tilesX,                      // final int         width,
				outliers_max_iter,           // final int         max_iter,
				false,                       // final boolean     fit_completely, // do not add tolerance when replacing
				THREADS_MAX,                  // final int         threadsMax,
				debug_level);                // final int         debug_level)
		dbg_img[9] = disp_outliers.clone();
		// remove extreme single-tile outliers (some may be strong - 0.404)
		disp_outliers = QuadCLT.removeDisparityOutliers(
				new double[][] {disp_outliers,  dls[2]}, //final double [][] dls,
				outliers_max_strength2,       // final double      max_strength,  // do not touch stronger
				outliers_nth_fromextrem2,     // final int         nth_fromextrem, // 0 - compare to max/min. 1 - second max/min, ... 
				outliers_tolerance_absolute2, // final double      tolerance_absolute,
				outliers_tolerance_relative2, // final double      tolerance_relative,
				tilesX,                      // final int         width,
				outliers_max_iter,           // final int         max_iter,
				false,                       //  final boolean     fit_completely, // do not add tolerance when replacing
				THREADS_MAX,                  // final int         threadsMax,
				debug_level);                // final int         debug_level)
		dbg_img[10] = disp_outliers.clone();
		double [] disp = QuadCLT.blurWeak(
				new double[][] {disp_outliers, dls[2]}, //final double [][] dls,
				min_strength_blur,    // double      min_strength_blur,
				min_strength_replace, // double      min_strength_replace,
				num_blur,             // int         n,
				tilesX,               // int         width,
				sigma);               // double      sigma);
		dbg_img[11] = disp.clone();
		double [][] ds = {disp,     dls[2]};
		if (blue_sky != null) { // Temporary, fix - pass blue_sky to fillDisparityStrength()
			ds[1] = ds[1].clone();
			for (int i = 0; i < clean_lma.length; i++) if (blue_sky[i]){
				ds[0][i] = 0.0;
				ds[1][i] = 0.0001;
			}
		}
		// ignores results of the last step that produces zero-strength?
		final double [][] ds_filled = QuadCLT.fillDisparityStrength(
				ds,                // final double [][] ds0,
				min_disparity,     // final double      min_disparity,
				max_sym_disparity, // final double      max_sym_disparity, // lower disparity - num_bottom = 8; 
				num_bottom,        // final int         num_bottom, // average this number of lowest disparity neighbors (of 8)
				10000, // num_passes,        // final int         num_passes,
				max_change,        // final double      max_change,
				tilesX,            // final int         width,
				THREADS_MAX,        // final int         threadsMax,
				debug_level+1); // final int         debug_level)
		if (blue_sky != null) {
			for (int i = 0; i < clean_lma.length; i++) if (blue_sky[i]){
				ds_filled[0][i] = 0.0;
			}
		}
		
		
		dbg_img[12] = ds_filled[0].clone();
		
		if ((debug_level > 0)) {// && (clt_parameters.ofp.enable_debug_images)) {
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					tilesX,
					tilesY,
					true,
					"filtered-"+scene.getImageName(),
					dbg_titles); //	dsrbg_titles);
		}
		
		// Mask bad tiles
		final boolean [] valid_tiles = new boolean [tiles]; // not used ?
		final double [][] pXpYD = new double [tiles][3];
		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 < tiles; nTile = ai.getAndIncrement()) {
						int tileY = nTile / tilesX;
						int tileX = nTile % tilesX;
						pXpYD[nTile][0] =  tileX * transform_size + transform_size/2;
						pXpYD[nTile][1] =  tileY * transform_size + transform_size/2;
						pXpYD[nTile][2] =  ds_filled[0][nTile];
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
///		TpTask[]  tp_tasks =  
				GpuQuad.setInterTasks(
				scene.getNumSensors(),
				scene.getGeometryCorrection().getSensorWH()[0],
				!scene.hasGPU(), // final boolean             calcPortsCoordinatesAndDerivatives, // GPU can calculate them centreXY
				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,
    			disparity_corr, // 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
		
		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 (!valid_tiles[nTile]) {
							ds_filled[0][nTile] = Double.NaN;
							ds_filled[1][nTile] = 0.0;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		
		
		if (debug_images) {
					dbg_titles = new String[]{"disp", "disp_outliers", "disp_blur", "disp_filled", "str", "str_filled"};
			dbg_img = new double[][]{dls[0], disp_outliers, ds[0], ds_filled[0], ds[1], ds_filled[1]};
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					tilesX,
					tilesY,
					true,
					"ref_fill_dispatity_gaps-"+scene.getImageName(),
					dbg_titles); //	dsrbg_titles);
		}
//		double dls_out = new double[][] {ds_filled[0],ds_filled[0],ds_filled[0],
		return ds_filled;
	}
	
	
	

	static double [][] getPoseFromErs(
			double k_prev,
			QuadCLT reference_QuadCLT,
			QuadCLT scene_QuadCLT,
			int debug_level)
	{
		ErsCorrection ersCorrection = reference_QuadCLT.getErsCorrection();
		String this_image_name = reference_QuadCLT.getImageName();
		if (debug_level > 0) {
			System.out.println("\n"+this_image_name+":\n"+ersCorrection.extrinsic_corr.toString());
			System.out.println(String.format("%s: ers_wxyz_center=     %f, %f, %f", this_image_name,
					ersCorrection.ers_wxyz_center[0], ersCorrection.ers_wxyz_center[1],ersCorrection.ers_wxyz_center[2] ));
			System.out.println(String.format("%s: ers_wxyz_center_dt=  %f, %f, %f",	this_image_name,
					ersCorrection.ers_wxyz_center_dt[0], ersCorrection.ers_wxyz_center_dt[1],ersCorrection.ers_wxyz_center_dt[2] ));
			System.out.println(String.format("%s: ers_wxyz_center_d2t= %f, %f, %f", this_image_name,
					ersCorrection.ers_wxyz_center_d2t[0], ersCorrection.ers_wxyz_center_d2t[1],ersCorrection.ers_wxyz_center_d2t[2] ));
			System.out.println(String.format("%s: ers_watr_center_dt=  %f, %f, %f", this_image_name,
					ersCorrection.ers_watr_center_dt[0], ersCorrection.ers_watr_center_dt[1],ersCorrection.ers_watr_center_dt[2] ));
			System.out.println(String.format("%s: ers_watr_center_d2t= %f, %f, %f", this_image_name,
					ersCorrection.ers_watr_center_d2t[0], ersCorrection.ers_watr_center_d2t[1],ersCorrection.ers_watr_center_d2t[2] ));
		}
		double dt = 0.0;
		if (scene_QuadCLT == null) {
			scene_QuadCLT = reference_QuadCLT;
		}
		ErsCorrection ersCorrectionPrev = (ErsCorrection) (scene_QuadCLT.geometryCorrection);
		dt = reference_QuadCLT.getTimeStamp() - scene_QuadCLT.getTimeStamp();
		if (dt < 0) {
			k_prev = (1.0-k_prev);
		}
		double frame_time_min = ersCorrectionPrev.line_time*ersCorrectionPrev.pixelCorrectionHeight;// (70fps)
		if (Math.abs(dt) > 2 * frame_time_min) { // 0.15) { // at least two frames TODO: use number of lines* line_time * ...? 
			k_prev = 0.5;
			System.out.println("Non-consecutive frames, dt = "+dt);
		}
		double [] wxyz_center_dt_prev =   ersCorrectionPrev.ers_wxyz_center_dt;
		double [] watr_center_dt_prev =   ersCorrectionPrev.ers_watr_center_dt; // is twice omega!
		double [] wxyz_delta = new double[3];
		double [] watr_delta = new double[3];
		for (int i = 0; i <3; i++) {
			wxyz_delta[i] =              dt * (k_prev * wxyz_center_dt_prev[i] + (1.0-k_prev) * ersCorrection.ers_wxyz_center_dt[i]);
			watr_delta[i] = 0.5 *        dt * (k_prev * watr_center_dt_prev[i] + (1.0-k_prev) * ersCorrection.ers_watr_center_dt[i]);
		}
		watr_delta[0] = -watr_delta[0]; /// TESTING!
		watr_delta[2] = -watr_delta[2]; /// TESTING!
		return new double [][] {wxyz_delta, watr_delta};
	}
	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;
	}
	
	/**
	 * Prepare and set GPU reference TD data to be used for interscene correlatiuon. 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 fro 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 ( // never used
			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);
		final double disparity_corr = clt_parameters.imp.disparity_corr + ref_scene.getDispInfinityRef() ; // 12/11/2025 - added ref_scene.getDispInfinityRef()
//ref_disparity	
		if (ref_disparity == null) {
			ref_disparity = ref_scene.getDLS()[use_lma_dsi?1:0];
		}
		if (ref_pXpYD == null) {
			ref_pXpYD = 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
					null, 			            // final float [][]          fclt, 
					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 double[][]  adjustPairsLMA(
			CLTParameters  clt_parameters,			
			QuadCLT        reference_QuadCLT,
			QuadCLT        scene_QuadCLT,
			double []      camera_xyz0,
			double []      camera_atr0,
			boolean[]      param_select,
			double []      param_regweights,
			double []      rms_out, // null or double [2]
			double [][]    dbg_img, // null or [3][]
			double         max_rms,
			int            debug_level)
	{
		boolean use3D = clt_parameters.ilp.ilma_3d;
		double disparity_weight = use3D? clt_parameters.ilp.ilma_disparity_weight : 0.0;
		TileProcessor tp = reference_QuadCLT.getTileProcessor();
		final int iscale = 8;
		boolean blur_reference = false;
		
		int tilesX =         tp.getTilesX();
		int tilesY =         tp.getTilesY();
		int transform_size = tp.getTileSize();
		int macroTilesX =    tilesX/transform_size;
		int macroTilesY =    tilesY/transform_size;
		
		if (debug_level > -1) {
			System.out.println("adjustPairsLMA():  "+IntersceneLma.printNameV3("ATR",camera_atr0)+
					" "+IntersceneLma.printNameV3("XYZ",camera_xyz0));
		}
		if (dbg_img != null) {
			for (int i = 0; i < dbg_img.length; i++) {
				dbg_img[i] = new double [macroTilesX * macroTilesY * clt_parameters.ilp.ilma_num_corr];
				Arrays.fill(dbg_img[i],Double.NaN);
			}
		}
		if (clt_parameters.ofp.enable_debug_images && (debug_level > 0)) {
			compareRefSceneTiles(
					"-before_LMA",      // String suffix,
					blur_reference,    // boolean blur_reference,
					camera_xyz0,       // double [] camera_xyz0,
					camera_atr0,       // double [] camera_atr0,
					reference_QuadCLT, // QuadCLT reference_QuadCLT,
					scene_QuadCLT,     // QuadCLT scene_QuadCLT,
					iscale);           // int iscale) // 8
		}
		IntersceneLma intersceneLma = new IntersceneLma(
				clt_parameters.ilp.ilma_thread_invariant,
				disparity_weight);
		int nlma = 0;
		int lmaResult = -1;
		// debug: had to clt_parameters.ofp.debug_level_iterate=-10 to stop images
		for (nlma = 0; nlma < clt_parameters.ilp.ilma_num_corr; nlma++) {
			boolean last_run = nlma == ( clt_parameters.ilp.ilma_num_corr - 1);
			int macroTiles = macroTilesX * macroTilesY; 
			double [][] flowXY = new double [macroTiles][2]; // zero pre-shifts
			//		double [][] flowXY_frac = new double [macroTiles][]; // Will contain fractional X/Y shift for CLT
			double [][] reference_tiles_macro = new double [macroTiles][];
			// hack below to pass nlma for debugging. Change  clt_parameters.ilp.ilma_num_corr to -11 after first pass
			int test_debug_level = clt_parameters.ilp.ilma_debug_invariant? (clt_parameters.ofp.debug_level_iterate-nlma): -11;
			double [][] vector_XYS = correlate2DIterate( // returns optical flow and confidence
					clt_parameters.img_dtt, // final ImageDttParameters  imgdtt_params,
					// for prepareSceneTiles()			
					camera_xyz0,                                 // final double []   scene_xyz,     // camera center in world coordinates
					camera_atr0,                                 // final double []   scene_atr,     // camera orientation relative to world frame
					scene_QuadCLT,                               // final QuadCLT     scene_QuadClt,
					reference_QuadCLT,                           // final QuadCLT     reference_QuadClt,
					reference_tiles_macro,                       //			final double [][] reference_tiles_macro,
					clt_parameters.ofp.center_occupancy_ref,         // final double      reference_center_occupancy,   // fraction of remaining  tiles in the center 8x8 area (<1.0)
					// flowXY should be initialized to all pairs of zeros (or deliberate pixel offset pairs if initial error is too high, will be modified with each iteration
					flowXY,                   // final double [][] flowXY, // per macro tile {mismatch in image pixels in X and Y directions // initialize to [reference_tiles.length][2]
					clt_parameters.ofp.tolerance_absolute_inter, // final double      tolerance_absolute, // absolute disparity half-range in each tile
					clt_parameters.ofp.tolerance_relative_inter, // final double      tolerance_relative, // relative disparity half-range in each tile
					clt_parameters.ofp.occupancy_inter,          // final double      occupancy,          // fraction of remaining  tiles (<1.0)
					clt_parameters.ofp.num_laplacian,           // final int         num_passes,
					clt_parameters.ofp.change_laplacian,        // final double      max_change,
					// for correlate2DSceneToReference ()
					clt_parameters.ofp.chn_weights,              // final double []   chn_weights, // absolute, starting from strength (strength,r,b,g)
					clt_parameters.ofp.corr_sigma,               // final double      corr_sigma,
					clt_parameters.ofp.fat_zero,                 //  final double      fat_zero,
					clt_parameters.ofp.late_normalize_iterate,   // final boolean     late_normalize,
					// for correlation2DToVectors_CM()
					clt_parameters.ofp.iradius_cm,               // final int         iradius,      // half-size of the square to process 
					clt_parameters.ofp.dradius_cm,               // final double      dradius,      // weight calculation (1/(r/dradius)^2 + 1)
					clt_parameters.ofp.refine_num_cm,            // final int         refine_num,   // number of iterations to apply weights around new center
					clt_parameters.ofp.num_refine_all,           // final int         num_run_all, // run all tiles for few iterations before filtering
					clt_parameters.ofp.max_refines,              // final int         max_tries,
					// for recalculateFlowXY()
					clt_parameters.ofp.magic_scale,              // final double      magic_scale, // 0.85 for CM
					clt_parameters.ofp.min_change,               // final double      min_change,
					clt_parameters.ofp.best_neibs_num,           // final int         best_num,
					clt_parameters.ofp.ref_stdev,                // final double      ref_stdev,
					test_debug_level, // clt_parameters.ofp.debug_level_iterate-nlma,      // final int         debug_level)
					clt_parameters.ofp.enable_debug_images);     //final boolean     enable_debug_images)
			if (dbg_img != null) {
				for (int iy = 0; iy < macroTilesY; iy++) {
					for (int ix = 0; ix < macroTilesX; ix++) {
						int ii = iy*macroTilesX + ix;
						if  (vector_XYS[ii] != null) {
							int ii1 = (iy * clt_parameters.ilp.ilma_num_corr + nlma) * macroTilesX + ix;
							for (int jj = 0; jj < dbg_img.length; jj++) {
								dbg_img[jj][ii1] = vector_XYS[ii][jj];
							}
						}
					}
				}
			}

			if (clt_parameters.ofp.enable_debug_images && (debug_level > 2)) {
				String dbg_title = "OpticalFlow-"+scene_QuadCLT.getImageName()+"-"+reference_QuadCLT.getImageName()+"-iteration_"+nlma;;
				showVectorXYConfidence(
						dbg_title, // String      title,
						vector_XYS, // double [][] flowXYS,
						macroTilesX); // int         width)	
			}
			int n = removeOutliers(
					clt_parameters.ofp.nsigma, // double nsigma, 1.5 - 2.0
					vector_XYS); // double [][] flowXYS)
			if (debug_level > 1) {
				System.out.println("Removed "+n+" outliers");
			}
			int n2 = removeOutliers(
					clt_parameters.ofp.nsigma2, // double nsigma, 1.5 - 2.0
					vector_XYS); // double [][] flowXYS)
			if (debug_level > 1) {
				System.out.println("Removed "+n2+" outliers in a second pass, total removed:"+(n+n2));
			}
			if (clt_parameters.ofp.enable_debug_images && (debug_level > 0)) {
				if ((debug_level > 1) || (nlma == 0)) { 
					String dbg_title = "OpticalFlowFiltered-"+scene_QuadCLT.getImageName()+"-"+reference_QuadCLT.getImageName()+"-iteration_"+nlma;
					showVectorXYConfidence(
							dbg_title, // String      title,
							vector_XYS, // double [][] flowXYS,
							macroTilesX); // int         width)
				}
			}

			if (clt_parameters.ilp.ilma_debug_invariant) { // dbg_img != null) {
				try {
					System.out.println("getChecksum(camera_xyz0) = " +           IntersceneLma.getChecksum(camera_xyz0));
					System.out.println("getChecksum(camera_atr0) = " +           IntersceneLma.getChecksum(camera_atr0));
					System.out.println("getChecksum(param_select) = " +          IntersceneLma.getChecksum(param_select));
					System.out.println("getChecksum(param_regweights) = " +      IntersceneLma.getChecksum(param_regweights));
					System.out.println("getChecksum(vector_XYS) = " +            IntersceneLma.getChecksum(vector_XYS));
					System.out.println("getChecksum(reference_tiles_macro) = " + IntersceneLma.getChecksum(reference_tiles_macro));
				} catch (NoSuchAlgorithmException | IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
//getChecksum
			intersceneLma.prepareLMA(
					camera_xyz0,                                    // final double []   scene_xyz0,     // camera center in world coordinates (or null to use instance)
					camera_atr0,                                    // final double []   scene_atr0,     // camera orientation relative to world frame (or null to use instance)
					null,                                           // final double []   scene_xyz_pull, // if both are not null, specify target values to pull to 
					null,                                           // final double []   scene_atr_pull, // 
					// reference atr, xyz are considered 0.0
					scene_QuadCLT,                                  // final QuadCLT     scene_QuadClt,
					reference_QuadCLT,                              // final QuadCLT     reference_QuadClt,
					param_select,                                   // final boolean[]   param_select,
					param_regweights,                               // final double []   param_regweights,
					vector_XYS,                                     // final double [][] vector_XYS, // optical flow X,Y, confidence obtained from the correlate2DIterate()
					0 , // final double      min_confidence,// minimal tile confidence to use. Use 0 to disable
					reference_tiles_macro,                          // final double [][] centers,    // macrotile centers (in pixels and average disparities
					false,                                          // final boolean     same_weights,
					(nlma == 0),                                    // boolean           first_run,
					clt_parameters.ilp.ilma_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.ilp.ilma_num_iter,          // int    num_iter,         // 20
					last_run,                                  // boolean last_run,
					clt_parameters.ilp.ilma_debug_level);      // int    debug_level)
			// debugging:
//			String [] lines1 = intersceneLma.printOldNew(false); // boolean allvectors)
//			System.out.println("lmaResult="+lmaResult+", RMS="+intersceneLma.getLastRms()[0]);
//			for (String line : lines1) {
//				System.out.println(line);
//			}
			
			if (lmaResult < 0) {
				System.out.println("LMA failed, nlma="+nlma);
				break;
			}
			camera_xyz0 = intersceneLma.getSceneXYZ(false); // true for initial values
			camera_atr0 = intersceneLma.getSceneATR(false); // true for initial values
			
			if (clt_parameters.ofp.enable_debug_images && (debug_level > 1)) {
				compareRefSceneTiles(
						"iteration_"+nlma,      // String suffix,
						blur_reference,    // boolean blur_reference,
						camera_xyz0,       // double [] camera_xyz0,
						camera_atr0,       // double [] camera_atr0,
						reference_QuadCLT, // QuadCLT reference_QuadCLT,
						scene_QuadCLT,     // QuadCLT scene_QuadCLT,
						iscale);           // int iscale) // 8
			}
			if (lmaResult <= 1) {
				break;
			}
		}
		if (debug_level > 2) { // duplecate - already reported in intersceneLma.runLma()
			System.out.println("LMA: full RMS="+intersceneLma.getLastRms()[0]+", pure RMS="+intersceneLma.getLastRms()[1]);
			String [] lines = intersceneLma.printOldNew(false); // boolean allvectors)
			for (String line : lines) {
				System.out.println(line);
			}
		}

		if (clt_parameters.ofp.enable_debug_images && (debug_level == 1))  {
///		if (!clt_parameters.ofp.enable_debug_images || (clt_parameters.ofp.enable_debug_images && (debug_level == 1)))  {
			compareRefSceneTiles(
					"-after_lma",      // String suffix,
					blur_reference,    // boolean blur_reference,
					camera_xyz0,       // double [] camera_xyz0,
					camera_atr0,       // double [] camera_atr0,
					reference_QuadCLT, // QuadCLT reference_QuadCLT,
					scene_QuadCLT,     // QuadCLT scene_QuadCLT,
					iscale);           // int iscale) // 8
		}
		if (rms_out != 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 new double [][] {camera_xyz0, camera_atr0};
	}
	
	/**
	 * Get per-tile motion blur vector
	 * @param ref_scene reference scene
	 * @param scene current scene (may be the same as reference)
	 * @param ref_pXpYD per-tile pX, pY, disparity for reference scene (some may be nulls) 
	 * @param camera_xyz camera x,y,z relative to the reference
	 * @param camera_atr camera azimuth, tilt, roll relative to the reference
	 * @param camera_xyz_dt camera linear velocities: x', y', z'
	 * @param camera_atr_dt camera angular velocities: azimuth', tilt', roll'
	 * @param shrink_gaps < 0 fill all gaps, 0 - do not fill gaps, >0 expand using growTiles, do not fill farther. 
	 * @param debug_level debug level
	 * @return per-tile array of [2][tiles] of dx/dt, dy/dt, some may be NaN
	 */
	public static double [][] getMotionBlur(
			QuadCLT        ref_scene,
			QuadCLT        scene,         // can be the same as ref_scene
			double [][]    ref_pXpYD,    // tilesX * tilesY
			double []      camera_xyz,
			double []      camera_atr,
			double []      camera_xyz_dt,
			double []      camera_atr_dt,
			int            shrink_gaps,  // will gaps, but not more that grow by this
			int            debug_level)

	{
		int       num_passes = 100;
		double    max_diff = 1E-4;
		boolean[] param_select = new boolean[ErsCorrection.DP_NUM_PARS];
		final int [] par_indices = new int[] {
				ErsCorrection.DP_DSAZ,
				ErsCorrection.DP_DSTL,
				ErsCorrection.DP_DSRL,
				ErsCorrection.DP_DSX,
				ErsCorrection.DP_DSY,
				ErsCorrection.DP_DSZ};
		for (int i: par_indices) {
			param_select[i]=true;
		}
		final double [] camera_dt = new double[] {
				camera_atr_dt[0], camera_atr_dt[1], camera_atr_dt[2],
				camera_xyz_dt[0], camera_xyz_dt[1], camera_xyz_dt[2]};
		final double [][] mb_vectors = new double [2][ref_pXpYD.length];
		Arrays.fill(mb_vectors[0], Double.NaN);
		Arrays.fill(mb_vectors[1], Double.NaN);
		final int tilesX = ref_scene.getTilesX();
//		final int tilesY = ref_scene.tp.getTilesY();
		IntersceneLma intersceneLma = new IntersceneLma(
				false,        // clt_parameters.ilp.ilma_thread_invariant);
				0.0); // always no disparity
		intersceneLma.prepareLMA(
				camera_xyz,    // final double []   scene_xyz0,     // camera center in world coordinates (or null to use instance)
				camera_atr,    // final double []   scene_atr0,     // camera orientation relative to world frame (or null to use instance)
				null,                                           // final double []   scene_xyz_pull, // if both are not null, specify target values to pull to 
				null,                                           // final double []   scene_atr_pull, // 
				scene,         // final QuadCLT     scene_QuadClt,
				ref_scene,     // final QuadCLT     reference_QuadClt,
				param_select,  // final boolean[]   param_select,
				null,          // final double []   param_regweights,
				null,          // final double [][] vector_XYS, // optical flow X,Y, confidence obtained from the correlate2DIterate()
				0 , // final double      min_confidence,// minimal tile confidence to use. Use 0 to disable
				ref_pXpYD,     // final double [][] centers,    // macrotile centers (in pixels and average disparities
				false,         // final boolean     same_weights,
				false,         // boolean           first_run,
				debug_level);  // final int         debug_level)
		final double [][] last_jt = intersceneLma. getLastJT(); // alternating x,y for each selected parameters
		int [] sensor_wh = ref_scene.getGeometryCorrection().getSensorWH();
		final double width = sensor_wh[0];
		final double height = sensor_wh[1];
		final double min_disparity = -0.5;
		final double max_disparity = 100.0;
		final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < ref_pXpYD.length; nTile = ai.getAndIncrement()) if (ref_pXpYD[nTile] != null){
						if (    (ref_pXpYD[nTile][0] < 0) || (ref_pXpYD[nTile][0] >= width) ||
								(ref_pXpYD[nTile][1] < 0) || (ref_pXpYD[nTile][1] >= height) ||
								(ref_pXpYD[nTile][2] < min_disparity) || (ref_pXpYD[nTile][2] >= max_disparity)) {
							continue;
						}
						mb_vectors[0][nTile] = 0.0;
						mb_vectors[1][nTile] = 0.0;
						for (int i = 0; i < par_indices.length; i++) {
							mb_vectors[0][nTile] += camera_dt[i] * last_jt[i][2*nTile + 0];
							mb_vectors[1][nTile] += camera_dt[i] * last_jt[i][2*nTile + 1];
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		if (shrink_gaps != 0) {
			for (int dim = 0; dim < mb_vectors.length; dim++) {
				mb_vectors[dim] =  fillGapsDouble(
						mb_vectors[dim], // double []  data,
						null, // boolean [] mask_in, // do not process if false (may be null)
						tilesX, // int       width,
						(shrink_gaps > 0) ? shrink_gaps: 0, // int       max_grow,
						num_passes, // int       num_passes,
						max_diff, // double    max_diff,
						QuadCLT.THREADS_MAX,          // int               threadsMax,
						debug_level); // int       debug_level)
			}
		}
		return mb_vectors;
	}
	
	// Use TileProcessor.fillNaNs
	public static double[] fillGapsDouble( 
			double []  data,
			boolean [] mask_in, // do not process if false (may be null)
			int       width,
			int       max_grow,
			int       num_passes,
			double    max_diff,
			int       threadsMax,
			int       debug_level)
	{
		final double    max_diff2 = max_diff * max_diff;
        final double   diagonal_weight = 0.5 * Math.sqrt(2.0); // relative to ortho
		double wdiag = 0.25 *diagonal_weight / (diagonal_weight + 1.0);
		double wortho = 0.25 / (diagonal_weight + 1.0);
		final double [] neibw = {wortho, wdiag, wortho, wdiag, wortho, wdiag, wortho, wdiag}; 
		final int tiles = data.length;
		final int height = tiles/width;
		final double [] data_in = data.clone();
		final double [] data_out = data.clone();
		final boolean [] mask = (mask_in==null) ? new boolean[tiles]: mask_in.clone();
		if (mask_in == null) {
			if (max_grow == 0) {
				Arrays.fill(mask,  true);
			} else {
				for (int i = 0; i < tiles; i++) {
					mask[i] = !Double.isNaN(data[i]);
				}
				TileProcessor.growTiles(
						max_grow, // grow,           // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
						mask,     // tiles,
						null,     // prohibit,
						width,
						height); 
			}
		}
		final TileNeibs tn =  new TileNeibs(width, height);
		final int [] tile_indices = new int [tiles];
		final boolean [] floating =      new boolean[tiles]; // which tiles will change
		final Thread[] threads = ImageDtt.newThreadArray(threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		final AtomicInteger anum_gaps = new AtomicInteger(0);
		final int dbg_tile = -3379;
		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 (mask[nTile] && Double.isNaN(data[nTile])){
							int indx = anum_gaps.getAndIncrement();
							tile_indices[indx] = nTile;
							floating[nTile] = true;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		ai.set(0);
		final int num_gaps = anum_gaps.get(); 
		if (num_gaps == 0) {
			return data_in; // no gaps already
		}
		
		final boolean [] fill_all = {false};
		DoubleAccumulator amax_diff =  new DoubleAccumulator (Double::max, Double.NEGATIVE_INFINITY);
		for (int npass = 0; npass < num_passes; npass+= fill_all[0]? 1:0 ) { // do not limit initial passes
			anum_gaps.set(0);
			amax_diff.reset();
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						for (int indx = ai.getAndIncrement(); indx < num_gaps; indx = ai.getAndIncrement()) {
							int nTile = tile_indices[indx];
							if ((debug_level >0) && (nTile == dbg_tile)) {
								System.out.println("fillDisparityStrength() nTile="+nTile);
							}
							if (!fill_all[0] && !Double.isNaN(data_in[nTile])) {
								continue; // fill only new
							}
							double swd = 0.0, sw = 0.0;
							for (int dir = 0; dir < 8; dir++) {
								int nt_neib = tn.getNeibIndex(nTile, dir);
								if ((nt_neib >= 0) && !Double.isNaN(data_in[nt_neib])) {
									sw += neibw[dir];
									swd +=  neibw[dir] * data_in[nt_neib];
								}
							}
							if (sw > 0) {
								double new_val = swd/sw;
								double d = new_val -  data_in[nTile];
								double d2 = d * d;
								amax_diff.accumulate(d2);
								data_out[nTile] = new_val;
							} else {
								anum_gaps.getAndIncrement();	
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
			ai.set(0);
			System.arraycopy(data_out, 0, data_in, 0, tiles);
			if ((debug_level > 0) && fill_all[0]) {
				System.out.println("fillGapsDouble() num_gaps="+num_gaps+", npass="+npass+", change="+Math.sqrt(amax_diff.get())+" ("+max_diff+")");
			}
			if (fill_all[0] && (amax_diff.get() < max_diff2)) {
				break; // all done
			}
			if (anum_gaps.get() == 0) { // no new tiles filled
				fill_all[0] = true; 
			}
			if ((debug_level>0) && (npass == (num_passes-1))){
				System.out.println("fillGapsDouble() LAST PASS ! npass="+npass+", change="+Math.sqrt(amax_diff.get())+" ("+max_diff+")");
				System.out.println("fillGapsDouble() LAST PASS ! npass="+npass+", change="+Math.sqrt(amax_diff.get())+" ("+max_diff+")");
				System.out.println("fillGapsDouble() LAST PASS ! npass="+npass+", change="+Math.sqrt(amax_diff.get())+" ("+max_diff+")");
			}
		} // for (int npass = 0; npass < num_passes; npass+= fill_all[0]? 1:0 )
		return data_out;
	}
	
	public double[][][]  test_LMA(
			CLTParameters  clt_parameters,			
			double k_prev,
			QuadCLT reference_QuadCLT,
			QuadCLT scene_QuadCLT,
			double corr_scale, //  = 0.75
			int debug_level)
	{
		boolean use3D = clt_parameters.ilp.ilma_3d;
		double disparity_weight = use3D? clt_parameters.ilp.ilma_disparity_weight : 0.0;
		TileProcessor tp = reference_QuadCLT.getTileProcessor();
		final int iscale = 8;
		boolean blur_reference = false;
		double ts =        reference_QuadCLT.getTimeStamp();
		double ts_prev =   ts;
		double [] camera_xyz0 = ZERO3.clone();
		double [] camera_atr0 = ZERO3.clone();
		
		ErsCorrection ersCorrection = reference_QuadCLT.getErsCorrection();
		String this_image_name = reference_QuadCLT.getImageName();
		
		System.out.println("\n"+this_image_name+":\n"+ersCorrection.extrinsic_corr.toString());
		System.out.println(String.format("%s: ers_wxyz_center=     %f, %f, %f", this_image_name,
				ersCorrection.ers_wxyz_center[0], ersCorrection.ers_wxyz_center[1],ersCorrection.ers_wxyz_center[2] ));
		System.out.println(String.format("%s: ers_wxyz_center_dt=  %f, %f, %f",	this_image_name,
				ersCorrection.ers_wxyz_center_dt[0], ersCorrection.ers_wxyz_center_dt[1],ersCorrection.ers_wxyz_center_dt[2] ));
		System.out.println(String.format("%s: ers_wxyz_center_d2t= %f, %f, %f", this_image_name,
				ersCorrection.ers_wxyz_center_d2t[0], ersCorrection.ers_wxyz_center_d2t[1],ersCorrection.ers_wxyz_center_d2t[2] ));
		System.out.println(String.format("%s: ers_watr_center_dt=  %f, %f, %f", this_image_name,
				ersCorrection.ers_watr_center_dt[0], ersCorrection.ers_watr_center_dt[1],ersCorrection.ers_watr_center_dt[2] ));
		System.out.println(String.format("%s: ers_watr_center_d2t= %f, %f, %f", this_image_name,
				ersCorrection.ers_watr_center_d2t[0], ersCorrection.ers_watr_center_d2t[1],ersCorrection.ers_watr_center_d2t[2] ));
		
		double dt = 0.0;
		if (scene_QuadCLT == null) {
			scene_QuadCLT = reference_QuadCLT;
		}
		if (scene_QuadCLT != null) {
			ts_prev = scene_QuadCLT.getTimeStamp();
			dt = ts-ts_prev;
			if (dt < 0) {
				k_prev = (1.0-k_prev);
			}
			if (Math.abs(dt) > 0.15) { // at least two frames TODO: use number of lines* line_time * ...? 
				k_prev = 0.5;
				System.out.println("Non-consecutive frames, dt = "+dt);
			}
			ErsCorrection ersCorrectionPrev = (ErsCorrection) (scene_QuadCLT.geometryCorrection);
			double [] wxyz_center_dt_prev =   ersCorrectionPrev.ers_wxyz_center_dt;
			double [] watr_center_dt_prev =   ersCorrectionPrev.ers_watr_center_dt;
			double [] wxyz_delta = new double[3];
			double [] watr_delta = new double[3];
			for (int i = 0; i <3; i++) {
				wxyz_delta[i] = corr_scale * dt * (k_prev * wxyz_center_dt_prev[i] + (1.0-k_prev) * ersCorrection.ers_wxyz_center_dt[i]);
				watr_delta[i] = corr_scale * dt * (k_prev * watr_center_dt_prev[i] + (1.0-k_prev) * ersCorrection.ers_watr_center_dt[i]);
			}
			camera_xyz0 = wxyz_delta;
			camera_atr0 = watr_delta;
		}
		
		int tilesX = tp.getTilesX();
		int tilesY = tp.getTilesY();
		if (debug_level > 0) {
			compareRefSceneTiles(
					"before_LMA",      // String suffix,
					blur_reference,    // boolean blur_reference,
					camera_xyz0,       // double [] camera_xyz0,
					camera_atr0,       // double [] camera_atr0,
					reference_QuadCLT, // QuadCLT reference_QuadCLT,
					scene_QuadCLT,     // QuadCLT scene_QuadCLT,
					iscale);           // int iscale) // 8
		}
		IntersceneLma intersceneLma = new IntersceneLma(
				clt_parameters.ilp.ilma_thread_invariant,
				disparity_weight);
		for (int nlma = 0; nlma < clt_parameters.ilp.ilma_num_corr; nlma++) {
			boolean last_run = nlma == ( clt_parameters.ilp.ilma_num_corr - 1);
			int transform_size = tp.getTileSize();
			int macroTilesX =          tilesX/transform_size;
			int macroTilesY =          tilesY/transform_size;
			int macroTiles = macroTilesX * macroTilesY; 
			double [][] flowXY = new double [macroTiles][2]; // zero pre-shifts
			//		double [][] flowXY_frac = new double [macroTiles][]; // Will contain fractional X/Y shift for CLT
			double [][] reference_tiles_macro = new double [macroTiles][];
			double [][] vector_XYS = correlate2DIterate( // returns optical flow and confidence
					clt_parameters.img_dtt, // final ImageDttParameters  imgdtt_params,
					// for prepareSceneTiles()			
					camera_xyz0,                                 // final double []   scene_xyz,     // camera center in world coordinates
					camera_atr0,                                 // final double []   scene_atr,     // camera orientation relative to world frame
					scene_QuadCLT,                               // final QuadCLT     scene_QuadClt,
					reference_QuadCLT,                           // final QuadCLT     reference_QuadClt,
					reference_tiles_macro,                       //			final double [][] reference_tiles_macro,
					clt_parameters.ofp.center_occupancy_ref,         // final double      reference_center_occupancy,   // fraction of remaining  tiles in the center 8x8 area (<1.0)
					// flowXY should be initialized to all pairs of zeros (or deliberate pixel offset pairs if initial error is too high, will be modified with each iteration
					flowXY,                   // final double [][] flowXY, // per macro tile {mismatch in image pixels in X and Y directions // initialize to [reference_tiles.length][2]
					clt_parameters.ofp.tolerance_absolute_inter, // final double      tolerance_absolute, // absolute disparity half-range in each tile
					clt_parameters.ofp.tolerance_relative_inter, // final double      tolerance_relative, // relative disparity half-range in each tile
					clt_parameters.ofp.occupancy_inter,          // final double      occupancy,          // fraction of remaining  tiles (<1.0)
					clt_parameters.ofp.num_laplacian,           // final int         num_passes,
					clt_parameters.ofp.change_laplacian,        // final double      max_change,
					// for correlate2DSceneToReference ()
					clt_parameters.ofp.chn_weights,              // final double []   chn_weights, // absolute, starting from strength (strength,r,b,g)
					clt_parameters.ofp.corr_sigma,               // final double      corr_sigma,
					clt_parameters.ofp.fat_zero,                 //  final double      fat_zero,
					clt_parameters.ofp.late_normalize_iterate,   // final boolean     late_normalize,
					// for correlation2DToVectors_CM()
					clt_parameters.ofp.iradius_cm,               // final int         iradius,      // half-size of the square to process 
					clt_parameters.ofp.dradius_cm,               // final double      dradius,      // weight calculation (1/(r/dradius)^2 + 1)
					clt_parameters.ofp.refine_num_cm,            // final int         refine_num,   // number of iterations to apply weights around new center
					clt_parameters.ofp.num_refine_all,           // final int         num_run_all, // run all tiles for few iterations before filtering
					clt_parameters.ofp.max_refines,              // final int         max_tries,
					// for recalculateFlowXY()
					clt_parameters.ofp.magic_scale,              // final double      magic_scale, // 0.85 for CM
					clt_parameters.ofp.min_change,               // final double      min_change,
					clt_parameters.ofp.best_neibs_num,           // final int         best_num,
					clt_parameters.ofp.ref_stdev,                // final double      ref_stdev,
					clt_parameters.ofp.debug_level_iterate,      // final int         debug_level)
					clt_parameters.ofp.enable_debug_images);     //final boolean     enable_debug_images)

			if (debug_level > 2) {
				String dbg_title = "OpticalFlow-"+scene_QuadCLT.getImageName()+"-"+reference_QuadCLT.getImageName()+"-iteration_"+nlma;;
				showVectorXYConfidence(
						dbg_title, // String      title,
						vector_XYS, // double [][] flowXYS,
						macroTilesX); // int         width)	
			}
			int n = removeOutliers(
					clt_parameters.ofp.nsigma, // double nsigma, 1.5 - 2.0
					vector_XYS); // double [][] flowXYS)
			if (debug_level > -1) {
				System.out.println("Removed "+n+" outliers");
			}
			int n2 = removeOutliers(
					clt_parameters.ofp.nsigma2, // double nsigma, 1.5 - 2.0
					vector_XYS); // double [][] flowXYS)
			if (debug_level > -1) {
				System.out.println("Removed "+n2+" outliers in a second pass, total removed:"+(n+n2));
			}
			if (debug_level > 1) {
				String dbg_title = "OpticalFlowFiltered-"+scene_QuadCLT.getImageName()+"-"+reference_QuadCLT.getImageName()+"-iteration_"+nlma;
				showVectorXYConfidence(
						dbg_title, // String      title,
						vector_XYS, // double [][] flowXYS,
						macroTilesX); // int         width)	
			}

			intersceneLma.prepareLMA(
					camera_xyz0,                                    // final double []   scene_xyz0,     // camera center in world coordinates (or null to use instance)
					camera_atr0,                                    // final double []   scene_atr0,     // camera orientation relative to world frame (or null to use instance)
					null,                                           // final double []   scene_xyz_pull, // if both are not null, specify target values to pull to 
					null,                                           // final double []   scene_atr_pull, // 
					// reference atr, xyz are considered 0.0
					scene_QuadCLT,                                  // final QuadCLT     scene_QuadClt,
					reference_QuadCLT,                              //final QuadCLT     reference_QuadClt,
					clt_parameters.ilp.ilma_lma_select,             // final boolean[]   param_select,
					clt_parameters.ilp.ilma_regularization_weights, //  final double []   param_regweights,
					vector_XYS,                                     // final double [][] vector_XYS, // optical flow X,Y, confidence obtained from the correlate2DIterate()
					0 , // final double      min_confidence,// minimal tile confidence to use. Use 0 to disable
					reference_tiles_macro,                          // final double [][] centers,    // macrotile centers (in pixels and average disparities
					false,                                          // final boolean     same_weights,
					(nlma == 0),                                    // boolean           first_run,
					debug_level);                                   // final int         debug_level)
			int 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.ilp.ilma_num_iter,          // int    num_iter,         // 20
					last_run,                                  // boolean last_run,
					clt_parameters.ilp.ilma_debug_level);      // int    debug_level)
			if (lmaResult < 0) {
				System.out.println("LMA failed");
				break;
			}
			camera_xyz0 = intersceneLma.getSceneXYZ(false); // true for initial values
			camera_atr0 = intersceneLma.getSceneATR(false); // true for initial values
			if (debug_level > 1) {
				compareRefSceneTiles(
						"iteration_"+nlma,      // String suffix,
						blur_reference,    // boolean blur_reference,
						camera_xyz0,       // double [] camera_xyz0,
						camera_atr0,       // double [] camera_atr0,
						reference_QuadCLT, // QuadCLT reference_QuadCLT,
						scene_QuadCLT,     // QuadCLT scene_QuadCLT,
						iscale);           // int iscale) // 8
			}
			if (lmaResult <= 1) {
				break;
			}
		}
		if (debug_level == 1)  {
			compareRefSceneTiles(
					"after_lma",      // String suffix,
					blur_reference,    // boolean blur_reference,
					camera_xyz0,       // double [] camera_xyz0,
					camera_atr0,       // double [] camera_atr0,
					reference_QuadCLT, // QuadCLT reference_QuadCLT,
					scene_QuadCLT,     // QuadCLT scene_QuadCLT,
					iscale);           // int iscale) // 8
		}
		reference_QuadCLT.getErsCorrection().addScene(scene_QuadCLT.getImageName(), camera_xyz0,camera_atr0);
		reference_QuadCLT.saveInterProperties( // save properties for interscene processing (extrinsics, ers, ...)
	            null, // String path,             // full name with extension or w/o path to use x3d directory
	            debug_level);
		return null;
	}
	
//		ErsCorrection ersCorrection = reference_QuadCLT.getErsCorrection();
	
	/**
	 * A top-level method for testing optical flow generating, currently includes temporary testing functionality 
	 * @param clt_parameters CLT parameters
	 * @param k_prev Coefficient of the previous (in time) frame weight to calculate initial estimation of the pose
	 *        differences from the single-scene ERS values determined from the Lazy Eye LMA adjustment. The ERS
	 *        parameters typically correspond to the second half of the image (top is usually inifinity/long range,
	 *        while the scene pose is calculated for the image center scanline.  Tested with k_prev = 0.75
	 * @param reference_QuadClt Reference QuadCLT instance.
	 * @param scene_QuadClt Scene QuadCLT instance.
	 * @param corr_scale Correction coefficient - still to find out the reason that the pose difference predicted
	 *        from the intrascene ERS should be reduced when calculating interscene pose difference. The heuristc
	 *        value is 0.75.
	 * @param debug_level Debug Level
	 * @return a pair of reference and interpolated scenes
	 */
	public double[][][]  get_pair(
			CLTParameters  clt_parameters,			
			double k_prev,
			QuadCLT reference_QuadCLT,
			QuadCLT scene_QuadCLT,
			double corr_scale, //  = 0.75 - REMOVE
			int debug_level)
	{
		TileProcessor tp = reference_QuadCLT.getTileProcessor();
		final int iscale = 8;
		double ts =        reference_QuadCLT.getTimeStamp();
		double ts_prev =   ts;
		double [] camera_xyz0 = ZERO3.clone();
		double [] camera_atr0 = ZERO3.clone();
		
		ErsCorrection ersCorrection = reference_QuadCLT.getErsCorrection();
		String this_image_name = reference_QuadCLT.getImageName();
		
		System.out.println("\n"+this_image_name+":\n"+ersCorrection.extrinsic_corr.toString());
		System.out.println(String.format("%s: ers_wxyz_center=     %f, %f, %f", this_image_name,
				ersCorrection.ers_wxyz_center[0], ersCorrection.ers_wxyz_center[1],ersCorrection.ers_wxyz_center[2] ));
		System.out.println(String.format("%s: ers_wxyz_center_dt=  %f, %f, %f",	this_image_name,
				ersCorrection.ers_wxyz_center_dt[0], ersCorrection.ers_wxyz_center_dt[1],ersCorrection.ers_wxyz_center_dt[2] ));
		System.out.println(String.format("%s: ers_wxyz_center_d2t= %f, %f, %f", this_image_name,
				ersCorrection.ers_wxyz_center_d2t[0], ersCorrection.ers_wxyz_center_d2t[1],ersCorrection.ers_wxyz_center_d2t[2] ));
		System.out.println(String.format("%s: ers_watr_center_dt=  %f, %f, %f", this_image_name,
				ersCorrection.ers_watr_center_dt[0], ersCorrection.ers_watr_center_dt[1],ersCorrection.ers_watr_center_dt[2] ));
		System.out.println(String.format("%s: ers_watr_center_d2t= %f, %f, %f", this_image_name,
				ersCorrection.ers_watr_center_d2t[0], ersCorrection.ers_watr_center_d2t[1],ersCorrection.ers_watr_center_d2t[2] ));
		
		double dt = 0.0;
		if (scene_QuadCLT == null) {
			scene_QuadCLT = reference_QuadCLT;
		}
		if (scene_QuadCLT != null) {
			ts_prev = scene_QuadCLT.getTimeStamp();
			dt = ts-ts_prev;
			if (dt < 0) {
				k_prev = (1.0-k_prev);
			}
			if (Math.abs(dt) > 0.15) { // at least two frames TODO: use number of lines* line_time * ...? 
				k_prev = 0.5;
				System.out.println("Non-consecutive frames, dt = "+dt);
			}
			ErsCorrection ersCorrectionPrev = (ErsCorrection) (scene_QuadCLT.geometryCorrection);
			double [] wxyz_center_dt_prev =   ersCorrectionPrev.ers_wxyz_center_dt;
			double [] watr_center_dt_prev =   ersCorrectionPrev.ers_watr_center_dt;
			double [] wxyz_delta = new double[3];
			double [] watr_delta = new double[3];
			for (int i = 0; i <3; i++) {
				wxyz_delta[i] = corr_scale * dt * (k_prev * wxyz_center_dt_prev[i] + (1.0-k_prev) * ersCorrection.ers_wxyz_center_dt[i]);
				watr_delta[i] = corr_scale * dt * (k_prev * watr_center_dt_prev[i] + (1.0-k_prev) * ersCorrection.ers_watr_center_dt[i]);
			}
			camera_xyz0 = wxyz_delta;
			camera_atr0 = watr_delta;
		}
		
		int tilesX = tp.getTilesX();
		int tilesY = tp.getTilesY();
		String [] dsrbg_titles = {"d", "s", "r", "b", "g"};
		String title = this_image_name+"-"+scene_QuadCLT.image_name+"-dt"+dt;
		double [][] dsrbg = transformCameraVew( // shifts previous image correctly (right)
				title,                   // final String    title,
				null, // final double [][] dsrbg_camera_in,
				camera_xyz0,             // double [] camera_xyz, // camera center in world coordinates
				camera_atr0,             //double [] camera_atr, // camera orientation relative to world frame
				scene_QuadCLT,           // QuadCLT   camera_QuadClt,
				reference_QuadCLT,       // reference
				iscale);
		double [][][] pair = {reference_QuadCLT.getDSRBG(),dsrbg};
		
		/*
		reference_QuadCLT.getErsCorrection().compareDSItoWorldDerivatives(
				reference_QuadCLT, // QuadCLT   scene_QuadClt,
				0.03,              // double    max_inf_disparity, // absolute value
				1);                // int       debug_level);
		*/
		reference_QuadCLT.getErsCorrection().comparePXYD_Derivatives(
				scene_QuadCLT,     // QuadCLT   scene_QuadClt,
				reference_QuadCLT, // QuadCLT   reference_QuadClt,
				0.03, // double    max_inf_disparity, // absolute value
				1); // int       debug_level
		
		
		if (debug_level > -100) {
			return pair;
		}
		
		// combine this scene with warped previous one
		if (debug_level > -2) {
			String [] rtitles = new String[2* dsrbg_titles.length];
			double [][] dbg_rslt = new double [rtitles.length][];
			for (int i = 0; i < dsrbg_titles.length; i++) {
				rtitles[2*i] =    dsrbg_titles[i]+"0";
				rtitles[2*i+1] =  dsrbg_titles[i];
				dbg_rslt[2*i] =   pair[0][i];
				dbg_rslt[2*i+1] = pair[1][i];
			}
//			String title = this_image_name+"-"+scene_QuadCLT.image_name+"-dt"+dt;
			ShowDoubleFloatArrays.showArrays(
					dbg_rslt,
					tilesX,
					tilesY,
					true,
					title,
					rtitles);
		}
///		double      tolerance_absolute = 0.25; // absolute disparity half-range in each tile
///		double      tolerance_relative = 0.2; // relative disparity half-range in each tile
///		double      center_occupancy =   0.25;   // fraction of remaining  tiles in the center 8x8 area (<1.0)
///		int         num_passes = 100;
///		double      max_change = 0.005 ;

///		double      tolerance_absolute_inter = 0.25; // absolute disparity half-range in each tile
///		double      tolerance_relative_inter = 0.2; // relative disparity half-range in each tile
///		double      occupancy_inter =         0.25;   // fraction of remaining  tiles in the center 8x8 area (<1.0)

		// Add limitations on disparity ? To be used with multi-tile consolidation
		// Check with walking, not only rotating
		int transform_size = tp.getTileSize();
		int macroTilesX =          tilesX/transform_size;
		int macroTilesY =          tilesY/transform_size;
		int macroTiles = macroTilesX * macroTilesY; 
		double [][] flowXY = new double [macroTiles][2]; // zero pre-shifts
		double [][] flowXY_frac = new double [macroTiles][]; // Will contain fractional X/Y shift for CLT
///		double []   chn_weights = {1.0,1.0,1.0,1.0}; // strength, r,b,g
//		double []   chn_weights = {1.0,0.0,0.0,0.0}; // strength, r,b,g
//		double []   chn_weights = {0.0,1.0,1.0,1.0}; // strength, r,b,g
		// Apply DOG to colors, normalize by standard deviation?
///		double      corr_sigma = 0.5;
///		double      fat_zero =   0.05;
///		double      frac_radius = 0.9;  // add to integer radius for window calculation
///		double      tolerance_absolute_inter_macro = 0.25; // absolute disparity half-range to consolidate macro tiles
///		double      tolerance_relative_inter_macro = 0.2;  // relative disparity half-range to consolidate macro tiles
///		int         iradius =    3;      // half-size of the square to process 
///		double      dradius =    1.5;      // weight calculation (1/(r/dradius)^2 + 1)
///		int         refine_num_cm = 5;   // number of iterations to apply weights around new center
///		int         num_refine_all = 3;
///		int         max_refines =   50;
////		int         max_rad = 3;
		
///		int         best_num = 4; // use  4 best neighbors to calculate std deviation
///		double      ref_stdev = 5.0; // strength 0.5 if standard deviation of best neighbors to tile difference is this.
		
///		boolean     combine_empty_only = true; // false;
///		double      magic_scale = 0.85; // 2.0 * 0.85;

///		boolean late_normalize_iterate = true;
		
		// for recalculateFlowXY()
///		double      min_change = 0.1; // 01;//   sqrt (dx*dx + dy*dy) for correction (int tiles) in pixels
		
///		int         debug_level_iterate = -1; // 2;

		
		double [][] vector_XYS = correlate2DIterate( // returns optical flow and confidence
				clt_parameters.img_dtt, // final ImageDttParameters  imgdtt_params,
				// for prepareSceneTiles()			
				camera_xyz0,                                 // final double []   scene_xyz,     // camera center in world coordinates
				camera_atr0,                                 // final double []   scene_atr,     // camera orientation relative to world frame
				scene_QuadCLT,                               // final QuadCLT     scene_QuadClt,
				reference_QuadCLT,                           // final QuadCLT     reference_QuadClt,
				null,                                        // final double [][] reference_tiles_macro,
				clt_parameters.ofp.center_occupancy_ref,         // final double      reference_center_occupancy,   // fraction of remaining  tiles in the center 8x8 area (<1.0)
				// flowXY should be initialized to all pairs of zeros (or deliberate pixel offset pairs if initial error is too high, will be modified with each iteration
				flowXY,                   // final double [][] flowXY, // per macro tile {mismatch in image pixels in X and Y directions // initialize to [reference_tiles.length][2]
				clt_parameters.ofp.tolerance_absolute_inter, // final double      tolerance_absolute, // absolute disparity half-range in each tile
				clt_parameters.ofp.tolerance_relative_inter, // final double      tolerance_relative, // relative disparity half-range in each tile
				clt_parameters.ofp.occupancy_inter,          // final double      occupancy,          // fraction of remaining  tiles (<1.0)
				clt_parameters.ofp.num_laplacian,           // final int         num_passes,
				clt_parameters.ofp.change_laplacian,        // final double      max_change,
				// for correlate2DSceneToReference ()
				clt_parameters.ofp.chn_weights,              // final double []   chn_weights, // absolute, starting from strength (strength,r,b,g)
				clt_parameters.ofp.corr_sigma,               // final double      corr_sigma,
				clt_parameters.ofp.fat_zero,                 //  final double      fat_zero,
				clt_parameters.ofp.late_normalize_iterate,   // final boolean     late_normalize,
				// for correlation2DToVectors_CM()
				clt_parameters.ofp.iradius_cm,               // final int         iradius,      // half-size of the square to process 
				clt_parameters.ofp.dradius_cm,               // final double      dradius,      // weight calculation (1/(r/dradius)^2 + 1)
				clt_parameters.ofp.refine_num_cm,            // final int         refine_num,   // number of iterations to apply weights around new center
				clt_parameters.ofp.num_refine_all,           // final int         num_run_all, // run all tiles for few iterations before filtering
				clt_parameters.ofp.max_refines,              // final int         max_tries,
				// for recalculateFlowXY()
				clt_parameters.ofp.magic_scale,              // final double      magic_scale, // 0.85 for CM
				clt_parameters.ofp.min_change,               // final double      min_change,
				clt_parameters.ofp.best_neibs_num,           // final int         best_num,
				clt_parameters.ofp.ref_stdev,                // final double      ref_stdev,
				clt_parameters.ofp.debug_level_iterate,      // final int         debug_level)
				clt_parameters.ofp.enable_debug_images);     //final boolean     enable_debug_images)
		
		if (debug_level > -2) {
			String dbg_title = "OpticalFlow-"+scene_QuadCLT.getImageName()+"-"+reference_QuadCLT.getImageName();
			showVectorXYConfidence(
					dbg_title, // String      title,
					vector_XYS, // double [][] flowXYS,
					macroTilesX); // int         width)	
		}
		
		
		if (debug_level > 0) {

			double [][][] reference_tiles = prepareReferenceTiles(
					reference_QuadCLT,        // final QuadCLT     qthis,
					clt_parameters.ofp.tolerance_absolute_ref, // final double      tolerance_absolute, // absolute disparity half-range in each tile
					clt_parameters.ofp.tolerance_relative_ref, // final double      tolerance_relative, // relative disparity half-range in each tile
					clt_parameters.ofp.center_occupancy_ref,   // final double      center_occupancy,   // fraction of remaining  tiles in the center 8x8 area (<1.0)
					-1); // -1); // 2); // final int         debug_level)

			fillTilesNans(
					reference_tiles,          // final double [][][] nan_tiles,
					reference_QuadCLT,                 // final QuadCLT     qthis,
					clt_parameters.ofp.num_laplacian, //   num_passes,            // final int         num_passes,
					clt_parameters.ofp.change_laplacian, // max_change,            // final double      max_change,
					-1); //-1); // 2);                    // final int         debug_level)


			double [][][] scene_tiles = prepareSceneTiles(// to match to reference
					// null for {scene,reference}{xyz,atr} uses instances globals 
					camera_xyz0,              // final double []   scene_xyz,     // camera center in world coordinates
					camera_atr0,              // final double []   scene_atr,     // camera orientation relative to world frame
					scene_QuadCLT,                    // final QuadCLT     scene_QuadClt,
					reference_QuadCLT,                    // final QuadCLT     reference_QuadClt,
					reference_tiles,          // final double [][][] reference_tiles, // prepared with prepareReferenceTiles() + fillTilesNans();
					flowXY,                   // final double [][] flowXY, // per macro tile {mismatch in image pixels in X and Y directions
					flowXY_frac,              // final double [][] flowXY_frac, // should be initialized as [number of macro tiles][] - returns fractional shifts [-0.5, 0.5)
					clt_parameters.ofp.tolerance_absolute_inter, // final double      tolerance_absolute, // absolute disparity half-range in each tile
					clt_parameters.ofp.tolerance_relative_inter, // final double      tolerance_relative, // relative disparity half-range in each tile
					clt_parameters.ofp.occupancy_inter,          // final double      occupancy,          // fraction of remaining  tiles (<1.0)
					clt_parameters.ofp.num_laplacian, //   num_passes,            // final int         num_passes,
					clt_parameters.ofp.change_laplacian, // max_change,            // final double      max_change,
					-1); //-1); // 1); // 2);                       // final int         debug_level)

			String dbg_title = "flowXY_frac-"+scene_QuadCLT.getImageName()+"-"+reference_QuadCLT.getImageName();
			String [] dbg_titles = {"dpX", "dpY"};
			double [][] dbg_img = new double [dbg_titles.length][macroTilesX*macroTilesY];
				Arrays.fill(dbg_img[0], Double.NaN);
				Arrays.fill(dbg_img[1], Double.NaN);
				for (int i = 0; i < flowXY_frac.length; i++) if (flowXY_frac[i] != null){
					dbg_img[0][i] = flowXY_frac[i][0];
					dbg_img[1][i] = flowXY_frac[i][1];
				}
			ShowDoubleFloatArrays.showArrays(
					dbg_img,
					macroTilesX,
					macroTilesY,
					true,
					dbg_title,
					dbg_titles);

			double [][][] corr2dscene_ref_multi = new double [clt_parameters.ofp.test_corr_rad_max + 2][][]; 
			corr2dscene_ref_multi[0] = correlate2DSceneToReference(// to match to reference
					clt_parameters.img_dtt, // final ImageDttParameters  imgdtt_params,
					scene_QuadCLT,          // final QuadCLT     scene_QuadClt,
					reference_QuadCLT,      // final QuadCLT     reference_QuadClt,
					scene_tiles,            // final double [][][] scene_tiles,     // prepared with prepareSceneTiles()
					reference_tiles,        // final double [][][] reference_tiles, // prepared with prepareReferenceTiles() + fillTilesNans(); - combine?
					flowXY_frac,            // final double [][] flowXY_frac, // X, YH fractional shift [-0.5,0.5) to implement with FD rotations
					clt_parameters.ofp.chn_weights,            // final double []   chn_weights, // absolute, starting from strength (strength,r,b,g)
					clt_parameters.ofp.corr_sigma,             // final double      corr_sigma,
					clt_parameters.ofp.fat_zero,               //  final double      fat_zero,
					false,                  // final boolean     late_normalize,
					clt_parameters.ofp.combine_empty_only,     //  final boolean     combine_empty_only, // only use neighbor correlations for empty tiles (false - any)
					0.0,     // final double      combine_dradius
					clt_parameters.ofp.tolerance_absolute_macro, // final double      tolerance_absolute, // absolute disparity half-range to consolidate tiles
					clt_parameters.ofp.tolerance_relative_macro, // final double      tolerance_relative, // relative disparity half-range to consolidate tiles
					-1); // 1); // final int         debug_level)

			for (int irad = 0; irad <= 3; irad++) {
				corr2dscene_ref_multi[irad+1]= correlate2DSceneToReference(// to match to reference
						clt_parameters.img_dtt, // final ImageDttParameters  imgdtt_params,
						scene_QuadCLT,          // final QuadCLT     scene_QuadClt,
						reference_QuadCLT,      // final QuadCLT     reference_QuadClt,
						scene_tiles,            // final double [][][] scene_tiles,     // prepared with prepareSceneTiles()
						reference_tiles,        // final double [][][] reference_tiles, // prepared with prepareReferenceTiles() + fillTilesNans(); - combine?
						flowXY_frac,            // final double [][] flowXY_frac, // X, YH fractional shift [-0.5,0.5) to implement with FD rotations
						clt_parameters.ofp.chn_weights,            // final double []   chn_weights, // absolute, starting from strength (strength,r,b,g)
						clt_parameters.ofp.corr_sigma,             // final double      corr_sigma,
						clt_parameters.ofp.fat_zero,               //  final double      fat_zero,
						true,                   // final boolean     late_normalize,
						clt_parameters.ofp.combine_empty_only,     //  final boolean     combine_empty_only, // only use neighbor correlations for empty tiles (false - any)
						irad + clt_parameters.ofp.frac_radius,     // final double      combine_dradius
						clt_parameters.ofp.tolerance_absolute_macro, // final double      tolerance_absolute, // absolute disparity half-range to consolidate tiles
						clt_parameters.ofp.tolerance_relative_macro, // final double      tolerance_relative, // relative disparity half-range to consolidate tiles
						-1); // 1); // final int         debug_level)
			}

			showCorrTiles(
					"scene:"+scene_QuadCLT.getImageName()+"-ref"+reference_QuadCLT.getImageName(),  //  String title,
					corr2dscene_ref_multi,           // double [][] source_tiles,
					tilesX/transform_size,     // int         tilesX,
					(2 * transform_size - 1),  // int         tile_width,
					(2 * transform_size - 1)); // int         tile_height) // extra margins over 16x16 tiles to accommodate distorted destination tiles
			//reference_tiles
			double [][][][] scene_to_ref = {reference_tiles, scene_tiles};
			if (debug_level > 0) {
				showCompareMacroTiles(
						"tiles_scene-"+scene_QuadCLT.getImageName()+"-ref"+reference_QuadCLT.getImageName(),// String title,
						scene_to_ref,      // double [][][][] source_tiles_sets,
						reference_QuadCLT, // final QuadCLT qthis,
						0);                // final int     margin) // extra margins over 16x16 tiles to accommodate distorted destination tiles
			}


			if (debug_level > 100) {
				String flowXYS_title =  (debug_level > 0)?("vectorXYS_"+scene_QuadCLT.getImageName()+"-ref"+reference_QuadCLT.getImageName()):null;
				//			double [][] vectorXYConfidence =  
				attachVectorConfidence(
						flowXY,         // final double [][] flowXY,
						macroTilesX,    // final int         width,
						clt_parameters.ofp.best_neibs_num,       // final int         best_num,
						clt_parameters.ofp.ref_stdev,      // final double      ref_stdev,
						flowXYS_title); // final String      debug_title);    

				double [][][] vectorsXYS = new double [corr2dscene_ref_multi.length][][];
				for (int i = 0; i < vectorsXYS.length; i++) {
					vectorsXYS[i] = correlation2DToVectors_CM(
							corr2dscene_ref_multi[i], // final double [][] corr2d_tiles, // per 2d calibration tiles (or nulls)
							transform_size, // final int         transform_size,
							clt_parameters.ofp.iradius_cm, // final int         iradius,      // half-size of the square to process 
							clt_parameters.ofp.dradius_cm,      // final double      dradius,      // weight calculation (1/(r/dradius)^2 + 1)
							clt_parameters.ofp.refine_num_cm,   // final int         refine_num,   // number of iterations to apply weights around new center
							1); //final int         debug_level)
				}
				if (debug_level > -1) {
					showVectorXYConfidence(
							"dXdYS-"+scene_QuadCLT.getImageName()+"-"+reference_QuadCLT.getImageName(), // String        title,
							vectorsXYS, // double [][][] flowXYSs,
							macroTilesX); // int           width)
				}
				
				int selected_index = 1; // single, post-norm
				double [][] flowXY1 = recalculateFlowXY(
						flowXY, // final double [][] currentFlowXY,
						vectorsXYS[selected_index], // final double [][] corr_vectorsXY,
						clt_parameters.ofp.magic_scale/transform_size); // final double      magic_scale) // 0.85 for CM

				double [][][] scene_tiles1 = prepareSceneTiles(// to match to reference
						// null for {scene,reference}{xyz,atr} uses instances globals 
						camera_xyz0,              // final double []   scene_xyz,     // camera center in world coordinates
						camera_atr0,              // final double []   scene_atr,     // camera orientation relative to world frame
						scene_QuadCLT,                    // final QuadCLT     scene_QuadClt,
						reference_QuadCLT,                    // final QuadCLT     reference_QuadClt,
						reference_tiles,          // final double [][][] reference_tiles, // prepared with prepareReferenceTiles() + fillTilesNans();
						flowXY1,                  // final double [][] flowXY, // per macro tile {mismatch in image pixels in X and Y directions
						flowXY_frac,              // final double [][] flowXY_frac, // should be initialized as [number of macro tiles][] - returns fractional shifts [-0.5, 0.5)
						clt_parameters.ofp.tolerance_absolute_inter, // final double      tolerance_absolute, // absolute disparity half-range in each tile
						clt_parameters.ofp.tolerance_relative_inter, // final double      tolerance_relative, // relative disparity half-range in each tile
						clt_parameters.ofp.occupancy_inter,          // final double      occupancy,          // fraction of remaining  tiles (<1.0)
						clt_parameters.ofp.num_laplacian, //   num_passes,            // final int         num_passes,
						clt_parameters.ofp.change_laplacian, // max_change,            // final double      max_change,
						1); //-1); // 1); // 2);                       // final int         debug_level)

				// single, late
				corr2dscene_ref_multi[0] = correlate2DSceneToReference(// to match to reference
						clt_parameters.img_dtt, // final ImageDttParameters  imgdtt_params,
						scene_QuadCLT,          // final QuadCLT     scene_QuadClt,
						reference_QuadCLT,      // final QuadCLT     reference_QuadClt,
						scene_tiles1,            // final double [][][] scene_tiles,     // prepared with prepareSceneTiles()
						reference_tiles,        // final double [][][] reference_tiles, // prepared with prepareReferenceTiles() + fillTilesNans(); - combine?
						flowXY_frac,            // final double [][] flowXY_frac, // X, YH fractional shift [-0.5,0.5) to implement with FD rotations
						clt_parameters.ofp.chn_weights,            // final double []   chn_weights, // absolute, starting from strength (strength,r,b,g)
						clt_parameters.ofp.corr_sigma,             // final double      corr_sigma,
						clt_parameters.ofp.fat_zero,               //  final double      fat_zero,
						false,                  // final boolean     late_normalize,
						clt_parameters.ofp.combine_empty_only,     //  final boolean     combine_empty_only, // only use neighbor correlations for empty tiles (false - any)
						0.0,                    // final double      combine_dradius
						clt_parameters.ofp.tolerance_absolute_macro, // final double      tolerance_absolute, // absolute disparity half-range to consolidate tiles
						clt_parameters.ofp.tolerance_relative_macro, // final double      tolerance_relative, // relative disparity half-range to consolidate tiles
						-1); // 1); // final int         debug_level)

				for (int irad = 0; irad <= 3; irad++) {
					corr2dscene_ref_multi[irad+1]= correlate2DSceneToReference(// to match to reference
							clt_parameters.img_dtt, // final ImageDttParameters  imgdtt_params,
							scene_QuadCLT,          // final QuadCLT     scene_QuadClt,
							reference_QuadCLT,      // final QuadCLT     reference_QuadClt,
							scene_tiles1,           // final double [][][] scene_tiles,     // prepared with prepareSceneTiles()
							reference_tiles,        // final double [][][] reference_tiles, // prepared with prepareReferenceTiles() + fillTilesNans(); - combine?
							flowXY_frac,            // final double [][] flowXY_frac, // X, YH fractional shift [-0.5,0.5) to implement with FD rotations
							clt_parameters.ofp.chn_weights,            // final double []   chn_weights, // absolute, starting from strength (strength,r,b,g)
							clt_parameters.ofp.corr_sigma,             // final double      corr_sigma,
							clt_parameters.ofp.fat_zero,               //  final double      fat_zero,
							true, // final boolean     late_normalize,
							clt_parameters.ofp.combine_empty_only,     //  final boolean     combine_empty_only, // only use neighbor correlations for empty tiles (false - any)
							irad + clt_parameters.ofp.frac_radius,     // final double      combine_dradius
							clt_parameters.ofp.tolerance_absolute_macro, // final double      tolerance_absolute, // absolute disparity half-range to consolidate tiles
							clt_parameters.ofp.tolerance_relative_macro, // final double      tolerance_relative, // relative disparity half-range to consolidate tiles
							-1); // 1); // final int         debug_level)
				}


				showCorrTiles(
						"scene:"+scene_QuadCLT.getImageName()+"-ref"+reference_QuadCLT.getImageName(),  //  String title,
						corr2dscene_ref_multi,           // double [][] source_tiles,
						tilesX/transform_size,     // int         tilesX,
						(2 * transform_size - 1),  // int         tile_width,
						(2 * transform_size - 1)); // int         tile_height) // extra margins over 16x16 tiles to accommodate distorted destination tiles

				double [][][] vectorsXYS1 = new double [corr2dscene_ref_multi.length][][];
				for (int i = 0; i < vectorsXYS1.length; i++) {
					vectorsXYS1[i] = correlation2DToVectors_CM(
							corr2dscene_ref_multi[i], // final double [][] corr2d_tiles, // per 2d calibration tiles (or nulls)
							transform_size, // final int         transform_size,
							clt_parameters.ofp.iradius_cm, // final int         iradius,      // half-size of the square to process 
							clt_parameters.ofp.dradius_cm,      // final double      dradius,      // weight calculation (1/(r/dradius)^2 + 1)
							clt_parameters.ofp.refine_num_cm,   // final int         refine_num,   // number of iterations to apply weights around new center
							1); //final int         debug_level)
				}
				if (debug_level > -1) {
					showVectorXYConfidence(
							"dXdYS1-"+scene_QuadCLT.getImageName()+"-"+reference_QuadCLT.getImageName(), // String        title,
							vectorsXYS1, // double [][][] flowXYSs,
							macroTilesX); // int           width)
				}
				double [][] flowXY2 = recalculateFlowXY(
						flowXY1, // final double [][] currentFlowXY,
						vectorsXYS1[selected_index], // final double [][] corr_vectorsXY,
						clt_parameters.ofp.magic_scale/transform_size); // final double      magic_scale) // 0.85 for CM

				//			double [][][] scene_tiles2 = 
				prepareSceneTiles(// to match to reference
						// null for {scene,reference}{xyz,atr} uses instances globals 
						camera_xyz0,              // final double []   scene_xyz,     // camera center in world coordinates
						camera_atr0,              // final double []   scene_atr,     // camera orientation relative to world frame
						scene_QuadCLT,                    // final QuadCLT     scene_QuadClt,
						reference_QuadCLT,                    // final QuadCLT     reference_QuadClt,
						reference_tiles,          // final double [][][] reference_tiles, // prepared with prepareReferenceTiles() + fillTilesNans();
						flowXY2,                  // final double [][] flowXY, // per macro tile {mismatch in image pixels in X and Y directions
						flowXY_frac,              // final double [][] flowXY_frac, // should be initialized as [number of macro tiles][] - returns fractional shifts [-0.5, 0.5)
						clt_parameters.ofp.tolerance_absolute_inter, // final double      tolerance_absolute, // absolute disparity half-range in each tile
						clt_parameters.ofp.tolerance_relative_inter, // final double      tolerance_relative, // relative disparity half-range in each tile
						clt_parameters.ofp.occupancy_inter,          // final double      occupancy,          // fraction of remaining  tiles (<1.0)
						clt_parameters.ofp.num_laplacian, //   num_passes,            // final int         num_passes,
						clt_parameters.ofp.change_laplacian, // max_change,            // final double      max_change,
						1); //-1); // 1); // 2);                       // final int         debug_level)
			}

		}	
		return pair;
	}
	

}
