package com.elphel.imagej.tileprocessor;
/**
 ** BiCamScan - class to represent multiple bi-quad camera measurements
 **
 ** Copyright (C) 2018 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  BiScan.java is free software: you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation, either version 3 of the License, or
 **  (at your option) any later version.
 **
 **  This program is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ** -----------------------------------------------------------------------------**
 **
 */
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicInteger;

import com.elphel.imagej.common.PolynomialApproximation;
import com.elphel.imagej.common.ShowDoubleFloatArrays;

public class BiScan {
	final static double THRESHOLD_LIN = 1.0E-20;  // threshold ratio of matrix determinant to norm for linear approximation (det too low - fail)
	final static double THRESHOLD_QUAD = 1.0E-30; // threshold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
	final static int    BISCAN_ANY =       -1;
	final static int    BISCAN_SINGLECORR = 0;
	final static int    BISCAN_AVGCORR =    1; // combined with low-texture averaging correlation
	final static int    BISCAN_POLE =       2; // combined with low-texture averaging correlation

	double []  disparity_measured;
	double []  target_disparity;
	double []  strength_measured;
	boolean [] strong_trusted; // sufficient strength without neighbors
	boolean [] trusted;
	boolean [] cond_trusted;
	boolean [] disabled_measurement; // should disable source, not this!
	int     [] src_index;       // index of the source scan which measured data is used here (applies to disparity_measured, strength_measured, disabled_measurement
	int        list_index = -1;
	int        scan_type = -1;
	BiCamDSI biCamDSI;
//	public BiScan(BiCamDSI biCamDSI) {
//		this.biCamDSI = biCamDSI;
//		int num_tiles = biCamDSI.tnImage.getSizeX()*biCamDSI.tnImage.getSizeY();
//		disparity= new double[num_tiles];
//		strength=  new double[num_tiles];
//		trusted=   new boolean[num_tiles];
//	}
	public BiScan(
			BiCamDSI   biCamDSI,
			int        indx,
			double []  disparity,
			double []  strength,
			boolean [] trusted,
			boolean [] disabled,
			int scan_type) {
		this.biCamDSI = biCamDSI;
		this.list_index = indx;
		this.scan_type = scan_type;
		int num_tiles = biCamDSI.tnImage.getSizeX()*biCamDSI.tnImage.getSizeY();
		if (disparity == null) {
			disparity= new double[num_tiles];
		} else {
			this.disparity_measured = disparity.clone();
			if (strength == null)  {
				strength=  new double[num_tiles];
				for (int nTile = 0; nTile < num_tiles; nTile++) strength[nTile] = Double.isNaN(disparity[nTile])?0.0:1.0;
			} else {
				this.strength_measured = strength.clone();
				for (int nTile = 0; nTile < num_tiles; nTile++) {
					if (Double.isNaN(disparity[nTile])) this.strength_measured [nTile] = 0.0;
					if (strength[nTile] == 0.0)         this.disparity_measured[nTile] = Double.NaN;
				}
			}
		}
		if (trusted == null)   trusted=   new boolean[num_tiles];
		if (disabled == null)  disabled=  new boolean[num_tiles];
		this.trusted = trusted;
		this.disabled_measurement = disabled;
		this.strong_trusted = new boolean[num_tiles];
		this.cond_trusted =   new boolean[num_tiles];
		src_index = new int[num_tiles];
		// set new measurement index to this, other to -1
		for (int i = 0; i < num_tiles; i++) {
			src_index[i] = (strength[i] > 0.0)? list_index:-1;
		}
	}

	public double []  getDisparityMeasured()   { return this.disparity_measured;} // FIXME!
	public double []  getStrengthMeasured()    { return this.strength_measured;} // FIXME
	public boolean [] getTrusted()             { return this.trusted;}
	public boolean [] getDisabledMeasurement() { return this.disabled_measurement;}

	public void disableTile (int nTile) {
		trusted[nTile] =        false;
		strong_trusted[nTile] = false;
		cond_trusted[nTile] =   false;
		//    	disabled[nTile] =       true;
		//    	if ((src_index[nTile] >= 0) && (src_index[nTile] != list_index)) {
		if (src_index[nTile] >= 0) {
			biCamDSI.getBiScan(src_index[nTile]).disabled_measurement[nTile] = true; // false; // may be source tile or this tile
		}
	}
    /**
     * Get disparity and strength from the scan, mask by boolean attributes
     * @param only_strong keep only trusted strong tiles
     * @param only_trusted keep any trusted tiles
     * @param only_enabled keep all but disabled tiles
     * @return array of two arrays {disparity, strength}
     */

    public double [][] getDisparityStrength( // FIXME
    		final boolean only_strong,
    		final boolean only_trusted,
    		final boolean only_enabled){
    	final int num_tiles = disparity_measured.length;
    	final double [][] ds = {disparity_measured.clone(), strength_measured.clone()}; // just to start with
		final Thread[] threads = ImageDtt.newThreadArray(biCamDSI.threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		final BiScan this_scan = this;
		// find definitely trusted and conditionally trusted tiles
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				@Override
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) {
						if ((only_strong && !strong_trusted[nTile]) ||
								(only_trusted && !trusted[nTile])) {
							ds[0][nTile] = Double.NaN;
							ds[1][nTile] = 0.0;
						} else { //if (src_index[nTile] != list_index){ // only one level of indirection?
							int src = src_index[nTile]; // same tile or different
							BiScan scan = this_scan;
							if (src < 0)  {
								src = list_index;
							} else {
								scan = biCamDSI.biScans.get(src);
							}
							boolean dsbl = scan.disabled_measurement[nTile];
							if (dsbl && only_enabled) { // src <0 only for the first scan where no data is available
								ds[0][nTile] = Double.NaN;
								ds[1][nTile] = 0.0;
							} else {
								ds[0][nTile] = scan.disparity_measured[nTile];
								ds[1][nTile] = scan.strength_measured[nTile];
							} // if (dsbl && only_enabled) -- else
						}
					}
				}
			};
		}
		ImageDtt.startAndJoin(threads);
    	return ds;
    }

    class DSIndex{
    	double disparity;
    	double strength;
    	int scan_index;
    	DSIndex (
    	    	double disparity,
    	    	double strength,
    	    	int scan_index)
    	{
        	this.disparity = disparity;
        	this.strength = strength;
        	this.scan_index = scan_index;

    	}
    }

    ArrayList<DSIndex> getTileDSList(
    		int           nTile,
    		final boolean only_strong,
    		final boolean only_trusted,
    		final boolean only_enabled)
    {
    	ArrayList<DSIndex> ds_list = new ArrayList<DSIndex>();
		for (int indx = list_index; indx >= 0; indx--) { // include the latest measurement
			BiScan scan = biCamDSI.getBiScan(indx);
			if (only_enabled && scan.disabled_measurement[nTile]) { //  || (scan.src_index[nTile] != indx)){ // skip all but enabled
				continue;
			}
			if (only_strong && !scan.strong_trusted[nTile]) {
				continue;
			}
			if	(only_trusted && !scan.trusted[nTile]) {
				continue;
			}
			if ((scan.strength_measured[nTile] > 0.0) && !Double.isNaN(scan.disparity_measured[nTile])) {
				ds_list.add(new DSIndex(scan.disparity_measured[nTile],scan.strength_measured[nTile],indx));
			}

		}
		Collections.sort(ds_list, new Comparator<DSIndex>() {
			@Override
			public int compare(DSIndex lhs, DSIndex rhs) {
				// -1 - less than, 1 - greater than, 0 - equal, all inverted for descending disparity
				return lhs.disparity > rhs.disparity ? -1 : (lhs.disparity < rhs.disparity ) ? 1 : 0;
			}
		});
    	return ds_list;


    }



    // trusted should be set, copied and replaced as needed
    public double [][] getFilteredDisparityStrength( // FIXME
			final boolean [] area_of_interest,
			final double [][] disparityStrength,
			final double     min_disparity,    // keep original disparity far tiles
		    final double     trusted_strength, // trusted correlation strength
			final double     strength_rfloor,   // strength floor - relative to trusted
		    final boolean    discard_unreliable,// replace v
		    final boolean    discard_weak,      // consider weak trusted tiles (not promoted to trusted) as empty
		    final boolean    discard_strong,    // suggest new disparitieas even for strong tiles
			final double     strength_pow,      // raise strength-floor to this power
			final double []  smpl_radius_array, // space-variant radius
			final int        smpl_radius,
			final int        smpl_num,   //         = 3;      // Number after removing worst (should be >1)
			final double     smpl_fract, // Number of friends among all neighbors
			final int        smpl_num_narrow,   //         = 3;      // Number after removing worst (should be >1)
			final double     max_adiff,  // Maximal absolute difference betweenthe center tile and friends
			final double     max_rdiff, //  Maximal relative difference between the center tile and friends
			final double     max_atilt, //  = 2.0; // pix per tile
			final double     max_rtilt, //  = 0.2; // (pix / disparity) per tile
			final double     smpl_arms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
			final double     smpl_rrms,        //      = 0.005;  // Maximal RMS/disparity in addition to smplRms
			final double     damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
			final double     rwsigma,           //  = 0.7; // influence of far neighbors diminish as a Gaussian with this sigma
			final double     rwsigma_narrow,    //  = used to determine initial tilt
			final double     center_weight,     // use center tile too (0.0 - do not use)
			final boolean    use_alt,           // use tiles from other scans if they fit better
			final double     goal_fraction_rms, // Try to make rms to be this fraction of maximal acceptable by removing outliers
			final double     boost_low_density, // 0 - strength is proportional to 1/density, 1.0 - same as remaining tiles
			final int        fourq_min,         // each of the 4 corners should have at least this number of tiles.
			final int        fourq_gap,         // symmetrical vertical and horizontal center areas that do not belong to any corner
			final int        dbg_x,
			final int        dbg_y,
			final int        debugLevel
    		){
		final int num_tiles = biCamDSI.tnImage.getSizeX()*biCamDSI.tnImage.getSizeY();
		double [][] ds0 = 	getDisparityStrength( // FIXME
	    		false,   // final boolean only_strong,
	    		false,   // final boolean only_trusted,
	    		true) ;  // final boolean only_enabled);


    	double [][] ds = new double[2][num_tiles];
    	for (int i = 0; i < num_tiles; i++) ds[0][i] = Double.NaN;
//    	double boost_low_density = 0.8; // 1.0; //0.2;
		  suggestNewScan(
				  area_of_interest,  // final boolean [] area_of_interest,
				  disparityStrength, // final double [][] disparityStrength,
				  trusted_strength,  // final double     trusted_strength, // trusted correlation strength
				  strength_rfloor,   // final double     strength_rfloor,   // strength floor - relative to trusted
				  true,              // final boolean    discard_cond,      // consider conditionally trusted tiles (not promoted to trusted) as empty
				  true,              // final boolean    discard_weak,      // consider conditionally trusted tiles (not promoted to trusted) as empty
				  true,              // final boolean    discard_strong,      // consider conditionally trusted tiles (not promoted to trusted) as empty
				  strength_pow,      // final double     strength_pow,      // raise strength-floor to this power
				  smpl_radius,       // final int        smpl_radius,
				  smpl_num,          // final int        smpl_num,   //         = 3;      // Number after removing worst (should be >1)
				  smpl_fract,        // final double     smpl_fract, // Number of friends among all neighbors
				  smpl_num_narrow,   // final int        smpl_num_narrow,   //         = 3;      // Number after removing worst (should be >1)
				  max_adiff,         // final double     max_adiff,  // Maximal absolute difference betweenthe center tile and friends
				  max_rdiff,         // final double     max_rdiff, //  Maximal relative difference between the center tile and friends
				  max_atilt,         // final double     max_atilt, //  = 2.0; // pix per tile
				  max_rtilt,         // final double     max_rtilt, //  = 0.2; // (pix / disparity) per tile
				  smpl_arms,         // final double     smpl_arms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
				  smpl_rrms,         // final double     smpl_rrms,        //      = 0.005;  // Maximal RMS/disparity in addition to smplRms
				  damp_tilt,         // final double     damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
				  rwsigma,           // final double     rwsigma,           //  = 0.7; // influence of far neighbors diminish as a Gaussian with this sigma
				  rwsigma_narrow,    // final double     rwsigma_narrow,    //  = used to determine initial tilt
				  0.0,               // final double     new_diff,            // minimal difference between the new suggested and the already tried/measured one
				  false,             // final boolean    remove_all_tried,  // remove from suggested - not only disabled, but all tried
				  center_weight,     // final double     center_weight,     // use center tile too (0.0 - do not use)
				  use_alt,           // final boolean    use_alt,           // use tiles from other scans if they fit better
				  boost_low_density, // final double     boost_low_density, // 0 - strength is proportional to 1/density, 1.0 - same as remaining tiles
				  goal_fraction_rms, // final double     goal_fraction_rms, // Try to make rms to be this fraction of maximal acceptable by removing outliers
				  ds,                // final double [][]  smooth_ds,   // optionally fill strength array when used for smoothing DSI
				  fourq_min,         // final int        fourq_min,         // each of the 4 corners should have at least this number of tiles.
				  fourq_gap,         // final int        fourq_gap,         // symmetrical vertical and horizontal center areas that do not belong to any corner
				  dbg_x,             // final int        dbg_x,
				  dbg_y,             // final int        dbg_y,
				  debugLevel);       // final int        debugLevel);
		for (int nTile = 0; nTile < num_tiles; nTile++) {
			if ((ds0[1][nTile] > 0.0) && (
					(ds[0][nTile] <= 0.0) ||
					!discard_unreliable ||
					(strong_trusted[nTile] && !discard_strong) ||
					(trusted[nTile] && !discard_weak) ||
					(ds[0][nTile] <  min_disparity))) {
				ds[0][nTile] = ds0[0][nTile];
				ds[1][nTile] = ds0[1][nTile];
			}
		}
    	return ds;
    }
	public void showScan(String title) {
		showScan(title,null);
	}

	public void showScan(String title, double [][] ext_ds) {
		String [] titles= {
				"ext_disp",          //    0
				"all",               // 0  1
				"enabled",           // 1  2
				"cond_trusted",      // 2  3
				"weak trusted",      // 3  4
				"strong trusted",    // 4  5
				"measured",          // 5  6
				"suggested",         // 6  7
				"ext strength",      //    8
				"measured strength", // 7  9
				"strength" };        // 8 10
		double [][] ds_all = getDisparityStrength(
	    		false,   // final boolean only_strong,
	    		false,   // final boolean only_trusted,
	    		false) ; // final boolean only_enabled);
		double [][] ds = getDisparityStrength(
	    		false,   // final boolean only_strong,
	    		false,   // final boolean only_trusted,
	    		true) ; // final boolean only_enabled);

		double [][] dbg_data = new double[titles.length][];
		dbg_data[ 6] = this.disparity_measured;
		dbg_data[ 1] = ds_all[0];
		dbg_data[ 9] = this.strength_measured;
		dbg_data[10] = ds_all[1];
		dbg_data[ 2] = ds[0];
		if (ext_ds != null) {
			dbg_data[ 0] = ext_ds[0];
			dbg_data[ 8] = ext_ds[1];
		}

		if (this.cond_trusted != null) {
			dbg_data[3] = ds[0].clone();
			for (int i = 0; i < this.cond_trusted.length; i++) if (!cond_trusted[i]) dbg_data[3][i] = Double.NaN;
		}
		if (this.trusted != null) {
			dbg_data[4] = ds[0].clone();
			for (int i = 0; i < this.trusted.length; i++) if (!trusted[i]) dbg_data[4][i] = Double.NaN;
		}
		if (this.strong_trusted != null) {
			dbg_data[5] =  ds[0].clone();
			for (int i = 0; i < this.strong_trusted.length; i++) if (!strong_trusted[i]) dbg_data[5][i] = Double.NaN;
		}
		if (this.target_disparity != null) {
			dbg_data[7] = this.target_disparity.clone();
		}
		ShowDoubleFloatArrays.showArrays(
				dbg_data,
				biCamDSI.tnImage.getSizeX(),
				biCamDSI.tnImage.getSizeY(),
				true,
				title,
				titles);
	}

	/**
	 * Reduce outliers on DSI when multiple "refined" disparity values exist for the same tile and the strongest does not seem to be the best
	 * Each disparity solution is compared to the weighted average of the neighbors and the strength is divided by the difference from that
	 * average value, so the closest to the neighbors gets strength boost.
	 * @param str_floor absolute strength to subtract from the measured
	 * @param pf_disp_afloor offset disparity to add to the disparity difference to avoid division by 0 or small numbers
	 * @param pf_disp_rfloor realtive to the disparity portion of the offset
	 * @return number of replaced tiles
	 */
	public int copyFittestEnabled(
			final double  str_floor,      // absolute strength floor
			final double  pf_disp_afloor, // =            0.1;    // When selecting the best fit from the alternative disparities, divide by difference increased by this
			final double  pf_disp_rfloor) //  =            0.02;   // Increase pf_disp_afloor for large disparities
		{
		final TileNeibs  tnImage = biCamDSI.tnImage;
		final int num_tiles = biCamDSI.tnImage.getSizeX()*biCamDSI.tnImage.getSizeY();

		final double [][] ds = getDisparityStrength( // FIXME
	    		false,   // final boolean only_strong,
	    		false,   // final boolean only_trusted,
	    		true) ; // final boolean only_enabled);

		final Thread[] threads = ImageDtt.newThreadArray(biCamDSI.threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		final int [] new_src = new int[num_tiles];
		final AtomicInteger num_changes = new AtomicInteger(0);
		int dbg_x = 157;
		int dbg_y = 212;
		int debugLevel = -10;
		final int dbg_tile = (debugLevel>-2)?(dbg_x + tnImage.sizeX*dbg_y):-1;

		ai.set(0);
		// find definitely trusted and conditionally trusted tiles
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				@Override
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) {
						if (nTile == dbg_tile) {
							System.out.println("copyFittestEnabled(): nTile="+nTile);
						}
						if (ds[1][nTile] > 0.0 ) { // && (src_index[nTile] != list_index)){ // should be already found
							int num_neib = 0;
							double sw = 0.0, swd = 0.0;
							double wdiag = 0.7;
							for (int dir = 0; dir <8; dir++) {
								int nTile1 = tnImage.getNeibIndex(nTile, dir);
								if ((nTile1 >= 0) && (ds[1][nTile1] > str_floor)) {
									double w = ds[1][nTile1] - str_floor;
									if ((dir & 1) != 0) w *= wdiag;
									sw += w;
									swd += ds[0][nTile1] * w;
									num_neib++;
								}
							}
							if (num_neib > 0) {
								double disp_mean = swd/sw;
								double disp_floor = pf_disp_afloor + disp_mean * pf_disp_rfloor;
								double disp_floor2 = disp_floor*disp_floor;

								int best_indx=-1;
								double best_strength =0.0;
								for (int indx = list_index; indx >= 0; indx--) { // include the latest measurement
									BiScan scan = biCamDSI.getBiScan(indx);
									if (scan.disabled_measurement[nTile]) { //  || (scan.src_index[nTile] != indx)){ // skip all but enabled
										continue;
									}
									// calculate effective strength
									double strength = scan.strength_measured[nTile] - str_floor;
									if (strength <= 0) {
										continue;
									}

									double diff = scan.disparity_measured[nTile] - disp_mean;
									double eff_strength = strength/Math.sqrt(diff*diff + disp_floor2);

									if (eff_strength > best_strength) {
										best_strength = eff_strength;
										best_indx = indx;
									}
								}
								if ((best_indx >= 0) &&  (best_indx != src_index[nTile])) { // not the same as already set
									new_src[nTile] = best_indx+1; // +1 so initial 0 will be "not set"
									num_changes.getAndIncrement();
								}
							}
						}
					}
				}
			};
		}
		ImageDtt.startAndJoin(threads);
		if (num_changes.get() > 0) {
			ai.set(0);
			// find definitely trusted and conditionally trusted tiles
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					@Override
					public void run() {
						for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) {
							if (nTile == dbg_tile) {
								System.out.println("copyFittestEnabled() 2 : nTile="+nTile);
							}
							if (new_src[nTile] > 0){
								int best_indx = new_src[nTile]-1;
								src_index[nTile] = best_indx;
							}
						}
					}
				};
			}
			ImageDtt.startAndJoin(threads);
		}
		// will need trusted* recalculated
		return num_changes.get();
	}

	/**
	 * Prefer thin FG (like thin poles) over textured BG (at first not directional), just prefer "good enough" FG over even stronger BG
	 * @param str_good_enough minimal strength for the FG tiles and neighbors
	 * @param min_FGtoBG minimal FG to BG disparity difference
	 * @param disp_atolerance absolute disparity difference for qualifying neighbors (in master camera pixels)
	 * @param disp_rtolerance add to tolerance for each pixel of disparity
	 * @param min_neib minimal number of neighbors that promoted tiles should have
	 * @return number of promoted FG tiles
	 */
	public int copyStrongFGEnabled(
			final double  str_good_enough, // absolute strength floor for good enough
			final double  min_FGtoBG,      // minimal disparity difference over
			final double  disp_atolerance, // =  0.1;    // Maximal absolute disparity difference to qualifying neighbor
			final double  disp_rtolerance, // =  0.02;   // Maximal relative (to absolute disparity) disparity difference to qualifying neighbor
			final int     min_neib)        // minimal number of qualifying neighbors to promote FG tile
		{
		final TileNeibs  tnImage = biCamDSI.tnImage;
		final int num_tiles = biCamDSI.tnImage.getSizeX()*biCamDSI.tnImage.getSizeY();

		final double [][] ds = getDisparityStrength( // used to comapre to see if re-arrangement is needed
	    		false,   // final boolean only_strong,
	    		false,   // final boolean only_trusted,
	    		true) ; // final boolean only_enabled);

		final Thread[] threads = ImageDtt.newThreadArray(biCamDSI.threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		final int [] new_src = new int[num_tiles];
		final AtomicInteger num_changes = new AtomicInteger(0); // number of tiles modified to FG
		int dbg_x = 193;
		int dbg_y = 162;
		int debugLevel = -1;
		final int dbg_tile = (debugLevel>-1)?(dbg_x + tnImage.sizeX*dbg_y):-1;

		ai.set(0);
		// find definitely trusted and conditionally trusted tiles
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				@Override
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) {
						if (nTile == dbg_tile) {
							System.out.println("copyStrongFGEnabled(): nTile="+nTile);
						}
						if (ds[1][nTile] < 0) { // no good d/s for this tile
							continue;
						}
						// get list in descending disparity order
						ArrayList<DSIndex> ds_list = getTileDSList(
								nTile, // int           nTile,
								false, // final boolean only_strong,
								false, // final boolean only_trusted,
								true); // final boolean only_enabled)
						if (ds_list.size() < 1) {
							continue;
						}

						// find strongest tile closer than default disparity
						for (int indx = 0; indx < ds_list.size(); indx++) {
							DSIndex dsi = ds_list.get(indx);
							if (dsi.disparity < (ds[0][nTile] + min_FGtoBG)){
								break; // not sufficiently closer than default;
							}
							if (dsi.strength > str_good_enough){
								double  disp_tolerance = disp_atolerance + dsi.disparity * disp_rtolerance; // disparity tolerance
								// see if it has strong enough neighbors with close disparity
								int num_neib = 0;
								for (int dir = 0; dir < 8; dir++) {
									int nTile1 = tnImage.getNeibIndex(nTile, dir);
									if (nTile1 > 0) {
										ArrayList<DSIndex> ds_neib = getTileDSList(
												nTile1, // int           nTile,
												false, // final boolean only_strong,
												false, // final boolean only_trusted,
												true); // final boolean only_enabled)
										for (DSIndex dsi_neib: ds_neib) {
											if ((dsi_neib.strength > str_good_enough) &&
													(Math.abs(dsi_neib.disparity - dsi.disparity ) <= disp_tolerance)) {
												num_neib++;
												break;
											}
										}
									}
								}
								if (num_neib > 0) {
									new_src[nTile] = dsi.scan_index + 1;
									num_changes.getAndIncrement();
									break; // for (int indx = 0; indx < ds_list.size(); indx++)
								}
							}
						}
					}
				}
			};
		}
		ImageDtt.startAndJoin(threads);
		if (num_changes.get() > 0) {
			ai.set(0);
			// find definitely trusted and conditionally trusted tiles
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					@Override
					public void run() {
						for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) {
							if (nTile == dbg_tile) {
								System.out.println("copyStrongFGEnabled() 2 : nTile="+nTile);
							}
							if (new_src[nTile] > 0){
								int best_indx = new_src[nTile]-1;
								src_index[nTile] = best_indx;
							}
						}
					}
				};
			}
			ImageDtt.startAndJoin(threads);
		}
		// will need trusted* recalculated
		return num_changes.get();
	}


	/**
	 * Copy data (if the current was not measured) from one of the previous scans - strongest that is not disabled. If last_priority is true
	 * the latest not disabled scan will be used, even if it is not the strongest
	 * @param last_priority
	 */

	public void copyLastStrongestEnabled(
			final boolean last_priority) // use last if exists and is not disabled
		{
		final int num_tiles = biCamDSI.tnImage.getSizeX()*biCamDSI.tnImage.getSizeY();
		final Thread[] threads = ImageDtt.newThreadArray(biCamDSI.threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
//		final boolean [] cond_trusted = new boolean [num_tiles];
		strong_trusted = new boolean [num_tiles];
		trusted =        new boolean [num_tiles];
		cond_trusted =   new boolean [num_tiles];

		ai.set(0);
		// find definitely trusted and conditionally trusted tiles
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				@Override
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) if (strength_measured[nTile] <= 0.0 ){ // keep last measurement for a while, even if it is not the best
						int best_indx=-1;
						boolean latest = true;
						double best_strength =0.0;
						for (int indx = list_index-1; indx >= 0; indx--) { // no need to try the latest - it is empty (strength_measured[nTile] <= 0.0 )
							BiScan scan = biCamDSI.getBiScan(indx);
							if (scan.disabled_measurement[nTile] || (scan.src_index[nTile] != indx)){ // skip all but enabled sources
								continue;
							}
							if (scan.strength_measured[nTile] > best_strength) {
								best_strength = scan.strength_measured[nTile];
								best_indx = indx;
							}
							if (last_priority && latest) {
								break; // best_indx should be set correctly, as strength > 0.0
							}
							latest = false; // first not disabled with strength>0 gets here
						}
						if (best_indx >= 0) {
							src_index[nTile] = best_indx;
						}
					}
				}
			};
		}
		ImageDtt.startAndJoin(threads);
	}




	public void showScans(String title) {
	}
	/**
	 * Suggest disparities to try for the tiles in poorly textured areas by fitting planes in DSI
	 * calcTrusted should be called before to set up trusted/cond_trusted tiles
	 * suggested tiles will be compared against and made sure they differ by more than a specified margin
	 * 1) current measured (refined) disparity value
	 * 2) target disparity that lead to the current measurement after refinement
	 * 3) any other disable measurement
	 * 4) any target disparity that lead to the disabled measurement
	 * @param area_of_interest - limit results to these tiles (if provided)
	 * @param disparityStrength - a pair of array or null. If null, will calculate fro the current scan
	 *        if not null - use as is
	 * @param trusted_strength strength to trust unconditionally
	 * @param strength_rfloor strength floor to subtract as a fraction of the trusted strength
	 * @param discard_cond if true may suggest new disparities for conditionally trusted tiles
	 * @param discard_weak if true may suggest new disparities over trusted weak tiles
	 * @param discard_stron if true may suggest new disparities over any tile
	 * @param strength_pow raise strength to this power (normally just 1.0)
	 * @param smpl_radius sample "radius", square side is  2 * smpl_radius + 1
	 * @param smpl_num minimal absolute number of samples required to try fit a plane and validate a tile
	 * If smpl_num == 0, faster calculation (single pass) using only *_narrow settings
	 * @param smpl_fract minimal fraction number of the neighbor samples that fit the rms filter required to try fit a plane and validate a tile
	 * @param smpl_num_narrow minimal absolute number of samples for preliminary fitting plane to trhe center area
	 * @param max_adiff maximal absolute difference from the center tile for initial neighbors selection
	 * @param max_rdiff maximal (additional) relative (to tile disparity) difference from the center tile for initial neighbors selection
	 * @param max_atilt maximal absolute tilt (pix/tile) for the tilted planes to fit
	 * @param max_rtilt maximal relative tilt (pix/tile per disparity pixel). min(max_rtilt*disparity, max_atilt) will be used
	 * @param smpl_arms maximal absolute rms of the weighted remaining samples for the successful plane fitting
	 * @param smpl_rrms maximal relative (additional)rms of the weighted remaining samples for the successful plane fitting
	 * @param damp_tilt regularization value to handle planes if the remaining samples are co-linear (or just a single tile)
	 * @param rwsigma weight Gaussian sigma to reduce influence of far tiles relative to smpl_radius
	 * @param rwsigma_narrow Gaussian sigma for the preliminary plain fitting using the closest tiles ~= 1/smpl_radius
	 * @param new_diff minimal difference between the new suggested and the already tried/measured one
	 * @param remove_all_tried remove from suggested - not only disabled, but all tried
	 * @param center_weight weight of the tile itself (0.0 - do not use). Should be set to 0.0 for suggesting, >0 - for "smoothing"
	 * @param use_alt use tiles from other scans if they fit better
	 * @param goal_fraction_rms try to make rms to be this fraction of maximal acceptable by removing outliers
	 * @param boost_low_density 0.0 - strength is proportional to 1/density, 1.0 - same as remaining tiles
	 * @param smooth_ds optionally fill disparity/strength  instead of the target_disparity
	 * @param fourq_min each of the 4 corners should have at least this number of tiles.
	 * @param fourq_gap symmetrical vertical and horizontal center areas that do not belong to any corner
	 * @param dbg_x tileX to debug
	 * @param dbg_y tileY to debug
	 * @param debugLevel debug level
	 * @return number of new tiles to measure in the  array of suggested disparities - Double.NaN - nothing suggested
	 *  for the tile. May need additional filtering to avoid suggested already tried disparities
	 */

	int  suggestNewScan(
			final boolean [] area_of_interest,
			final double [][] disparityStrength,
		    final double     trusted_strength, // trusted correlation strength
			final double     strength_rfloor,   // strength floor - relative to trusted
		    final boolean    discard_cond,      // consider conditionally trusted tiles (not promoted to trusted) as empty
		    final boolean    discard_weak,      // consider weak trusted tiles (not promoted to trusted) as empty
		    final boolean    discard_strong,    // suggest new disparities even for strong tiles
			final double     strength_pow,      // raise strength-floor to this power
			final int        smpl_radius,
			final int        smpl_num,   //         = 3;      // Number after removing worst (should be >1)
			final double     smpl_fract, // Number of friends among all neighbors
			final int        smpl_num_narrow,   //         = 3;      // Number after removing worst (should be >1)
			final double     max_adiff,  // Maximal absolute difference between the center tile and friends
			final double     max_rdiff, //  Maximal relative difference between the center tile and friends
			final double     max_atilt, //  = 2.0; // pix per tile
			final double     max_rtilt, //  = 0.2; // (pix / disparity) per tile
			final double     smpl_arms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
			final double     smpl_rrms,        //      = 0.005;  // Maximal RMS/disparity in addition to smplRms
			final double     damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
			final double     rwsigma,           //  = 0.7; // influence of far neighbors diminish as a Gaussian with this sigma
			final double     rwsigma_narrow,    //  = used to determine initial tilt
			final double     new_diff,          // minimal difference between the new suggested and the already tried/measured one
			final boolean    remove_all_tried,  // remove from suggested - not only disabled, but all tried
			final double     center_weight,     // use center tile too (0.0 - do not use)
			final boolean    use_alt,           // use tiles from other scans if they fit better
			final double     goal_fraction_rms, // Try to make rms to be this fraction of maximal acceptable by removing outliers
			final double     boost_low_density, // 0 - strength is proportional to 1/density, 1.0 - same as remaining tiles
			final double [][] smooth_ds,        // optionally fill disparity/strength  instead of the target_disparity
			final int        fourq_min,         // each of the 4 corners should have at least this number of tiles.
			final int        fourq_gap,         // symmetrical vertical and horizontal center areas that do not belong to any corner
			final int        dbg_x,
			final int        dbg_y,
			final int        debugLevel
			) {
		final TileNeibs  tnImage = biCamDSI.tnImage;
		final int dbg_tile = (debugLevel > 0)?(dbg_y * tnImage.sizeX + dbg_x): -1;
		final double     wsigma = rwsigma*smpl_radius;
		final double     wsigma_narrow = rwsigma_narrow*smpl_radius;
		final int        smpl_side = 2 * smpl_radius + 1; // Sample size (side of a square)
		final int        smpl_len = smpl_side*smpl_side;
		final int        smpl_center = (smpl_side + 1) * smpl_radius;
		// prepare window
		final double [][] weights =        new double [smpl_radius + 1][smpl_radius + 1];
		final double [][] weights_narrow = new double [smpl_radius + 1][smpl_radius + 1];
		{ // normalize
			for (int i = 0; i <= smpl_radius; i++) {
				for (int j = i; j <= smpl_radius; j++) {
					weights[i][j] = (wsigma >0.0) ?Math.exp(-(i*i+j*j)/(2*wsigma*wsigma)):1.0;
					weights[j][i] = weights[i][j];
					weights_narrow[i][j] = (wsigma_narrow >0.0) ?Math.exp(-(i*i+j*j)/(2*wsigma_narrow*wsigma_narrow)):1.0;
					weights_narrow[j][i] = weights_narrow[i][j];
				}
			}
			weights[0][0] *=        center_weight;
			weights_narrow[0][0] *= center_weight;
			double sw_full = 0.0, sw_narrow = 0.0;
			for (int i = 0; i <= smpl_radius; i++) {
				for (int j = i; j <= smpl_radius; j++) {
					sw_full +=   weights[i][j];
					sw_narrow += weights_narrow[i][j];
				}
			}
			double k_full = 1.0/sw_full;
			double k_narrow = 1.0/sw_narrow;
			for (int i = 0; i <= smpl_radius; i++) {
				for (int j = i; j <= smpl_radius; j++) {
					weights[i][j] *=k_full;
					weights_narrow[i][j]*=k_narrow;
				}
			}
		}
		final int [] fourq_corner = new int[smpl_len];
		for (int i = 0; i < smpl_side; i++) {
			for (int j = 0; j < smpl_side; j++) {
				int indx = i* smpl_side + j;
				if     (((i > (smpl_radius - fourq_gap)) && (i < (smpl_radius + fourq_gap))) ||
						((j > (smpl_radius - fourq_gap)) && (j < (smpl_radius + fourq_gap)))){
					fourq_corner[indx] = 4; // will not be used

				} else {
					fourq_corner[indx] = ((i > smpl_radius) ? 2 : 0) + ((j > smpl_radius) ? 1 : 0); //122
				}
			}
		}


		final double [][] ds = (disparityStrength != null) ? disparityStrength: getDisparityStrength(
	    		false,   // final boolean only_strong,
	    		false,   // final boolean only_trusted,
	    		true) ; // final boolean only_enabled);
		final int num_tiles = tnImage.getSizeX()*tnImage.getSizeY();
		final double     strength_floor = trusted_strength * strength_rfloor;

		final boolean [] trusted_sw = discard_weak ? (this.strong_trusted) : (discard_cond ? this.trusted: this.cond_trusted);

		final Thread[] threads = ImageDtt.newThreadArray(biCamDSI.threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
//		final double [] suggested_disparity = new double [num_tiles];
		target_disparity = new double [num_tiles];
		for (int i = 0; i < num_tiles; i++) target_disparity[i] = Double.NaN;
//		cond_trusted and trusted should be set;
		ai.set(0);
//		final BiScan this_scan = this;
		final AtomicInteger num_new = new AtomicInteger(0);
		// find definitely trusted and conditionally trusted tiles
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				@Override
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) if (
							((area_of_interest == null) || area_of_interest[nTile]) && (discard_strong || !trusted_sw[nTile])){
						boolean debug = nTile == dbg_tile;
						if (debug) {
							System.out.println("suggestNewScan(): debbugging nTile="+nTile);
							System.out.println("suggestNewScan(): debbugging nTile="+nTile);
						}
						// Select all neighbors, then filter
						double [] smpl_d =           new double  [smpl_len];
						double [] smpl_w =           new double  [smpl_len];
						double [] smpl_w_narrow =    new double  [smpl_len];
						double [] smpl_p =           new double  [smpl_len]; // plane disparity,
						double [] smpl_wsw= smpl_w_narrow;
						double [][] weights_sw = weights_narrow;

						int nall = 0;
						double sw = 0, swd = 0;
						for (int dy = -smpl_radius; dy <= smpl_radius; dy++) {
							int ady = (dy > 0)? dy:(-dy);
							for (int dx = -smpl_radius; dx <= smpl_radius; dx++) { // if ((dx != 0) || (dy != 0)){
								int nTile1 =  tnImage.getNeibIndex(nTile, dx, dy);
								if ((nTile1 >= 0) && trusted[nTile1]) { // weak trusted OK
									nall++;
									int adx = (dx > 0)? dx:(-dx);
									int smpl_indx = smpl_center + dy*smpl_side + dx;
									double w = ds[1][nTile1]-strength_floor;
									if ( w > 0) {
										if (strength_pow != 1.0) {
											w = Math.pow(w, strength_pow);
										}

										smpl_d[smpl_indx] =       ds[0][nTile1];
										smpl_w[smpl_indx] =        w * weights[ady][adx];
										smpl_w_narrow[smpl_indx] = w * weights_narrow[ady][adx];
										sw += smpl_w_narrow[smpl_indx];
										swd += smpl_w_narrow[smpl_indx]* smpl_d[smpl_indx];
									}
								}
							}
						}
						if (debug) {
							System.out.println("suggestNewScan(): sw="+sw);
						}

						if (sw == 0.0) {
							continue; //
						}
						double disp_mean = swd/sw; // preliminary reference disparity
						double max_tilt = max_rtilt * disp_mean;
						if (max_tilt > max_atilt) {
							max_tilt = max_atilt;
						}

						int fin_samples= (int) ( nall * smpl_fract);
						// smpl_num == 0 - special (fast) case do not use wide selection at all
						if ((smpl_num == 0) || (fin_samples < smpl_num)) fin_samples = smpl_num;

						// fit plane to mostly centertiles
						int nsmpls = nall;
						if (nsmpls < smpl_num_narrow) { // no tiles even to start
							continue; //
						}
						double max_rms = smpl_arms + smpl_rrms * disp_mean; // do not need to wait fro the final disparity for this estimate
						double [] fit_rslt = fitPlaneRemoveOutliers(
								smpl_radius,        // int                     smpl_radius,
								max_tilt,           // double                  max_tilt,
								damp_tilt,          // double                  damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
								true,               // boolean                 full_plane,
								smpl_d,             // double []               smpl_d,
								smpl_w_narrow,      // double []               smpl_w, // will be modified,
								smpl_p,             // double []               smpl_p, // will be set if provided
								smpl_num_narrow,    // int                     fin_samples, // remove until this number remain
								goal_fraction_rms*max_rms, // double                  fin_rms,
								false,              // boolean                 keep_center, // do not remove center tile - it is the tile that should be verified by neighbors
								fourq_min,          // int                     fourq_min,         // each of the 4 corners should have at least this number of tiles.
								fourq_corner,       // int [] 	               fourq_corner, //  array specifying corner number (0..3), -1 - gap. null when not used
								debugLevel);        // int                     debugLevel)
//						if ( (fit_rslt == null) ||  (fit_rslt[0] > (smpl_arms + smpl_rrms * fit_rslt[1]))){
						if ( (fit_rslt == null) ||  (fit_rslt[0] > max_rms)){
							continue; // narrow selection - too high rms
						}
						max_rms = smpl_arms + smpl_rrms * fit_rslt[1]; // updated center disparity and so rms
						disp_mean = fit_rslt[1]; // smpl_p[smpl_center]; // center of the fitted plane
						// try to use alternatives for the discarded (not only) tiles
						if (use_alt) {
							int nnew = findBetterFitToPlane( // ; // new assignments
									smpl_radius,     // int       smpl_radius,
									nTile,           // int       nTile,
									strength_floor,  // double    strength_floor,
									strength_pow,    // double    strength_pow,
									weights,         // double [][] weights,
									weights_narrow,  // double [][] weights_narrow,
									smpl_w,          // double [] smpl_w,
									smpl_w_narrow,   // double [] smpl_w_narrow,
									smpl_d,          // double [] smpl_d,
									smpl_p);         // double [] smpl_p);

							if (nnew > 0) { // there were some changes, recalculate narrow fit plane
								fit_rslt = fitPlaneRemoveOutliers(
										smpl_radius,        // int                     smpl_radius,
										max_tilt,           // double                  max_tilt,
										damp_tilt,          // double                  damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
										true,               // boolean                 full_plane,
										smpl_d,             // double []               smpl_d,
										smpl_w_narrow,      // double []               smpl_w, // will be modified,
										smpl_p,             // double []               smpl_p, // will be set if provided
										smpl_num_narrow,    // int                     fin_samples, // remove until this number remain
										goal_fraction_rms*max_rms,            // double                  fin_rms,
										false,              // boolean                 keep_center, // do not remove center tile - it is the tile that should be verified by neighbors
										fourq_min,          // int                     fourq_min,         // each of the 4 corners should have at least this number of tiles.
										fourq_corner,       // int [] 	               fourq_corner, //  array specifying corner number (0..3), -1 - gap. null when not used
										debugLevel);        // int                     debugLevel)
//								if ( (fit_rslt == null) ||  (fit_rslt[0] > (smpl_arms + smpl_rrms * fit_rslt[1]))){
								if ( (fit_rslt == null) ||  (fit_rslt[0] > max_rms)){
									continue; // narrow selection - too high rms
								}
								disp_mean = fit_rslt[1]; // smpl_p[smpl_center]; // center of the fitted plane
							}
// plane may have changed, look for the best fit in history again
							 nnew = findBetterFitToPlane( // ; // new assignments
										smpl_radius,     // int       smpl_radius,
										nTile,           // int       nTile,
										strength_floor,  // double    strength_floor,
										strength_pow,    // double    strength_pow,
										weights,         // double [][] weights,
										weights_narrow,  // double [][] weights_narrow,
										smpl_w,          // double [] smpl_w,
										smpl_w_narrow,   // double [] smpl_w_narrow,
										smpl_d,          // double [] smpl_d,
										smpl_p);         // double [] smpl_p);
						} //if (use_alt) {

						// re-select tiles to fit the plane and use wide weights
						double max_diff = max_adiff + max_rdiff * disp_mean; // no provisions for tilt (or add a fraction)?
						nsmpls = 0;
						for (int indxs = 0; indxs < smpl_len; indxs++) if (smpl_w[indxs]>0){
							if (Math.abs(smpl_d[indxs] - smpl_p[indxs]) < max_diff) {
								nsmpls++;
							} else {
								smpl_w[indxs] = 0.0;
							}
						}
						if (fin_samples > 0) {

							if (nsmpls < fin_samples) { // no tiles even to satrt
								continue; //
							}
							fit_rslt = fitPlaneRemoveOutliers(
									smpl_radius,        // int                     smpl_radius,
									max_tilt,           // double                  max_tilt,
									damp_tilt,          // double                  damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
									true,               // boolean                 full_plane,
									smpl_d,             // double []               smpl_d,
									smpl_w,             // double []               smpl_w, // will be modified,
									smpl_p,             // double []               smpl_p, // will be set if provided
									fin_samples,        // int                     fin_samples, // remove until this number remain
									goal_fraction_rms*max_rms,            // double                  fin_rms,
									false,              // boolean                 keep_center, // do not remove center tile - it is the tile that should be verified by neighbors
									fourq_min,          // int                     fourq_min,         // each of the 4 corners should have at least this number of tiles.
									fourq_corner,       // int [] 	               fourq_corner, //  array specifying corner number (0..3), -1 - gap. null when not used
									debugLevel);        // int                     debugLevel)
							smpl_wsw = smpl_w;
							weights_sw = weights;
							//						if ( (fit_rslt == null) ||  (fit_rslt[0] > (smpl_arms + smpl_rrms * fit_rslt[1]))){
							if ( (fit_rslt == null) ||  (fit_rslt[0] > max_rms)){
								continue; // narrow selection - too high rms
							}
						}
						boolean valid_suggestion = true;
						if ((disparityStrength == null) && (new_diff > 0.0)) {
							if (Math.abs(fit_rslt[1] - ds[0][nTile]) < new_diff) { // suggested is too close to already measured
								continue; // already measured for this tile
							}
							// compare to the previous suggestion
							int previous_indx = ((src_index[nTile] < 0)? list_index:src_index[nTile]) -1;
							double previous_target = (previous_indx >= 0)? biCamDSI.getBiScan(previous_indx).target_disparity[nTile]:Double.NaN; // Nothing is known about the target of the 0-scan
							if (Math.abs(fit_rslt[1] - previous_target) < new_diff) { // suggested is too close to already suggested and result - disabled
								continue; // already measured for this tile
							}
							// see if close one was already disabled
							// do not compare with the scans that were not disabled - re-try them?remove_all_tried
							// If disparityStrength[][] is provided, do not use history. If difference is <= 0 - also no sense to look there
							//							for (BiScan other_scan:biCamDSI.biScans) if (other_scan.disabled_measurement[nTile] || remove_all_tried) {
							for (BiScan other_scan:biCamDSI.biScans) if ( (other_scan.list_index <= list_index) && (other_scan.disabled_measurement[nTile] || remove_all_tried)) {

								//							int other_indx = (other_scan.src_index[nTile] < 0)? other_scan.list_index:other_scan.src_index[nTile];
								//							double other_disparity = biCamDSI.getBiScan(other_indx).disparity_measured[nTile];
								double other_disparity = other_scan.disparity_measured[nTile];
								if (Math.abs(fit_rslt[1] - other_disparity) < new_diff) { // suggested is too close to already measured and disabled
									valid_suggestion = false;
									break; // already measured for this tile
								}
								int other_indx = other_scan.list_index;
								double other_target = (other_indx > 0)? biCamDSI.getBiScan(other_indx - 1).target_disparity[nTile]:Double.NaN; // Nothing is known about the target of the 0-scan
								if (Math.abs(fit_rslt[1] - other_target) < new_diff) { // suggested is too close to already suggested and result - disabled
									valid_suggestion = false;
									break; // already measured for this tile
								}
							}
						}
						if (valid_suggestion) {
							num_new.getAndIncrement();
							if (smooth_ds == null) { // fill target disparity
								target_disparity[nTile] = fit_rslt[1];
							} else { // this method is used to provide a filtered DSI - no changes to the target_disparity
								smooth_ds[0][nTile] = fit_rslt[1];
								double s0 = 0, s1 = 0;
								for (int dy = -smpl_radius; dy <= smpl_radius; dy++) {
									int ady = (dy > 0)? dy:(-dy);
									for (int dx = -smpl_radius; dx <= smpl_radius; dx++) { // if ((dx != 0) || (dy != 0)){
										int adx = (dx > 0)? dx:(-dx);
										int smpl_indx = smpl_center + dy*smpl_side + dx;
										if (smpl_wsw[smpl_indx] > 0.0) { // either smpl_w_narrow or smpl_w
											s0+= weights_sw[ady][adx];
//											s1+= weights[ady][adx] * smpl_w[smpl_indx];
											s1+= smpl_wsw[smpl_indx]; // already was multiplied by weights[ady][adx]
										}
									}
								}
								//boost_low_density, // false weight of low density tiles is reduced, true - boosted
								double w = s1/s0;
//								if (boost_low_density > 0.0) {
//									w/= Math.pow(s0, boost_low_density);
//								}
								if (strength_pow != 1.0) {
									w = Math.pow(w, 1.0 / strength_pow);
								}
								w += strength_floor;
								smooth_ds[1][nTile] = w;
								if (Double.isNaN(w)) {
									System.out.println("suggestNewScan(): nTile="+nTile+" w="+w);
									System.out.println("suggestNewScan(): nTile="+nTile+" w="+w);
								}
//								if (nTile == 66945) {
//									System.out.println("suggestNewScan(): nTile="+nTile+" w="+w);
//									System.out.println("suggestNewScan(): nTile="+nTile+" w="+w);
//								}

							}
						}
					}
				}
			};
		}
		ImageDtt.startAndJoin(threads);
		// remove duplicates from what was suggested or measured before
		return num_new.get();
	}



    private int findBetterFitToPlane(
    		int       smpl_radius,
    		int       nTile,
    		double    strength_floor,
    		double    strength_pow,
    		double [][] weights,
    		double [][] weights_narrow,
    		double [] smpl_w,
    		double [] smpl_w_narrow,
    		double [] smpl_d,
    		double [] smpl_p
    		)
    {
		final TileNeibs  tnImage = biCamDSI.tnImage;
		final int        smpl_side = 2 * smpl_radius + 1; // Sample size (side of a square)
//		final int        smpl_len = smpl_side*smpl_side;
		final int        smpl_center = (smpl_side + 1) * smpl_radius;
		int nnew = 0; // new assignments
		for (int dy = -smpl_radius; dy <= smpl_radius; dy++) {
			int ady = (dy > 0)? dy:(-dy);
			for (int dx = -smpl_radius; dx <= smpl_radius; dx++){
				int adx = (dx > 0)? dx:(-dx);
				int smpl_indx = smpl_center + dy*smpl_side + dx;
				if  (smpl_w [smpl_indx] > 0.0) { // is in "untouched" weights
					int nTile1 =  tnImage.getNeibIndex(nTile, dx, dy);
//					if ((nTile1 >= 0) && trusted[nTile1]) { // weak trusted OK
					if (nTile1 >= 0) { // will use any strength > 0
						// see if there is a measurement that fits better to the plane
						int best_indx=-1;
						double best_diff = Math.abs(smpl_d[smpl_indx] - smpl_p[smpl_indx]);
						for (int scan_indx = list_index; scan_indx >= 0; scan_indx--) { // include the latest measurement
							BiScan scan = biCamDSI.getBiScan(scan_indx);
							if (scan.disabled_measurement[nTile]) { //  || (scan.src_index[nTile] != indx)){ // skip all but enabled
								continue;
							}
							double disp = scan.disparity_measured[nTile1];
							if (Double.isNaN(disp)) {
								continue;
							}
							if (scan.strength_measured[nTile1] <= strength_floor) {
								continue;
							}
							double diff = Math.abs(disp - smpl_p[smpl_indx]);
							if ((best_indx < 0) || (diff < best_diff)){
								best_indx = scan_indx;
								best_diff = diff;
							}
						}
						if (best_indx >= 0) {
							BiScan scan = biCamDSI.getBiScan(best_indx);
							smpl_d[smpl_indx] = scan.disparity_measured[nTile1];
							double w =          scan.strength_measured[nTile1] -strength_floor ;
							if (w > 0.0) { // should be anyway
								if (strength_pow != 1.0) {
									w = Math.pow(w, strength_pow);
								}
								smpl_w[smpl_indx] =        w * weights[ady][adx];
								smpl_w_narrow[smpl_indx] = w * weights_narrow[ady][adx];
								nnew++;
							}
						}
					}
				}
			}
		}
		return nnew;
    }


// Simple version for non-flat strong areas - try duplicating neighbor
	int  suggestNewScan(
			final int []     dxy,               //up,down,right,left
		    final boolean    discard_cond,      // consider conditionally trusted tiles (not promoted to trusted) as empty
		    final boolean    discard_weak,      // consider weak trusted tiles (not promoted to trusted) as empty
		    final boolean    discard_strong,    // suggest new disparitieas even for strong tiles
			final double     new_diff,          // minimal difference between the new suggested and the already tried/measured one
			final boolean    remove_all_tried,  // remove from suggested - not only disabled, but all tried
			final int        dbg_x,
			final int        dbg_y,
			final int        debugLevel
			) {
//		int dbg_x = 193;
//		int dbg_y = 162;
//		int debugLevel = -1;
//		final int dbg_tile = (debugLevel>-2)?(dbg_x + tnImage.sizeX*dbg_y):-1;

		final TileNeibs  tnImage = biCamDSI.tnImage;
		final double [][] ds = getDisparityStrength( // FIXME
	    		false,   // final boolean only_strong,
	    		false,   // final boolean only_trusted,
	    		true) ;  // final boolean only_enabled);

		final int num_tiles = tnImage.getSizeX()*tnImage.getSizeY();

		final boolean [] trusted_sw = discard_weak ? (this.strong_trusted) : (discard_cond ? this.trusted: this.cond_trusted);
		final boolean [] trusted_weak = this.cond_trusted; // or even any?

		final Thread[] threads = ImageDtt.newThreadArray(biCamDSI.threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		target_disparity = new double [num_tiles];
		for (int i = 0; i < num_tiles; i++) target_disparity[i] = Double.NaN;
		ai.set(0);
		final AtomicInteger num_new = new AtomicInteger(0);
		// find definitely trusted and conditionally trusted tiles
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				@Override
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) if (discard_strong || !trusted_sw[nTile]){
						if (nTile == 52681) {
							System.out.println("suggestNewScan(), nTIle="+nTile+", dxy={"+dxy[0]+","+dxy[1]+"}");

						}
						int nTile1 =  tnImage.getNeibIndex(nTile, dxy[0], dxy[1]);
//						if ((nTile1 >= 0) && trusted_weak[nTile1]) { // weak trusted OK, maybe even any measured
						if ((nTile1 >= 0) && (ds[0][nTile1] > 0)) { // weak trusted OK, maybe even any measured
							double new_disp = ds[0][nTile1];
							if (Math.abs(new_disp - ds[0][nTile]) < new_diff) { // suggested is too close to already measured
								continue; // already measured for this tile
							}
							// compare to the previous suggestion
							int previous_indx = ((src_index[nTile] < 0)? list_index:src_index[nTile]) -1;
							double previous_target = (previous_indx >= 0)? biCamDSI.getBiScan(previous_indx).target_disparity[nTile]:Double.NaN; // Nothing is known about the target of the 0-scan
							if (Math.abs(new_disp - previous_target) < new_diff) { // suggested is too close to already suggested and result - disabled
								continue; // already measured for this tile
							}
							// see if close one was already disabled
							boolean valid_suggestion = true;
							for (BiScan other_scan:biCamDSI.biScans) if ( (other_scan.list_index <= list_index) && (other_scan.disabled_measurement[nTile] || remove_all_tried)) {
//								int other_indx = (other_scan.src_index[nTile] < 0)? other_scan.list_index:other_scan.src_index[nTile];
//								double other_disparity = biCamDSI.getBiScan(other_indx).disparity_measured[nTile];
								double other_disparity = other_scan.disparity_measured[nTile];
								if (Math.abs(new_disp - other_disparity) < new_diff) { // suggested is too close to already measured and disabled
									valid_suggestion = false;
									break; // already measured for this tile
								}
								int other_indx = other_scan.list_index;
								double other_target = (other_indx > 0)? biCamDSI.getBiScan(other_indx - 1).target_disparity[nTile]:Double.NaN; // Nothing is known about the target of the 0-scan
								if (Math.abs(new_disp - other_target) < new_diff) { // suggested is too close to already suggested and result - disabled
									valid_suggestion = false;
									break; // already measured for this tile
								}
							}
							if (valid_suggestion) {
								target_disparity[nTile] = new_disp;
								num_new.getAndIncrement();
							}
						}
					}
				}
			};
		}
		ImageDtt.startAndJoin(threads);
		return num_new.get();
	}






	/**
	 * Calculate trusted tiles from the strength and disparity. Trusted are tiles that are really strong
	 * (then they are trusted regardless of neighbors) or are somewhat strong and have sufficient neighbors
	 * that (together with this tile) make a good (tilted) plane
	 * @param trusted_strength strength to trust unconditionally
	 * @param strength_rfloor strength floor to subrtact as a fraction of the trusted strength
	 * @param cond_rtrusted fraction of the trusted strength (after subtracting str4ength_floor) that is sufficient
	 * to participate in plane fitting, if successful - make a tile trusted
	 * @param strength_pow raise strength to thyis power (normally just 1.0)
	 * @param smpl_radius sample "radius", square side is  2 * smpl_radius + 1
	 * @param smpl_num minimal absolute number of samples required to try fit a plane and validate a tile
	 * @param smpl_fract minimal fraction number of the neighbor samples that fit the rms filter required to try fit a plane and validate a tile
	 * @param max_adiff maximal absolute difference from the center tile for initial neighbors selection
	 * @param max_rdiff maximal (additional) relative (to tile disparity) difference from the center tile for initial neighbors selection
	 * @param max_atilt maximal absolute tilt (pix/tile) for the tilted planes to fit
	 * @param max_rtilt maximal relative tilt (pix/tile per disparity pixel). min(max_rtilt*disparity, max_atilt) will be used
	 * @param smpl_arms maximal absolute rms of the weighted remaining samples for the successful plane fitting
	 * @param smpl_rrms maximal relative (additional)rms of the weighted remaining samples for the successful plane fitting
	 * @param damp_tilt regularization value to handle planes if the remaining samples are co-linear (or just a single tile)
	 * @param rwsigma weight Gaussina sigma to reduce influence of far tiles relative to smpl_radius
	 * @param dbg_x tileX to debug
	 * @param dbg_y tileY to debug
	 * @param debugLevel debug level
	 * @return array of 3 numers: number of trusted strong tiles, number of additional trusted by plane fitting, and number of all
	 * somewhat strong tiles
	 */


	int [] calcTrusted(
		    final double     trusted_strength, // trusted correlation strength
			final double     strength_rfloor,   // strength floor - relative to trusted
		    final double     cond_rtrusted,     // minimal strength to consider - fraction of trusted
			final double     strength_pow,      // raise strength-floor to this power
			final int        smpl_radius,
			final int        smpl_num,   //         = 3;      // Number after removing worst (should be >1)
			final double     smpl_fract, // Number of friends among all neighbors
			final double     max_adiff,  // Maximal absolute difference between the center tile and friends
			final double     max_rdiff, //  Maximal relative difference between the center tile and friends
			final double     max_atilt, //  = 2.0; // pix per tile
			final double     max_rtilt, //  = 0.2; // (pix / disparity) per tile
			final double     smpl_arms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
			final double     smpl_rrms,        //      = 0.005;  // Maximal RMS/disparity in addition to smplRms
			final double     damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
			final double     rwsigma,           //  = 0.7; // influence of far neighbors diminish as a Gaussian with this sigma
			final int        dbg_x,
			final int        dbg_y,
			final int        debugLevel

			) {
		final double goal_fraction_rms = 0.5;
		final TileNeibs  tnImage = biCamDSI.tnImage;
		final double     wsigma = rwsigma*smpl_radius;
		final double [][] ds = getDisparityStrength( // already has disabled zeroed
	    		false,   // final boolean only_strong,
	    		false,   // final boolean only_trusted,
	    		true) ;  // final boolean only_enabled);
		final int num_tiles = tnImage.getSizeX()*tnImage.getSizeY();
		final double     strength_floor = trusted_strength * strength_rfloor;
		final double     min_strength = strength_floor + (trusted_strength - strength_floor) *  cond_rtrusted;
		final int        smpl_side = 2 * smpl_radius + 1; // Sample size (side of a square)
		final int        smpl_len = smpl_side*smpl_side;
		// prepare window
		final double [][] weights = new double [smpl_radius + 1][smpl_radius + 1];
		for (int i = 0; i <weights.length; i++) {
			for (int j = i; j <weights[i].length; j++) {
				weights[i][j] = (wsigma >0.0) ?Math.exp(-(i*i+j*j)/(2*wsigma*wsigma)):1.0;
				weights[j][i] = weights[i][j];
			}
		}
		final boolean [][] halves = new boolean [8][smpl_len];
		{
			for (int i = 0; i < smpl_side; i++) {
				for (int j = 0; j < smpl_side; j++) {
					int indxs = i*smpl_side+ j;
					halves[0][indxs] = (i <= smpl_radius);
					halves[4][indxs] = (i >= smpl_radius);
					halves[2][indxs] = (j >= smpl_radius);
					halves[6][indxs] = (j <= smpl_radius);
					halves[1][indxs] = (j >= i);
					halves[5][indxs] = (j <= i);
					halves[3][indxs] = ((i + j) >= (2 * smpl_radius));
					halves[7][indxs] = ((i + j) <= (2 * smpl_radius));
				}
			}
		}



		final Thread[] threads = ImageDtt.newThreadArray(biCamDSI.threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		final AtomicInteger num_non_weak = new AtomicInteger(0);
		final AtomicInteger num_trusted_strong = new AtomicInteger(0);
		final AtomicInteger num_trusted_plane = new AtomicInteger(0);
//		final boolean [] cond_trusted = new boolean [num_tiles];
		strong_trusted = new boolean [num_tiles];
		trusted =        new boolean [num_tiles];
		cond_trusted =   new boolean [num_tiles];

		ai.set(0);
		// find definitely trusted and conditionally trusted tiles
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				@Override
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()){
						if (!Double.isNaN(ds[0][nTile]) && (ds[1][nTile] >= min_strength) || (ds[1][nTile] > 0)) {
							num_non_weak.getAndIncrement();
							cond_trusted[nTile] = true;
							if (ds[1][nTile] > trusted_strength) {
								strong_trusted[nTile] = true;
								trusted[nTile] =        true;
								num_trusted_strong.getAndIncrement();
							}
						}
					}
				}
			};
		}
		ImageDtt.startAndJoin(threads);

		ai.set(0);
		// find definitely trusted and conditionally trusted tiles
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				@Override
				public void run() {
					int smpl_center = (smpl_side + 1) * smpl_radius;
					for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) if (cond_trusted[nTile] && !trusted[nTile]){
						double [] smpl_d =    new double  [smpl_len];
						double [] smpl_w =    new double  [smpl_len];
						double [] smpl_p =    new double  [smpl_len]; // plane disparity,
						boolean [] smpl_trusted = new boolean [smpl_len];
						// copy neighbor tiles
						double disp_center = ds[0][nTile];
						double max_diff = max_adiff + max_rdiff * disp_center;
						double max_tilt = max_rtilt * disp_center;
						if (max_tilt > max_atilt) {
							max_tilt = max_atilt;
						}

						int nsmpls = 0;
						int nall = 0;

						for (int dy = -smpl_radius; dy <= smpl_radius; dy++) {
							int ady = (dy > 0)? dy:(-dy);
							for (int dx = -smpl_radius; dx <= smpl_radius; dx++) {
								int nTile1 =  tnImage.getNeibIndex(nTile, dx, dy);
								if ((nTile1 >= 0) && cond_trusted[nTile1]) {
									int adx = (dx > 0)? dx:(-dx);
									double max_fdiff = max_diff + (ady+adx) * max_tilt;
									int smpl_indx = smpl_center + dy*smpl_side + dx;
									smpl_trusted[smpl_indx] = true;
									nall++;
									if (Math.abs(ds[0][nTile1] - disp_center) <= max_fdiff) {
//										smpl_sel[smpl_indx] = true;
										smpl_d[smpl_indx] = ds[0][nTile1];
										smpl_w[smpl_indx] = (ds[1][nTile1]-strength_floor) * weights[ady][adx];
										if (strength_pow != 1.0) {
											smpl_w[smpl_indx] = Math.pow(smpl_w[smpl_indx], strength_pow);
										}
										nsmpls ++;
									}
								}
							}
						}
						int fin_samples= (int) ( nall * smpl_fract);
						if (fin_samples < smpl_num) fin_samples = smpl_num;
						double max_rms = smpl_arms + smpl_rrms * disp_center;
						if (nsmpls >= fin_samples) {
							double [] smpl_w_persistent = smpl_w.clone();
							double [] fit_rslt = fitPlaneRemoveOutliers(
									smpl_radius, // int                     smpl_radius,
									max_tilt,    // double                  max_tilt,
									damp_tilt,   // double                  damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
									false,       // boolean                 full_plane,
									smpl_d,      // double []               smpl_d,
									smpl_w,      // double []               smpl_w, // will be modified,
									smpl_p,      // double []               smpl_p, // will be set if provided
									fin_samples, // int                     fin_samples, // remove until this number remain
									goal_fraction_rms*max_rms,    // double                  fin_rms,
									true,        // boolean                 keep_center, // do not remove center tile - it is the tile that should be verified by neighbors
									0,           // int                     fourq_min,         // each of the 4 corners should have at least this number of tiles.
									null,        // int [] 	                fourq_corner, //  array specifying corner number (0..3), -1 - gap. null when not used
									debugLevel); // int                     debugLevel)
//							if ( (fit_rslt != null) && (fit_rslt[0] < (smpl_arms + smpl_rrms * fit_rslt[1]))){
							if ( (fit_rslt != null) && (fit_rslt[0] < max_rms)){
								// Trusted tile, save it
								trusted[nTile] = true;
								num_trusted_plane.getAndIncrement();
								continue;
							}
							// try 8 "halves" around the tile - it may be weak background close to the strong foreground (or stray FG tile)
							for (int dir = 0; dir < halves.length; dir++) {
								nsmpls = 0;
								nall = 0;
								smpl_w = new double [smpl_len];
								for (int indxs = 0; indxs < smpl_len; indxs++) if (halves[dir][indxs]) {
									if (smpl_trusted[indxs]) nall++;
									if (smpl_w_persistent[indxs] > 0.0) {
										smpl_w[indxs] = smpl_w_persistent[indxs];
										nsmpls++;
									}
								}
								fin_samples= (int) ( nall * smpl_fract);
								if (fin_samples < smpl_num) fin_samples = smpl_num;
								if (nsmpls >= fin_samples) {
									fit_rslt = fitPlaneRemoveOutliers(
											smpl_radius, // int                     smpl_radius,
											max_tilt,    // double                  max_tilt,
											damp_tilt,   // double                  damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
											false,       // boolean                 full_plane,
											smpl_d,      // double []               smpl_d,
											smpl_w,      // double []               smpl_w, // will be modified,
											smpl_p,      // double []               smpl_p, // will be set if provided
											fin_samples, // int                     fin_samples, // remove until this number remain
											goal_fraction_rms*max_rms,    // double                  fin_rms,
											true,        // boolean                 keep_center, // do not remove center tile - it is the tile that should be verified by neighbors
											0,           // int                     fourq_min,         // each of the 4 corners should have at least this number of tiles.
											null,        // int [] 	                fourq_corner, //  array specifying corner number (0..3), -1 - gap. null when not used
											debugLevel); // int                     debugLevel)
									if ((fit_rslt != null) && (fit_rslt[0] < max_rms)){
										// Trusted tile, save it
										trusted[nTile] = true;
										num_trusted_plane.getAndIncrement();
										break; // from for (int dir = 0; dir < halves.length; dir++) {
									}
								}
							}
						}
					}
				}
			};
		}
		ImageDtt.startAndJoin(threads);
		int [] numTrustedAll = {num_trusted_strong.get(), num_trusted_plane.get(), num_non_weak.get()};
		return numTrustedAll;
	}

	/**
	 * Remove stray tiles that are closer than closest neighbor and weaker than it or
	 *  trusted_strength * min_rstrength
	 * @param trusted_strength absolute raw strength to trust
	 * @param min_rstrength minimal strength to allow lone FG, relative to trusted_strength
	 * @param max_disp_inc maximal disparity difference between this tile and the nearest neighbor
	 * @param dbg_x
	 * @param dbg_y
	 * @param debugLevel
	 * @return number of disabled tiles
	 */

	int  trimWeakLoneFG(
		    final double     trusted_strength, // trusted correlation strength
			final double     min_rstrength,   // strength floor - relative to trusted
			final double     max_disp_inc,
			final int        dbg_x,
			final int        dbg_y,
			final int        debugLevel
			) {
		final AtomicInteger ai_trimmed = new AtomicInteger(0);
		final double min_strength = trusted_strength * min_rstrength;
		final TileNeibs  tnImage = biCamDSI.tnImage;
		final int num_tiles = tnImage.sizeX * tnImage.sizeY;
		final Thread[] threads = ImageDtt.newThreadArray(biCamDSI.threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		final double [][] ds = getDisparityStrength( // already has disabled zeroed
	    		false,   // final boolean only_strong,
	    		false,   // final boolean only_trusted,
	    		true) ;  // final boolean only_enabled);

		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				@Override
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) if (!Double.isNaN(ds[0][nTile])){
						double max_disp = 0;
						double max_disp_w = 0;
						double d_lim = ds[0][nTile] - max_disp_inc;
						double w =     ds[1][nTile];
						for (int dir = 0; dir < 8; dir++) {
							int nTile1 = tnImage.getNeibIndex(nTile, dir);
							if ((nTile1 >=0) && (ds[0][nTile1] > max_disp)){
								 max_disp =   ds[0][nTile1];
								 max_disp_w = ds[1][nTile1];
							}
							if (max_disp > d_lim) {
								break;
							}
						}
						if ((max_disp <= d_lim) && ((w < max_disp_w) || (w < min_strength))) {
							disableTile(nTile);
							ai_trimmed.getAndIncrement();
							if (debugLevel > -1) {
								System.out.println("trimWeakLoneFG: removing tile "+nTile+" ("+(nTile%tnImage.sizeX)+":"+(nTile/tnImage.sizeX)+")");
							}
						}
					}
				}
			};
		}
		ImageDtt.startAndJoin(threads);
		return ai_trimmed.get();
	}

	// FG edge should be strong
	// Trimming(disabling) weak (trusted but not strong_trusted) tiles if on any one side:
	// a) there are no same plane or closer tiles
	// b) there are no in-plane or closer strong tiles, but there are some (strong or any?) farther tiles
	// repeat while more are trimmed
	// maybe, if there are both strong in-plane and far - see which are closer

	/**
	 * Disable low-textured tiles are not between strong tiles, but on one side of it.
	 * This method relies on the assumption that FG edge should bave strong correlation, so it tries multiple directions
	 * from the weak (not trusted strong) tiles and trims tiles that eithre do not have anything in that direction or have
	 * farther tiles.
	 * Trimming(disabling) weak (trusted but not strong_trusted) tiles if on any one side:
	 *   a) there are no same plane or closer tiles
	 *   b) there are no in-plane or closer strong tiles, but there are some (strong or any?) farther tiles
	 * repeat while more are trimmed
	 * maybe, if there are both strong in-plane and far - see which are closer
	 * @param trusted_strength strength to trust unconditionally
	 * @param strength_rfloor strength floor to subrtact as a fraction of the trusted strength
	 * @param cond_rtrusted fraction of the trusted strength (after subtracting str4ength_floor) that is sufficient
	 * to participate in plane fitting, if successful - make a tile trusted
	 * @param strength_pow raise strength to thyis power (normally just 1.0)
	 * @param smpl_radius sample "radius", square side is  2 * smpl_radius + 1
	 * @param smpl_num minimal absolute number of samples required to try fit a plane and validate a tile
	 * @param smpl_fract minimal fraction number of the neighbor samples that fit the rms filter required to try fit a plane and validate a tile
	 * @param max_adiff maximal absolute difference from the center tile for initial neighbors selection
	 * @param max_rdiff maximal (additional) relative (to tile disparity) difference from the center tile for initial neighbors selection
	 * @param max_atilt maximal absolute tilt (pix/tile) for the tilted planes to fit
	 * @param max_rtilt maximal relative tilt (pix/tile per disparity pixel). min(max_rtilt*disparity, max_atilt) will be used
	 * @param smpl_arms maximal absolute rms of the weighted remaining samples for the successful plane fitting
	 * @param smpl_rrms maximal relative (additional)rms of the weighted remaining samples for the successful plane fitting
	 * @param damp_tilt regularization value to handle planes if the remaining samples are co-linear (or just a single tile)
	 * @param rwsigma weight Gaussina sigma to reduce influence of far tiles relative to smpl_radius
	 * @param atolerance absolute disparity tolerance to what to consider "far"
	 * @param rtolerance relative to disparity tolerance to what to consider "far"
	 * @param num_dirs number of directions per 2*PI to try
	 * @param blind_dist when trying directions require distance in that direction to exceed this value to count
	 * @param strong_only_far true: require far tiles to be trusted_strong, false - just trusted is OK
	 * @param num_strong_far number of strong trusted far tiles to make this FG tile hanging
	 * @param num_weak_far number of strong trusted far tiles to make this FG tile hanging
	 * @param dbg_x tileX to debug
	 * @param dbg_y tileY to debug
	 * @param debugLevel debug level
	 * @return
	 */

	int  trimWeakFG(
		    final double     trusted_strength, // trusted correlation strength
			final double     strength_rfloor,   // strength floor - relative to trusted
		    final double     cond_rtrusted,     // minimal strength to consider - fraction of trusted
			final double     strength_pow,      // raise strength-floor to this power
			final int        smpl_radius,
			final int        smpl_num,   //         = 3;      // Number after removing worst (should be >1)
			final double     smpl_fract, // Number of friends among all neighbors
			final double     max_adiff,  // Maximal absolute difference betweenthe center tile and friends
			final double     max_rdiff, //  Maximal relative difference between the center tile and friends
			final double     max_atilt, //  = 2.0; // pix per tile
			final double     max_rtilt, //  = 0.2; // (pix / disparity) per tile
			final double     smpl_arms, //         = 0.1;    // Maximal RMS of the remaining tiles in a sample
			final double     smpl_rrms,        //      = 0.005;  // Maximal RMS/disparity in addition to smplRms
			final double     damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
			final double     rwsigma,           //  = 0.7; // influence of far neighbors diminish as a Gaussian with this sigma

			final double     atolerance,  // When deciding closer/farther
			final double     rtolerance,  // same, scaled with disparity
			final int        num_dirs,    // number of directions to try
			final double     blind_dist,  // analyze only tiles farther than this in the selected direction
			final boolean    strong_only_far, // in variant b) only compare with strong far
			final int        num_strong_far,    // number of directions to try
			final int        num_weak_far,     // number of directions to try

			final int        dbg_x,
			final int        dbg_y,
			final int        debugLevel

			) {
		final double goal_fraction_rms = 0.5;
		final TileNeibs  tnImage = biCamDSI.tnImage;
		final int dbg_tile = (debugLevel>-2)?(dbg_x + tnImage.sizeX*dbg_y):-1;
		final double     wsigma = rwsigma*smpl_radius;
		final double [][] ds = getDisparityStrength( // already has disabled zeroed
	    		false,   // final boolean only_strong,
	    		false,   // final boolean only_trusted,
	    		true) ;  // final boolean only_enabled);

		final int num_tiles = tnImage.getSizeX()*tnImage.getSizeY();
		final double     strength_floor = trusted_strength * strength_rfloor;
//		final double     min_strength = strength_floor + (trusted_strength - strength_floor) *  cond_rtrusted;
		final int        smpl_side = 2 * smpl_radius + 1; // Sample size (side of a square)
		final int        smpl_len = smpl_side*smpl_side;
		// prepare window
		final double [][] weights = new double [smpl_radius + 1][smpl_radius + 1];
		for (int i = 0; i <weights.length; i++) {
			for (int j = i; j <weights[i].length; j++) {
				weights[i][j] = (wsigma >0.0) ?Math.exp(-(i*i+j*j)/(2*wsigma*wsigma)):1.0;
				weights[j][i] = weights[i][j];
			}
		}
		final Thread[] threads = ImageDtt.newThreadArray(biCamDSI.threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		final AtomicInteger num_disabled = new AtomicInteger(0);
		int total_disabled = 0;
        int npass=0;
		while (true) {
			num_disabled.set(0);
			final boolean [] new_disabled = new boolean[num_tiles]; // this.disabled.clone();
			ai.set(0);
			// find definitely trusted and conditionally trusted tiles
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					@Override
					public void run() {
						int smpl_center = (smpl_side + 1) * smpl_radius;
						for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) if (trusted[nTile] && !strong_trusted[nTile]){
							boolean dbg_this = (dbg_tile == nTile);
							if (dbg_this) {
								System.out.println("trimWeakFG(): debugging tile"+nTile);
							}
							double [] smpl_d =       new double  [smpl_len];
							double [] smpl_w =       new double  [smpl_len];
							double [] smpl_w_all =   new double  [smpl_len];
							double [] smpl_p =       new double  [smpl_len]; // plane disparity,
							boolean [] smpl_strong = new boolean[smpl_len];
							// copy neighbor tiles
							double disp_center = ds[0][nTile];
							double max_diff = max_adiff + max_rdiff * disp_center;
							double max_tilt = max_rtilt * disp_center;
							if (max_tilt > max_atilt) {
								max_tilt = max_atilt;
							}

							int nsmpls = 0;
							int nall = 0;

							for (int dy = -smpl_radius; dy <= smpl_radius; dy++) {
								int ady = (dy > 0)? dy:(-dy);
								for (int dx = -smpl_radius; dx <= smpl_radius; dx++) {
									int nTile1 =  tnImage.getNeibIndex(nTile, dx, dy);
									//								if ((nTile1 >= 0) && cond_trusted[nTile1]) {
									if ((nTile1 >= 0) && trusted[nTile1]) {

										nall++;
										int adx = (dx > 0)? dx:(-dx);
										double max_fdiff = max_diff + (ady+adx) * max_tilt;
										int smpl_indx = smpl_center + dy*smpl_side + dx;
										smpl_d[smpl_indx] = ds[0][nTile1];
										double w = (ds[1][nTile1]-strength_floor) * weights[ady][adx];
										if (smpl_d[smpl_indx] < 0.0) { // discard stray negative disparity
											w = 0.0;
										}
										if (strength_pow != 1.0) {
											w = Math.pow(w, strength_pow);
										}
										smpl_w_all[smpl_indx] = w;
										if (Math.abs(ds[0][nTile1] - disp_center) <= max_fdiff) {
											smpl_w[smpl_indx] = w;
											nsmpls ++;
										}
										smpl_strong[smpl_indx]= strong_trusted[nTile1];
									}
								}
							}
							int fin_samples= (int) ( nall * smpl_fract);
							if (fin_samples < smpl_num) fin_samples = smpl_num;
							double max_rms = smpl_arms + smpl_rrms * disp_center;
//		final double goal_fraction_rms = 0.5;

							if (nsmpls >= fin_samples) {
								double [] fit_rslt = fitPlaneRemoveOutliers(
										smpl_radius, // int                     smpl_radius,
										max_tilt,    // double                  max_tilt,
										damp_tilt,   // double                  damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
										true,       // boolean                 full_plane,
										smpl_d,      // double []               smpl_d,
										smpl_w,      // double []               smpl_w, // will be modified,
										smpl_p,      // double []               smpl_p, // will be set if provided
										fin_samples, // int                     fin_samples, // remove until this number remain
										goal_fraction_rms*max_rms,     // double                  fin_rms,
										true,        // boolean                 keep_center, // do not remove center tile - it is the tile that should be verified by neighbors
										0,           // int                     fourq_min,         // each of the 4 corners should have at least this number of tiles.
										null,        // int [] 	               fourq_corner, //  array specifying corner number (0..3), -1 - gap. null when not used
										debugLevel); // int                     debugLevel)
//								if ( (fit_rslt != null) && (fit_rslt[0] < (smpl_arms + smpl_rrms * fit_rslt[1]))){
								if ( (fit_rslt != null) && (fit_rslt[0] < max_rms)){
									// check directions here, use smpl_w_all, smpl_d and smpl_p
									//								int [] num_this_closer =        new int [num_dirs];
									//								int [] num_this_closer_strong = new int [num_dirs];
									//								int [] num_farther =            new int [num_dirs];



									double thershold_disp = -(atolerance + rtolerance*fit_rslt[1]);
									double [][] dbg_data = dbg_this?(new double [5+num_dirs][smpl_len]):null;
									String [] dbg_titles = dbg_this?(new String [5+num_dirs]):null;
									if (dbg_this) {
										for (int indxs = 0; indxs < smpl_len; indxs++) {
											dbg_data[0][indxs] = smpl_d[indxs];
											dbg_data[1][indxs] = smpl_strong[indxs]? smpl_d[indxs]: Double.NaN;
											dbg_data[2][indxs] = smpl_p[indxs];
											dbg_data[3][indxs] = smpl_w_all[indxs];
											dbg_data[4][indxs] = smpl_d[indxs]-smpl_p[indxs];
										}
										dbg_titles[0]= "smpl_d";
										dbg_titles[1]= "smpl_d-strong";
										dbg_titles[2]= "smpl_p";
										dbg_titles[3]= "smpl_w_all";
										dbg_titles[4]= "smpl_d-smpl_p";
										for (int idir = 0; idir<num_dirs; idir++) {
											dbg_titles[5 + idir]="dir-"+idir;
											for (int indxs = 0; indxs < smpl_len; indxs++) {
												dbg_data[5+idir][indxs] = Double.NaN;
											}
										}
									}

									for (int idir = 0; idir<num_dirs; idir++) {
										double a = 2*Math.PI*idir/num_dirs;
										double ca = Math.cos(a);
										double sa = Math.sin(a);
										int num_this_closer =        0;
										int num_this_closer_strong = 0;
										int num_farther =            0;
										int num_farther_strong =     0;


										for (int indxs = 0; indxs < smpl_len; indxs++) if (smpl_w_all[indxs] > 0.0) {
											int dy = indxs/smpl_side - smpl_radius;
											int dx = indxs%smpl_side - smpl_radius;
											double d = dx*ca + dy*sa;
											if (dbg_this) {
												dbg_data[5+idir][indxs] = d - blind_dist;
											}
											if (d >= blind_dist) {
												boolean farther = (smpl_d[indxs]-smpl_p[indxs]) < thershold_disp;
												if (farther) {
													num_farther ++;
//													if (!strong_only_far || smpl_strong[indxs]) num_farther++;
													if (smpl_strong[indxs]) num_farther_strong++;
												} else {
													num_this_closer++;
													if (smpl_strong[indxs]) num_this_closer_strong++;
												}
											}

										}
										if (dbg_this) {
											System.out.println("trimWeakFG(): idir="+idir+" num_this_closer="+num_this_closer+
													", num_this_closer_strong="+num_this_closer_strong+", num_farther="+num_farther);
										}
										boolean far_exist = (num_farther_strong > num_strong_far) || (!strong_only_far && (num_farther > num_weak_far));

										if ((num_this_closer ==0) || far_exist) {
											new_disabled[nTile] =    true;
											num_disabled.getAndIncrement();
											if (dbg_this) {
												System.out.println("trimWeakFG(): DISABLED idir="+idir+" num_this_closer="+num_this_closer+
														", num_this_closer_strong="+num_this_closer_strong+", num_farther="+num_farther+", num_farther_strong="+num_farther_strong);
											}
											if (!dbg_this) {
												break; // already disabled
											}

										}
									}
									if (dbg_this) {
									ShowDoubleFloatArrays.showArrays(
											dbg_data,
											smpl_side,
											smpl_side,
											true,
											"trimWeakFG_T"+nTile,
											dbg_titles);
									}


								} else { // may get even after initial filtering (by calcTrusted()) here because some neighbors became disabled? loosen flatness? Keep non-flat
									if (dbg_this) {
										System.out.println("trimWeakFG(): Too high RMS");
									}

								}
							} else { // may get even after initial filtering (by calcTrusted()) here because some neighbors became disabled?
								if (dbg_this) {
									System.out.println("trimWeakFG(): Insufficient points");
								}
							}
						}
					}
				};
			}
			ImageDtt.startAndJoin(threads);
			npass++;
			if (debugLevel > -2) {
				System.out.println("trimWeakFG(): npass="+npass+" removed="+num_disabled.get());
			}
			if (num_disabled.get() == 0) {
				break;
			}
			ai.set(0);
			// find definitely trusted and conditionally trusted tiles
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					@Override
					public void run() {
						for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement())	if (new_disabled[nTile]) {
							disableTile(nTile); // disabled source and this trusted*
						}
					}
				};
			}
			ImageDtt.startAndJoin(threads);
			if (debugLevel > -2) {
				biCamDSI.getLastBiScan(BISCAN_ANY).showScan(
						"trimWeakFG_"+npass);
			}

			total_disabled += num_disabled.get();
		}
		return total_disabled; // numTrustedAll;
	}



//					smpl_p[indxs] = approx2d[0][0] * (sx - smpl_radius) + approx2d[0][1] * (sy - smpl_radius) + approx2d[0][2];

	/**
	 * Remove worst outliers when fitting planes until the specified number of samples are left, re-fitting
	 * plane after each iteration.
	 * Additional mode for filling gaps (not extending) - "four quadrants" is activated when  fourq_min > 0
	 * In this mode each of the four corners of the sample square (after removing center 2*fourq_gap -1 rows
	 * and columns should have at least fourq_min remaining tiles
	 * @param smpl_radius sample "radius", square side is  2 * smpl_radius + 1
	 * @param max_tilt maximal DSI plane tilt (pixels/tile)
	 * @param damp_tilt tilt cost for damping insufficient plane data
	 * @param full_plane generate full tilted plane, not only for non-zero tiles
	 * @param smpl_d  array of disparity values to approximate
	 * @param smpl_w  weights - only >0.0 are processed
	 * @param smpl_p approximated disparity values, may be null. When provided (not null), will have calculated disparity approximation
	 * @param fin_samples remove until this number of tiles remain
	 * @param fin_rms - OK RMS - exit if it is below (set to 0.0 if unknown yet)
	 * @param keep_center do not remove center tile - it is the tile that should be verified by neighbors
	 * @param fourq_min each of the 4 corners should have at least this number of tiles.
	 * @param fourq_corner array specifying corner number (0..3), -1 - gap. null when not used
	 * @param debugLevel debug level
	 * @return array of 4 values: rms of the remaining samples,  average (center) disparity value, tiltX, tiltY
	 */
	public double [] fitPlaneRemoveOutliers(
			int                     smpl_radius,
			double                  max_tilt,
			double                  damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
			boolean                 full_plane,
			double []               smpl_d,
			double []               smpl_w, // will be modified,
			double []               smpl_p, // will be set if provided
			int                     fin_samples, // remove until this number remain
			double                  fin_rms,
			boolean                 keep_center, // do not remove center tile - it is the tile that should be verified by neighbors
			int                     fourq_min,
			int []                  fourq_corner,
			int                     debugLevel)
	{
		int [] num_in_corners = ((fourq_corner == null) || (fourq_min == 0)) ? null: new int [5]; // last is not used
		final int keep_tile = keep_center? (2*smpl_radius*(smpl_radius+1)): -1; // do not remove tile with this number
		double [] disp_tilts = null;
		final int        smpl_side = 2 * smpl_radius + 1; // Sample size (side of a square)
		int num_in_sample = 0;
		for (int i = 0; i < smpl_w.length; i++) if (smpl_w[i]> 0.0) {
			num_in_sample++;
			if (num_in_corners != null) num_in_corners[fourq_corner[i]]++;
		}

		int smpl_len = smpl_w.length;
		if (num_in_sample < fin_samples) {
			return null;
		}
		if (num_in_corners != null) {
			for (int i = 0; i < 4; i++) {
				if (num_in_corners[i] < fourq_min) {
					return null;
				}
			}
		}
		double rms = Double.NaN;
		if (smpl_p == null) smpl_p = new double [smpl_len];
		while (num_in_sample >= fin_samples) {
			// fit plane to the selected and remove outliers after each iteration
			disp_tilts = fitPlane(
					smpl_radius,  //int        smpl_radius,
					max_tilt,     //double     max_tilt,
					damp_tilt,    //double     damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
					full_plane,   // boolean                 full_plane,
					smpl_d,       // double []  smpl_d,
					smpl_w,       // double []  smpl_w,
					smpl_p,       //double []  smpl_p, // will be set if provided
					debugLevel);  // int        debugLevel)
			// calculate fitting quality
			if (disp_tilts == null) {
				return null;
			}
//			double d_center = disp_tilts[0];
			double sw = 0.0, sd2 = 0.0;
			for (int indxs = 0; indxs < smpl_len;indxs++) if (smpl_w[indxs] > 0) {
				double d = smpl_d[indxs] - smpl_p[indxs];
				double dw = d * smpl_w[indxs];
				sd2 += dw * smpl_d[indxs];
				sw +=       smpl_w[indxs];
			}
			rms = (sw > 0.0)? Math.sqrt(sd2/sw): Double.NaN;

			// remove worst - it should not make remaining set
			if ((num_in_sample > fin_samples) && !(rms < fin_rms)) { // remove worst if it is not the last run where only calculations are needed
				int iworst = -1;
				double dworst2 = 0.0;
				for (int indxs = 0; indxs < smpl_len; indxs++) if (smpl_w[indxs] > 0.0) {
					if (indxs == keep_tile) { // it was the center tile disabled from removing
						continue;
					}
					// verify that the worst tile will not leave the corner too empty
					int num_corner = 4; // unused, gap
					if (num_in_corners != null) {
						num_corner =  fourq_corner[indxs];
						if ((num_corner < 4) && (num_in_corners[num_corner] <= fourq_min)) {
							continue; // can not remove this tile
						}
					}

					double d2 = smpl_d[indxs] - smpl_p[indxs];
					d2 *=d2;
					if (d2 > dworst2) {
						if ((damp_tilt !=0.0) || notColinearWithout (
								indxs, // int        indx,
								smpl_w, // boolean [] sel,
								smpl_side)) { // int side))
							iworst = indxs;
							dworst2 = d2;
						}
					}
				}
				if (iworst < 0){ // can happen if damp_tilt == 0.0 and will become colinear
					if (debugLevel > 0) {
						System.out.println("**** this may be BUG in fitPlaneRemoveOutliers() can not find the worst sample  - all tiles fit perfectly ****");
					}
					// this can happen if some samples are the same and all the pixels fit exactly - use all of them
					break;
				}
				// remove worst sample
				if (num_in_corners != null) {
					num_in_corners[fourq_corner[iworst]]--; // corner counter - can remove from the gap, but nobody count that
				}
				smpl_w[iworst] = 0.0;
				num_in_sample --;
			} else {
				break;
			}
		} // while (nsmpls >= min_sample) {
		double [] rslt = {rms, disp_tilts[0], disp_tilts[1], disp_tilts[1]};
		return rslt;
	}

	/**
	 * Verify that selected points are not all on the same line, even if the specified one is removed
	 * @param indx index of the point to be removed
	 * @param smpl_w 2-d sample weights in linescan order (used zero/non-zero only)
	 * @param side square samples side
	 * @return true if there are enough samples for plane extraction, false otherwise
	 */

	public boolean notColinearWithout (
			int        indx,
			double [] smpl_w,
			int side)
	{
		if (smpl_w[indx] == 0.0){
			throw new IllegalArgumentException ("notCoplanarWithout(): specified is the non existing index");
		}
		double w = smpl_w[indx];
		smpl_w[indx] = 0.0;
		boolean rslt = notColinear ( smpl_w, side);
		smpl_w[indx] = w; // restore value
		return rslt;
	}
	/**
	 * Verify that selected points are not all on the same line
	 * @param smpl_w 2-d sample weights in linescan order (used zero/non-zero only)
	 * @param side square samples side
	 * @return true if there are enough samples for plane extraction, false otherwise
	 */
	public boolean notColinear (
			double [] smpl_w,
			int side)
	{
		int indx0, indx1;
		for (indx0 = 0; indx0 < smpl_w.length; indx0++){
			if (smpl_w[indx0] > 0.0) break;
		}
		for (indx1 = indx0+1; indx1 < smpl_w.length; indx1++){
			if (smpl_w[indx0] > 0.0) break;
		}
		if (indx1 >= smpl_w.length) return false; // too few points;
		int sx0 = indx0 % side;
		int sy0 = indx0 / side;
		int sx1 = indx1 % side;
		int sy1 = indx1 / side;
		for (int indx = indx1 +1; indx < smpl_w.length; indx++){
			int sx = indx % side;
			int sy = indx / side;
			if ((sx - sx0) * (sy - sy1) != (sx - sx1) * (sy - sy0)){
				return true;
			}
		}
		return false;
	}



	/**
	 * Approximate tile disparity by a tilted DSI plane
	 * @param smpl_radius sample "radius", square side is  2 * smpl_radius + 1
	 * @param max_tilt maximal DSI plane tilt (pixels/tile)
	 * @param damp_tilt tilt cost for damping insufficient plane data
	 * @param full_plane generate full tilted plane, not only for non-zero tiles
	 * @param smpl_d  array of disparity values to approximate
	 * @param smpl_w  weights - only >0.0 are processed
	 * @param smpl_p approximated disparity values, amy be null. When provided (not null), will have calculated disparity approximation
	 * @param debugLevel debug level
	 * @return array of 3 values: average (center) disparity value, tiltX, tiltY
	 */
	public double [] fitPlane(
			int                     smpl_radius,
			double                  max_tilt,
			double                  damp_tilt, //   =     0.001; // Tilt cost for damping insufficient plane data
			boolean                 full_plane,
			double []               smpl_d,
			double []               smpl_w,
			double []               smpl_p, // will be set if provided
			int                     debugLevel)
	{
		PolynomialApproximation pa = new PolynomialApproximation();
		final double [] damping = {damp_tilt, damp_tilt, 0.0}; // 0.0 will be applied to average value, tilt_cost - to both tilts
		final int        smpl_side = 2 * smpl_radius + 1; // Sample size (side of a square)
		final int        smpl_len = smpl_side*smpl_side;
		if (smpl_p == null) smpl_p = new double [smpl_len];
		int num_in_sample = 0;
		for (int i = 0; i < smpl_w.length; i++) if (smpl_w[i] > 0.0) num_in_sample++;

		double [][][] mdata = new double [num_in_sample][3][];
		int mindx = 0;
		for (int sy = 0; sy < smpl_side; sy++){
			for (int sx = 0; sx < smpl_side; sx++){
				int indxs = sy * smpl_side + sx;
				if (smpl_w[indxs] > 0.0) {
					mdata[mindx][0] = new double [2];
					mdata[mindx][0][0] =  sx - smpl_radius;
					mdata[mindx][0][1] =  sy - smpl_radius;
					mdata[mindx][1] = new double [1];
					mdata[mindx][1][0] = smpl_d[indxs];
					mdata[mindx][2] = new double [1];
					mdata[mindx][2][0] =  smpl_w[indxs];
					mindx ++;
				}
			}
		}
		double[][] approx2d = pa.quadraticApproximation(
				mdata,
				true,          // boolean forceLinear,  // use linear approximation
				damping,       // double [] damping,
				THRESHOLD_LIN,  // threshold ratio of matrix determinant to norm for linear approximation (det too low - fail)
				THRESHOLD_QUAD, // threshold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
				debugLevel);
		if (approx2d == null){
			if (debugLevel > -1){
				System.out.println("getDisparityStrengthML(): can not find linear approximation");
			}
			return null;
		}
		// limit tilt to be within range
		//											double     max_abs_tilt, //  = 2.0; // pix per tile
		//											double     max_rel_tilt, //  = 0.2; // (pix / disparity) per tile
//		double max_tilt = Math.min(mlfp.max_abs_tilt, mlfp.max_rel_tilt * approx2d[0][2]);
		boolean overlimit = (Math.abs(approx2d[0][0]) > max_tilt) || (Math.abs(approx2d[0][1]) > max_tilt);
		if (overlimit) {
			approx2d[0][0] = Math.min(approx2d[0][0],  max_tilt);
			approx2d[0][1] = Math.min(approx2d[0][1],  max_tilt);
			approx2d[0][0] = Math.max(approx2d[0][0], -max_tilt);
			approx2d[0][1] = Math.max(approx2d[0][1], -max_tilt);
		}
		// subtract tilt from disparity
		for (int sy = 0; sy < smpl_side; sy++){
			for (int sx = 0; sx < smpl_side; sx++){
				int indxs = sy * smpl_side + sx;
				if ((smpl_w[indxs] > 0.0) || full_plane) {
					smpl_p[indxs] = approx2d[0][0] * (sx - smpl_radius) + approx2d[0][1] * (sy - smpl_radius) + approx2d[0][2];
				}
			}
		}

		if (overlimit){ // re-calculate disparity average (in the center)
			double sw = 0.0;
			double sd=0.0;
			for (int indxs = 0; indxs < smpl_len;indxs++) if (smpl_w[indxs] > 0.0) {
				double d = smpl_d[indxs] - smpl_p[indxs];
				double dw = d * smpl_w[indxs];
				sd += dw;
				sw += smpl_w[indxs];
			}
			sd /= sw;
			for (int indxs = 0; indxs < smpl_len;indxs++) if ((smpl_w[indxs] > 0.0) || full_plane) {
				smpl_p[indxs] += sd;
			}
			approx2d[0][2] += sd;
		}
		double [] rslt = {approx2d[0][2], approx2d[0][0],approx2d[0][1]}; // {center, tiltX, tiltY
		return rslt;
	}

	public double [] getDensity(
			final boolean strong_only,
			final int need_tiles,
			final int max_radius,
			final int dbg_x,
			final int dbg_y,
			final int debugLevel )
	{
		final int [][][] incr = {
				{{ 1,-1},{ 0, 1}},  // top right corner, going down
				{{ 1, 1},{-1, 0}},  // bottom right corner, going left
				{{-1, 1},{ 0,-1}},  // bottom left corner, going up
				{{-1,-1},{ 1, 0}}}; // top left corner, going right

		final TileNeibs  tnImage = biCamDSI.tnImage;
		final int dbg_tile = (debugLevel > -2)? (dbg_y * tnImage.sizeX + dbg_x):-1;
		final boolean [] en = strong_only ? strong_trusted: trusted;
    	final int num_tiles = en.length;
    	final double [] density = new double[num_tiles];
		final Thread[] threads = ImageDtt.newThreadArray(biCamDSI.threadsMax);
		final AtomicInteger ai = new AtomicInteger(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				@Override
				public void run() {
					for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) {
						if (nTile == dbg_tile) {
							System.out.println("getDensity(): nTile="+nTile);
						}
//						int num_tried = 0, num_found =0;
						int sd = 0, sdr2 = 0, s0=0, sr2 = 0;
						int dx = 0,dy = 0;
						int r = 0;
						label_snail: {
							for (; r <= max_radius; r++) {
								int nd = (r == 0) ? 1: 4;
								int nl = (r == 0) ? 1: (2 * r);
								for (int dir = 0; dir < nd; dir ++) {
									for (int l = 0; l < nl; l++) {
										dx = r * incr[dir][0][0] + l * incr[dir][1][0];
										dy = r * incr[dir][0][1] + l * incr[dir][1][1];
										int nTile1 = tnImage.getNeibIndex(nTile, dx,dy);
										if (nTile1 >= 0) {
											int r2 = dx*dx + dy*dy;
											s0++;
											sr2 += r2;
											if (en[nTile1]) {
												sd++;
												sdr2 += r2;
											}
//											num_found++;
											if (sd >= need_tiles) {
												break label_snail;
											}
										}
									}
								}
							}
						}
						int r02 = 2 * r * r;
						int num = r02 * sd - sdr2;
						int denom = r02 * s0 - sr2;
//						int num = sd;
//						int denom = s0;

						if (denom > 0) {
							density[nTile] = (1.0* num) / denom;
						}
					}
				}
			};
		}
		ImageDtt.startAndJoin(threads);
		return density;
	}

	/**
	 * Select low-textured tiles for averaging measurements
	 * @param min_disparity minimal disparity to accept
	 * @param max_density maximal trusted tile density (density varies from 0.0 to 1.0)
	 * @param grow how many layers of tiles should be added after filling gaps and removing small clusters
	 * @param max_gap_radius maximal radius of a void to be filled
	 * @param min_clust_radius minimal original cluster radius to survive
	 * @param density per-tile values of the density of trusted tiles around it.
	 * @param src_disparity - source disparity array. If null will only use density (that should be > 0)
	 * @return selection of the low-textured tiles to be processed with averaging correlation (3x3 or 5x5 tiles)
	 */
	public boolean [] selectLowTextures(
			double    min_disparity,
			double    max_density,
			int       grow,
			int       max_gap_radius,
			int       min_clust_radius,
			double [] density,
			double [] src_disparity)
	{
		boolean [] selection = new boolean [density.length];
		if (src_disparity == null) {
			for (int nTile = 0; nTile < selection.length; nTile++) {
				if ((density[nTile] <= max_density) && (density[nTile] <= max_density)) { // disparity has NaN-s, they will fail comparisons
					selection[nTile] = true;
				}
			}
		} else {
			for (int nTile = 0; nTile < selection.length; nTile++) {
				if ((src_disparity[nTile] >= min_disparity) && (density[nTile] <= max_density)) { // disparity has NaN-s, they will fail comparisons
					selection[nTile] = true;
				}
			}
		}
		final TileNeibs  tnImage = biCamDSI.tnImage;

		tnImage.growSelection(
				2* max_gap_radius, // int        grow,           // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
				selection, // boolean [] tiles,
				null); // boolean [] prohibit)

		tnImage.shrinkSelection(
				2*(max_gap_radius + min_clust_radius), // int        grow,           // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
				selection, // boolean [] tiles,
				null); // boolean [] prohibit)
		tnImage.growSelection(
				2 * (min_clust_radius + grow), // int        grow,           // grow tile selection by 1 over non-background tiles 1: 4 directions, 2 - 8 directions, 3 - 8 by 1, 4 by 1 more
				selection, // boolean [] tiles,
				null); // boolean [] prohibit)
		return selection;
	}


	/**
	 * Fill the gaps (where provided src_disparity is NaN) and optionally smooth it using pull to the weighted average of 8 neighbors
	 * @param src_disparity source disparity values, some may be undefined (Double.NaN)
	 * @param src_strength optional strengths of the initial values (should be with floor subtracted)
	 * @param selection tile selection to process
	 * @param neib_pull pull of the average neighbor weight relative to the original disparity value of a tile. If 0 - only gaps
	 *  (Double.NaN) values are filled, all the defined disparities remain as they were provided
	 * @param max_iterations Maximal number of the iteration steps
	 * @param min_change exit iterations when the maximal disparity change to a tile is less than this value
	 * @param dbg_x debug tile X coordinate
	 * @param dbg_y debug tile Y coordinate
	 * @param debugLevel debug level
	 * @return processed disparity and optional strength array. Normally only unselected tiles should remain Double.NaN, all other should be interpolated
	 */
	public double [][] fillAndSmooth(
			final double [] src_disparity,
			final double [] src_strength, // if not null will be used for weighted pull
			final boolean [] selection,
			final double     neib_pull, // pull to weighted average relative to pull to the original disparity value. If 0.0 - will only update former NaN-s
			final int max_iterations,
			final double min_change,
			final int dbg_x,
			final int dbg_y,
			final int debugLevel) {
		final double [] weights = {1.0, 0.7}; // {ortho, corners},
		final boolean adjust_all = (neib_pull > 0.0);
//		final double fneib_pull = adjust_all ? neib_pull: 1.0;
		final TileNeibs  tnImage = biCamDSI.tnImage;
//		final int dbg_tile = (debugLevel > -2)? (dbg_y * tnImage.sizeX + dbg_x):-1;
		final int dbg_tile = (debugLevel > 0)? (dbg_y * tnImage.sizeX + dbg_x):-1;
    	final int num_tiles = src_disparity.length;
		final Thread[] threads = ImageDtt.newThreadArray(biCamDSI.threadsMax);
		// max_changes may have Double.NaN value (here meaning larger than any)
		final double [] max_changes = new double [biCamDSI.threadsMax]; // Each thread provides its own maximal change, then they are combined
		final double [] disparity = src_disparity.clone();
		final double [] new_disparity = src_disparity.clone();
		final double [] strength = (src_strength != null)? src_strength: null; // new double [src_disparity.length];
		final double [] new_strength = (src_strength != null)? src_disparity.clone(): null;
//		if (src_strength == null) for (int i = 0; i < strength.length; i++) if (selection[i] && !Double.isNaN(src_disparity[i])) strength[i] = 1.0;
		final AtomicInteger ai = new AtomicInteger(0);
		final AtomicInteger ai_numThread = new AtomicInteger(0);
		for (int num_iter = 0; num_iter < max_iterations; num_iter++) {
			ai.set(0);
			ai_numThread.set(0);
			final AtomicInteger ai_count=new AtomicInteger(0);
			for (int i = 0; i < max_changes.length; i++) max_changes[i] = 0.0;
			final int fnum_iter = num_iter;
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					@Override
					public void run() {
						int numThread = ai_numThread.getAndIncrement(); // unique number of thread to write to max_changes[numThread]
//						max_changes[numThread]
						for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) if (selection[nTile]){
							if (nTile == dbg_tile) {
								System.out.println("fillAndSmooth(): iteration "+fnum_iter+" nTile="+nTile);
							}
							if (!adjust_all && !Double.isNaN(src_disparity[nTile])) {
// nothing to do - new_disparity and new_strength are already same as new_*
							} else {
								double sw=0.0, swd =0.0, sww = 0.0;


								for (int dir = 0; dir < 8; dir++) {
									int nTile1 = tnImage.getNeibIndex(nTile, dir);
									if (nTile1 >= 0) {
										double w = weights[dir & 1];
										double s = (strength == null) ? 1.0 : (Math.max(strength[nTile1], 0.0));
										double d = disparity[nTile1];
										double ww = w * s;
										if ((ww > 0.0) && !Double.isNaN(d)) {
											sw += w;
											sww += ww;
											swd += ww * d;
										}
									}
								}
								if (sww > 0.0) { // then sw
									double d_mean = swd/sww;
									double w_mean = sww/sw;
									if (Double.isNaN(src_disparity[nTile])) {
										new_disparity[nTile] =  d_mean;
										if (strength != null) {
											new_strength[nTile] = w_mean;
										}
									} else {
										double pull_origin = (strength==null)? 1.0: strength[nTile];
										new_disparity[nTile] =  (d_mean *(neib_pull * w_mean) + src_disparity[nTile] * pull_origin)/((neib_pull * w_mean) + pull_origin);
										if (strength != null) {
											new_strength[nTile] =  (w_mean * neib_pull + src_strength[nTile])/(neib_pull + 1);
										}
									}
									ai_count.getAndIncrement();
									double adiff = Math.abs(new_disparity[nTile] - disparity[nTile]); // disparity[nTile] may be NaN then adiff will be NaN as intended
									if (!(adiff < max_changes[numThread])) {
										max_changes[numThread] = adiff; // NaN will be copied
									}
								}
							}
						}
					}
				};
			}
			ImageDtt.startAndJoin(threads);
			System.arraycopy(new_disparity, 0, disparity, 0, num_tiles);
			if (strength != null) {
				System.arraycopy(new_strength, 0, strength, 0, num_tiles);
			}
			double change = 0.0;
			for (int i = 0; i < ai_numThread.get(); i++) {
				if (Double.isNaN(max_changes[i]) || (change < max_changes[i])) { // max_changes[i] may be NaN
					change = max_changes[i];
					if (!(change <= min_change)) { // change may be NaN
						if (debugLevel < -2) {
							// may exit here, continue to get debug info
							break;
						}
					}
				}
			}
			if (debugLevel > 0) {
				System.out.println("fillAndSmooth(): iteration "+fnum_iter+" change="+change+" (min_change="+min_change+")+ tiles updated="+ai_count.get());
			}
			if (change <= min_change) { // change may be NaN
				break; // from the main loop
			}
		}
		double [][] ds = {disparity,strength}; // strength may be null
		return ds;
	}
	/**
	 * Extend low texture areas horizontally if both ends are OK: either belong to the low texture areas selection (lt_sel)
	 * or are trusted and closer or the same (to the provided tolerance)
	 * @param tolerance strong limit should not have disparity lower by more than tolerance than low textured area
	 * @param ds_lt disparity/strength for the low textured area
	 * @param d_single disparity measured for the single-tile correlation
	 * @param lt_sel low -textured selection
	 * @param exp_sel expanded selection (does not intersect with lt_sel
	 * @param trusted trusted tiles selection
	 * @return extended disparity/strength data
	 */
	  public double [][] getLTExpanded(
			  final double      tolerance,
			  final double [][] ds_lt,
			  final double []   d_single,
			  final boolean []  lt_sel, // lt_sel and exp_sel do not intersect
			  final boolean []  exp_sel,
			  final boolean []  trusted)
	  {
//		final int dbg_tile = (debugLevel>-2)?(dbg_x + tnImage.sizeX*dbg_y):-1;
		  final int num_tiles = exp_sel.length;
		  final double [][] ds = new double [2][num_tiles];
		  for (int i = 0; i < num_tiles; i++) ds[0][i] = Double.NaN;
		  final Thread[] threads = ImageDtt.newThreadArray(biCamDSI.threadsMax);
		  final AtomicInteger ai = new AtomicInteger(0);
		  final TileNeibs         tnImage = biCamDSI.tnImage;
		  for (int ithread = 0; ithread < threads.length; ithread++) {
			  threads[ithread] = new Thread() {
				  @Override
				  public void run() {
					  //						max_changes[numThread]
					  for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement())  if (lt_sel[nTile]) {
						  // is low texture = - just copy
						  ds[0][nTile] = ds_lt[0][nTile];
						  ds[1][nTile] = ds_lt[1][nTile];
					  } else if (exp_sel[nTile]) {
						  int dbg_tileX = nTile%tnImage.sizeX;
						  int dbg_tileY = nTile/tnImage.sizeX;
//						  if ((dbg_tileY == 156) || (dbg_tileY == 157)) {
//							  System.out.println("getLTExpanded(): tileX="+dbg_tileX+", tileY="+dbg_tileY);
//							  System.out.println("getLTExpanded(): tileX="+dbg_tileX+", tileY="+dbg_tileY);
//						  }
						  int nTile0= tnImage.getNeibIndex(nTile,-1,0);
						  if ((nTile0 < 0) || !exp_sel[nTile0]){
							  boolean OK0 = (nTile0 < 0) || lt_sel[nTile0] || (trusted[nTile0] && (d_single[nTile0] >= (ds_lt[0][nTile] - tolerance)));
							  if (OK0) {
								  int nTile1= tnImage.getNeibIndex(nTile,1,0);
								  int l = 1;
								  while ((nTile1 >= 0) && exp_sel[nTile1]) {
									  nTile1= tnImage.getNeibIndex(nTile1,1,0);
									  l++;
								  }

								  boolean OK1 = (nTile1 < 0) || lt_sel[nTile1] || (trusted[nTile1] && (d_single[nTile1] >= (ds_lt[0][nTile1-1] - tolerance)));
								  if (OK1) {
									  for (int i = 0; i < l; i++) {
										  int nt = nTile+ i;
										  ds[0][nt] = ds_lt[0][nt];
										  ds[1][nt] = ds_lt[1][nt];
									  }
								  }
							  }
						  }
					  }
				  }
			  };
		  }
		  ImageDtt.startAndJoin(threads);
		  return ds;

	  }


}
