/**
 **
 ** MatchSimulatedPattern.java - Determine simulation pattern parameters to match
 ** the acquired image
 **
 ** Copyright (C) 2010-2014 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **  
 **  MatchSimulatedPattern.java is free software: you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation, either version 3 of the License, or
 **  (at your option) any later version.
 **
 **  This program is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ** -----------------------------------------------------------------------------**
 **
 */

import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import javax.swing.SwingUtilities;

import Jama.LUDecomposition;
import Jama.Matrix;  // Download here: http://math.nist.gov/javanumerics/jama/
import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.gui.GenericDialog;
import ij.gui.Roi;
import ij.process.FHT; // get rid, change to double
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;


public class MatchSimulatedPattern {
	private showDoubleFloatArrays SDFA_INSTANCE= new showDoubleFloatArrays(); // just for debugging?
	public int debugLevel=2;
	public int FFT_SIZE=256;
	public double [][][][] PATTERN_GRID=null; // global to be used with threads? TODO: Same as DIST_ARRAY - merge?
	public int [] minUV={0,0};
    public int [][] reMap=null;               // maps grid coordinates from laser pointers to PATTERN_GRID u,v (2x3 - rotation + translation) - SEEMS NOT USED!
    public int [] UVShiftRot={0,0,0}; // {shift U, shift V, rot (1 of 8 - 4 non-mirrored, 4 - mirrored}
    public int [][][] targetUV=null; // maps PATTERN_GRID cells to the target (absolute) UV
//    public double [][][] pixelsUV=null; // made of PATTERN_GRID, but does not have any wave vectors. Calculated during laser calibration
    public double [][][] pXYUV=null; // made of PATTERN_GRID, but does not have any wave vectors. Calculated during laser calibration
    public double [][][] gridContrastBrightness=null; //{grid contrast, grid intensity red, grid intensity green, grid intensity blue}[v][u]
	public Rectangle DIST_SELECTION=null;
	public int [] UV_INDEX=null; // array containing index of the pattern UV (scanline order, U first), or -1 for the areas with no pattern
	public int  UV_INDEX_WIDTH=0; // width of UV_INDEX (== full image, not selection, width)
	public int [] debugUV={-1,-1}; // debug the same cell on the second pass
	public int passNumber=0;
	public boolean [] correlationSizesUsed=null;
	public double [] gridFFCorr=null; // array matching greens with the flat field correction for the grid (zero outside of detected grid?)
	public double [] flatFieldForGrid=null; // array matching image pixels, divide the input pixels by these values (if not null)
	public boolean [] focusMask=null; // array matching image pixels, used with focusing (false outside sample areas)
	final private int [][][] rotations={
			   {{ 1, 0},{ 0, 1}}, // not mirrored
			   {{ 0, 1},{-1, 0}},
			   {{-1, 0},{ 0,-1}},
			   {{ 0,-1},{ 1, 0}},
			   
			   {{ 1, 0},{ 0,-1}}, // mirrored
			   {{ 0, 1},{ 1, 0}},
			   {{-1, 0},{ 0, 1}},
			   {{ 0,-1},{-1, 0}}};
	   // shifts when rotating around unknown center (make it white)
	final private int [][] dfltShifts={
			   {0,0},
			   {0,1},
			   {0,0},
			   {1,0},
			   {0,1},
			   {0,0},
			   {1,0},
			   {0,0}};
	final private int [][] combinedRotations={
			{0,	1,	2,	3,	4,	5,	6,	7},
			{1,	2,	3,	0,	7,	4,	5,	6},
			{2,	3,	0,	1,	6,	7,	4,	5},
			{3,	0,	1,	2,	5,	6,	7,	4},
			{4,	5,	6,	7,	0,	1,	2,	3},
			{5,	6,	7,	4,	3,	0,	1,	2},
			{6,	7,	4,	5,	2,	3,	0,	1},
			{7,	4,	5,	6,	1,	2,	3,	0}};

    public MatchSimulatedPattern (){ }
    public MatchSimulatedPattern (int fft_size){
    	this.FFT_SIZE=fft_size;
    }
// not real clone, just for threads - if there will be FFT - keep individual
    public MatchSimulatedPattern clone(){ // used in createPSFMap when creating threads
    	MatchSimulatedPattern msp=new MatchSimulatedPattern (this.FFT_SIZE);
// cloning should be thread safe, when using DoubleFHT - use individual instances     	
    	msp.debugLevel=this.debugLevel;
    	msp.PATTERN_GRID=this.PATTERN_GRID; // global to be used with threads? TODO: Same as DIST_ARRAY - merge?
//    	msp.DIST_ARRAY=this.DIST_ARRAY;
    	msp.DIST_SELECTION=this.DIST_SELECTION;
    	msp.UV_INDEX=this.UV_INDEX; // array containing index of the pattern UV (scanline order, U first), or -1 for the areas with no pattern
    	msp.UV_INDEX_WIDTH=this.UV_INDEX_WIDTH;
    	msp.reMap=this.reMap;
    	msp.UVShiftRot=this.UVShiftRot.clone();
//    public int [] UVShiftRot={0,0,0}; // {shift U, shift V, rot (1 of 8 - 4 non-mirrored, 4 - mirrored}
    	
    	msp.targetUV=this.targetUV; // 
    	msp.pXYUV=this.pXYUV;
    	msp.flatFieldForGrid=this.flatFieldForGrid;
    	msp.focusMask=this.focusMask;
    	return msp;
    }

    public MatchSimulatedPattern cloneDeep(
    		boolean clonePATTERN_GRID,
    		boolean cloneTargetUV,
    		boolean clonePixelsUV,
    		boolean cloneFlatFieldForGrid,
    		boolean cloneFocusMask
    		){ // used in createPSFMap when creating threads
    	MatchSimulatedPattern msp=new MatchSimulatedPattern (this.FFT_SIZE);
// cloning should be thread safe, when using DoubleFHT - use individual instances     	
    	msp.debugLevel=this.debugLevel;
//    	msp.PATTERN_GRID=this.PATTERN_GRID; // global to be used with threads? TODO: Same as DIST_ARRAY - merge?
    	if ( clonePATTERN_GRID && (msp.PATTERN_GRID!=null)){
    		msp.PATTERN_GRID=new double [this.PATTERN_GRID.length][this.PATTERN_GRID[0].length][][];
    		for (int i=0;i<this.PATTERN_GRID.length;i++) for (int j=0;j<this.PATTERN_GRID[i].length;j++) {
    			if (this.PATTERN_GRID[i][j]!=null){
    				msp.PATTERN_GRID[i][j]=new double [this.PATTERN_GRID[i][j].length][];
    				for (int k=0;k<this.PATTERN_GRID[i][j].length;k++){
    					if (this.PATTERN_GRID[i][j][k]!=null) msp.PATTERN_GRID[i][j][k]=this.PATTERN_GRID[i][j][k].clone();
    					else msp.PATTERN_GRID[i][j][k]=null;
    				}
    			} else msp.PATTERN_GRID[i][j]=null;
    		}
    	} else msp.PATTERN_GRID=this.PATTERN_GRID;
//    	msp.DIST_ARRAY=this.DIST_ARRAY;
    	if (this.DIST_SELECTION!=null) msp.DIST_SELECTION=new Rectangle(this.DIST_SELECTION);
    	else msp.DIST_SELECTION=null;
    	
    	if (this.UV_INDEX!=null) msp.UV_INDEX=this.UV_INDEX.clone(); // array containing index of the pattern UV (scanline order, U first), or -1 for the areas with no pattern
    	else msp.UV_INDEX=null;

    	msp.UV_INDEX_WIDTH=this.UV_INDEX_WIDTH;
    	
    	if (this.reMap!=null) { // probably not used
    		msp.reMap=new int [2][];
    		msp.reMap[0]=this.reMap[0].clone();
    		msp.reMap[1]=this.reMap[1].clone();
    	} else msp.reMap=null;
    	
    	msp.UVShiftRot=this.UVShiftRot.clone();
//     public int [] UVShiftRot={0,0,0}; // {shift U, shift V, rot (1 of 8 - 4 non-mirrored, 4 - mirrored}
//    	msp.targetUV=this.targetUV; 
    	if (cloneTargetUV && (this.targetUV != null)) {
    		msp.targetUV=new int [this.targetUV.length][this.targetUV[0].length][];
    		for (int i=0;i<this.targetUV.length;i++) for (int j=0;j<this.targetUV[i].length;j++){
    			if (this.targetUV[i][j]!=null) msp.targetUV[i][j]=this.targetUV[i][j].clone();
    			else msp.targetUV[i][j]=null;
    		}
    	} else msp.targetUV=this.targetUV;
    	
//    	msp.pixelsUV=this.pixelsUV;
    	
    	if (clonePixelsUV && (this.pXYUV != null)) {
    		msp.pXYUV=new double [this.pXYUV.length][this.pXYUV[0].length][];
    		for (int i=0;i<this.pXYUV.length;i++) for (int j=0;j<this.pXYUV[i].length;j++){
    			if (this.pXYUV[i][j]!=null) msp.pXYUV[i][j]=this.pXYUV[i][j].clone();
    			else msp.pXYUV[i][j]=null;
    		}
    	} else msp.targetUV=this.targetUV;
    	
//    	msp.flatFieldForGrid=this.flatFieldForGrid;
    	if (cloneFlatFieldForGrid && (this.flatFieldForGrid!=null)) msp.flatFieldForGrid=this.flatFieldForGrid.clone();
    	else msp.flatFieldForGrid=this.flatFieldForGrid;
    	
//		msp.focusMask=this.focusMask;
    	if (cloneFocusMask && (this.focusMask!=null)) msp.focusMask=this.focusMask.clone();
    	else msp.focusMask=this.focusMask;
    	return msp;
    }
    
    public int [] getUVShiftRot(boolean shift){
    	if (!shift) return this.UVShiftRot;
    	int [][] reReMap=getRemapMatrix(this.UVShiftRot);
    	int [] UVShiftRotCorr=this.UVShiftRot.clone();
    	UVShiftRotCorr[0]-=reReMap[0][0]*this.minUV[0]+reReMap[0][1]*this.minUV[1];
    	UVShiftRotCorr[1]-=reReMap[1][0]*this.minUV[0]+reReMap[1][1]*this.minUV[1];
    	System.out.println("getUVShiftRot(true): minUV[0]="+minUV[0]+" minUV[1]="+minUV[1]);
    	return UVShiftRotCorr;
    }
    
    public int [][] getRemapMatrix(
 		   int [] UVShiftRot){
/*        	   int [][] reReMap={
 			   {rotations[UVShiftRot[2]][0][0],rotations[UVShiftRot[2]][0][1],
 		       -(rotations[UVShiftRot[2]][0][0]*UVShiftRot[0] +rotations[UVShiftRot[2]][0][1]*UVShiftRot[1])},
 		       {rotations[UVShiftRot[2]][1][0],rotations[UVShiftRot[2]][1][1],
     		       -(rotations[UVShiftRot[2]][1][0]*UVShiftRot[0] +rotations[UVShiftRot[2]][1][1]*UVShiftRot[1])}};
*/
 	   // Moved shift calculation to calibrateGrid(), here just a regular R,T 2x3 matix
 	   int [][] reReMap={
 			   {rotations[UVShiftRot[2]][0][0],rotations[UVShiftRot[2]][0][1], UVShiftRot[0]},
 		       {rotations[UVShiftRot[2]][1][0],rotations[UVShiftRot[2]][1][1], UVShiftRot[1]}};
 	   return reReMap;
    }
    
    public int [] combineUVShiftRot(
 		   int [] UVShiftRotA,
 		   int [] UVShiftRotB){
 	   int [][] reReMapA=getRemapMatrix(UVShiftRotA);
 	   int [][] reReMapB=getRemapMatrix(UVShiftRotB);
 	   int [] UVShiftRot=new int [3];
 	   UVShiftRot[0] = reReMapB[0][0]*reReMapA[0][2]+ reReMapB[0][1]*reReMapA[1][2] + reReMapB[0][2];
 	   UVShiftRot[1] = reReMapB[1][0]*reReMapA[0][2]+ reReMapB[1][1]*reReMapA[1][2] + reReMapB[1][2];
 	   UVShiftRot[2] = combinedRotations[UVShiftRotA[2]][UVShiftRotB[2]];
 	   return UVShiftRot; 
    }
    
    public double focusQualityOld(
    		ImagePlus imp,
    		int fftSize,
    		int spotSize, //5
    		double x0,
    		double y0,
    		int debugLevel
    		){
    	int ix0=(int) Math.round(x0);
    	int iy0=(int) Math.round(y0);
    	Rectangle selection=new Rectangle(ix0-fftSize,iy0-fftSize,fftSize*2,fftSize*2);
    	double [][] pixels=splitBayer(imp, selection,true);
//    	SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"bayer");
		DoubleFHT fht_instance =new DoubleFHT(); // provide DoubleFHT instance to save on initializations (or null)
		double [] hamming1d=fht_instance.getHamming1d(fftSize);
		for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
			double sum=0.0;
			for (int i=0;i<pixels[c].length;i++) sum+=pixels[c][i];
			double average=sum/pixels[c].length;
			int index=0;
			for (int y=0;y<fftSize;y++)for (int x=0;x<fftSize;x++){
				pixels[c][index]=(pixels[c][index]-average)*hamming1d[y]*hamming1d[x];
				index++;
			}
		}
//    	SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"bayer-winowed");
		for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
			fht_instance.swapQuadrants(pixels[c]);
			fht_instance.transform(pixels[c]);
			pixels[c]=fht_instance.calculateAmplitude(pixels[c]);
		}
    	if (debugLevel>2) SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"amplitudes");
    	int [][][] spectrumMaximums=new int [pixels.length][][];
    	double [] spectralContrast=new double [pixels.length];
    	int hsize=fftSize/2;
    	int radius=spotSize/2; // 2
    	int [][]dirs={
    			{-1, 0},
    			{-1, 1},
    			{ 0, 1},
    			{ 1, 1},
    			{ 1, 0},
    			{ 1,-1},
    			{ 0,-1},
    			{-1,-1}};
		int lowLim= (fftSize*3)/8;
		int highLim=(fftSize*5)/8;
    	for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
    		spectrumMaximums[c]=new int [2][2];
    		double max=0.0;
			for (int y=lowLim;y<=fftSize/2;y++)for (int x=lowLim;x<highLim;x++){
				if ((y>=(hsize-radius)) && (x>=(hsize-radius)) && (x<=(hsize+radius))) continue; // do not count zero freq
				if (max<pixels[c][y*fftSize+x]) {
					max=pixels[c][y*fftSize+x];
					spectrumMaximums[c][0][0]=x;
					spectrumMaximums[c][0][1]=y;
				}
			}
    		max=0.0;
			for (int y=lowLim;y<=fftSize/2;y++)for (int x=lowLim;x<highLim;x++){
				if ((y>=(hsize-radius)) && (x>=(hsize-radius)) && (x<=(hsize+radius))) continue; // do not count zero freq
				if ((y>=(spectrumMaximums[c][0][1]-radius)) && (y<=(spectrumMaximums[c][0][1]+radius)) &&
						(x>=(spectrumMaximums[c][0][0]-radius)) && (x<=(spectrumMaximums[c][0][0]+radius))) continue; // do not count zero freq
				if ((y>=((fftSize-spectrumMaximums[c][0][1])-radius)) && (y<=((fftSize-spectrumMaximums[c][0][1])+radius)) &&
						(x>=((fftSize-spectrumMaximums[c][0][0])-radius)) && (x<=((fftSize-spectrumMaximums[c][0][0])+radius))) continue; // do not count zero first maximum
				if (max<pixels[c][y*fftSize+x]) {
					max=pixels[c][y*fftSize+x];
					spectrumMaximums[c][1][0]=x;
					spectrumMaximums[c][1][1]=y;
				}
			}
			if (debugLevel>2) System.out.println("spectrumMaximums["+c+"]="+(spectrumMaximums[c][0][0]-hsize)+":"+(spectrumMaximums[c][0][1]-hsize)+", "+
			""+(spectrumMaximums[c][1][0]-hsize)+":"+(spectrumMaximums[c][1][1]-hsize));
			double s1=0,s2=0;
			int [][] xy={
					{3*spectrumMaximums[c][0][0]-fftSize,3*spectrumMaximums[c][0][1]-fftSize},
					{3*spectrumMaximums[c][1][0]-fftSize,3*spectrumMaximums[c][1][1]-fftSize},
					{(spectrumMaximums[c][0][0]+spectrumMaximums[c][1][0])/2,(spectrumMaximums[c][0][1]+spectrumMaximums[c][1][1])/2},
					{(fftSize+spectrumMaximums[c][0][0]-spectrumMaximums[c][1][0])/2,(fftSize+spectrumMaximums[c][0][1]-spectrumMaximums[c][1][1])/2}};
//			if (debugLevel>3){ 
			if (debugLevel>2){ 
			System.out.println("xy[0][0]="+xy[0][0]+" xy[0][1]="+xy[0][1]);
			System.out.println("xy[1][0]="+xy[1][0]+" xy[1][1]="+xy[1][1]);
			System.out.println("xy[2][0]="+xy[2][0]+" xy[2][1]="+xy[2][1]);
			System.out.println("xy[3][0]="+xy[3][0]+" xy[3][1]="+xy[3][1]);
			}
			// make 3-rd harmonic frequency adjustment
			for (int n=0;n<2;n++) for (int i=0;i<2;i++){
				max=pixels[c][xy[i][1]*fftSize+xy[i][0]];
				int d=-1;
				for (int dir=0;dir<dirs.length;dir++) {
					double v=pixels[c][(xy[i][1]+dirs[dir][1])*fftSize+(xy[i][0]+dirs[dir][0])];
					if (max<v){
						max=v;
						d=dir;
					}
				}
				if (d>=0) {
					xy[i][0]+=dirs[d][0];
					xy[i][1]+=dirs[d][1];
				}
			}
			if (debugLevel>2) { 
			System.out.println("xy[0][0]="+xy[0][0]+" xy[0][1]="+xy[0][1]);
			System.out.println("xy[1][0]="+xy[1][0]+" xy[1][1]="+xy[1][1]);
			System.out.println("xy[2][0]="+xy[2][0]+" xy[2][1]="+xy[2][1]);
			System.out.println("xy[3][0]="+xy[3][0]+" xy[3][1]="+xy[3][1]);
			}
			for (int i=-radius;i<=radius;i++) for (int j=-radius;j<=radius;j++){
				s1+=pixels[c][(xy[0][1]+i)*fftSize+(xy[0][0]+j)];
				s1+=pixels[c][(xy[1][1]+i)*fftSize+(xy[1][0]+j)];
				s2+=pixels[c][(xy[2][1]+i)*fftSize+(xy[2][0]+j)];
				s2+=pixels[c][(xy[3][1]+i)*fftSize+(xy[3][0]+j)];
				spectralContrast[c]=Math.log(s1/s2);
			}
			if (debugLevel>2) System.out.println("spectrumContrast["+c+"]="+spectralContrast[c]);
    	} else{
    		spectrumMaximums[c]=null;
    		spectralContrast[c]=Double.NaN;
    	}
 	    return 0.25*(spectralContrast[0]+spectralContrast[1]+spectralContrast[2]+spectralContrast[3]);
    }
    public double focusQualityOld1(
    		ImagePlus imp,
    		int fftSize,
    		int spotSize, //5
    		double x0,
    		double y0,
    		int debugLevel
    		){
    	int ix0=(int) Math.round(x0);
    	int iy0=(int) Math.round(y0);
    	Rectangle selection=new Rectangle(ix0-fftSize,iy0-fftSize,fftSize*2,fftSize*2);
    	double [][] pixels=splitBayer(imp, selection,true);
//    	SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"bayer");
		DoubleFHT fht_instance =new DoubleFHT(); // provide DoubleFHT instance to save on initializations (or null)
		double [] hamming1d=fht_instance.getHamming1d(fftSize);
		for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
			double sum=0.0;
			for (int i=0;i<pixels[c].length;i++) sum+=pixels[c][i];
			double average=sum/pixels[c].length;
			int index=0;
			for (int y=0;y<fftSize;y++)for (int x=0;x<fftSize;x++){
				pixels[c][index]=(pixels[c][index]-average)*hamming1d[y]*hamming1d[x];
				index++;
			}
		}
//    	SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"bayer-winowed");
		for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
			fht_instance.swapQuadrants(pixels[c]);
			fht_instance.transform(pixels[c]);
			pixels[c]=fht_instance.calculateAmplitude(pixels[c]);
		}
		if (debugLevel>2) SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"amplitudes");
		/*
		DoubleGaussianBlur gb=new DoubleGaussianBlur();
		for (int i=0;i<pixels.length;i++)
			if (pixels[i]!=null) gb.blurDouble(
					pixels[i],
					fftSize,
					fftSize,
					2.0, // fixed sigma
					2.0,
					0.01);
		if (debugLevel>1) SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"amplitudes");
		*/
    	
    	int [][][] spectrumMaximums=new int [pixels.length][][];
    	double [] spectralContrast=new double [pixels.length];
    	int hsize=fftSize/2;
    	int radius=spotSize/2; // 2
    	int [][]dirs={
    			{-1, 0},
    			{-1, 1},
    			{ 0, 1},
    			{ 1, 1},
    			{ 1, 0},
    			{ 1,-1},
    			{ 0,-1},
    			{-1,-1}};
		int lowLim= (fftSize*3)/8;
		int highLim=(fftSize*5)/8;
    	for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
    		spectrumMaximums[c]=new int [2][2];
    		double max=0.0;
			for (int y=lowLim;y<=fftSize/2;y++)for (int x=lowLim;x<highLim;x++){
				if ((y>=(hsize-radius)) && (x>=(hsize-radius)) && (x<=(hsize+radius))) continue; // do not count zero freq
				if (max<pixels[c][y*fftSize+x]) {
					max=pixels[c][y*fftSize+x];
					spectrumMaximums[c][0][0]=x;
					spectrumMaximums[c][0][1]=y;
				}
			}
    		max=0.0;
			for (int y=lowLim;y<=fftSize/2;y++)for (int x=lowLim;x<highLim;x++){
				if ((y>=(hsize-radius)) && (x>=(hsize-radius)) && (x<=(hsize+radius))) {
					if ((debugLevel>2) && (c==0))System.out.println("x]="+x+" y="+y+" - to close to 0,0");
					continue; // do not count zero freq
				}
				if ((y>=(spectrumMaximums[c][0][1]-radius)) && (y<=(spectrumMaximums[c][0][1]+radius)) &&
						(x>=(spectrumMaximums[c][0][0]-radius)) && (x<=(spectrumMaximums[c][0][0]+radius))){
					if ((debugLevel>2) && (c==0))System.out.println("x="+x+" y="+y+" - too close to first max "+spectrumMaximums[c][0][0]+":"+spectrumMaximums[c][0][1]);
					continue; // do not count zero freq
				}
				if ((y>=((fftSize-spectrumMaximums[c][0][1])-radius)) && (y<=((fftSize-spectrumMaximums[c][0][1])+radius)) &&
						(x>=((fftSize-spectrumMaximums[c][0][0])-radius)) && (x<=((fftSize-spectrumMaximums[c][0][0])+radius))){
					if ((debugLevel>2) && (c==0))System.out.println("x="+x+" y="+y+" - too close to alias "+(fftSize-spectrumMaximums[c][0][0])+":"+(fftSize-spectrumMaximums[c][0][1]));
					continue; // do not count zero first maximum
				}
				if (max<pixels[c][y*fftSize+x]) {
					max=pixels[c][y*fftSize+x];
					spectrumMaximums[c][1][0]=x;
					spectrumMaximums[c][1][1]=y;
					if ((debugLevel>2) && (c==0))System.out.println("New max at x="+x+" y="+y+": "+max);
				}
			}
			if (debugLevel>2) System.out.println("spectrumMaximums["+c+"]="+(spectrumMaximums[c][0][0]-hsize)+":"+(spectrumMaximums[c][0][1]-hsize)+", "+
			""+(spectrumMaximums[c][1][0]-hsize)+":"+(spectrumMaximums[c][1][1]-hsize));
			double s1=0,s2=0;
			int [][] xy={
					{3*spectrumMaximums[c][0][0]-fftSize,3*spectrumMaximums[c][0][1]-fftSize},
					{3*spectrumMaximums[c][1][0]-fftSize,3*spectrumMaximums[c][1][1]-fftSize},
					{2*spectrumMaximums[c][0][0]-hsize,2*spectrumMaximums[c][0][1]-hsize},
					{2*spectrumMaximums[c][1][0]-hsize,2*spectrumMaximums[c][1][1]-hsize}};
//			if (debugLevel>3){ 
			if (debugLevel>2){ 
			System.out.println(" xy[0][0]="+xy[0][0]+" xy[0][1]="+xy[0][1]);
			System.out.println(" xy[1][0]="+xy[1][0]+" xy[1][1]="+xy[1][1]);
			System.out.println(" xy[2][0]="+xy[2][0]+" xy[2][1]="+xy[2][1]);
			System.out.println(" xy[3][0]="+xy[3][0]+" xy[3][1]="+xy[3][1]);
			}
			// make 3-rd  harmonic frequency adjustment
			for (int n=0;n<2;n++) for (int i=0;i<2;i++){
				max=pixels[c][xy[i][1]*fftSize+xy[i][0]];
				int d=-1;
				for (int dir=0;dir<dirs.length;dir++) {
					double v=pixels[c][(xy[i][1]+dirs[dir][1])*fftSize+(xy[i][0]+dirs[dir][0])];
					if (max<v){
						max=v;
						d=dir;
					}
				}
				if (d>=0) {
					xy[i][0]+=dirs[d][0];
					xy[i][1]+=dirs[d][1];
				}
			}
//			if (debugLevel>2) { 
			if (debugLevel>2) { 
			System.out.println("*xy[0][0]="+xy[0][0]+" xy[0][1]="+xy[0][1]);
			System.out.println("*xy[1][0]="+xy[1][0]+" xy[1][1]="+xy[1][1]);
			System.out.println("*xy[2][0]="+xy[2][0]+" xy[2][1]="+xy[2][1]);
			System.out.println("*xy[3][0]="+xy[3][0]+" xy[3][1]="+xy[3][1]);
			}
			for (int i=-radius;i<=radius;i++) for (int j=-radius;j<=radius;j++){
				s1+=pixels[c][(xy[0][1]+i)*fftSize+(xy[0][0]+j)];
				s1+=pixels[c][(xy[1][1]+i)*fftSize+(xy[1][0]+j)];
				s2+=pixels[c][(xy[2][1]+i)*fftSize+(xy[2][0]+j)];
				s2+=pixels[c][(xy[3][1]+i)*fftSize+(xy[3][0]+j)];
				spectralContrast[c]=Math.log(s1/s2);
			}
			if (debugLevel>2) System.out.println("spectrumContrast["+c+"]="+spectralContrast[c]);
    	} else{
    		spectrumMaximums[c]=null;
    		spectralContrast[c]=Double.NaN;
    	}
 	    return 0.25*(spectralContrast[0]+spectralContrast[1]+spectralContrast[2]+spectralContrast[3]);
    }

    public double focusQualityR2(
    		ImagePlus imp,
    		int fftSize,
    		int spotSize, //5
    		double x0,
    		double y0,
    		int debugLevel
    		){
    	int ix0=(int) Math.round(x0);
    	int iy0=(int) Math.round(y0);
    	Rectangle selection=new Rectangle(ix0-fftSize,iy0-fftSize,fftSize*2,fftSize*2);
    	double [][] pixels=splitBayer(imp, selection,true);
//    	SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"bayer");
		DoubleFHT fht_instance =new DoubleFHT(); // provide DoubleFHT instance to save on initializations (or null)
		double [] hamming1d=fht_instance.getHamming1d(fftSize);
		for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
			double sum=0.0;
			for (int i=0;i<pixels[c].length;i++) sum+=pixels[c][i];
			double average=sum/pixels[c].length;
			int index=0;
			for (int y=0;y<fftSize;y++)for (int x=0;x<fftSize;x++){
				pixels[c][index]=(pixels[c][index]-average)*hamming1d[y]*hamming1d[x];
				index++;
			}
		}
		// slightly blur the image to be sure there are no aliases in the low frequencies
		double preBlurSigma=1.5;
		DoubleGaussianBlur gb=new DoubleGaussianBlur();
		for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
			gb.blurDouble(
					pixels[c],
					fftSize,
					fftSize,
					preBlurSigma,
					preBlurSigma,
					0.01);

			
		}
//    	SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"bayer-winowed");
		for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
			fht_instance.swapQuadrants(pixels[c]);
			fht_instance.transform(pixels[c]);
			pixels[c]=fht_instance.calculateAmplitude(pixels[c]);
		}
		if (debugLevel>1) SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"amplitudes");
    	int [][][] spectrumMaximums=new int [pixels.length][][];
    	double [] spectralContrast=new double [pixels.length];
    	int hsize=fftSize/2;
    	int radius=spotSize/2; // 2
    	int [][]dirs={
    			{-1, 0},
    			{-1, 1},
    			{ 0, 1},
    			{ 1, 1},
    			{ 1, 0},
    			{ 1,-1},
    			{ 0,-1},
    			{-1,-1}};
		int lowLim= (fftSize*3)/8;
		int highLim=(fftSize*5)/8;
    	for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
    		spectrumMaximums[c]=new int [2][2];
    		double max=0.0;
			for (int y=lowLim;y<=fftSize/2;y++)for (int x=lowLim;x<highLim;x++){
				if ((y>=(hsize-radius)) && (x>=(hsize-radius)) && (x<=(hsize+radius))) continue; // do not count zero freq
				if (max<pixels[c][y*fftSize+x]) {
					max=pixels[c][y*fftSize+x];
					spectrumMaximums[c][0][0]=x;
					spectrumMaximums[c][0][1]=y;
				}
			}
    		max=0.0;
			for (int y=lowLim;y<=fftSize/2;y++)for (int x=lowLim;x<highLim;x++){
				if ((y>=(hsize-radius)) && (x>=(hsize-radius)) && (x<=(hsize+radius))) {
					if ((debugLevel>1) && (c==0))System.out.println("x]="+x+" y="+y+" - too close to 0,0");
					continue; // do not count zero freq
				}
				if ((y>=(spectrumMaximums[c][0][1]-radius)) && (y<=(spectrumMaximums[c][0][1]+radius)) &&
						(x>=(spectrumMaximums[c][0][0]-radius)) && (x<=(spectrumMaximums[c][0][0]+radius))){
					if ((debugLevel>1) && (c==0))System.out.println("x="+x+" y="+y+" - too close to first max "+spectrumMaximums[c][0][0]+":"+spectrumMaximums[c][0][1]);
					continue; // do not count zero freq
				}
				if ((y>=((fftSize-spectrumMaximums[c][0][1])-radius)) && (y<=((fftSize-spectrumMaximums[c][0][1])+radius)) &&
						(x>=((fftSize-spectrumMaximums[c][0][0])-radius)) && (x<=((fftSize-spectrumMaximums[c][0][0])+radius))){
					if ((debugLevel>1) && (c==0))System.out.println("x="+x+" y="+y+" - too close to alias "+(fftSize-spectrumMaximums[c][0][0])+":"+(fftSize-spectrumMaximums[c][0][1]));
					continue; // do not count zero first maximum
				}
				if (max<pixels[c][y*fftSize+x]) {
					max=pixels[c][y*fftSize+x];
					spectrumMaximums[c][1][0]=x;
					spectrumMaximums[c][1][1]=y;
					if ((debugLevel>1) && (c==0))System.out.println("New max at x="+x+" y="+y+": "+max);
				}
			}
			if (debugLevel>1) System.out.println("spectrumMaximums["+c+"]="+(spectrumMaximums[c][0][0]-hsize)+":"+(spectrumMaximums[c][0][1]-hsize)+", "+
			""+(spectrumMaximums[c][1][0]-hsize)+":"+(spectrumMaximums[c][1][1]-hsize));
//			double s1=0,s2=0;
			int [][] xy3={
					{3*spectrumMaximums[c][0][0]-fftSize,3*spectrumMaximums[c][0][1]-fftSize},
					{3*spectrumMaximums[c][1][0]-fftSize,3*spectrumMaximums[c][1][1]-fftSize}};
//			if (debugLevel>3){ 
			if ((c==0) && (debugLevel>1)){ 
			System.out.println(" xy3[0][0]="+xy3[0][0]+" xy3[0][1]="+xy3[0][1]);
			System.out.println(" xy3[1][0]="+xy3[1][0]+" xy3[1][1]="+xy3[1][1]);
			}
			// make 3-rd  harmonic frequency adjustment
			// May be improved by quadratic maximums
			for (int n=0;n<2;n++) for (int i=0;i<2;i++){
				max=pixels[c][xy3[i][1]*fftSize+xy3[i][0]];
				int d=-1;
				for (int dir=0;dir<dirs.length;dir++) {
					double v=pixels[c][(xy3[i][1]+dirs[dir][1])*fftSize+(xy3[i][0]+dirs[dir][0])];
					if (max<v){
						max=v;
						d=dir;
					}
				}
				if (d>=0) {
					xy3[i][0]+=dirs[d][0];
					xy3[i][1]+=dirs[d][1];
				}
			}
//			if (debugLevel>2) { 
			if ((c==0) && (debugLevel>1)) { 
				System.out.println("*xy3[0][0]="+xy3[0][0]+" xy3[0][1]="+xy3[0][1]);
				System.out.println("*xy3[1][0]="+xy3[1][0]+" xy3[1][1]="+xy3[1][1]);
			}
			double [][] xy={
					{(xy3[0][0]-hsize)/3.0,(xy3[0][1]-hsize)/3.0},
					{(xy3[1][0]-hsize)/3.0,(xy3[1][1]-hsize)/3.0}};
			if ((c==0) && (debugLevel>1)) { 
				System.out.println(" xy[0][0]="+IJ.d2s(xy[0][0],1)+" xy3[0][1]="+IJ.d2s(xy[0][1],1));
				System.out.println(" xy[1][0]="+IJ.d2s(xy[1][0],1)+" xy3[1][1]="+IJ.d2s(xy[1][1],1));
			}
// once more - correct 5-th:
			int [][] xy5={
					{hsize+ (int) Math.round(5*xy[0][0]),hsize+ (int) Math.round(5*xy[0][1])},
					{hsize+ (int) Math.round(5*xy[1][0]),hsize+ (int) Math.round(5*xy[1][1])}};
// just once			

			if ((c==0) && (debugLevel>1)) { 
				System.out.println(" xy5[0][0]="+xy5[0][0]+" xy5[0][1]="+xy5[0][1]);
				System.out.println(" xy5[1][0]="+xy5[1][0]+" xy5[1][1]="+xy5[1][1]);
			}
			for (int i=0;i<2;i++){
				max=pixels[c][xy5[i][1]*fftSize+xy5[i][0]];
				int d=-1;
				for (int dir=0;dir<dirs.length;dir++) {
					double v=pixels[c][(xy5[i][1]+dirs[dir][1])*fftSize+(xy5[i][0]+dirs[dir][0])];
					if (max<v){
						max=v;
						d=dir;
					}
				}
				if (d>=0) {
					xy5[i][0]+=dirs[d][0];
					xy5[i][1]+=dirs[d][1];
				}
			}
			if ((c==0) && (debugLevel>1)) { 
				System.out.println("*xy5[0][0]="+xy5[0][0]+" xy5[0][1]="+xy5[0][1]);
				System.out.println("*xy5[1][0]="+xy5[1][0]+" xy5[1][1]="+xy5[1][1]);
			}
			for (int i=0;i<2;i++) for (int j=0;j<2;j++) xy[i][j]=(xy5[i][j]-hsize)/5.0;
			if ((c==0) && (debugLevel>1)) { 
				System.out.println(" xy[0][0]="+IJ.d2s(xy[0][0],1)+" xy3[0][1]="+IJ.d2s(xy[0][1],1));
				System.out.println(" xy[1][0]="+IJ.d2s(xy[1][0],1)+" xy3[1][1]="+IJ.d2s(xy[1][1],1));
			}
			
			
// now generate mask and then blur it
			int maxMode=7; // 1, 3-rd and 5-th harmonics, should be odd (now 7-th included)
			double [][] dirNeg={{-0.5,-0.5},{0.5,-0.5},{-0.5,0.5},{0.5,0.5}}; // direction to negative mask
			double [] mask = new double [fftSize*fftSize];
			for (int i=0;i<mask.length;i++) mask[i]=0.0;
			for (int i=-maxMode;i<=maxMode;i+=2) for (int j=-maxMode;j<=maxMode;j+=2){
				double xpc=-0.5*(xy[0][0]*(i+j)+xy[1][0]*(i-j));
				double ypc=-0.5*(xy[0][1]*(i+j)+xy[1][1]*(i-j));
				double xp=xpc+hsize;
				double yp=ypc+hsize;
				double w=xpc*xpc+ypc*ypc; // increase weight of high frequencies
				int ixp=(int) Math.round(xp);
				int iyp=(int) Math.round(yp);
				if ((ixp<0) ||(ixp>=fftSize) || (iyp<0) ||(iyp>=fftSize)) continue;
				mask[iyp*fftSize+ixp]+=w;
				if ((c==0) && (debugLevel>1)) 	System.out.println(" xp="+IJ.d2s(xp,1)+" xm="+IJ.d2s(yp,1));
				for (int d=0;d<dirNeg.length;d++){
					double xm=xp+dirNeg[d][0]*xy[0][0]+dirNeg[d][1]*xy[1][0];
					double ym=yp+dirNeg[d][0]*xy[0][1]+dirNeg[d][1]*xy[1][1];
					int ixm=(int) Math.round(xm);
					int iym=(int) Math.round(ym);
					ixm=(ixm+fftSize)%fftSize;
					iym=(iym+fftSize)%fftSize;
					mask[iym*fftSize+ixm]-=w/dirNeg.length;
				}
			}
			double sigmaScale=0.2;
			double averageLength=Math.sqrt((xy[0][0]*xy[0][0] + xy[0][1]*xy[0][1] +xy[1][0]*xy[1][0] +xy[1][1]*xy[1][1])/2);
			if ((c==0) && (debugLevel>1)) SDFA_INSTANCE.showArrays(mask, fftSize, fftSize, "mask_color");
				gb.blurDouble(
						mask,
						fftSize,
						fftSize,
						sigmaScale*averageLength,
						sigmaScale*averageLength,
						0.01);
				if ((c==0) && (debugLevel>1)) SDFA_INSTANCE.showArrays(mask, fftSize, fftSize, "mask_color_blured"+IJ.d2s(sigmaScale*averageLength,3));
			double SFR2=0.0,SFP=0.0;
			for (int i=0;i<mask.length;i++){
				int x=(i % fftSize)-hsize;
				int y=(i / fftSize)-hsize;
				SFR2+=pixels[c][i]*mask[i]*(x*x+y*y);
				if (mask[i]>0) SFP+=pixels[c][i]*mask[i]; // sum only positive masks? Or abs value? Does not really matter, it is just a scale
			}
			spectralContrast[c]=Math.sqrt(SFR2/SFP)/averageLength;
			if ((c==0) && (debugLevel>1)) System.out.println("SFR2="+SFR2+" SFP="+SFP+" averageLength="+averageLength);
			if ( (debugLevel>1)) System.out.println("spectrumContrast["+c+"]="+spectralContrast[c]);
			
    	} else{
    		spectrumMaximums[c]=null;
    		spectralContrast[c]=Double.NaN;
    	}
// 	    return 0.25*(spectralContrast[0]+spectralContrast[1]+spectralContrast[2]+spectralContrast[3]);
 	    return 0.5*(spectralContrast[0]+spectralContrast[3]); // green only
    }
    public double focusQuality(
    		ImagePlus imp,
    		int fftSize,
    		int spotSize, //5
    		double x0,
    		double y0,
    		int debugLevel
    		){
    	int ix0=(int) Math.round(x0);
    	int iy0=(int) Math.round(y0);
    	Rectangle selection=new Rectangle(ix0-fftSize,iy0-fftSize,fftSize*2,fftSize*2);
    	double [][] pixels=splitBayer(imp, selection,true);
//    	SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"bayer");
		DoubleFHT fht_instance =new DoubleFHT(); // provide DoubleFHT instance to save on initializations (or null)
		double [] hamming1d=fht_instance.getHamming1d(fftSize);
		for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
			double sum=0.0;
			for (int i=0;i<pixels[c].length;i++) sum+=pixels[c][i];
			double average=sum/pixels[c].length;
			int index=0;
			for (int y=0;y<fftSize;y++)for (int x=0;x<fftSize;x++){
				pixels[c][index]=(pixels[c][index]-average)*hamming1d[y]*hamming1d[x];
				index++;
			}
		}
		// slightly blur the image to be sure there are no aliases in the low frequencies
		double preBlurSigma=1.5;
		DoubleGaussianBlur gb=new DoubleGaussianBlur();
		for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
			gb.blurDouble(
					pixels[c],
					fftSize,
					fftSize,
					preBlurSigma,
					preBlurSigma,
					0.01);

			
		}
//    	SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"bayer-winowed");
		for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
			fht_instance.swapQuadrants(pixels[c]);
			fht_instance.transform(pixels[c]);
			pixels[c]=fht_instance.calculateAmplitude(pixels[c]);
		}
		if (debugLevel>1) SDFA_INSTANCE.showArrays(pixels, fftSize, fftSize, true,"amplitudes");
    	int [][][] spectrumMaximums=new int [pixels.length][][];
    	double [] spectralContrast=new double [pixels.length];
    	int hsize=fftSize/2;
    	int radius=spotSize/2; // 2
    	int [][]dirs={
    			{-1, 0},
    			{-1, 1},
    			{ 0, 1},
    			{ 1, 1},
    			{ 1, 0},
    			{ 1,-1},
    			{ 0,-1},
    			{-1,-1}};
		int lowLim= (fftSize*3)/8;
		int highLim=(fftSize*5)/8;
    	for (int c=0;c<pixels.length;c++) if (pixels[c]!=null){
    		spectrumMaximums[c]=new int [2][2];
    		double max=0.0;
			for (int y=lowLim;y<=fftSize/2;y++)for (int x=lowLim;x<highLim;x++){
				if ((y>=(hsize-radius)) && (x>=(hsize-radius)) && (x<=(hsize+radius))) continue; // do not count zero freq
				if (max<pixels[c][y*fftSize+x]) {
					max=pixels[c][y*fftSize+x];
					spectrumMaximums[c][0][0]=x;
					spectrumMaximums[c][0][1]=y;
				}
			}
    		max=0.0;
			for (int y=lowLim;y<=fftSize/2;y++)for (int x=lowLim;x<highLim;x++){
				if ((y>=(hsize-radius)) && (x>=(hsize-radius)) && (x<=(hsize+radius))) {
					if ((debugLevel>1) && (c==0))System.out.println("x]="+x+" y="+y+" - too close to 0,0");
					continue; // do not count zero freq
				}
				if ((y>=(spectrumMaximums[c][0][1]-radius)) && (y<=(spectrumMaximums[c][0][1]+radius)) &&
						(x>=(spectrumMaximums[c][0][0]-radius)) && (x<=(spectrumMaximums[c][0][0]+radius))){
					if ((debugLevel>1) && (c==0))System.out.println("x="+x+" y="+y+" - too close to first max "+spectrumMaximums[c][0][0]+":"+spectrumMaximums[c][0][1]);
					continue; // do not count zero freq
				}
				if ((y>=((fftSize-spectrumMaximums[c][0][1])-radius)) && (y<=((fftSize-spectrumMaximums[c][0][1])+radius)) &&
						(x>=((fftSize-spectrumMaximums[c][0][0])-radius)) && (x<=((fftSize-spectrumMaximums[c][0][0])+radius))){
					if ((debugLevel>1) && (c==0))System.out.println("x="+x+" y="+y+" - too close to alias "+(fftSize-spectrumMaximums[c][0][0])+":"+(fftSize-spectrumMaximums[c][0][1]));
					continue; // do not count zero first maximum
				}
				if (max<pixels[c][y*fftSize+x]) {
					max=pixels[c][y*fftSize+x];
					spectrumMaximums[c][1][0]=x;
					spectrumMaximums[c][1][1]=y;
					if ((debugLevel>1) && (c==0))System.out.println("New max at x="+x+" y="+y+": "+max);
				}
			}
			if (debugLevel>1) System.out.println("spectrumMaximums["+c+"]="+(spectrumMaximums[c][0][0]-hsize)+":"+(spectrumMaximums[c][0][1]-hsize)+", "+
			""+(spectrumMaximums[c][1][0]-hsize)+":"+(spectrumMaximums[c][1][1]-hsize));
//			double s1=0,s2=0;
			int [][] xy3={
					{3*spectrumMaximums[c][0][0]-fftSize,3*spectrumMaximums[c][0][1]-fftSize},
					{3*spectrumMaximums[c][1][0]-fftSize,3*spectrumMaximums[c][1][1]-fftSize}};
//			if (debugLevel>3){ 
			if ((c==0) && (debugLevel>1)){ 
			System.out.println(" xy3[0][0]="+xy3[0][0]+" xy3[0][1]="+xy3[0][1]);
			System.out.println(" xy3[1][0]="+xy3[1][0]+" xy3[1][1]="+xy3[1][1]);
			}
			// make 3-rd  harmonic frequency adjustment
			// May be improved by quadratic maximums
			for (int n=0;n<2;n++) for (int i=0;i<2;i++){
				max=pixels[c][xy3[i][1]*fftSize+xy3[i][0]];
				int d=-1;
				for (int dir=0;dir<dirs.length;dir++) {
					double v=pixels[c][(xy3[i][1]+dirs[dir][1])*fftSize+(xy3[i][0]+dirs[dir][0])];
					if (max<v){
						max=v;
						d=dir;
					}
				}
				if (d>=0) {
					xy3[i][0]+=dirs[d][0];
					xy3[i][1]+=dirs[d][1];
				}
			}
//			if (debugLevel>2) { 
			if ((c==0) && (debugLevel>1)) { 
				System.out.println("*xy3[0][0]="+xy3[0][0]+" xy3[0][1]="+xy3[0][1]);
				System.out.println("*xy3[1][0]="+xy3[1][0]+" xy3[1][1]="+xy3[1][1]);
			}
			double [][] xy={
					{(xy3[0][0]-hsize)/3.0,(xy3[0][1]-hsize)/3.0},
					{(xy3[1][0]-hsize)/3.0,(xy3[1][1]-hsize)/3.0}};
			if ((c==0) && (debugLevel>1)) { 
				System.out.println(" xy[0][0]="+IJ.d2s(xy[0][0],1)+" xy3[0][1]="+IJ.d2s(xy[0][1],1));
				System.out.println(" xy[1][0]="+IJ.d2s(xy[1][0],1)+" xy3[1][1]="+IJ.d2s(xy[1][1],1));
			}
// once more - correct 5-th:
			int [][] xy5={
					{hsize+ (int) Math.round(5*xy[0][0]),hsize+ (int) Math.round(5*xy[0][1])},
					{hsize+ (int) Math.round(5*xy[1][0]),hsize+ (int) Math.round(5*xy[1][1])}};
// just once			

			if ((c==0) && (debugLevel>1)) { 
				System.out.println(" xy5[0][0]="+xy5[0][0]+" xy5[0][1]="+xy5[0][1]);
				System.out.println(" xy5[1][0]="+xy5[1][0]+" xy5[1][1]="+xy5[1][1]);
			}
			for (int i=0;i<2;i++){
				max=pixels[c][xy5[i][1]*fftSize+xy5[i][0]];
				int d=-1;
				for (int dir=0;dir<dirs.length;dir++) {
					double v=pixels[c][(xy5[i][1]+dirs[dir][1])*fftSize+(xy5[i][0]+dirs[dir][0])];
					if (max<v){
						max=v;
						d=dir;
					}
				}
				if (d>=0) {
					xy5[i][0]+=dirs[d][0];
					xy5[i][1]+=dirs[d][1];
				}
			}
			if ((c==0) && (debugLevel>1)) { 
				System.out.println("*xy5[0][0]="+xy5[0][0]+" xy5[0][1]="+xy5[0][1]);
				System.out.println("*xy5[1][0]="+xy5[1][0]+" xy5[1][1]="+xy5[1][1]);
			}
			for (int i=0;i<2;i++) for (int j=0;j<2;j++) xy[i][j]=(xy5[i][j]-hsize)/5.0;
			if ((c==0) && (debugLevel>1)) { 
				System.out.println(" xy[0][0]="+IJ.d2s(xy[0][0],1)+" xy3[0][1]="+IJ.d2s(xy[0][1],1));
				System.out.println(" xy[1][0]="+IJ.d2s(xy[1][0],1)+" xy3[1][1]="+IJ.d2s(xy[1][1],1));
			}
			
			
// now generate mask and then blur it
			int maxMode=7; // 1, 3-rd and 5-th harmonics, should be odd (now 7-th included)
			double [][] dirNeg={{-0.5,-0.5},{0.5,-0.5},{-0.5,0.5},{0.5,0.5}}; // direction to negative mask
			double [] mask = new double [fftSize*fftSize];
			for (int i=0;i<mask.length;i++) mask[i]=0.0;
			for (int i=-maxMode;i<=maxMode;i+=2) for (int j=-maxMode;j<=maxMode;j+=2){
				double xpc=-0.5*(xy[0][0]*(i+j)+xy[1][0]*(i-j));
				double ypc=-0.5*(xy[0][1]*(i+j)+xy[1][1]*(i-j));
				double xp=xpc+hsize;
				double yp=ypc+hsize;
				double w=xpc*xpc+ypc*ypc; // increase weight of high frequencies
				w*=w; // even sharper
				int ixp=(int) Math.round(xp);
				int iyp=(int) Math.round(yp);
				if ((ixp<0) ||(ixp>=fftSize) || (iyp<0) ||(iyp>=fftSize)) continue;
				mask[iyp*fftSize+ixp]+=w;
				if ((c==0) && (debugLevel>1)) 	System.out.println(" xp="+IJ.d2s(xp,1)+" xm="+IJ.d2s(yp,1));
				for (int d=0;d<dirNeg.length;d++){
					double xm=xp+dirNeg[d][0]*xy[0][0]+dirNeg[d][1]*xy[1][0];
					double ym=yp+dirNeg[d][0]*xy[0][1]+dirNeg[d][1]*xy[1][1];
					int ixm=(int) Math.round(xm);
					int iym=(int) Math.round(ym);
					ixm=(ixm+fftSize)%fftSize;
					iym=(iym+fftSize)%fftSize;
					mask[iym*fftSize+ixm]-=w/dirNeg.length;
				}
			}
			double sigmaScale=0.2;
			double averageLength=Math.sqrt((xy[0][0]*xy[0][0] + xy[0][1]*xy[0][1] +xy[1][0]*xy[1][0] +xy[1][1]*xy[1][1])/2);
			if ((c==0) && (debugLevel>1)) SDFA_INSTANCE.showArrays(mask, fftSize, fftSize, "mask_color");
				gb.blurDouble(
						mask,
						fftSize,
						fftSize,
						sigmaScale*averageLength,
						sigmaScale*averageLength,
						0.01);
				if ((c==0) && (debugLevel>1)){
					SDFA_INSTANCE.showArrays(mask, fftSize, fftSize, "mask_color_blured"+IJ.d2s(sigmaScale*averageLength,3));
					double [] ppixels=new double [fftSize*fftSize];
					for (int i=0;i<ppixels.length;i++) ppixels[i]=pixels[c][i]*mask[i];
					SDFA_INSTANCE.showArrays(ppixels, fftSize, fftSize, "masked-amplitude"+IJ.d2s(sigmaScale*averageLength,3));
				}
			double SFM=0.0,SF=0.0,SM=0;
			for (int i=0;i<mask.length;i++){
//				int x=(i % fftSize)-hsize;
//				int y=(i / fftSize)-hsize;
				SFM+=pixels[c][i]*mask[i];
				SF+=pixels[c][i];
				if (mask[i]>0) SM+=mask[i]; // sum only positive masks? Or abs value? Does not really matter, it is just a scale
				
			}
			int S0=fftSize*fftSize;
			spectralContrast[c]=S0*SFM/SF/SM;
			if ((c==0) && (debugLevel>1)) System.out.println("SFM="+(SFM/S0)+" SF="+(SF/S0)+" SM="+(SM/S0)+" averageLength="+averageLength);
			if ( (debugLevel>1)) System.out.println("spectrumContrast["+c+"]="+spectralContrast[c]);
			
    	} else{
    		spectrumMaximums[c]=null;
    		spectralContrast[c]=Double.NaN;
    	}
// 	    return 0.25*(spectralContrast[0]+spectralContrast[1]+spectralContrast[2]+spectralContrast[3]);
 	    return 0.5*(spectralContrast[0]+spectralContrast[3]); // green only
    }

    public void resetCorrelationSizesUsed(){
    	this.correlationSizesUsed=null;
    	
    }
    public void setCorrelationSizesUsed(int size){
    	int maxSize=20;
    	if (this.correlationSizesUsed==null) {
    		this.correlationSizesUsed=new boolean [maxSize];
    		for (int i=0;i<maxSize;i++) this.correlationSizesUsed[i]=false;
    	}
		int ln2;
		for (ln2=0;size>(1<<ln2); ln2++);
		if (size!=(1<<ln2)){
    		String msg="Not a power of 2 :"+ size;
    		IJ.showMessage("Error",msg);
    		throw new IllegalArgumentException (msg);
		}
		if (ln2>=maxSize) {
    		String msg="Too large array length, increase maxSize (it is now "+maxSize+", wanted "+ln2+")";
    		IJ.showMessage("Error",msg);
    		throw new IllegalArgumentException (msg);
		}
		this.correlationSizesUsed[ln2]=true;
    	
    }
	   public boolean [] getCorrelationSizesUsed(){
		   return this.correlationSizesUsed;
	   }

	/* returns array of 3 arrays: first two are 3-element wave vectors (x,y,phase), last - 3-rd order correction coefficients */
	public double[][] findPatternDistorted(double [][] bayer_pixels, // pixel array to process (no windowing!), two greens will be used
			PatternDetectParameters patternDetectParameters,
			boolean                                  greens,  // this is a pattern for combined greens (diagonal), adjust results accordingly
			String                                    title){ // title prefix to use for debug  images
		if (bayer_pixels==null) return null;
		if (bayer_pixels.length<4) return null;
		if (bayer_pixels[0]==null) return null;
		if (bayer_pixels[3]==null) return null;
		int size2=bayer_pixels[0].length;
		int size=(int) Math.sqrt(size2);
		int hsize=size/2;
		int hsize2=hsize*hsize;
		double [] quarterHamming=initWindowFunction(hsize, patternDetectParameters.gaussWidth);
		double [] patternCorr=new double[6]; // second order non-linear pattern correction (perspective+distortion)
		double [] green0= new double[hsize2];
		double [] green3= new double[hsize2];

		double [][]   quarter_pixels =new double[9][];
		double [][][] quarter_patterns =new double[9][][];
		int [] quarterIndex={0, // top left
				size/2, // top right
				size*size/2, // bottom left
				(size+1)*size/2, // bottom right
				(size+1)*size/4, // center
				size/4, // top
				size*size/4,// left
				(size+2)*size/4,   // right
				(2*size+1)*size/4};   // bottom

		int i,j,iq;
		int index,qindex;
		if (this.debugLevel>2) SDFA_INSTANCE.showArrays(bayer_pixels, size, size, title+"-bayer");
		for (iq=0; iq<9;iq++) {
			index=quarterIndex[iq];
			qindex=0;
			for (i=0;i<hsize;i++) {
				for (j=0;j<hsize;j++) { //quarter_pixels[iq][qindex++]=input_pixels[index++];
					green0[qindex]=  bayer_pixels[0][index];
					green3[qindex++]=bayer_pixels[3][index++];
				}
				quarter_pixels[iq]=combineDiagonalGreens (green0, green3,  hsize, hsize);
				index+=hsize; // jump to the next line
			}
			quarter_pixels[iq]= normalizeAndWindow (quarter_pixels[iq], quarterHamming);
			if (this.debugLevel>2) SDFA_INSTANCE.showArrays(quarter_pixels[iq],hsize, hsize, title+"-new"+iq);
			quarter_patterns[iq]   =findPattern(quarter_pixels[iq],
					hsize,
					patternDetectParameters,             
					greens,
					title+"Q_"+iq);

			if (quarter_patterns[iq]==null) return null;
		}
		if (this.debugLevel>2) {
			for (iq=0; iq<9;iq++) {
				System.out.println("Quarter="+iq+
						" W0x="+     IJ.d2s(quarter_patterns[iq][0][0],4)+
						" W0y="+     IJ.d2s(quarter_patterns[iq][0][1],4)+
						" W0_phase="+IJ.d2s(quarter_patterns[iq][0][2],2)+
						" W1x="+     IJ.d2s(quarter_patterns[iq][1][0],4)+
						" W1y="+     IJ.d2s(quarter_patterns[iq][1][1],4)+
						" W1_phase="+IJ.d2s(quarter_patterns[iq][1][2],2));
			}
		}
		/* Filter pattern coefficients to make sure they all match between quadrants (match to the center one)*/
		boolean patternsMatchedInitially=matchPatterns(quarter_patterns,quarter_patterns[4]);    // use center pattern
		if (this.debugLevel>2) {
			System.out.println(patternsMatchedInitially?"All quadrant wave vectors matched initially, no correction needed":"Some quadrant wave vectors were adjusted to match");
		}

		patternCorr=calcPatternNonLinear(quarter_patterns); // divide results by ,(FFT_SIZE/2)^2 - only first 5 patterns are used
		if (this.debugLevel>2) { /* increase LEVEL later */
			System.out.println("Pre- (1000x)   "+
					" Ax="+     IJ.d2s(1000*patternCorr[0]/(FFT_SIZE/2),5)+
					" Bx="+     IJ.d2s(1000*patternCorr[1]/(FFT_SIZE/2),5)+
					" Cx="+     IJ.d2s(1000*patternCorr[2]/(FFT_SIZE/2),5)+
					" Ay="+     IJ.d2s(1000*patternCorr[3]/(FFT_SIZE/2),5)+
					" By="+     IJ.d2s(1000*patternCorr[4]/(FFT_SIZE/2),5)+
					" Cy="+     IJ.d2s(1000*patternCorr[5]/(FFT_SIZE/2),5)+
					" Dx="+     IJ.d2s(1000*patternCorr[6],5)+
					" Ex="+     IJ.d2s(1000*patternCorr[7],5)+
					" Dy="+     IJ.d2s(1000*patternCorr[8],5)+
					" Ey="+     IJ.d2s(1000*patternCorr[9],5));
		}
		patternCorr=refinePatternNonLinear(quarter_patterns, // [tl,tr,bl,br, center][wv0, wv1][x,y,phase]
				patternCorr, //[ax,bx,cx,ay,by,cy]
				hsize ); // distance to quadrats center in sensor pixels ==FFT_SIZE/2



		//    for (i=0;i<patternCorr.length;i++)patternCorr[i]/= hsize;
		for (i=0;i<6;i++)patternCorr[i]/= hsize; /* Not linear Dx,Ex, Dy,Ey! */

		if (this.debugLevel>2) { /* increase LEVEL later */
			System.out.println("Corr (1000x)   "+
					" Ax="+     IJ.d2s(1000*patternCorr[0],5)+
					" Bx="+     IJ.d2s(1000*patternCorr[1],5)+
					" Cx="+     IJ.d2s(1000*patternCorr[2],5)+
					" Ay="+     IJ.d2s(1000*patternCorr[3],5)+
					" By="+     IJ.d2s(1000*patternCorr[4],5)+
					" Cy="+     IJ.d2s(1000*patternCorr[5],5)+
					" Dx="+     IJ.d2s(1000*patternCorr[6],5)+
					" Ex="+     IJ.d2s(1000*patternCorr[7],5)+
					" Dy="+     IJ.d2s(1000*patternCorr[8],5)+
					" Ey="+     IJ.d2s(1000*patternCorr[9],5));
		}

		double [][]result=new double [3][];
		result[0]=quarter_patterns[4][0].clone();
		result[1]=quarter_patterns[4][1].clone();
		result[2]=patternCorr.clone();
		return result;
	}
/* ======================================================================== */
	public  double [] correlationContrast (
			double [] pixels,       // square pixel array
			double [] widowedGreens, // array to normailze correlation result
			double [][] wVectors,   // wave vectors (same units as the pixels array)
//			double ringWidth,       // ring (around r=0.5 dist to opposite corr) width
			double  contrastSelectSigma, // Gaussian sigma to select correlation centers (fraction of UV period), 0.1
			double  contrastAverageSigma, // Gaussian sigma to average correlation variations (as contrast reference) 0.5
			
			double x0,              // center coordinates
			double y0,
			String title){
		// for now - just comparison, later - switch to
		return correlationContrast (
				pixels,       // square pixel array
				wVectors,   // wave vectors (same units as the pixels array)
				contrastSelectSigma, // Gaussian sigma to select correlation centers (fraction of UV period), 0.1
				x0,              // center coordinates
				y0,
				title, // title base for optional plots names
				this.debugLevel);		
/*		
		
		return correlationContrast (
				pixels,       // square pixel array
				widowedGreens,
				wVectors,   // wave vectors (same units as the pixels array)
//				ringWidth,       // ring (around r=0.5 dist to opposite corr) width
				contrastSelectSigma, // Gaussian sigma to select correlation centers (fraction of UV period), 0.1
				contrastAverageSigma, // Gaussian sigma to average correlation variations (as contrast reference) 0.5
				x0,              // center coordinates
				y0,
				title, // title base for optional plots names
				this.debugLevel);
*/				
	}
	public  double correlationContrastOld ( double [] pixels,       // square pixel array
			double [][] wVectors,   // wave vectors (same units as the pixels array)
			double ringWidth,       // ring (around r=0.5 dist to opposite corr) width
			double x0,              // center coordinates
			double y0,
			String title, // title base for optional plots names
			int debugLevel){
		int size=(int) Math.sqrt(pixels.length);
		double [] xy= new double [2];
		double [] uv; 
		double r2,d;
		int i,j;
/* opposite sign correlation points in uv are at uv=(0,-0.5),(0,0.5), (-0.5,0) and (0.5,0), with radius of (1/2)
    selecting center circle and a ring from 0.25 to 0.75 of the distance to opposite sign correlations */

		double r2WingsOuter= 0.0625*(1.0+ringWidth)*(1.0+ringWidth);
		double r2WingsInner= 0.0625*(1.0-ringWidth)*(1.0-ringWidth);
		double r2Center=0.0625*(ringWidth)*(ringWidth);
		if (debugLevel>2) System.out.println("rWingsOuter="+Math.sqrt(r2WingsOuter)+" rWingsInner="+Math.sqrt(r2WingsInner)+" rCenter="+Math.sqrt(r2Center));

		double valCenter=0.0;
		double valWings=0.0;
		double numCenter=0.0;
		double numWings=0.0;
		for (i=0;i<size;i++) {
			xy[1]=i-size/2-y0;
			for (j=0;j<size;j++) {
				xy[0]=j-size/2-x0;
				uv=matrix2x2_mul(wVectors,xy);
				r2=uv[0]*uv[0]+uv[1]*uv[1];
				if (r2<=r2WingsOuter) {
					d=pixels[i*size+j];
					if (r2<=r2Center){
						valCenter+=d*d;
						numCenter+=1.0;
					} else if (r2>r2WingsInner){
						valWings+=d*d;
						numWings+=1.0;
					}
				}
			}
		}
		if ((numWings==0.0) || (numCenter==0.0)) {
			if (debugLevel>1) System.out.println("Not enough data for correlation contrast: numCenter="+numCenter+" numWings="+numWings+
					" valCenter="+IJ.d2s(valCenter,2)+" valWings="+IJ.d2s(valWings,2));
			return -1.0;
		}
		double contrast=Math.sqrt((valCenter/numCenter)/(valWings/numWings));
		if (debugLevel>2) {
			System.out.println("Correlation contrast is "+contrast);
			double [] maskedPixels=new double[size*size];
			double [] u_value=new double[size*size];
			double [] v_value=new double[size*size];
			int index;
			for (i=0;i<size;i++) {
				xy[1]=i-size/2-y0;
				for (j=0;j<size;j++) {
					xy[0]=j-size/2-x0;
					uv=matrix2x2_mul(wVectors,xy);
					r2=uv[0]*uv[0]+uv[1]*uv[1];
					index=i*size+j;
					u_value[index]=uv[0];
					v_value[index]=uv[1];
					/*          r=Math.sqrt(r2);
          r-=Math.floor(r);
          floatPixels[index]=(float) r;*/
					if (((r2<=r2WingsOuter) && (r2>r2WingsInner)) || (r2<=r2Center)){
						maskedPixels[index]= pixels[index];
					} else {
						maskedPixels[index]=0.0;
					}
				}
			}
			double [][] dbgPixels={pixels,maskedPixels,u_value,v_value};
			String [] titles={"all","masked","u","v"};
			(new showDoubleFloatArrays()).showArrays(
					dbgPixels,
					size,
					size,
					true,
					title+"_CORR_MASK",
					titles);
		}
		return contrast;
	}

	public double [] correlationContrast (
			double [] pixels,       // square pixel array
//			double [] widowedGreens, // array to normailze correlation result
			double [][] wVectors,   // wave vectors (same units as the pixels array)
			double sigma,
			double x0,              // center coordinates
			double y0,
			String title, // title base for optional plots names
			int debugLevel){
		double [] badContrasts={-1.0,-1.0};
		double sigma32=9*sigma*sigma;
		double k=-0.5/(sigma*sigma);
		double [][] sampleCentersXY={{0.0,0.0},{0.25,0.25},{0.25,-0.20},{-0.25,0.25},{-0.25,-0.25}};
		int [] sampleTypes = {0,1,1,1,1}; 
		int size=(int) Math.sqrt(pixels.length);
		double [] xy= new double [2];
		double [] uv; 
		double r2;
		int i,j;
	
		double [] dbgMask= new double[size*size];
		for (int n=0;n<dbgMask.length;n++) dbgMask[n]=0.0;
		double [] s={0.0,0.0};
		double [] w={0.0,0.0};

		for (i=0;i<size;i++) {
			xy[1]=i-size/2-y0;
			for (j=0;j<size;j++) {
				int index=i*size+j;
				xy[0]=j-size/2-x0;
				uv=matrix2x2_mul(wVectors,xy);
				for (int np=0;np<sampleCentersXY.length;np++){
					double dx=uv[0]-sampleCentersXY[np][0];
					double dy=uv[1]-sampleCentersXY[np][1];
					r2=dx*dx+dy*dy;
					if (r2<sigma32){
						double m=Math.exp(k*r2);
						dbgMask[index]+=m;
						w[sampleTypes[np]]+=m;
						double d=m*pixels[index];
						if (sampleTypes[np]>0)d*=pixels[index]; // squared
						s[sampleTypes[np]]+=d;
					}
				}
			}
		}
		if ((w[0]==0.0) || (w[1]==0.0)) {
			if (debugLevel>1) System.out.println("Not enough data for correlation contrast: center - w[0]="+w[0]+" opposite - w[1]="+w[1]);
			return badContrasts;
		}
		double aCenter= s[0]/w[0];
		double aQuiet=Math.sqrt(s[1]/w[1]);
		double rContrast=aCenter/aQuiet;
		double aContrast=aCenter/size/size;
		
		double [] contrasts={rContrast,aContrast};
		if (debugLevel>2){
			System.out.println("correlationContrast() rContrast="+rContrast+" aContrast="+ aContrast+" aCenter="+aCenter+" aQuiet="+aQuiet+" w[0]="+w[0]+" w[1]="+w[1]+" s[0]="+s[0]+" s[1]="+s[1]);
		}
		if (debugLevel>2) {
			System.out.println("Correlation contrast is: relative="+rContrast+" absolute="+aContrast);
			double [][] dbgPixels={pixels,dbgMask};
			String [] titles={"all","mask"};
			(new showDoubleFloatArrays()).showArrays(
					dbgPixels,
					size,
					size,
					true,
					title+"_MASK",
					titles);
		}
		return contrasts;
	}
	public  double correlationContrastOld2 (
			double [] pixels,       // square pixel array
			double [] widowedGreens, // array to normailze correlation result
			double [][] wVectors,   // wave vectors (same units as the pixels array)
			double sigma,
			double sigmaNorm,       // to measure variations for normalization of the contrast
			double x0,              // center coordinates
			double y0,
			String title, // title base for optional plots names
			int debugLevel){
// TODO: make configurable parameters
//		double sigma=0.1;
//		double sigmaNorm=0.5; // to measure variations for normalization of the contrast

		double sigma32=9*sigma*sigma;
		double k=-0.5/(sigma*sigma);
		
		double sigmaNorm32=9*sigmaNorm*sigmaNorm;
		double kNorm=-0.5/(sigmaNorm*sigmaNorm);
		
		double [][] sampleCentersXY={{0.0,0.0},{0.0,0.5},{0.5,0.0},{0.0,-0.5},{-0.5,0.0}};
		int [] sampleTypes = {0,1,1,1,1}; 
		int size=(int) Math.sqrt(pixels.length);
		double [] xy= new double [2];
		double [] uv; 
		double r2;
		int i,j;
	
/* opposite sign correlation points in uv are at uv=(0,-0.5),(0,0.5), (-0.5,0) and (0.5,0), with radius of (1/2)
    selecting center circle and a ring from 0.25 to 0.75 of the distance to opposite sign correlations */
		double [] dbgMask= new double[size*size];
		for (int n=0;n<dbgMask.length;n++) dbgMask[n]=0.0;
		double [] s={0.0,0.0};
		double [] w={0.0,0.0};
		double S0=0.0,S1=0.0,S2=0.0;
		double SG1=0.0,SG2=0.0;
		// Find measured pixels variations in the window
		for (i=0;i<size;i++) {
			xy[1]=i-size/2-y0;
			for (j=0;j<size;j++) {
				int index=i*size+j;
				xy[0]=j-size/2-x0;
				uv=matrix2x2_mul(wVectors,xy);
				for (int np=0;np<sampleCentersXY.length;np++){
					double dx=uv[0]-sampleCentersXY[np][0];
					double dy=uv[1]-sampleCentersXY[np][1];
					r2=dx*dx+dy*dy;
					if (r2<sigma32){
						double m=Math.exp(k*r2);
						dbgMask[index]+=m;
						w[sampleTypes[np]]+=m;
						s[sampleTypes[np]]+=m*pixels[index];
					}
				}
				r2=uv[0]*uv[0]+uv[1]*uv[1];
				if (r2<sigmaNorm32){
					double m=Math.exp(kNorm*r2);
					S0+=m;
					S1+=m*pixels[index];
					S2+=m*pixels[index]*pixels[index];
					SG1=m*widowedGreens[index];
					SG2=m*widowedGreens[index]*widowedGreens[index];
				}
			}
		}
		if ((w[0]==0.0) || (w[1]==0.0)) {
			if (debugLevel>1) System.out.println("Not enough data for correlation contrast: center - w[0]="+w[0]+" opposite - w[1]="+w[1]);
			return -1.0;
		}
		double ref=Math.sqrt(S2*S0-S1*S1)/S0;
		double refG=Math.sqrt(SG2*S0-SG1*SG1)/S0;
		double contrast=((s[0]/w[0]) -(s[1]/w[1]))/ref;
		double contrastG=((s[0]/w[0]) -(s[1]/w[1]))/refG; ///size;
		if (debugLevel>2){
			System.out.println("correlationContrast() corr_diff="+(((s[0]/w[0]) -(s[1]/w[1])))+" contrast="+contrast+" w[0]="+w[0]+" w[1]="+w[1]+" s[0]="+s[0]+" s[1]="+s[1]);
			System.out.println("correlationContrast() S0="+S0+" S1="+S1+" S2="+S2+" ref="+ref);
			System.out.println("correlationContrast() contrastG="+contrastG+" S0="+S0+" SG1="+SG1+" SG2="+SG2+" refG="+refG);
		}
//		if (contrast>3.0){
//			System.out.println("correlationContrast() contrast="+contrast+" w[0]="+w[0]+" w[1]="+w[1]+" s[0]="+s[0]+" s[1]="+s[1]+" S0="+S0+" S1="+S1+" S2="+S2+" ref="+ref);
//		}
//		double contrast=Math.sqrt((s[0]/w[0]) /(s[1]/w[1]));
//		double contrast=((s[0]/w[0]) -(s[1]/w[1]))/(size*size);
		if (debugLevel>2) {
			System.out.println("Correlation contrast is "+contrast);
			double [][] dbgPixels={pixels,dbgMask};
			String [] titles={"all","mask"};
			(new showDoubleFloatArrays()).showArrays(
					dbgPixels,
					size,
					size,
					true,
					title+"_CORR_MASK",
					titles);
		}
		return contrast;
	}
	
	
	
/* ======================================================================== */
	public  double[] correlateWithModel (double [] imagePixels,  // measured pixel array
			double [] modelPixels,  // simulated (model) pixel array)
			double sigma,   // Sigma for high pass filtering TODO: implement!
			String title) { // title base for optional plots names

		if (imagePixels.length!=modelPixels.length) {
			IJ.showMessage("Error","Arrays have different sizes - imagePixels.length="+imagePixels.length+", modelPixels.length="+ modelPixels.length);
			return null;
		}
		int size = (int) Math.sqrt(imagePixels.length);
		ImageProcessor ip,ip_model;
		FHT fht,fht_model;
		double[][][] fft_complex,fft_model;
		int i,j;
		double a;

		float [] floatImagePixels=new float[size*size];
/* convert to float for image processor; */
		for (i=0;i<(size*size); i++) floatImagePixels[i]=(float) imagePixels[i];
		ip = new FloatProcessor(size,size);
		ip.setPixels(floatImagePixels);
		fht =  new FHT(ip);
		// Swapping quadrants, so the center will be 0,0
		fht.swapQuadrants();
		// get to frequency domain
		fht.transform();
		floatImagePixels=(float []) fht.getPixels();

		if((this.debugLevel>5) && (title!="")) {
			ImageProcessor ip_fht = new FloatProcessor(size,size);
			ip_fht.setPixels(floatImagePixels);
			ip_fht.resetMinAndMax();
			ImagePlus imp_fht= new ImagePlus(title+"_FHT_image", ip_fht);
			imp_fht.show();
		}
		// Convert from FHT to complex FFT
		fft_complex= FHT2FFTHalf (fht,size);
		float [] floatModelPixels=new float[size*size];
		// convert to float for image processor;
		for (i=0;i<(size*size); i++) floatModelPixels[i]=(float) modelPixels[i];
		ip_model = new FloatProcessor(size,size);
		ip_model.setPixels(floatModelPixels);
		fht_model =  new FHT(ip_model);
		// Swapping quadrants, so the center will be 0,0
		fht_model.swapQuadrants();
		// get to frequency domain
		fht_model.transform();
		floatModelPixels=(float []) fht_model.getPixels();
		if ((this.debugLevel>5) && (title!="")) {
			ImageProcessor ip_fht_model = new FloatProcessor(size,size);
			ip_fht_model.setPixels(floatModelPixels);
			ip_fht_model.resetMinAndMax();
			ImagePlus imp_fht_model= new ImagePlus(title+"_FHT_model", ip_fht_model);
			imp_fht_model.show();
		}
		// Convert from FHT to complex FFT
		fft_model= FHT2FFTHalf (fht_model,size);

		// multiply fft_complex by fft_nominator
		for (i=0;i<fft_complex.length; i++) for (j=0;j<fft_complex[0].length;j++) {
			a=                    fft_complex[i][j][0]*fft_model[i][j][0]+fft_complex[i][j][1]*fft_model[i][j][1]; // already changed Im() sign
			fft_complex[i][j][1]=-fft_complex[i][j][0]*fft_model[i][j][1]+fft_complex[i][j][1]*fft_model[i][j][0]; // already changed Im() sign
			fft_complex[i][j][0]=a;
		}   
/* Add sigma high=pass filtering here */
		// Convert fft array back to fht array and
		// set fht_target pixels with new values 
		fht.setPixels (floatFFTHalf2FHT (fft_complex,size));
		/// optionally show the result
		if ((this.debugLevel>5) && (title!="")) {
			ImageProcessor ip_fht2 = new FloatProcessor(size,size);
			ip_fht2.setPixels(floatFFTHalf2FHT (fft_complex,size));
			ip_fht2.resetMinAndMax();
			ImagePlus imp_fht2= new ImagePlus(title+"-corr-sigma"+sigma, ip_fht2);
			imp_fht2.show();
		}

		/// transform to space

		fht.inverseTransform();
		fht.swapQuadrants();
		fht.resetMinAndMax();

		//   ImagePlus imp= new ImagePlus(title, ip_fht);
		if ((this.debugLevel>2) && (title!="")) {
			ImagePlus imp_corr= new ImagePlus(title+"_Correlated_filt-"+sigma, fht);
			imp_corr.show();
		}
		//   return direct_target;
		floatImagePixels =(float[])fht.getPixels();
		double [] pixels=new double[floatImagePixels.length];
		for (i=0;i<floatImagePixels.length;i++) pixels[i]=floatImagePixels[i];
		return pixels;
	}
	
		
	/**
	Refining non-linear mesh matching by comparing phases in the centers of 4 quadrants and the very center.
	Can only compensate to a fraction of mesh period (TBD - total range, probably +/-half period),
	so non-linear coefficients should be already known to that precision
	9 measurements are used here - top-left, top-right,bottom-left, bottom-right, center, top, left, right,bottom

	 */
	private	double [] refinePatternNonLinear(double [][][] qp, // [tl,tr,bl,br, center][wv0, wv1][x,y,phase]
			double [] nonlin, //[ax,bx,cx,ay,by,cy]
			int size ) { // distance to quadrants center in sensor pixels ==FFT_SIZE/2

		int iq,i,j;
		double [][] xy=new double [qp.length][2];
		double [] uv= new double [2];
		double [] duv=new double [2];

		//  double [][][] wl=new double [5][2][2]; // Wave length vectors - same direction as wavevectors, length=distance between wavefronts
		double [][][] wp=new double [9][2][3]; // pattern vectors (with phase)
		double x1,y1;
		double xq=0;
		double yq=0;
		//probably only wl[4] is needed
		for (iq=0;iq<9;iq++) for (i=0;i<2;i++) for (j=0;j<2;j++) wp[iq]= waveVectorsToPatternVectors(qp[iq][0], qp[iq][1]);

		if (this.debugLevel>2) { /* increase LEVEL later */
			for (iq=0;iq<5;iq++){
				System.out.println(" wp["+iq+"][0][0]="+     IJ.d2s(wp[iq][0][0],4)+
						" wp["+iq+"][0][1]="+     IJ.d2s(wp[iq][0][1],4)+
						" wp["+iq+"][0][2]="+     IJ.d2s(wp[iq][0][2],4)+"("+ IJ.d2s(wp[iq][0][2]/2/Math.PI,4)+")"+
						" wp["+iq+"][1][0]="+     IJ.d2s(wp[iq][1][0],4)+
						" wp["+iq+"][1][1]="+     IJ.d2s(wp[iq][1][1],4)+
						" wp["+iq+"][1][2]="+     IJ.d2s(wp[iq][1][2],4)+"("+ IJ.d2s(wp[iq][1][2]/2/Math.PI,4)+")");
			}
		}
		for (iq=0;iq<9;iq++) {
			if (iq==4) continue; // nothing to calculate in the center
			switch (iq){
			case 0: xq=-1.0;yq=-1.0; break;
			case 1: xq= 1.0;yq=-1.0; break;
			case 2: xq=-1.0;yq= 1.0; break;
			case 3: xq= 1.0;yq= 1.0; break;
			case 4: xq= 0.0;yq= 0.0; break;
			case 5: xq= 0.0;yq=-1.0; break;
			case 6: xq=-1.0;yq= 0.0; break;
			case 7: xq= 1.0;yq= 0.0; break;
			case 8: xq= 0.0;yq= 1.0; break;
			}

			x1=size*(xq + nonlin[0]*xq*xq+ nonlin[1]*yq*yq+ 2* nonlin[2]*xq*yq +nonlin[6]*xq+ nonlin[7]*yq); // in pixels
			y1=size*(yq + nonlin[3]*xq*xq+ nonlin[4]*yq*yq+ 2* nonlin[5]*xq*yq +nonlin[8]*xq+ nonlin[9]*yq); // in pixels

			/* convert x1,y1 into wp vector coordiantes */
			uv[0]=(wp[4][1][1]*x1-wp[4][1][0]*y1)/(wp[4][0][0]*wp[4][1][1]-wp[4][0][1]*wp[4][1][0]); // wl in center vectors, not local !
			uv[1]=(wp[4][0][0]*y1-wp[4][0][1]*x1)/(wp[4][0][0]*wp[4][1][1]-wp[4][0][1]*wp[4][1][0]);
			/* Actually phases seem to be the same? */
			duv[0]=uv[0]-Math.round(uv[0])- (wp[iq][0][2]-wp[4][0][2])/(Math.PI*2);
			duv[1]=uv[1]-Math.round(uv[1])- (wp[iq][1][2]-wp[4][1][2])/(Math.PI*2);

			if (this.debugLevel>2) { /* increase LEVEL later */
				System.out.println("iq=  "+ iq+
						" x1="+     IJ.d2s(x1,4)+
						" y1="+     IJ.d2s(y1,4)+
						" uv[0]"+   IJ.d2s(uv[0],4)+
						" uv[1]"+   IJ.d2s(uv[1],4)+
						" duv[0]"+   IJ.d2s(duv[0],4)+
						" duv[1]"+   IJ.d2s(duv[1],4));
			}
			/* re-normalize phases to be +/- 0.5 (+/-PI) range */
			duv[0]=duv[0]-Math.round(duv[0]);
			duv[1]=duv[1]-Math.round(duv[1]);
			if (this.debugLevel>2) { /* increase LEVEL later */
				System.out.println("iq=  "+ iq+" ------- "+
						" duv[0]"+   IJ.d2s(duv[0],4)+	/* ======================================================================== */

						" duv[1]"+   IJ.d2s(duv[1],4));
			}
			/* Fix half period vertical/half period horizontal shift - is that needed?*/
			if (Math.abs(duv[0])>0.25) {
				duv[0]+=0.5;
				duv[1]+=0.5;
				duv[0]=duv[0]-Math.round(duv[0]);
				duv[1]=duv[1]-Math.round(duv[1]);
				if (this.debugLevel>2) {
					System.out.println("Correct phase shift >0.25 in quadrant "+ iq+ ", now"+
							" duv[0]"+   IJ.d2s(duv[0],4)+
							" duv[1]"+   IJ.d2s(duv[1],4));
				}
			}

			/* Verify here that phase adjustment is within range, fail otherwise */
			if ((Math.abs(duv[0])>0.5) || (Math.abs(duv[1])>0.5)) {
				if (this.debugLevel>0) {
					System.out.println("Error: in quadrant "+ iq+" - attempted to adjust phase too much (>+/- pi/2), keeping initial parematers");
				}
				return nonlin;
			}

			/* convert duv to x,y */
			xy[iq][0]=x1-wp[4][0][0]*duv[0]-wp[4][1][0]*duv[1];
			xy[iq][1]=y1-wp[4][0][1]*duv[0]-wp[4][1][1]*duv[1];


			if (this.debugLevel>2) { /* increase LEVEL later */
				System.out.println(" xy["+iq+"][0]="+   IJ.d2s(xy[iq][0],4)+
						" xy["+iq+"][1]="+   IJ.d2s(xy[iq][1],4));
			}
			/* convert xy to non-linear differences, remove pixels dimensions - quadrats +/- 1.0 */
			xy[iq][0]=(xy[iq][0]-size*xq)/size;
			xy[iq][1]=(xy[iq][1]-size*yq)/size;

			if (this.debugLevel>2) { /* increase LEVEL later */
				System.out.println("Quadrant "+iq+": Original non-linear difference was"+
						" x="+   IJ.d2s(nonlin[0]*xq*xq+ nonlin[1]*yq*yq+ nonlin[2]*xq*yq,4)+
						" y="+   IJ.d2s(nonlin[3]*xq*xq+ nonlin[4]*yq*yq+ nonlin[5]*xq*yq,4));
				System.out.println("Quadrant "+iq+": Refined  non-linear difference is"+
						" x="+   IJ.d2s(xy[iq][0],4)+
						" y="+   IJ.d2s(xy[iq][1],4));
			}

		}

		/* Do the refinement itself - recalculate coefficients minimizing errors in 5 points */
		/**

	    x1=size*(xq + nonlin[0]*xq*xq+ nonlin[1]*yq*yq+ 2* nonlin[2]*xq*yq +nonlin[6]*xq+ nonlin[7]*yq); // in pixels
	    y1=size*(yq + nonlin[3]*xq*xq+ nonlin[4]*yq*yq+ 2* nonlin[5]*xq*yq +nonlin[8]*xq+ nonlin[9]*yq); // in pixels
	  dx=AX*x^2 +BX*y^2 +2*CX*x*y + DX*x +EX *y
	  dy=AY*x^2 +BY*y^2 +2*CY*x*y + DY*x +EY *y

	  dx0= AX +BX +2*CX -DX -EX
	  dy0= AY +BY +2*CY -DY -EY

	  dx1= AX +BX -2*CX +DX -EX
	  dy1= AY +BY -2*CY +DY -EY

	  dx2= AX +BX -2*CX -DX +EX
	  dy2= AY +BY -2*CY -DY +EY

	  dx3= AX +BX +2*CX +DX +EX
	  dy3= AY +BY +2*CY +DY +EY

	  dx5=    +BX           -EX
	  dy5=    +BY           -EY

	  dx6= AX           -DX
	  dy6= AY           -DY

	  dx7= AX           +DX
	  dy7= AY           +DY

	  dx8=    +BX           +EX
	  dy8=    +BY           +EY

	//---------
	-(1)   dx0= AX +BX +2*CX -DX -EX
	-(2)   dx1= AX +BX -2*CX +DX -EX
	+(3)   dx2= AX +BX -2*CX -DX +EX
	+(4)   dx3= AX +BX +2*CX +DX +EX
	-(5)   dx5=    +BX           -EX
	(6)   dx6= AX           -DX
	(7)   dx7= AX           +DX
	+(8)   dx8=    +BX           +EX
	-(1)+(2)-(3)+(4)-(6)+(7) DX=()-dx0+dx1-dx2+dx3-dx6+dx7)/6
	-(1)-(2)+(3)+(4)-(5)+(8) EX=()-dx0-dx1+dx2+dx3-dx5+dx8)/6
	-(1)-(2)+(3)+(4)-(5)+(8) EY=()-dy0-dy1+dy2+dy3-dy5+dy8)/6
	-(1)+(2)-(3)+(4)-(6)+(7) DY=()-dy0+dy1-dy2+dy3-dy6+dy7)/6

	  AX+BX+2*CX = p1x= (dx0+dx3)/2
	  AY+BY+2*CY = p1y= (dy0+dy3)/2
	  AX+BX-2*CX = p2x= (dx1+dx2)/2
	  AY+BY-2*CY = p2y= (dy1+dy2)/2
	     BX      = p3x= (dx5+dx8)/2
	     BY      = p3y= (dy5+dy8)/2
	  AX         = p4x= (dx6+dx7)/2
	  AY         = p4y= (dy6+dy7)/2
	//minimizing sum of squares of errors:
	  CX= (p1x-p2x)/4
	  CY= (p1y-p2y)/4
	  AX= (3*p4x-2*p3x+p1x+p2x)/5
	  AY= (3*p4y-2*p3y+p1y+p2y)/5
	  BX= (3*p3x-2*p4x+p1x+p2x)/5
	  BY= (3*p3y-2*p4y+p1y+p2y)/5
		 */
		double p1x =(xy[0][0]+xy[3][0])/2;
		double p1y =(xy[0][1]+xy[3][1])/2;

		double p2x =(xy[1][0]+xy[2][0])/2;
		double p2y =(xy[1][1]+xy[2][1])/2;

		double p3x =(xy[5][0]+xy[8][0])/2;
		double p3y =(xy[5][1]+xy[8][1])/2;

		double p4x =(xy[6][0]+xy[7][0])/2;
		double p4y =(xy[6][1]+xy[7][1])/2;

		double [] rslt=new double[10];
		rslt[2]=(p1x-p2x)/4; //CX
		rslt[5]=(p1y-p2y)/4; //CY
		rslt[0]= (3*p4x-2*p3x+p1x+p2x)/5 ; //AX
		rslt[3]= (3*p4y-2*p3y+p1y+p2y)/5; // AY
		rslt[1]= (3*p3x-2*p4x+p1x+p2x)/5; // BX
		rslt[4]= (3*p3y-2*p4y+p1y+p2y)/5; // BY
		/*
	-(1)+(2)-(3)+(4)-(6)+(7) DX=(-dx0+dx1-dx2+dx3-dx6+dx7)/6
	-(1)-(2)+(3)+(4)-(5)+(8) EX=(-dx0-dx1+dx2+dx3-dx5+dx8)/6
	-(1)-(2)+(3)+(4)-(5)+(8) EY=(-dy0-dy1+dy2+dy3-dy5+dy8)/6
	-(1)+(2)-(3)+(4)-(6)+(7) DY=(-dy0+dy1-dy2+dy3-dy6+dy7)/6
		 */
		rslt[6]=(-xy[0][0]+xy[1][0]-xy[2][0]+xy[3][0]-xy[6][0]+xy[7][0])/6; // DX=(-dx0+dx1-dx2+dx3-dx6+dx7)/6
		rslt[7]=(-xy[0][0]-xy[1][0]+xy[2][0]+xy[3][0]-xy[5][0]+xy[8][0])/6; // EX=(-dx0-dx1+dx2+dx3-dx5+dx8)/6
		rslt[8]=(-xy[0][1]+xy[1][1]-xy[2][1]+xy[3][1]-xy[6][1]+xy[7][1])/6; // EY=(-dy0-dy1+dy2+dy3-dy5+dy8)/6
		rslt[9]=(-xy[0][1]-xy[1][1]+xy[2][1]+xy[3][1]-xy[5][1]+xy[8][1])/6; // DY=(-dy0+dy1-dy2+dy3-dy6+dy7)/6

		return rslt; // temporary
	}
	/* ======================================================================== */
	public double[][] findPattern(double [] input_pixels, // pixel array to process
			int size, // FFT size
			PatternDetectParameters patternDetectParameters,
			boolean greens, // this is a pattern for combined greens (diagonal), adjust results accordingly
			String title) { // title prefix to use for debug  images
		double [] pixels=input_pixels.clone();
		double [][]result=new double [2][3];
		//System.out.println("pixels.length="+pixels.length); //4096

		ImageProcessor ip, ip1;
		FHT fht, fht1;
		double[][][] fft_complex,fft_corr;
		double[][] fft_gamma;
		int i,j;
		double DCLevel=0.0;
		double a;
		float []floatPixels=new float[pixels.length];
		for (i=0;i<pixels.length; i++)  DCLevel+=pixels[i];
		DCLevel/=(size*size);
		for (i=0;i<pixels.length; i++)  pixels[i]-=DCLevel;
		// convert to float for image processor;
		for (i=0;i<pixels.length; i++) floatPixels[i]=(float) pixels[i];
		ip = new FloatProcessor(size,size);
		ip.setPixels(floatPixels);
		if (this.debugLevel>8) {
			ip.resetMinAndMax();
			ImagePlus imp_direct=  new ImagePlus(title+"_Direct_"+patternDetectParameters.corrGamma, ip);
			imp_direct.show();
		}
		fht =  new FHT(ip);
		// Swapping quadrants, so the center will be 0,0
		fht.swapQuadrants();
		// get to frequency domain
		fht.transform();
		if (this.debugLevel>5) {
			floatPixels=(float []) fht.getPixels();
			ImageProcessor ip_fht = new FloatProcessor(size,size);
			ip_fht.setPixels(floatPixels);
			ip_fht.resetMinAndMax();
			ImagePlus imp_fht= new ImagePlus(title+"_FHT", ip_fht);
			imp_fht.show();
		}

		// Convert from FHT to complex FFT
		fft_complex= FHT2FFTHalf (fht,size);
		// will need fft_complex  again later for later phase pattern measurements, calculate fft_gamma for correlation (pattern 2 frequencies measurement)
		fft_gamma=new double [size][size];
		floatPixels=new float[pixels.length];
		DCLevel=0.0;
		for (i=0;i<fft_complex.length; i++) for (j=0;j<fft_complex[0].length;j++) {
			fft_gamma[i][j]=Math.pow(fft_complex[i][j][0]*fft_complex[i][j][0]+fft_complex[i][j][1]*fft_complex[i][j][1],patternDetectParameters.corrGamma);
			DCLevel+=fft_gamma[i][j];
			floatPixels[i*size+j]=(float) fft_gamma[i][j];
		}
		DCLevel/=(fft_complex.length*fft_complex[0].length);
		for (i=0;i<fft_complex.length; i++) for (j=0;j<fft_complex[0].length;j++) {
			floatPixels[i*size+j]-=DCLevel;
			if ((i>0)&& (i<(size/2))){
				floatPixels[(size-i)*size+((size-j)%size)]=floatPixels[i*size+j];
			}
		}

		/* TODO:  maybe it is better to find the pattern frequencies just here, without converting back.
    After rejecting low frequencies, there seem to be just 2 nice maximums - easy to extract*/
		// now perform direct FFT of gamma(power spectrum)  
		ip1 = new FloatProcessor(size,size);
		ip1.setPixels(floatPixels);
		if (this.debugLevel>7) {
			ip1.resetMinAndMax();
			ImagePlus imp1=  new ImagePlus(title+"_gamma(ps)_"+patternDetectParameters.corrGamma, ip1);
			imp1.show();
		}
		fht1 =  new FHT(ip1);
		// Swapping quadrants, so the center will be 0,0
		fht1.swapQuadrants();
		fht1.transform();
		fft_corr= FHT2FFTHalf (fht1,size);
		double[] highPassFilter=new double[fft_complex[0].length];
		double expK=(patternDetectParameters.corrSigma>0)?(1.0/(2*patternDetectParameters.corrSigma*patternDetectParameters.corrSigma)):0.0;
		for (j=0;j<=fft_complex[0].length/2;j++) {
			highPassFilter[j]=(expK>0.0)?(1.0-Math.exp(-(expK*j*j))):1.0;
			if (j>0) highPassFilter[highPassFilter.length-j]=highPassFilter[j];
		}

		for (i=0;i<fft_complex.length; i++) for (j=0;j<fft_complex[0].length;j++) {
			fft_corr[i][j][0]=highPassFilter[i]*highPassFilter[j]*(fft_corr[i][j][0]*fft_corr[i][j][0]+fft_corr[i][j][1]*fft_corr[i][j][1]);
			fft_corr[i][j][1]=0.0;
		}

		// Convert fft array back to fht array and
		// set fht_target pixels with new values 
		fht1.setPixels (floatFFTHalf2FHT (fft_corr,size));   /* FIXME: - done, there is no difference as Im()==0 */
		/// optionally show the result
		if (this.debugLevel>7) {
			ImageProcessor ip_fht2 = new FloatProcessor(size,size);
			ip_fht2.setPixels(floatFFTHalf2FHT (fft_corr,size));
			ip_fht2.resetMinAndMax();
			ImagePlus imp_fht2= new ImagePlus(title+"_fht_corr_"+patternDetectParameters.corrGamma, ip_fht2);
			imp_fht2.show();
		}

		/// transform to space
		fht1.inverseTransform();
		floatPixels=(float []) fht1.getPixels();
		a=1/floatPixels[0];
		for (i=0; i<floatPixels.length; i++){
			floatPixels[i]*=a;
		}
		fht1.setPixels(floatPixels);

		//System.out.println("2:y="+y+" x="+x+" base_b="+base_b+" base="+base);

		fht1.swapQuadrants();

		if (this.debugLevel>2) {
			fht1.resetMinAndMax();
			ImagePlus imp_corr= new ImagePlus(title+"_corr_"+patternDetectParameters.corrGamma, fht1);
			imp_corr.show();
		}
		//   return direct_target;
		floatPixels =(float[])fht1.getPixels();
		for (i=0;i<floatPixels.length;i++) pixels[i]=floatPixels[i];


		int [][] max2OnSpectrum=  findFirst2MaxOnSpectrum (fft_complex, // complex, top half, starting from 0,0
				1,   // skip +- from (0,0) and previous max - add parameter to dialog?
				0.5); // 0.5 - 30deg. orthogonality of 2 vectors - 1.0 - perpendicular, 0.0 - parallel - add parameter to dialog?
		/**TODO:  get out on failure */
		if (max2OnSpectrum==null) {
			if (this.debugLevel>2){
				System.out.println("findPattern() 1: Failed to find a pattern");
				if (this.debugLevel>2){
					SDFA_INSTANCE.showArrays(input_pixels, "failed-findPattern-1-");
				}
			}
			return null;
		}
		/* Trying to filter out unreasonable maximums (if there is no pattern at all) */
		double maxFrequency=0.25*fft_complex.length;
		if ((Math.abs(max2OnSpectrum[0][0])>maxFrequency) ||
				(Math.abs(max2OnSpectrum[0][1])>maxFrequency) ||
				(Math.abs(max2OnSpectrum[1][0])>maxFrequency) ||
				(Math.abs(max2OnSpectrum[1][1])>maxFrequency)) {
			if (this.debugLevel>2) {
				System.out.println("Failed to detect pattern, as frequecy is above limit="+IJ.d2s(maxFrequency,2));
				System.out.println("Maximum 1 on spectrum:  x="+IJ.d2s(max2OnSpectrum[0][0],4)+" y="+IJ.d2s(max2OnSpectrum[0][1],4));
				System.out.println("Maximum 2 on spectrum:  x="+IJ.d2s(max2OnSpectrum[1][0],4)+" y="+IJ.d2s(max2OnSpectrum[1][1],4));
			}
			return null;
		}
		if (this.debugLevel>6) {
			System.out.println("Maximum 1 on spectrum:  x="+IJ.d2s(max2OnSpectrum[0][0],4)+" y="+IJ.d2s(max2OnSpectrum[0][1],4));
			System.out.println("Maximum 2 on spectrum:  x="+IJ.d2s(max2OnSpectrum[1][0],4)+" y="+IJ.d2s(max2OnSpectrum[1][1],4));
		}

		int [][] startPoints={{max2OnSpectrum[0][0]+max2OnSpectrum[1][0], max2OnSpectrum[0][1]+max2OnSpectrum[1][1]},
				{max2OnSpectrum[0][0]-max2OnSpectrum[1][0], max2OnSpectrum[0][1]-max2OnSpectrum[1][1]}};
		if (startPoints[1][1] <0) { /* startPoints[1][1] > 0 anyway */
			startPoints[1][0]= -startPoints[1][0];
			startPoints[1][1]= -startPoints[1][1];
		}
		if (this.debugLevel>2) {
			System.out.println("Predicted correlation maximum 1 from spectrum:  x="+IJ.d2s(startPoints[0][0],4)+" y="+IJ.d2s(startPoints[0][1],4));
			System.out.println("Predicted correlation maximum 2 from spectrum:  x="+IJ.d2s(startPoints[1][0],4)+" y="+IJ.d2s(startPoints[1][1],4));
		}

		double[][] max2=  findFirst2MaxOnCorrelation(
				pixels,
				startPoints,
				patternDetectParameters
		);

		/**TODO:  get out on failure */
		if (max2==null) {
			if (this.debugLevel>2){
				System.out.println("findPattern() 2: Failed to find a pattern");
				if (this.debugLevel>2){
					SDFA_INSTANCE.showArrays(input_pixels, "failed-findPattern-2-");
				}
				
			}
			return null;
		}
		/* these are combined greens, convert vectors to original pixel space) */
		if (greens) { 
			double [][] rotMatrix= {{1.0,-1.0},{1.0,1.0}};
			double [][] max2orig= matrix2x2_mul(max2,rotMatrix);
			for (i=0;i<2;i++) for (j=0;j<2;j++) result[i][j]=max2orig[i][j]; // result is [2][3], max2orig is [2][2]
			if (this.debugLevel>2) {
				System.out.println("Corrected to original pixels[0]  x="+IJ.d2s(result[0][0],4)+" y="+IJ.d2s(result[0][1],4));
				System.out.println("Corrected to original pixels[1]  x="+IJ.d2s(result[1][0],4)+" y="+IJ.d2s(result[1][1],4));
			}
		} else {
			for (i=0;i<2;i++) for (j=0;j<2;j++) result[i][j]=max2[i][j]; // result is [2][3], max2 is [2][2]
		}
		/* Calculate locations of the maximums on FFT (corresponding to the diagonals of the checkerboard pattern) */
		double [][] maxOnFFT = {{2*size*(max2[0][0]-max2[1][0]),2*size*(max2[0][1]-max2[1][1])},
				{2*size*(max2[0][0]+max2[1][0]),2*size*(max2[0][1]+max2[1][1])}};
		/*  We have only one half of the FFT data  so rotate 180-degrees around the center if the point is in the bottom half*/
		double [] maxPhases=new double[2];
		boolean [] invertPhaseSign={false,false};
		int maxIndex; // iterate through the two maximums on FFT for phase measurement
		int [][]      interpolateXY= new int [2][2]; 
		double [][]   interpolateKxy=new double [2][2];
		double [][][] interpolatePhases=new double [2][2][2];
		double [][][] interpolateAmplitudes=new double [2][2][2];// Maybe use it? if the amplitudes are very different?
		int ix,iy;
		boolean phaseCorr; // phase shift before averaging, to prevent rollover
		for (maxIndex=0;maxIndex<2;maxIndex++) {
			if (maxOnFFT[maxIndex][1]<0) {
				invertPhaseSign[maxIndex]=true;
				maxOnFFT[maxIndex][0]=-maxOnFFT[maxIndex][0];
				maxOnFFT[maxIndex][1]=-maxOnFFT[maxIndex][1];
			}
			interpolateXY [maxIndex][1] = (int) maxOnFFT[maxIndex][1];
			interpolateKxy[maxIndex][1] = maxOnFFT[maxIndex][1] - interpolateXY [maxIndex][1];
			if (maxOnFFT[maxIndex][0]<0) {
				interpolateXY  [maxIndex][0] = (int) (size+ maxOnFFT[maxIndex][0]);
				interpolateKxy [maxIndex][0] = (size+ maxOnFFT[maxIndex][0]) - interpolateXY[maxIndex][0];
			} else {
				interpolateXY  [maxIndex][0] = (int) maxOnFFT[maxIndex][0];
				interpolateKxy [maxIndex][0] = maxOnFFT[maxIndex][0] - interpolateXY[maxIndex][0];
			}
			for (j=0;j<2;j++) {
				ix=(interpolateXY[maxIndex][0]+j) % size;
				for (i=0;i<2;i++) {
					iy=interpolateXY[maxIndex][1]+i;
					interpolateAmplitudes[maxIndex][i][j]= Math.sqrt(fft_complex[iy][ix][0]*fft_complex[iy][ix][0]+fft_complex[iy][ix][1]*fft_complex[iy][ix][1]);
					interpolatePhases[maxIndex][i][j]= Math.atan2((invertPhaseSign[maxIndex]?-1.0:1.0)*fft_complex[iy][ix][1], fft_complex[iy][ix][0]);
					if (this.debugLevel>5) {
						System.out.println("maxIndex="+maxIndex+" ix="+ix+" iy="+iy+" phase="+IJ.d2s(interpolatePhases[maxIndex][i][j],4)+" amplitude="+interpolateAmplitudes[maxIndex][i][j]);
					}
				}
			}
			phaseCorr=false;
			if ((interpolatePhases[maxIndex][0][0]> Math.PI/2) ||
					(interpolatePhases[maxIndex][0][1]> Math.PI/2) ||
					(interpolatePhases[maxIndex][1][0]> Math.PI/2) ||
					(interpolatePhases[maxIndex][1][1]> Math.PI/2) ||
					(interpolatePhases[maxIndex][0][0]<-Math.PI/2) ||
					(interpolatePhases[maxIndex][0][1]<-Math.PI/2) ||
					(interpolatePhases[maxIndex][1][0]<-Math.PI/2) ||
					(interpolatePhases[maxIndex][1][1]<-Math.PI/2)) {
				phaseCorr=true;
				interpolatePhases[maxIndex][0][0]+= (interpolatePhases[maxIndex][0][0]<0)?Math.PI:-Math.PI;
				interpolatePhases[maxIndex][0][1]+= (interpolatePhases[maxIndex][0][1]<0)?Math.PI:-Math.PI;
				interpolatePhases[maxIndex][1][0]+= (interpolatePhases[maxIndex][1][0]<0)?Math.PI:-Math.PI;
				interpolatePhases[maxIndex][1][1]+= (interpolatePhases[maxIndex][1][1]<0)?Math.PI:-Math.PI;
				if (this.debugLevel>5) {
					System.out.println("Shifting phases by PI/2 before averaging (to avoid rollover)");
				}
			}

			maxPhases[maxIndex]=       interpolateKxy[maxIndex][1] *(interpolateKxy[maxIndex][0] * interpolatePhases[maxIndex][1][1] + (1.0-interpolateKxy[maxIndex][0])* interpolatePhases[maxIndex][1][0])+
			(1.0 - interpolateKxy[maxIndex][1])*(interpolateKxy[maxIndex][0] * interpolatePhases[maxIndex][0][1] + (1.0-interpolateKxy[maxIndex][0])* interpolatePhases[maxIndex][0][0]);
			if (phaseCorr) maxPhases[maxIndex]+=(maxPhases[maxIndex]<0)?Math.PI:-Math.PI;
			if (this.debugLevel>5) {
				System.out.println("kx="+IJ.d2s(interpolateKxy [maxIndex][0],4)+ " ky="+IJ.d2s(interpolateKxy [maxIndex][1],4));
			}
			if (this.debugLevel>2) {
				System.out.println("maxIndex="+maxIndex+" phase="+IJ.d2s(maxPhases[maxIndex],4));
			}
		}
		double [] checkerPhases= findCheckerPhases(max2, maxPhases); /* may be different for greens==true . No, the same */
		for (i=0;i<2;i++) result[i][2]=checkerPhases[i];
		if (this.debugLevel>2)  System.out.println();
		return result;
	}
	/* ======================================================================== */
	private	int [][] findFirst2MaxOnSpectrum (double [][][] pixels, // complex, top half, starting from 0,0
			/* May need to reduce the skip_around to be able to handle smaller number of pattern periods? Or re-try if failed? Guess somehow?*/
			int skip_around, // skip +- from (0,0) and previous max 
			double minOrtho) { // 0.5 - 30deg. orthogonality of 2 vectors - 1.0 - perpendicular, 0.0 - parallel
		int [][] max2={{0,0},{0,0}};
		double thisMax=0.0;
		int x,y,sx1,sx2;
		double p,a;
		/* find first point */
		for (y=0;y<pixels.length;y++) for (x=0;x<pixels[0].length; x++ ) {
			p=pixels[y][x][0]*pixels[y][x][0]+pixels[y][x][1]*pixels[y][x][1];
			if (p>thisMax) {
				if ((y<=skip_around) && ((x<=skip_around) || (x>=pixels[0].length-skip_around))) continue; /* too close to [0,0] */
				max2[0][0]=x;
				max2[0][1]=y;
				thisMax=p;
			}
		}
		thisMax=0.0;
		sx1=(max2[0][0]>(pixels[0].length/2))?(max2[0][0]-pixels[0].length):max2[0][0]; /* y is always positive here */
		/* find second point */
		// Maybe also check if it is a local maximum (not on the border with protected area (0, first point, y=0, ...) ?
		for (y=0;y<pixels.length;y++) for (x=0;x<pixels[0].length; x++ ) {
			p=pixels[y][x][0]*pixels[y][x][0]+pixels[y][x][1]*pixels[y][x][1];
			if (p>thisMax) {
				/* Is this a local maximum? */
				if (y>0) {
					if (                             p< (pixels[y-1][x  ][0]*pixels[y-1][x  ][0]+pixels[y-1][x  ][1]*pixels[y-1][x  ][1]))  continue;
					if ((x>0) &&                    (p< (pixels[y-1][x-1][0]*pixels[y-1][x-1][0]+pixels[y-1][x-1][1]*pixels[y-1][x-1][1]))) continue;
					if ((x<(pixels[0].length-1)) && (p< (pixels[y-1][x+1][0]*pixels[y-1][x+1][0]+pixels[y-1][x+1][1]*pixels[y-1][x+1][1]))) continue;
				}
				if (y< (pixels.length-1)) {
					if (                             p< (pixels[y+1][x  ][0]*pixels[y+1][x  ][0]+pixels[y+1][x  ][1]*pixels[y+1][x  ][1]))  continue;
					if ((x>0) &&                    (p< (pixels[y+1][x-1][0]*pixels[y+1][x-1][0]+pixels[y+1][x-1][1]*pixels[y+1][x-1][1]))) continue;
					if ((x<(pixels[0].length-1)) && (p< (pixels[y+1][x+1][0]*pixels[y+1][x+1][0]+pixels[y+1][x+1][1]*pixels[y+1][x+1][1]))) continue;
				}
				if ((x>0) &&                      (p< (pixels[y  ][x-1][0]*pixels[y  ][x-1][0]+pixels[y  ][x-1][1]*pixels[y  ][x-1][1]))) continue;
				if ((x<(pixels[0].length-1)) &&   (p< (pixels[y  ][x+1][0]*pixels[y  ][x+1][0]+pixels[y  ][x+1][1]*pixels[y  ][x+1][1]))) continue;
				if ((y<=skip_around) && ((x<=skip_around) || (x>=pixels[0].length-skip_around))) {
					if (this.debugLevel>5) {
						System.out.println("rejecting point ["+x+","+y+"] it is too close to [0,0]");
					}
					continue; /* too close to [0,0] */
				}

				if ((y<=skip_around) && ((x<=skip_around) || (x>=pixels[0].length-skip_around))) {
					if (this.debugLevel>5) {
						System.out.println("rejecting point ["+x+","+y+"] it is too close to [0,0]");
					}
					continue; /* too close to [0,0] */
				}
				if (((y<=(max2[0][1]+skip_around)) && (y>=(max2[0][1]-skip_around))) &&
						(((x<=(max2[0][0]+skip_around)) && (x>=(max2[0][0]-skip_around))) ||
								(x>=(max2[0][0]+pixels[0].length-skip_around)) || 
								(x<=(max2[0][0]-pixels[0].length+skip_around)))) {
					if (this.debugLevel>5) {
						System.out.println("rejecting point ["+x+","+y+"] as it is too close to the first one - ["+max2[0][0]+"("+sx1+"),"+max2[0][1]+"]");
					}
					continue; /* too close to first maximum */
				}
				sx2=(x>(pixels[0].length/2))?(x-pixels[0].length):x;
				a=(sx1*y -max2[0][1]*sx2);
				a=a*a/(sx1*sx1+max2[0][1]*max2[0][1])/(sx2*sx2+y*y);
				if (a < (minOrtho*minOrtho)) { /* vectors are too close to parallel */
					if (this.debugLevel>5) {
						System.out.println("rejecting point ["+x+"("+sx2+"),"+y+"] as the vector is too close to the first one - ["+max2[0][0]+"("+sx1+"),"+max2[0][1]+"]");
						System.out.println("pixels.length="+pixels.length+" pixels[0].length="+pixels[0].length);
					}
					continue;
				}
				max2[1][0]=x;
				max2[1][1]=y;
				thisMax=p;
			}
		}
		if (  thisMax==0.0) {
/*TODO: THat really happens on the real data */			
			System.out.println("Failed to find a second maximum"); 
			return null;
		}
		if (max2[0][0]>(pixels[0].length/2)) max2[0][0]-=pixels[0].length;
		if (max2[1][0]>(pixels[0].length/2)) max2[1][0]-=pixels[0].length;
		return max2;
	}
	/* ======================================================================== */
	/* Can it handle negative y if the refined maximum goes there? (maximal value on positive Y) */
	private double[][] findFirst2MaxOnCorrelation(double [] pixels,
			int [][] startPoints,
			PatternDetectParameters patternDetectParameters
	) {
		double reasonbleFrequency=2.0; // reject frequencies below that
		int size =(int) Math.sqrt (pixels.length);
		int [][] imax =startPoints.clone();
		int [][] imax2 =new int [2*patternDetectParameters.multiplesToTry][2];
		boolean  []maxDefined=new boolean [2*patternDetectParameters.multiplesToTry];

		double  [] maxValues =new double [startPoints.length];
		double  [] max2Values =new double [2];
		double [][] max2 =new double [2*patternDetectParameters.multiplesToTry][2];
		int lim=size/2-2; // safety measure
		int nmax=0;
		int x=1;
		int y=0;
		int indx;
		int [] dirs = {-1, -size-1, -size, -size+1, 1, size+1, size, size-1};

		boolean isMax;
		int i,j,k, xmn,xmx,ymn,ymx;
		int halfSize=size/2;
		double [] vlengths={Math.sqrt(startPoints[0][0]*startPoints[0][0]+startPoints[0][1]*startPoints[0][1]),
				Math.sqrt(startPoints[1][0]*startPoints[1][0]+startPoints[1][1]*startPoints[1][1])};
		boolean tooFar=false;
		int numVect;

		/* Look for the maximal values around startPoints (+/-diff_spectr_corr )*/
		for (nmax=0; nmax<startPoints.length; nmax++) {
			ymn=imax[nmax][1]-patternDetectParameters.diffSpectrCorr;
			ymx=imax[nmax][1]+patternDetectParameters.diffSpectrCorr; 
			if (ymx>lim) ymx=lim;
			xmn=imax[nmax][0]-patternDetectParameters.diffSpectrCorr;
			if (xmn<-lim) xmx=-lim;
			xmx=imax[nmax][0]+patternDetectParameters.diffSpectrCorr;
			if (xmx>lim) xmx=lim;
			indx=(size+1)*size/2 + imax[nmax][1] *size+imax[nmax][0];
			if ((Math.abs(imax[nmax][0])>lim) || (Math.abs(imax[nmax][1])>lim)) {
				if (this.debugLevel>2) {
					System.out.println("Bad start point  imax[0][0]="+imax[0][0]+" imax[0][1]="+imax[0][1]+" imax[1][0]="+imax[1][0]+" imax[1][1]="+imax[1][1]+ " lim="+lim);
				}
				return null;
			}
			//       if ((indx<0) || (indx>=pixels.length)) {
			//          System.out.println(" imax[0][0]="+imax[0][0]+" imax[0][1]="+imax[0][1]+" imax[1][0]="+imax[1][0]+" imax[1][1]="+imax[1][1]+ " lim="+lim);
			//       }
			maxValues[nmax]=pixels[indx];
			for (y=ymn;y<=ymx;y++) for (x=xmn;x<=xmx; x++) {
				indx=(size+1)*size/2 + y *size+x;
				if (pixels[indx]>maxValues[nmax]) {
					/* Make sure it is closer to this point than to any other or [0,0] */
					tooFar=false;
					for (numVect=0;numVect<startPoints.length;numVect++) {
						if (Math.abs((x-imax[nmax][0])*imax[numVect][0]+(y-imax[nmax][1])*imax[numVect][1])>0.5*vlengths[nmax]*vlengths[numVect]) {
							tooFar=true;
							break;
						}
					}
					if (tooFar) {
						if (this.debugLevel>5) {
							System.out.println("rejecting point ["+x+","+y+"] as the vector is closer to other max than ["+imax[nmax][0]+","+imax[nmax][0]+"]"+
									" in the (+/-) direction: ["+imax[numVect][0]+","+imax[numVect][0]+"]");
						}
						continue;
					}
					maxValues[nmax]=pixels[indx];
					imax[nmax][0]=x;
					imax[nmax][1]=y;
				}
			}
			/* Make sure the maximum in the scanned area is also a local maximum */
			isMax=true;
			indx=(size+1)*size/2 + imax[nmax][1] *size+imax[nmax][0];
			for (j=0;j<7;j++) if (pixels[indx]<pixels[indx+dirs[j]]) {
				isMax=false;
				break;
			}
			if (!isMax) {
				if (this.debugLevel>2) {
					System.out.println("This should not happen:");
					System.out.println("Maximum is not a local maximum - BUG or consider changing patternDetectParameters.diffSpectrCorr="+patternDetectParameters.diffSpectrCorr);
					System.out.println("point #"+(nmax+1)+" (of 2), x0="+startPoints[nmax][0]+" y0="+startPoints[nmax][1]+ " x="+imax[nmax][0]+" y="+imax[nmax][1]);
				}
				/* Maybe return from here with null?*/
				while (!isMax && (indx>0) && (indx>pixels.length)) {
					isMax=true;
					for (j=0;j<7;j++) if (pixels[indx]<pixels[indx+dirs[j]]) {
						isMax=false;
						indx+=dirs[j];
						imax[nmax][1]=(indx / size) - halfSize;
						imax[nmax][1]=(indx % size) - halfSize;
						break;
					}
				}
				if (!isMax) {
					if (this.debugLevel>2) {
						System.out.println("Maximum still not reached, bailing out");
						System.out.println("point #"+(nmax+1)+" (of 2), x0="+startPoints[nmax][0]+" y0="+startPoints[nmax][1]+ " x="+imax[nmax][0]+" y="+imax[nmax][1]);
					}
					return null;
				} else {
					if (this.debugLevel>2) {
						System.out.println("point #"+(nmax+1)+" (of 2), corrected local maximum is  x="+imax[nmax][0]+" y="+imax[nmax][1]);
					}
				}

			}
		}
		/* Sort maximums so first vector to second vector will be clockwise (positive y is downwards) */
		j=0; k=1;
		if ((imax[j][0]*imax[k][1]-imax[k][0]*imax[j][1])<0) {
			j=1;k=0;
		}
		imax2[0][0]=imax[j][0];
		imax2[0][1]=imax[j][1];
		imax2[1][0]=imax[k][0];
		imax2[1][1]=imax[k][1];


		/* Now define maximal radius of cluster (~0.7 of the average distance from 0,0 to the 2 start points */
		int maxX2Y2=0;
		for (i=0;i<2;i++) for (j=0;j<2;j++) maxX2Y2+=imax2[i][j]*imax2[i][j];
		maxX2Y2/=4;
		//System.out.println("maxX2Y2="+maxX2Y2);

		for (i=0;i<2*patternDetectParameters.multiplesToTry;i++)  maxDefined[i]=(i<2);

		nmax=2*patternDetectParameters.multiplesToTry; /* but only the first two are known by now */
		for (i=0;i<2;i++) {
			if (this.debugLevel>5) System.out.println("i="+i+" x="+imax2[i][0]+" y="+imax2[i][1]+" value="+max2Values[i]);
		}

		int clusterNumber;
		List <Integer> pixelList=new ArrayList<Integer>(100);
		Integer Index, NewIndex, HillIndex;
		double cx,cy,cm,minInCluster,f;

		int []clusterMap=new int[pixels.length];
		for (i=0;i<clusterMap.length;i++) clusterMap[i]=0; /// 0 - unused, -1 - "do not use"
		int listIndex;
		boolean noHills;
		int clusterSize;
		boolean isLocalMax;
		int pair;
		for (clusterNumber=0;clusterNumber<nmax;clusterNumber++) {
			pair=clusterNumber/2+1;
			if (!maxDefined[clusterNumber] && maxDefined[clusterNumber-2]) {
				/* We do not know the seed for this maximum, but the previous (of the same direction) may be known */
				x= (int) (max2[clusterNumber-2][0]*pair)/(pair-1);
				y= (int) (max2[clusterNumber-2][1]*pair)/(pair-1);
				if ((x>(-lim+1)) && (x<(lim-1)) && (y>(-lim+1)) && (y<(lim-1))) {
					Index=(y+halfSize)*size + x+halfSize;
					/* there should be local maximum not more than "deviationSteps" steps from the x,y */
					isLocalMax=false;
					i=patternDetectParameters.deviationSteps;
					while ((i>0) && !isLocalMax) {
						isLocalMax=true;
						for (j=0;j<dirs.length;j++) {
							NewIndex=Index+dirs[j];
							if ((NewIndex>=0) && (NewIndex<clusterMap.length) && (pixels[NewIndex]>pixels[Index])) {
								isLocalMax=false;
								Index=NewIndex;
								i--;
								if (this.debugLevel>5) System.out.println("i="+i+" x="+((Index % size) - halfSize)+" y="+((Index / size) - halfSize)+" value="+pixels[Index]);
								break;
							}
						}
					}
					if (isLocalMax && (clusterMap[Index]==0)) { // not yet used
						imax2[clusterNumber][0]= (Index % size) - halfSize;
						imax2[clusterNumber][1]= (Index / size) - halfSize;
						maxDefined[clusterNumber]=true;   
					}
				}
			}
			if (maxDefined[clusterNumber]) { // skip if seed for the cluster is not defined
				/* Grow cluster around maximum, find centroid */
				Index=(imax2[clusterNumber][1]+halfSize)*size + imax2[clusterNumber][0]+halfSize;
				pixelList.clear();
				pixelList.add (Index);
				clusterMap[Index]=clusterNumber+1;
				listIndex=0;
				while (listIndex<pixelList.size() ) {
					Index=pixelList.get(listIndex++);
					for (j=0;j<dirs.length;j++) {
						NewIndex=Index+dirs[j];
						if ((NewIndex>=0) && (NewIndex<clusterMap.length) && (clusterMap[NewIndex]==0) && (pixels[NewIndex]<pixels[Index])) {
							/* did we get too far?*/           
							y=(NewIndex/size) - halfSize - imax2[clusterNumber][1];
							x=(NewIndex % size) - halfSize - imax2[clusterNumber][0];
							//System.out.println(" dy="+y+" dx="+x+" dx*dx+dy*dy="+(x*x+y*y));
							if ((x*x+y*y) <= maxX2Y2) {
								/* See if there is any neighbor of the new pixel that is higher and not yet marked (prevent rivers flowing between hills) */
								noHills=true;
								for (k=0;k<dirs.length;k++) {
									HillIndex=NewIndex+dirs[k];
									if ((HillIndex>=0) && (HillIndex<clusterMap.length) && (clusterMap[HillIndex]!=(clusterNumber+1)) && (pixels[HillIndex]>pixels[NewIndex])) {
										noHills=false;
										break;
									}
								}
								if (noHills) {
									pixelList.add (NewIndex);
									clusterMap[NewIndex]=clusterNumber+1;
									//System.out.println("NewIndex="+NewIndex+" y="+(NewIndex/size - halfSize)+" x="+((NewIndex % size) - halfSize)+" new pixel="+pixels[NewIndex]+" old pixel="+pixels[Index]);
								}
							}
						}
					}
				}
				/* Shrink clusters to a fraction of initial size */
				// TODO: shring to a value (between min and max) if there is a sharp maximum??
				//, double patternDetectParameters.shrinkClusters
				if (patternDetectParameters.shrinkClusters==0.0) { // use "smart" size
					clusterSize=(int) Math.sqrt(5* pixelList.size()); // use proportional size
				} else if (patternDetectParameters.shrinkClusters<0) {
					clusterSize=(int)(- patternDetectParameters.shrinkClusters ); // use specified size
				} else {
					clusterSize=(int) (pixelList.size()*patternDetectParameters.shrinkClusters); // use proportional size
				}
				if (clusterSize<5) clusterSize=5;
				while (pixelList.size()>clusterSize) {
					i=0;
					f=pixels[pixelList.get(i)];
					for (j=1;j<pixelList.size();j++) if (pixels[pixelList.get(j)]<f){
						i=j;
						f=pixels[pixelList.get(j)];
					}
					clusterMap[pixelList.get(i)]=-1; // Do not use looking for the next cluster
					pixelList.remove(i);
				}



				/* now find centroid of the cluster */
				minInCluster=pixels[pixelList.get(0)];
				for (i=1;i<pixelList.size();i++) if (minInCluster>pixels[pixelList.get(i)]) minInCluster = pixels[pixelList.get(i)];
				cx=0.0; cy=0.0; cm=0.0;
				for (i=0;i<pixelList.size();i++) {
					j=pixelList.get(i);
					y=j / size - halfSize;
					x=j % size - halfSize;
					f=pixels[j]-minInCluster;
					cm+=f;
					cx+=f*x;
					cy+=f*y;
				}
				cx/=cm;
				cy/=cm;
				max2[clusterNumber][0]=cx;
				max2[clusterNumber][1]=cy;
				f=0.0;
				if (pair>1) {
					cx=max2[clusterNumber-2][0]*pair/(pair-1)-max2[clusterNumber][0];
					cy=max2[clusterNumber-2][1]*pair/(pair-1)-max2[clusterNumber][1];
					f=Math.sqrt(cx*cx+cy*cy);
					/* Verify deviation here */
					if (f>patternDetectParameters.deviation) maxDefined[clusterNumber]=false;
				}
				if (this.debugLevel>6) System.out.println("pixelList.size()="+pixelList.size()+" centroid sum="+cm);
				if (this.debugLevel>5) System.out.println("clusterNumber="+clusterNumber+" x="+max2[clusterNumber][0]+" y="+max2[clusterNumber][1] + " x0="+(max2[clusterNumber][0]/pair)+" y0="+(max2[clusterNumber][1]/pair)+" deviat="+f);
				if ((cm==0.0) || (pixelList.size()<3)) maxDefined[clusterNumber]=false;
				/* Filter out unreasonably low frequencies*/
				if ((max2[clusterNumber][0]*max2[clusterNumber][0]+max2[clusterNumber][1]*max2[clusterNumber][1])<(reasonbleFrequency*reasonbleFrequency)) {
					if (this.debugLevel>2) System.out.println("Frequency too low:clusterNumber="+clusterNumber+" x="+max2[clusterNumber][0]+" y="+max2[clusterNumber][1]+ ", minimal allowed frequency is "+reasonbleFrequency);
					maxDefined[clusterNumber]=false;
				}
			}
		}
		/* Average (or just use farthest?) multiple maximums */
		if (this.debugLevel>2){
			float [] dbg_pixels=new float[clusterMap.length];
			for (j=0;j<dbg_pixels.length;j++) dbg_pixels[j]=(float)clusterMap[j];
			dbg_pixels[(size+1)*size/2]=-1; // mark center

			ImageProcessor ip=new FloatProcessor(size,size);
			ip.setPixels(dbg_pixels);
			ip.resetMinAndMax();
			ImagePlus imp=  new ImagePlus("clusters", ip);
			imp.show();
		}

		//    for (i=0;i<2;i++) for (j=0;j<2;j++) max2[i][j]=imax2[i][j];
		double [][]maxFinal=new double[2][2];
		boolean [] definedFinal={false,false};
		for (i=0;i<2;i++) {
			maxFinal[i][0]=0.0;
			maxFinal[i][1]=0.0;
			j=0;
			for (pair=0;pair<nmax/2;pair++) {
				if (maxDefined[i+2*pair]) {
					j++;
					maxFinal[i][0]+=max2[i+2*pair][0]/(pair+1);
					maxFinal[i][1]+=max2[i+2*pair][1]/(pair+1);
					definedFinal[i]=true;
					//          System.out.println("i="+i+" pair="+pair+" j="+j+" maxFinal["+i+"][0]="+maxFinal[i][0]+" maxFinal["+i+"][1]="+maxFinal[i][1]);
				}
			}
			maxFinal[i][0]/=j;
			maxFinal[i][1]/=j;
			if (j==0) definedFinal[i]=false;
			/* These two vectors correspond to checker diagonals and they are calculated for the correlation space.
 Actual frequencies for the checker board will be 1/4 of these, also divide by FFT size so the result will be in cycles per pixel */
			maxFinal[i][0]/=size*4;
			maxFinal[i][1]/=size*4;
		}
		if (this.debugLevel>2) {
			System.out.println("Checkerboard frequency[0]  x="+IJ.d2s(maxFinal[0][0],4)+" y="+IJ.d2s(maxFinal[0][1],4));
			System.out.println("Checkerboard frequency[1]  x="+IJ.d2s(maxFinal[1][0],4)+" y="+IJ.d2s(maxFinal[1][1],4));
			//      System.out.println();
		}
		if (!definedFinal[0] || !definedFinal[1]) {
			if (this.debugLevel>2) {
				System.out.println("Undefined frequency(ies)");
			}
			return null;
		}
		return maxFinal;
	}
	/* ======================================================================== */
	private double [] findCheckerPhases(double [][] WVectors, double [] P) {
		double [][] DWVectors = {{WVectors[0][0]-WVectors[1][0],WVectors[0][1]-WVectors[1][1]},
				{WVectors[0][0]+WVectors[1][0],WVectors[0][1]+WVectors[1][1]}};
		if (this.debugLevel>3)  System.out.println("      DWVectors[0][0]="+IJ.d2s(DWVectors[0][0],4)+"  DWVectors[0][1]="+IJ.d2s(DWVectors[0][1],4));
		if (this.debugLevel>3)  System.out.println("      DWVectors[1][0]="+IJ.d2s(DWVectors[1][0],4)+"  DWVectors[1][1]="+IJ.d2s(DWVectors[1][1],4));
		double [] DWVectorsAbs2={DWVectors[0][0]*DWVectors[0][0]+DWVectors[0][1]*DWVectors[0][1],
				DWVectors[1][0]*DWVectors[1][0]+DWVectors[1][1]*DWVectors[1][1]};
		if (this.debugLevel>3)  System.out.println("      sqrt(DWVectorsAbs2[0])="+IJ.d2s(Math.sqrt(DWVectorsAbs2[0]),4)+"  sqrt(DWVectorsAbs2[1])="+IJ.d2s(Math.sqrt(DWVectorsAbs2[1]),4));
		double [][] DL= {{DWVectors[0][0]/DWVectorsAbs2[0],DWVectors[0][1]/DWVectorsAbs2[0]},
				{DWVectors[1][0]/DWVectorsAbs2[1],DWVectors[1][1]/DWVectorsAbs2[1]}};
		if (this.debugLevel>3)  System.out.println("      DL[0][0]="+IJ.d2s(DL[0][0],4)+"  DL[0][1]="+IJ.d2s(DL[0][1],4));
		if (this.debugLevel>3)  System.out.println("      DL[1][0]="+IJ.d2s(DL[1][0],4)+"  DL[1][1]="+IJ.d2s(DL[1][1],4));

		double v= (DL[0][0]*(DL[1][0]*P[1]-DL[0][0]*P[0]) +  DL[0][1]*(DL[1][1]*P[1]-DL[0][1]*P[0]))/(DL[0][0]*DL[1][1]-DL[0][1]*DL[1][0])/(2*Math.PI);
		if (this.debugLevel>2)  System.out.println("v="+IJ.d2s(v,4));


		double [] WC={-DL[1][0] * P[1] / (2*Math.PI) + DL[1][1] * v,
				-DL[1][1] * P[1] / (2*Math.PI) - DL[1][0] * v};
		if (this.debugLevel>2)  System.out.println("WC[0]="+IJ.d2s(WC[0],4)+"  WC[1]="+IJ.d2s(WC[1],4));

		double [] phases={-2*Math.PI *(WC[0] * WVectors[0][0] + WC[1] * WVectors[0][1]),
				-2*Math.PI *(WC[0] * WVectors[1][0] + WC[1] * WVectors[1][1])};
		if (this.debugLevel>2)  System.out.println("phases[0]="+IJ.d2s(phases[0],4)+"  phases[1]="+IJ.d2s(phases[1],4));
		return phases;
	}
	/* ======================================================================== */
	private	boolean matchPatterns(double [][][] patterns, double [][] targetPattern) {
		int n,i,j;
		double [][] sp=new double[2][2];
		double [] target_lengths= new double[2];
		double [] current_lengths=new double[2];
		double [] swap_wv;
		double    swap;
		boolean noCorrectionWasNeeded=true;
		if (patterns==null) return false;
		if (targetPattern==null) return false;
		for (n=0;n<patterns.length; n++) {
			for (i=0;i<2;i++) {
				target_lengths[i]= Math.sqrt(targetPattern[i][0]*targetPattern[i][0]+targetPattern[i][1]*targetPattern[i][1]);
				current_lengths[i]=Math.sqrt(patterns[n][i][0]*patterns[n][i][0]+patterns[n][i][1]*patterns[n][i][1]);

			}
			for (i=0;i<2;i++) for (j=0;j<2;j++){
				sp[i][j]=    (patterns[n][i][0]*targetPattern[j][0]+patterns[n][i][1]*targetPattern[j][1])/current_lengths[i]/ target_lengths[j];
			}
			/* Swap vectors regardless of sign (parallel/anti-parallel)*/
			if ((Math.abs(sp[0][0])<Math.abs(sp[0][1])) || (Math.abs(sp[0][0])<Math.abs(sp[1][0]))) {
				noCorrectionWasNeeded=false;
				if (this.debugLevel>0) System.out.println("Swapped wave vectors in quadrant "+n);
				swap_wv=patterns[n][0];   patterns[n][0]=patterns[n][1]; patterns[n][1]=swap_wv;
				swap=sp[0][0];            sp[0][0]=sp[1][0];             sp[1][0]=swap;
				swap=sp[0][1];            sp[0][1]=sp[1][1];             sp[1][1]=swap;
			}
			/* Now correct vector signs if needed */
			for (i=0;i<2;i++) {
				if (sp[i][i] <0) {
					noCorrectionWasNeeded=false;
					if (this.debugLevel>0) System.out.println("Changing wave vector "+(i+1)+" direction in quadrant "+n);
					for (j=0;j<patterns[n][i].length;j++) patterns[n][i][j]=-patterns[n][i][j]; /// Will negate phase if available
				}
			}
		}
		return noCorrectionWasNeeded;
	}
	/*	** converts 2 wave vectors (WVx,WVy,phase) into two checker pattern vectors (VPx,VPy, phase)
	phase is in the point x=0,y=0*/
	private	double [][] waveVectorsToPatternVectors(double [] wv0, double [] wv1) {
		double [][] v=new double [2][3];
		double vect_wv0_x_wv1=wv0[0]*wv1[1]-wv0[1]*wv1[0];
		//v[0][0]= wv0[1]/vect_wv0_x_wv1;
		//v[0][1]=-wv0[0]/vect_wv0_x_wv1;
		//v[1][0]=-wv1[1]/vect_wv0_x_wv1;
		//v[1][1]= wv1[0]/vect_wv0_x_wv1;

		v[0][0]= wv1[1]/vect_wv0_x_wv1;
		v[0][1]=-wv1[0]/vect_wv0_x_wv1;
		v[1][0]=-wv0[1]/vect_wv0_x_wv1;
		v[1][1]= wv0[0]/vect_wv0_x_wv1;

		v[0][2]=wv0[2];
		v[1][2]=wv1[2];
		/* "white square" center had coordinates
	-wv0[0]
		 */
		return v;
	}
	/**
	  Matching non-linear mesh using wave vectors in the centers of four quadrants, phases are ignored here
	  processes wave vectors for the four quadrants (top-left,tr,bl,br) and calculates second degree polynominal correction
	  X1= Ax*X^2 + Bx*Y^2 + 2Cx*X*Y
	  Y1= Ay*X^2 + By*Y^2 + 2Cy*X*Y
	  where X and Y are normalized so top left corner is (-1,-1), top right - (+1,-1) , bottom left - (-1,+1), bottom right - (+1,+1)
	  returns array of 6 elements {Ax,Bx,Cx,Ay,By,Cy}
	 */
	private	double [] calcPatternNonLinear(double [][][] qp) {
		int iq;
		/* Calculate center WV */

		double [][][] mCorners=    new double [4][][] ; // only two 2x2 are needed, other two - just for debugging
		mCorners[0]= matrixToConvertTwoPairs(qp[0][0], qp[0][1], qp[4][0], qp[4][1]);
		mCorners[1]= matrixToConvertTwoPairs(qp[1][0], qp[1][1], qp[4][0], qp[4][1]);
		mCorners[2]= matrixToConvertTwoPairs(qp[2][0], qp[2][1], qp[4][0], qp[4][1]);
		mCorners[3]= matrixToConvertTwoPairs(qp[3][0], qp[3][1], qp[4][0], qp[4][1]);

		/**
	x,y - image coordinates (distorted), with no center shift (later use effective radius)
	x1,y1 - linear mesh

	x1=x+Ax*x^2+Bx*y^2+2*Cx*x*y+Dx*x+Ex*y
	y1=y+Ay*x^2+By*y^2+2*Cy*x*y+Dy*x+Ey*y

	dx1/dx=1+2*Ax*x+2*Cx*y+Dx
	dx1/dy=0+2*Cx*x+2*Bx*y+Ex

	dy1/dx=0+2*Ay*x+2*Cy*y+Dy
	dy1/dy=1+2*Cy*x+2*By*y+Ey

	| dx1 | = |  1+2*Ax*x+2*Cx*y+Dx     2*Cx*x+2*Bx*y+Ex | * | dx |
	| dy1 |   |    2*Ay*x+2*Cy*y+Dy   1+2*Cy*x+2*By*y+Ey |   | dy |

	for x=-1,y=-1
	M[0]=|  1-2*Ax-2*Cx+Dx    -2*Cx-2*Bx+Ex |
	   |   -2*Ay-2*Cy+Dy   1-2*Cy-2*By+Ey |

	for x=+1,y=-1
	M[1]=|  1+2*Ax-2*Cx+Dx    +2*Cx-2*Bx+Ex |
	   |   +2*Ay-2*Cy+Dy   1+2*Cy-2*By+Ey |

	for x=-1,y=+1
	M[2]=|  1-2*Ax+2*Cx+Dx    -2*Cx+2*Bx+Ex |
	   |   -2*Ay+2*Cy+Dy   1-2*Cy+2*By+Ey |

	for x=+1,y=+1
	M[2]=|  1+2*Ax+2*Cx+Dx    +2*Cx+2*Bx+Ex |
	   |   +2*Ay+2*Cy+Dy   1+2*Cy+2*By+Ey |


		 */
		double [] det=  {mCorners[0][0][0]*mCorners[0][1][1]-mCorners[0][0][1]*mCorners[0][1][0],
				mCorners[1][0][0]*mCorners[1][1][1]-mCorners[1][0][1]*mCorners[1][1][0],
				mCorners[2][0][0]*mCorners[2][1][1]-mCorners[2][0][1]*mCorners[2][1][0],
				mCorners[3][0][0]*mCorners[3][1][1]-mCorners[3][0][1]*mCorners[3][1][0]};

		double [][][] M={{{ mCorners[0][1][1]/det[0], -mCorners[0][1][0]/det[0]},
			{-mCorners[0][0][1]/det[0],  mCorners[0][0][0]/det[0]}},
			{{ mCorners[1][1][1]/det[1], -mCorners[1][1][0]/det[1]},
				{-mCorners[1][0][1]/det[1],  mCorners[1][0][0]/det[1]}},
				{{ mCorners[2][1][1]/det[2], -mCorners[2][1][0]/det[2]},
					{-mCorners[2][0][1]/det[2],  mCorners[2][0][0]/det[2]}},
					{{ mCorners[3][1][1]/det[3], -mCorners[3][1][0]/det[3]},
						{-mCorners[3][0][1]/det[3],  mCorners[3][0][0]/det[3]}}};
		/**
	 Overdefined - 16 equations for 10 unknowns
	1   M[0][0][0]=1-2*Ax-2*Cx+Dx
	2   M[0][0][1]= -2*Cx-2*Bx+Ex
	3   M[0][1][0]= -2*Ay-2*Cy+Dy
	4   M[0][1][1]=1-2*Cy-2*By+Ey
	5   M[1][0][0]=1+2*Ax-2*Cx+Dx
	6   M[1][0][1]=  2*Cx-2*Bx+Ex
	7   M[1][1][0]=  2*Ay-2*Cy+Dy
	8   M[1][1][1]=1+2*Cy-2*By+Ey

	9   M[2][0][0]=1-2*Ax+2*Cx+Dx
	10   M[2][0][1]= -2*Cx+2*Bx+Ex
	11   M[2][1][0]= -2*Ay+2*Cy+Dy
	12   M[2][1][1]=1-2*Cy+2*By+Ey
	13   M[3][0][0]=1+2*Ax+2*Cx+Dx
	14   M[3][0][1]=  2*Cx+2*Bx+Ex
	15   M[3][1][0]=  2*Ay+2*Cy+Dy
	16   M[3][1][1]=1+2*Cy+2*By+Ey

	( 1)   M[0][0][0]=1-2*Ax     -2*Cx                +Dx
	( 2)   M[0][0][1]=      -2*Bx-2*Cx                      +Ex
	( 3)   M[0][1][0]=                -2*Ay     -2*Cy    +Dy
	( 4)   M[0][1][1]=1                    -2*By-2*Cy          +Ey
	( 5)   M[1][0][0]=1+2*Ax     -2*Cx                +Dx
	( 6)   M[1][0][1]=      -2*Bx+2*Cx                      +Ex
	( 7)   M[1][1][0]=                +2*Ay     -2*Cy    +Dy
	( 8)   M[1][1][1]=1                    -2*By+2*Cy          +Ey

	( 9)   M[0][0][0]=1-2*Ax     +2*Cx                +Dx
	(10)   M[0][0][1]=      +2*Bx-2*Cx                      +Ex
	(11)   M[0][1][0]=                -2*Ay     +2*Cy    +Dy
	(12)   M[0][1][1]=1                    +2*By-2*Cy          +Ey
	(13)   M[1][0][0]=1+2*Ax     +2*Cx                +Dx
	(14)   M[1][0][1]=      +2*Bx+2*Cx                      +Ex
	(15)   M[1][1][0]=                +2*Ay     +2*Cy    +Dy
	(16)   M[1][1][1]=1                    +2*By+2*Cy          +Ey

	( 1)+( 5)+( 9)+(13)                     Dx=( M[0][0][0]+M[1][0][0]+M[2][0][0]+M[3][0][0])/4-1
	( 2)+( 6)+(10)+(14)                     Ex=( M[0][0][1]+M[1][0][1]+M[2][0][1]+M[3][0][1])/4
	( 3)+( 7)+(11)+(15)                     Dy=( M[0][1][0]+M[1][1][0]+M[2][1][0]+M[3][1][0])/4
	( 4)+( 8)+(12)+(16)                     Ey=( M[0][1][1]+M[1][1][1]+M[2][1][1]+M[3][1][1])/4-1

	-( 1)+( 5)-( 9)+(13)                     Ax=(-M[0][0][0]+M[1][0][0]-M[2][0][0]+M[3][0][0])/8
	-( 2)-( 6)+(10)+(14)                     Bx=(-M[0][0][1]-M[1][0][1]+M[2][0][1]+M[3][0][1])/8
	-( 2)+( 6)-(10)+(14)-( 1)-( 5)+( 9)+(13) Cx=(-M[0][0][1]+M[1][0][1]-M[2][0][1]+M[3][0][1]-M[0][0][0]-M[1][0][0]+M[2][0][0]+M[3][0][0])/16
	-( 3)+( 7)-(11)+(15)                     Ay=(-M[0][1][0]+M[1][1][0]-M[2][1][0]+M[3][1][0])/8
	-( 4)-( 8)+(12)+(16)                     By=(-M[0][1][1]-M[1][1][1]+M[2][1][1]+M[3][1][1])/8
	-( 4)+( 8)-(12)+(16)-( 3)-( 7)+(11)+(15) Cy=(-M[0][1][1]+M[1][1][1]-M[2][1][1]+M[3][1][1]-M[0][1][0]-M[1][1][0]+M[2][1][0]+M[3][1][0])/16
		 */

		double [] rslt = {(-M[0][0][0]+M[1][0][0]-M[2][0][0]+M[3][0][0])/8, // Ax
				(-M[0][0][1]-M[1][0][1]+M[2][0][1]+M[3][0][1])/8, // Bx
				(-M[0][0][1]+M[1][0][1]-M[2][0][1]+M[3][0][1]-M[0][0][0]-M[1][0][0]+M[2][0][0]+M[3][0][0])/16,  // Cx
				(-M[0][1][0]+M[1][1][0]-M[2][1][0]+M[3][1][0])/8,  // Ay
				(-M[0][1][1]-M[1][1][1]+M[2][1][1]+M[3][1][1])/8,  // By
				(-M[0][1][1]+M[1][1][1]-M[2][1][1]+M[3][1][1]-M[0][1][0]-M[1][1][0]+M[2][1][0]+M[3][1][0])/16,   // Cy
				( M[0][0][0]+M[1][0][0]+M[2][0][0]+M[3][0][0])/4-1, // Dx
				( M[0][0][1]+M[1][0][1]+M[2][0][1]+M[3][0][1])/4,   // Ex
				( M[0][1][0]+M[1][1][0]+M[2][1][0]+M[3][1][0])/4,   // Dy
				( M[0][1][1]+M[1][1][1]+M[2][1][1]+M[3][1][1])/4-1  // Ey
		};

		if (this.debugLevel>2) { /* increase LEVEL later */
			/*          System.out.println("Center   "+
	                           " W0x="+     IJ.d2s(centerWV[0][0],4)+
	                           " W0y="+     IJ.d2s(centerWV[0][1],4)+
	                           " W1x="+     IJ.d2s(centerWV[1][0],4)+
	                           " W1y="+     IJ.d2s(centerWV[1][1],4));*/
			for (iq=0; iq<4;iq++) {
				System.out.println("Matrix= "+iq+
						" M00="+     IJ.d2s(mCorners[iq][0][0],4)+
						" M01="+     IJ.d2s(mCorners[iq][0][1],4)+
						" M10="+     IJ.d2s(mCorners[iq][1][0],4)+
						" M11="+     IJ.d2s(mCorners[iq][1][1],4));
			}
			for (iq=0; iq<4;iq++) {
				System.out.println(" M["+iq+"][0][0]="+  IJ.d2s(M[iq][0][0],4)+
						" M["+iq+"][0][1]="+  IJ.d2s(M[iq][0][1],4)+
						" M["+iq+"][1][0]="+  IJ.d2s(M[iq][1][0],4)+
						" M["+iq+"][1][1]="+  IJ.d2s(M[iq][1][1],4));
			}
		}
		if (this.debugLevel>2) { /* increase LEVEL later */
			System.out.println("Corr   "+
					" Ax="+     IJ.d2s(rslt[0],5)+
					" Bx="+     IJ.d2s(rslt[1],5)+
					" Cx="+     IJ.d2s(rslt[2],5)+
					" Ay="+     IJ.d2s(rslt[3],5)+
					" By="+     IJ.d2s(rslt[4],5)+
					" Cy="+     IJ.d2s(rslt[5],5)+
					" Dx="+     IJ.d2s(rslt[6],5)+
					" Ex="+     IJ.d2s(rslt[7],5)+
					" Dy="+     IJ.d2s(rslt[8],5)+
					" Ey="+     IJ.d2s(rslt[9],5));
		}
		return rslt;
	};
	/* ======================================================================== */
	/* TODO: REPLACE doubleFHT  */
	/* converts FHT results (frequency space) to complex numbers of [fftsize/2+1][fftsize] */
	private double[][][] FHT2FFTHalf (FHT fht, int fftsize) {
		float[] fht_pixels=(float[])fht.getPixels();
		double[][][] fftHalf=new double[(fftsize>>1)+1][fftsize][2];
		int row1,row2,col1,col2;

		for (row1=0;row1<=(fftsize>>1);row1++) {
			row2=(fftsize-row1) %fftsize;
			for (col1=0;col1<fftsize;col1++) {
				col2=(fftsize-col1) %fftsize;
				fftHalf[row1][col1][0]=   0.5*(fht_pixels[row1*fftsize+col1] + fht_pixels[row2*fftsize+col2]);
				fftHalf[row1][col1][1]=   0.5*(fht_pixels[row2*fftsize+col2] - fht_pixels[row1*fftsize+col1]);
			}
		}
		return fftHalf;
	}
	/* ======================================================================== */
	public double[][][] FHT2FFTHalf (double [] fht_pixels, int fftsize) {
		double[][][] fftHalf=new double[(fftsize>>1)+1][fftsize][2];
		int row1,row2,col1,col2;

		for (row1=0;row1<=(fftsize>>1);row1++) {
			row2=(fftsize-row1) %fftsize;
			for (col1=0;col1<fftsize;col1++) {
				col2=(fftsize-col1) %fftsize;
				fftHalf[row1][col1][0]=   0.5*(fht_pixels[row1*fftsize+col1] + fht_pixels[row2*fftsize+col2]);
				fftHalf[row1][col1][1]=   0.5*(fht_pixels[row2*fftsize+col2] - fht_pixels[row1*fftsize+col1]);
			}
		}
		return fftHalf;
	}
	/* ======================================================================== */
	/* converts FFT arrays of complex numbers of [fftsize/2+1][fftsize] to FHT arrays */
	private float[] floatFFTHalf2FHT (double [][][] fft, int fftsize) {
		float[] fht_pixels=new float [fftsize*fftsize];
		int row1,row2,col1,col2;
		for (row1=0;row1<=(fftsize>>1);row1++) {
			row2=(fftsize-row1) %fftsize;
			for (col1=0;col1 < fftsize;col1++) {
				col2=(fftsize-col1) %fftsize;
				fht_pixels[row1*fftsize+col1]=(float)(fft[row1][col1][0]-fft[row1][col1][1]);
				fht_pixels[row2*fftsize+col2]=(float)(fft[row1][col1][0]+fft[row1][col1][1]);
			}
		}
		return fht_pixels;
	}
	/* ======================================================================== */
	public	 double[][] normalizeAndWindow (double [][] pixels, double [] windowFunction) {
		return normalizeAndWindow (pixels, windowFunction, true);
	}
	private	 double[] normalizeAndWindow (double [] pixels, double [] windowFunction) {
		return normalizeAndWindow (pixels, windowFunction, true);
	}
	private	 double[][] normalizeAndWindow (double [][] pixels, double [] windowFunction, boolean removeDC) {
		int i;
		for (i=0;i<pixels.length;i++)  if (pixels[i]!=null) pixels[i]=normalizeAndWindow (pixels[i],  windowFunction, removeDC);
		return pixels;
	}
	private	 double[] normalizeAndWindow (double [] pixels, double [] windowFunction, boolean removeDC) {
		int j;
		if (pixels==null) return null;
		double s=0.0,s0=0.0;
		if (removeDC) {
			for (j=0;j<pixels.length;j++){
				s+=pixels[j]*windowFunction[j];
				s0+=windowFunction[j];
				
			}
			s/=s0;
		}
		for (j=0;j<pixels.length;j++) pixels[j]=(pixels[j]-s)*windowFunction[j];
		return pixels;
	}
	/* ======================================================================== */
	public	double [][] matrix2x2_invert(double [][] m ){
		double det=m[0][0]*m[1][1]-m[0][1]*m[1][0];
		double [][] rslt= {{ m[1][1]/det,  -m[0][1]/det},
				{-m[1][0]/det,   m[0][0]/det}};
		return rslt;
	}
	public	double [][] matrix2x2_mul(double [][] a, double [][] b ){
		double [][] rslt={{a[0][0]*b[0][0]+a[0][1]*b[1][0], a[0][0]*b[0][1]+a[0][1]*b[1][1]},
				{a[1][0]*b[0][0]+a[1][1]*b[1][0], a[1][0]*b[0][1]+a[1][1]*b[1][1]}};
		return rslt;
	}
	public	double []   matrix2x2_mul(double [][] a, double [] b ){
		double [] rslt={a[0][0]*b[0]+a[0][1]*b[1],
				a[1][0]*b[0]+a[1][1]*b[1]};
		return rslt;
	}
	public	double [][] matrix2x2_scale(double [][] a, double  b ){
		double [][] rslt={{a[0][0]*b, a[0][1]*b},
				{a[1][0]*b, a[1][1]*b}};
		return rslt;
	}
	public	double [] matrix2x2_scale(double [] a, double  b ){
		double [] rslt={a[0]*b, a[1]*b};
		return rslt;
	}
	public	double [][] matrix_add(double [][] a, double [][]  b ){
		double [][] rslt= new double [a.length][a[0].length];
		int i,j;
		for (i=0;i<rslt.length;i++) for (j=0;j<rslt[0].length;j++) rslt[i][j]=a[i][j]+b[i][j];
		return rslt;
	}
	public	double []   vector_add(double [] a, double [] b ){
		double [] rslt= new double [a.length];
		int i;
		for (i=0;i<rslt.length;i++) rslt[i]=a[i]+b[i];
		return rslt;
	}
	/* calculates 2x2 matrix that converts two pairs of vectors: u2=M*u1, v2=M*v1*/
	private	double [][] matrixToConvertTwoPairs(double [] u1, double [] v1, double [] u2, double [] v2) {
		double [][] rslt= {{(u2[0]*v1[1]-v2[0]*u1[1])/(u1[0]*v1[1]-v1[0]*u1[1]),
			(v2[0]*u1[0]-u2[0]*v1[0])/(u1[0]*v1[1]-v1[0]*u1[1])},
			{(u2[1]*v1[1]-v2[1]*u1[1])/(u1[0]*v1[1]-v1[0]*u1[1]),
				(v2[1]*u1[0]-u2[1]*v1[0])/(u1[0]*v1[1]-v1[0]*u1[1])}};
		return rslt;
	}

	public double [][] matrix2x2_add(double [][] a, double [][] b ){
		double [][] rslt={{a[0][0]+b[0][0], a[0][1]+b[0][1]},
		         		  {a[1][0]+b[1][0], a[1][1]+b[1][1]}};
		return rslt;
	}

	public double [] matrix2x2_add(double [] a, double [] b ){
		double [] rslt={a[0]+b[0], a[1]+b[1]};
		return rslt;
	}

	public int [][] matrix2x2_add(int [][] a, int [][] b ){
		int [][] rslt={{a[0][0]+b[0][0], a[0][1]+b[0][1]},
		         		  {a[1][0]+b[1][0], a[1][1]+b[1][1]}};
		return rslt;
	}

	public int [] matrix2x2_add(int [] a, int [] b ){
		int [] rslt={a[0]+b[0], a[1]+b[1]};
		return rslt;
	}

	public double [][] matrix2x2_transp(double [][] m ){
		double [][] rslt= {{ m[0][0],  m[1][0]},
		            	   { m[0][1],  m[1][1]}};
		return rslt;
	}
	
	/* ======================================================================== */
	private	 double [] combineDiagonalGreens (double [] green0, double []green3, int half_width, int half_height) {
		int y,x,base;
		int base_b=0;
		double [] result= new double [green0.length];
		for (y=0;y<half_height/2; y++){
			base=half_height*half_width/2+ y* (half_width+1);
			for (x=0; x<half_width/2; x++) {
				result[base_b++]=green0[base];
				base-=half_width;
				result[base_b++]=green3[base++];
			}
			base=half_height*half_width/2+ y* (half_width+1);
			for (x=0; x<half_width/2; x++) {
				//System.out.println("2:y="+y+" x="+x+" base_b="+base_b+" base="+base);
				result[base_b++]=green3[base++];
				result[base_b++]=green0[base];
				base-=half_width;
			}
		}
		return result;
	}
	/* ======================================================================== */
	public double[] initWindowFunction(int size, double gaussWidth) {
		return initWindowFunction(size, gaussWidth, 0);
	}
	public double[] initWindowFunction(int size, double gaussWidth, int zeros) {
		double [] windowFunction =new double [size*size];
		double [] windowFunction_line=new double [size];
		double a,k;
		int i,j;
		int size1=size-zeros;
		int i0=(zeros+1)/2;
		if (gaussWidth==0) {
			for (i=0; i<size; i++) windowFunction_line[i]= 1.0;
		} else if (gaussWidth<0) {
			for (i=0; i<size1; i++) windowFunction_line[i+i0]= (0.54-0.46*Math.cos((i*2.0*Math.PI)/size1));
		} else {
			k=2.0/(size*gaussWidth);
			for (i=i0; i<i0+size1; i++) {
				a=(i-size/2)*k;
				windowFunction_line[i]= Math.exp( - a*a);
				if (zeros>0) windowFunction_line[i]*=(0.54-0.46*Math.cos(((i-i0)*2.0*Math.PI)/size1)); // additionally multiply by Hamming
			}
		}
		if (zeros>0){ // make window to be exact zero for certain number of samples (for correlation)
			for (i=0;i<i0;i++) windowFunction_line[i]=0.0;
			for (i=i0+size1;i<size;i++) windowFunction_line[i]=0.0;
			
			
		}
		for (i=0; i<size; i++) for (j=0; j<size; j++){
			windowFunction[size*i+j]=windowFunction_line[i]*windowFunction_line[j];
		}
		return windowFunction;
	}
/* ============================= Distortions ===================================*/
	   public void distortionsTest (
			   final DistortionParameters distortionParameters, //
			   final MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
			   final	SimulationPattern.SimulParameters  simulParameters,
			   final boolean equalizeGreens,
			   final ImagePlus imp, // image to process
			   final int threadsMax,
			   final boolean updateStatus,
			   final int debug_level){// debug level used inside loops
			   
		    if (imp==null) return;

			Roi roi= imp.getRoi();
			final Rectangle selection;
			if (roi==null){
				selection=new Rectangle(0, 0, imp.getWidth(), imp.getHeight());
			} else {
				selection=roi.getBounds();
			}
			 MatchSimulatedPattern matchSimulatedPattern=new MatchSimulatedPattern(distortionParameters.FFTSize);
			 matchSimulatedPattern.debugLevel=debugLevel;
			 MatchSimulatedPattern matchSimulatedPatternCorr=new MatchSimulatedPattern(distortionParameters.correlationSize);
			 matchSimulatedPatternCorr.debugLevel=debugLevel;
			 final SimulationPattern.SimulParameters  thisSimulParameters=simulParameters.clone();
			 thisSimulParameters.subdiv=     distortionParameters.patternSubdiv;
			 thisSimulParameters.bPatternSigma=distortionParameters.bPatternSigma;
			 thisSimulParameters.barraySigma=distortionParameters.barraySigma;
			 SimulationPattern simulationPattern= new SimulationPattern(thisSimulParameters);
			 final double [] bPattern= simulationPattern.patternGenerator(simulParameters); // reuse pattern for next time
//find center of the selection (to be used to find initial pattern approximation)
			 if (debugLevel>2)	System.out.println("bPattern.length="+bPattern.length);
		 int xc,yc;
		 xc=2*((2*selection.x+selection.width+1)/4);
		 yc=2*((2*selection.y+selection.height+1)/4);
		 Rectangle initialPatternCell=new Rectangle(xc-distortionParameters.FFTSize,
				                                    yc-distortionParameters.FFTSize,
				                                    2*distortionParameters.FFTSize,2*distortionParameters.FFTSize);       
//create diagonal green selection around xc,yc
		 double [][] input_bayer=splitBayer (imp,initialPatternCell,equalizeGreens);
		 if (debugLevel>2) SDFA_INSTANCE.showArrays(input_bayer,  true, "selection-bayer-distortionsTest");
		 double [] windowFunction=initWindowFunction(distortionParameters.FFTSize, distortionParameters.fftGaussWidth);
		 final double [] windowFunctionCorr=initWindowFunction(distortionParameters.correlationSize,distortionParameters.correlationGaussWidth,distortionParameters.zeros);
		 double [] greens=normalizeAndWindow (input_bayer[4], windowFunction);

		 double [][] pattern=matchSimulatedPattern.findPattern(
              greens,
				 distortionParameters.FFTSize,
				 patternDetectParameters,
				 true, // this is a pattern for combined greens (diagonal), adjust results accordingly
				 "Pattern"); // title - will not be used
		if (pattern==null) {
			System.out.println("Error - pattern not found");
		  	IJ.showMessage("Error","Failed to find pattern");
		  	return;
		}
		if (debugLevel>2) System.out.println("FX1="+pattern[0][0]+"  FY1="+pattern[0][1]+"  phase1="+pattern[0][2]);
		if (debugLevel>2) System.out.println("FX2="+pattern[1][0]+"  FY2="+pattern[1][1]+"  phase2="+pattern[1][2]);
		double [] ll2=new double[2];
		double [][] dxy=new double[2][2];
		double [][] phases=new double[2][2];
		int i,j,k;
		for (i=0;i<2;i++) {
			ll2[i]=(pattern[i][0]*pattern[i][0]+pattern[i][1]*pattern[i][1])*2*Math.PI;
		}

		if (debugLevel>2) System.out.println("phase1/2pi="+(pattern[0][2]/2/Math.PI)+"  phase2/2pi="+(pattern[1][2]/2/Math.PI));
		for (k=0;k<2;k++) {
		  for (j=0;j<2;j++) {
			phases[k][j]= pattern[j][2]-Math.PI/2+((k>0)? Math.PI:0.0);
			while (phases[k][j]<-Math.PI) phases[k][j]+=2*Math.PI;
			while (phases[k][j]>Math.PI)  phases[k][j]-=2*Math.PI; 
		  }	
 		  if (debugLevel>2) System.out.println("phase1/2pi="+(phases[k][0]/2/Math.PI)+"  phase2/2pi="+(phases[k][1]/2/Math.PI));
 			for (i=0;i<2;i++) {
 				dxy[k][i]=0;
 				for (j=0;j<2;j++) {
 					dxy[k][i]+=(phases[k][j])*pattern[j][i]/ll2[j];
 				}
 			}
 			if (debugLevel>2) System.out.println("dX["+k+"]="+dxy[k][0]+" dY["+k+"]="+dxy[k][1]);
		}
		int phaseSel=((dxy[0][0]*dxy[0][0]+dxy[0][1]*dxy[0][1])<(dxy[1][0]*dxy[1][0]+dxy[1][1]*dxy[1][1]))?0:1;
		if (debugLevel>1) System.out.println("xc="+xc+" yc="+yc);
		if (debugLevel>1) System.out.println("dX="+dxy[phaseSel][0]+" dY="+dxy[phaseSel][1]);
		 double [] centerXY0={xc-dxy[phaseSel][0],yc-dxy[phaseSel][1]};
		 if (debugLevel>1) System.out.println("+++ Initial center x="+IJ.d2s(centerXY0[0],3)+" y="+ 	IJ.d2s(centerXY0[1],3));
		 
// debug mode - scan correlation around center point, show result and exit:
			  SDFA_INSTANCE.showArrays(simulationPattern.bPattern, "bPattern");
//			 double [] barray= new double [simulationPattern.barray.length*simulationPattern.barray[0].length];
//			 for (i=0;i<barray.length;i++) {
//				 barray[i]=simulationPattern.barray[i/simulationPattern.barray[0].length][i % simulationPattern.barray[0].length];
//			 }
//			 SDFA_INSTANCE.showArrays(barray, simulationPattern.barray[0].length, simulationPattern.barray.length,"barray");
			 SDFA_INSTANCE.showArrays(simulationPattern.barray, "barray");
			 
			 double [][][] scanXY=scanPatternCrossLocation(
					 distortionParameters.correlationDx, // range
					 (int) Math.round(distortionParameters.correlationDx/distortionParameters.correlationDy)+1,
					    centerXY0, // initial coordinates of the pattern cross point
						pattern[0][0],
						pattern[0][1],
						pattern[1][0],
						pattern[1][1],
						imp,                       // image data (Bayer mosaic)
						distortionParameters,      //
						patternDetectParameters,
						matchSimulatedPatternCorr, // correlationSize
						thisSimulParameters,
						equalizeGreens,			
						windowFunctionCorr,        // window function
						simulationPattern,
						false, // if true - invert pattern
						null); // will create new instance of DoubleFHT class
			  double [][] scanImg= new double [4][scanXY.length*scanXY[0].length];
//			  System.out.println("scanImg[0].length="+scanImg[0].length);
			  for (i=0;i<scanImg[0].length;i++) {
				  scanImg[0][i]=scanXY[i/scanXY[0].length][i % scanXY[0].length][0];
				  scanImg[1][i]=scanXY[i/scanXY[0].length][i % scanXY[0].length][1];
				  scanImg[2][i]=scanXY[i/scanXY[0].length][i % scanXY[0].length][2];
				  scanImg[3][i]=scanXY[i/scanXY[0].length][i % scanXY[0].length][3];
			  }
			  SDFA_INSTANCE.showArrays(scanImg, true, "scan_correlation");
			  SDFA_INSTANCE.showArrays(scanImg, false,"scan_correlation");
			  return; 
	   }
/*
 *  Try point x,y, test for pattern, return x,y, contrast (or null)
 *   [0][0] - x
 *   [0][1] - y
 *   [0][2] - contrast
 *   [1][0] - Wave vector 1 x component
 *   [1][1] - Wave vector 1 y component
 *   [1][2] - Wave vector 1 phase (not used here)
 *   [2][0] - Wave vector 2 x component
 *   [2][1] - Wave vector 2 y component
 *   [2][2] - Wave vector 2 phase (not used here)
 */
	   public double[][] tryPattern (
			   double [] point, // xy to try
			   final DistortionParameters distortionParameters, //
			   final MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
			   final SimulationPattern.SimulParameters  thisSimulParameters,
			   final MatchSimulatedPattern matchSimulatedPattern,
			   final MatchSimulatedPattern matchSimulatedPatternCorr,
			   final SimulationPattern simulationPattern,
			   final boolean equalizeGreens,
			   final ImagePlus imp, // image to process
			   double [] bPattern,
			   double [] windowFunction,
			   double [] windowFunctionCorr,
			   double [] windowFunctionCorr2,
			   double [] windowFunctionCorr4,
			   double[][] locsNeib, // which neibors to try (here - just the center)
			   String dbgStr
			   ){

		   if (imp==null) {
			   if (dbgStr!=null) System.out.println(dbgStr+" imp==null");
			   return null;
		   }
		   int xc= (int)(2*Math.round(0.5*point[0]));
		   int yc= (int)(2*Math.round(0.5*point[1]));
			Roi roi= imp.getRoi();
			final Rectangle selection;
			if (roi==null){
				selection=new Rectangle(0, 0, imp.getWidth(), imp.getHeight());
			} else {
				selection=roi.getBounds();
			}
		   Rectangle initialPatternCell=new Rectangle(xc-distortionParameters.FFTSize,
					                                    yc-distortionParameters.FFTSize,
					                                    2*distortionParameters.FFTSize,2*distortionParameters.FFTSize); 
		   if (!selection.contains(initialPatternCell)) {
			   if (dbgStr!=null) System.out.println(dbgStr+" selection ("+
		   selection.x+","+selection.y+","+selection.width+","+selection.height+ ") does not contain cell ("+
		   initialPatternCell.x+","+initialPatternCell.y+","+initialPatternCell.width+","+initialPatternCell.height+ ")");
			   return null; // area for FFT is not inside the initial selection
		   }
		   //create diagonal green selection around xc,yc
		   double [][] input_bayer=splitBayer (imp,initialPatternCell,equalizeGreens);
		   if (debugLevel>2) SDFA_INSTANCE.showArrays(input_bayer,  true, "selection--bayer");


		   double [] greens=normalizeAndWindow (input_bayer[4], windowFunction);

		   double [][] pattern=matchSimulatedPattern.findPattern(
				   greens,
				   distortionParameters.FFTSize,
				   patternDetectParameters,
				   true, // this is a pattern for combined greens (diagonal), adjust results accordingly
				   "Pattern"); // title - will not be used
		   if (pattern==null) {
			   //				System.out.println("Error - pattern not found");
			   //			  	IJ.showMessage("Error","Failed to find pattern");
			   if (dbgStr!=null) System.out.println(dbgStr+" matchSimulatedPattern.findPattern->null");
			   return null;
		   }
		   if (debugLevel>2) System.out.println("FX1="+pattern[0][0]+"  FY1="+pattern[0][1]+"  phase1="+pattern[0][2]);
		   if (debugLevel>2) System.out.println("FX2="+pattern[1][0]+"  FY2="+pattern[1][1]+"  phase2="+pattern[1][2]);
		   double [] ll2=new double[2];
		   double [][] dxy=new double[2][2];
		   double [][] phases=new double[2][2];
		   int i,j,k;
		   for (i=0;i<2;i++) {
			   ll2[i]=(pattern[i][0]*pattern[i][0]+pattern[i][1]*pattern[i][1])*2*Math.PI;
		   }

		   if (debugLevel>2) System.out.println("phase1/2pi="+(pattern[0][2]/2/Math.PI)+"  phase2/2pi="+(pattern[1][2]/2/Math.PI));
		   for (k=0;k<2;k++) {
			   for (j=0;j<2;j++) {
				   phases[k][j]= pattern[j][2]-Math.PI/2+((k>0)? Math.PI:0.0);
				   while (phases[k][j]<-Math.PI) phases[k][j]+=2*Math.PI;
				   while (phases[k][j]>Math.PI)  phases[k][j]-=2*Math.PI; 
			   }	
			   if (debugLevel>2) System.out.println("phase1/2pi="+(phases[k][0]/2/Math.PI)+"  phase2/2pi="+(phases[k][1]/2/Math.PI));
			   for (i=0;i<2;i++) {
				   dxy[k][i]=0;
				   for (j=0;j<2;j++) {
					   dxy[k][i]+=(phases[k][j])*pattern[j][i]/ll2[j];
				   }
			   }
			   if (debugLevel>2) System.out.println("dX["+k+"]="+dxy[k][0]+" dY["+k+"]="+dxy[k][1]);
		   }
		   int phaseSel=((dxy[0][0]*dxy[0][0]+dxy[0][1]*dxy[0][1])<(dxy[1][0]*dxy[1][0]+dxy[1][1]*dxy[1][1]))?0:1;
		   if (debugLevel>2) System.out.println("xc="+xc+" yc="+yc);
		   if (debugLevel>2) System.out.println("dX="+dxy[phaseSel][0]+" dY="+dxy[phaseSel][1]);
		   double [] centerXY0={xc-dxy[phaseSel][0],yc-dxy[phaseSel][1]};
		   if (debugLevel>1) System.out.println("+++ Initial center x="+IJ.d2s(centerXY0[0],3)+" y="+ 	IJ.d2s(centerXY0[1],3));
		   double [] centerXY=correctedPatternCrossLocation(
				   centerXY0, // initial coordinates of the pattern cross point
				   pattern[0][0],
				   pattern[0][1],
				   pattern[1][0],
				   pattern[1][1],
				   null, // correction
				   imp,      // image data (Bayer mosaic)
				   distortionParameters, //
				   patternDetectParameters,
				   matchSimulatedPatternCorr, // correlationSize
				   thisSimulParameters,
				   equalizeGreens,			
				   windowFunctionCorr,   // window function
				   windowFunctionCorr2,   // window function
				   windowFunctionCorr4,   // window function
				   simulationPattern,
				   false, // if true - invert pattern
				   null, // will create new instance of DoubleFHT class
				   distortionParameters.fastCorrelationOnFirstPass,
				   locsNeib,
				   debugLevel,
				   dbgStr);
		   if (debugLevel>1) System.out.println("--- Initial center x="+IJ.d2s(centerXY0[0],3)+" y="+ 	IJ.d2s(centerXY0[1],3)+
				   " -> "+((centerXY==null)?" NULL ":(IJ.d2s(centerXY[0],3)+" : "+ 	IJ.d2s(centerXY[1],3))));
		   double [][] node = {centerXY,pattern[0],pattern[1]};
		   if (dbgStr!=null) {
			   if (centerXY==null) System.out.println(dbgStr+" correctedPatternCrossLocation->null");
			   else System.out.println(dbgStr+" matchSimulatedPattern.findPattern->{{"+centerXY[0]+","+centerXY[1]+","+centerXY[2]+"}..}");
		   }
		   return node;
	   }
/* ================================================================*/
// Optionally remove the outer (possibly corrupted) layer of the detected pattern nodes, extrapolate new layers of the nodes
// without pattern matching
	   
	   public double[][][][] finalizeDistortionsBorder (
//			   final double [][][][] patternGrid,
			   final DistortionParameters distortionParameters, //
			   final boolean updateStatus,
			   final int debug_level){// debug level used inside loops
//		    double[][][][] patternGrid=this.PATTERN_GRID;

//	        final int [][] directionsUV=  {{1,0},{0,1},{-1,0},{0,-1}}; // should have opposite direction shifted by half
		    final int [][] directionsUV8= {{1,0},{0,1},{-1,0},{0,-1},{1,1},{-1,1},{-1,-1},{1,-1}}; // first 8 should be the same as in directionsUV

	        final List <Integer> waveFrontList=new ArrayList<Integer>(1000);
// create list of all nodes that have undefined neigbors (up/down/right/left)			
			int umax=0, vmax=0, vmin=this.PATTERN_GRID.length, umin=this.PATTERN_GRID[0].length;
			for (int i=0;i<this.PATTERN_GRID.length;i++) for (int j=0;j<this.PATTERN_GRID[i].length;j++) {
				if ((this.PATTERN_GRID[i][j]!=null) && (this.PATTERN_GRID[i][j][0]!=null)) {
					if (vmin > i) vmin = i;
					if (vmax < i) vmax = i;
					if (umin > j) umin = j;
					if (umax < j) umax = j;
				}
			}
			int [] uvNew=new int [2];
			int [] iUV=  new int [2];
			int [] uvdir; // (u,v,direction}
			double [][][] wave;
			for (uvNew[1]=vmin;uvNew[1]<=vmax;uvNew[1]++) for (uvNew[0]=umin;uvNew[0]<=umax;uvNew[0]++) if (isCellDefined(this.PATTERN_GRID,uvNew)){
				for (int dir=0;dir<directionsUV8.length;dir++) {
					iUV[0]=uvNew[0]+directionsUV8[dir][0];
					iUV[1]=uvNew[1]+directionsUV8[dir][1];
					if (!isCellDefined(this.PATTERN_GRID,iUV)){
						putInWaveList (waveFrontList, uvNew, dir); // direction does not matter here
						break;
					}
				}

			}
			final double [][] extrapolationWeights=generateWeights (
					 distortionParameters.extrapolationSigma,
					 distortionParameters.correlationRadiusScale); //  if 0 - use sigma as radius, inside - 1.0, outside 0.0. If >0 - size of array n*sigma

			if (debugLevel>1) System.out.println("***** finalizeDistortionsBorder, initial wave length="+waveFrontList.size());
// optionally remove outer (possibly corruopted) layer of nodes			
		    if (distortionParameters.removeLast) for (int i=0;i<waveFrontList.size();i++){
		    	if (distortionParameters.numberExtrapolated==0)	invalidatePatternGridCell(this.PATTERN_GRID, getWaveList (waveFrontList,i));
		    	else 	                                              initPatternGridCell(this.PATTERN_GRID, getWaveList (waveFrontList,i));
		    }
		    for (int layer=0;(layer<distortionParameters.numberExtrapolated) && (waveFrontList.size()>0);layer++){

		    	if ((layer>0) || !distortionParameters.removeLast) { // build new layer around the current one
		    		while (waveFrontList.size()>0) { // will normally break out of the cycle
		    			uvdir= getWaveList (waveFrontList,0);
//							if (this.PATTERN_GRID[uvdir[1]][uvdir[0]]==null) break; // finished adding new layer
		    			if (!isCellDefined(this.PATTERN_GRID,uvdir)) break; // finished adding new layer, hit one of the newely added
		    			for (int dir=0;dir<directionsUV8.length;dir++) {
		    				iUV[0]=uvdir[0]+directionsUV8[dir][0];
		    				iUV[1]=uvdir[1]+directionsUV8[dir][1];
		    				if ((iUV[0]<0) || (iUV[1]<0) ||
		    						(iUV[0]>=distortionParameters.gridSize) || (iUV[1]>=distortionParameters.gridSize)) continue; // don't fit into UV grid
		    				if (!isCellNew(this.PATTERN_GRID,iUV)) continue; // already processed
// add uv and dir to the list
		    				putInWaveList (waveFrontList, iUV, dir); //  direction is not used
		    				initPatternGridCell(this.PATTERN_GRID, iUV);					
//								if (debugLevel>1) System.out.println("-->iUV= "+iUV[0]+",  "+iUV[1]+",  "+((dir+(directionsUV.length/2))%directionsUV.length));
		    			}
		    			waveFrontList.remove(0);  // remove first element from the list
//		    			if (debugLevel>1) System.out.println("xx> remove(0), (waveFrontList.size()="+(waveFrontList.size()));
		    		}
		    	}
// extrapolate x,y for the new layer of pixels (not yet using the new pixels in this layer)
		    	if (updateStatus) IJ.showStatus("Extrapolating border, layer "+(layer+1)+", length "+waveFrontList.size());
		    	if (debugLevel>1) System.out.println("Extrapolating border, layer "+(layer+1)+", length "+waveFrontList.size());
		    	wave = new double [waveFrontList.size()][][];
		    	for (int i=0;i<wave.length;i++) {
		    		wave[i]=estimateCell(
							 this.PATTERN_GRID,
							 getWaveList (waveFrontList,i),
							 extrapolationWeights, // quadrant of sample weights
							 true, // useContrast
							 !distortionParameters.useQuadratic,  // use linear approximation (instead of quadratic)
							 1.0E-10,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
							 1.0E-20  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
					 );
		    		if (wave[i]==null) { // try w/o contrast, just x,y
			    		wave[i]=estimateCell(
								 this.PATTERN_GRID,
								 getWaveList (waveFrontList,i),
								 extrapolationWeights, // quadrant of sample weights
								 false, // do not use Contrast, keep old contrast (even if it is NaN)
								 !distortionParameters.useQuadratic,  // use linear approximation (instead of quadratic)
								 1.0E-10,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
								 1.0E-20  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
						 );
		    			
		    		}
		    	}
// set new values, removed failed cells (normally should not be any)
		    	for (int i=wave.length-1;i>=0;i--) {
		    		uvdir=getWaveList (waveFrontList,i);
	    			this.PATTERN_GRID[uvdir[1]][uvdir[0]]=wave[i]; // null OK
		    		if (wave[i]==null) {
				    	if (debugLevel>0) System.out.println("Removing failed node (normally should not happen!), u="+uvdir[0]+", v="+uvdir[1]);
		    			waveFrontList.remove(i);
		    		}
		    		
		    	}

		    }
		   
		   return null;
	   }
/* ================================================================*/
// it now can start with non-empty Grid
// 	public boolean [] focusMask=null; // array matching image pixels, used with focusing (false outside sample areas)
	   
//	   public double[][][][] distortions(
	   public int distortions( // returns number of grid cells
//			   final int [] startScanIndex, // [0] will be updated
			   final boolean [] triedIndices,
			   final DistortionParameters distortionParameters, //
			   final MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
			   final SimulationPattern.SimulParameters  simulParameters,
			   final boolean equalizeGreens,
			   final ImagePlus imp, // image to process
			   final int threadsMax,
			   final boolean updateStatus,
			   final int debug_level, // debug level used inside loops
			   final int global_debug_level){
		   // moved to caller
		   //			this.PATTERN_GRID=null;
		   //	    	invalidateCalibration();
		   //	    	invalidateFlatFieldForGrid();
		   if (imp==null) return 0;
		   final int [][] directionsUV=  {{1,0},{0,1},{-1,0},{0,-1}}; // should have opposite direction shifted by half
		   final int [][] directionsUV8= {{1,0},{0,1},{-1,0},{0,-1},{1,1},{-1,1},{-1,-1},{1,-1}}; // first 8 should be the same as in directionsUV
		   final int [] directionsBits8= {1,4,1,4,2,8,2,8}; // should match directionsUV8
		   int neibBits;
		   final Thread[] threads = newThreadArray(threadsMax);
		   final AtomicInteger cellNum = new AtomicInteger(0);
		   final List <Integer> waveFrontList=new ArrayList<Integer>(1000);
		   final int [] centerUV= {distortionParameters.gridSize/2, distortionParameters.gridSize/2};

		   final double [][] locsNeib=calcNeibLocsWeights (distortionParameters,false); // no neibors to average 
		   Roi roi= imp.getRoi();
		   final Rectangle selection;
		   if (PATTERN_GRID==null) {
			   if (roi==null){
				   selection=new Rectangle(0, 0, imp.getWidth(), imp.getHeight());
			   } else {
				   selection=roi.getBounds();
			   }
		   } else {
			   if ((getImageHeight()!=imp.getHeight()) || (getImageWidth()!=imp.getWidth())){
				   String msg="Supplied image does not match in dimensions "+imp.getWidth()+"x"+imp.getHeight()+
				   " the one for wich grid was calculated ("+getImageWidth()+"x"+getImageHeight()+")";
				   IJ.showMessage("Error",msg);
				   throw new IllegalArgumentException (msg);
			   }
			   selection=new Rectangle(0, 0, getImageWidth(), getImageHeight()); 
		   }
		   MatchSimulatedPattern matchSimulatedPattern=new MatchSimulatedPattern(distortionParameters.FFTSize);
		   matchSimulatedPattern.debugLevel=debugLevel;
		   MatchSimulatedPattern matchSimulatedPatternCorr=new MatchSimulatedPattern(distortionParameters.correlationSize);
		   matchSimulatedPatternCorr.debugLevel=debugLevel;
		   final SimulationPattern.SimulParameters  thisSimulParameters=simulParameters.clone();
		   thisSimulParameters.subdiv=     distortionParameters.patternSubdiv;
		   thisSimulParameters.bPatternSigma=distortionParameters.bPatternSigma;
		   thisSimulParameters.barraySigma=distortionParameters.barraySigma;
		   SimulationPattern simulationPattern= new SimulationPattern(thisSimulParameters);
		   final double [] bPattern= simulationPattern.patternGenerator(simulParameters); // reuse pattern for next time
		   //find center of the selection (to be used to find initial pattern approximation)
		   if (debugLevel>2)	System.out.println("bPattern.length="+bPattern.length);
		   //		 int xc,yc;
		   //		 xc=2*((2*selection.x+selection.width+1)/4);
		   //		 yc=2*((2*selection.y+selection.height+1)/4);
		   double [] windowFunction=initWindowFunction(distortionParameters.FFTSize, distortionParameters.fftGaussWidth);

		   // may need to decrease relative gauss width for larger windows	
		   //			public boolean absoluteCorrelationGaussWidth=false; // do not scale correlationGaussWidth when the FFT size is increased  
		   ///(distortionParameters.absoluteCorrelationGaussWidth?0.5:1.0)
		   final double [] windowFunctionCorr= initWindowFunction(
				   distortionParameters.correlationSize,
				   distortionParameters.correlationGaussWidth,
				   distortionParameters.zeros);
		   final double [] windowFunctionCorr2=initWindowFunction(
				   2*distortionParameters.correlationSize,
				   (distortionParameters.absoluteCorrelationGaussWidth?0.5:1.0)*distortionParameters.correlationGaussWidth,
				   distortionParameters.zeros);
		   final double [] windowFunctionCorr4=initWindowFunction(
				   4*distortionParameters.correlationSize,
				   (distortionParameters.absoluteCorrelationGaussWidth?0.25:1.0)*distortionParameters.correlationGaussWidth,
				   distortionParameters.zeros);

		   DistortionParameters thisDistortionParameters=distortionParameters.clone();
		   thisDistortionParameters.correlationMaxOffset=0; // no verification of the offset here
		   thisDistortionParameters.correlationMinContrast=  distortionParameters.correlationMinInitialContrast; // different contrast minimum here
		   thisDistortionParameters.correlationMinAbsoluteContrast=  distortionParameters.correlationMinAbsoluteInitialContrast; // different contrast minimum here
		   int was_debug_level=debugLevel;
		   int [] iUV=  new int [2];
		   final boolean updating=(PATTERN_GRID!=null);
           final boolean useFocusMask=updating && (focusMask!=null); // do not expand wave beyound 1 grid step from needed image regions 
//		   double [][][] nodes=null;
		   Queue<GridNode> nodeQueue = new ConcurrentLinkedQueue<GridNode>();		   
		   boolean fromVeryBeginning=true;
		   if (!updating) {
			   for (int i=3;i<triedIndices.length;i++) if (triedIndices[i]){ // do not count first three NPE
				   fromVeryBeginning=false;
				   break;
			   }
			   double [] point = new double[2];
			   int tryHor=0,tryVert=0;
//				distortionParameters.searchOverlap=goniometerParameters.searchOverlap;
// with distortionParameters.searchOverlap==0.5 (default) step will be FFTSize original pixels, so half of the (2xFFTSize) square processed simultaneously			   
			   if (distortionParameters.searchOverlap<0.1) distortionParameters.searchOverlap=0.1;
			   int effectiveWidth=(int) (selection.width*0.5/distortionParameters.searchOverlap);
			   int effectiveHeight=(int) (selection.height*0.5/distortionParameters.searchOverlap);

			   for (int i=distortionParameters.FFTSize;i<effectiveWidth;i*=2) tryHor++;
			   for (int i=distortionParameters.FFTSize;i<effectiveHeight;i*=2) tryVert++;
			   
			   int numTries=triedIndices.length-1; // Should be equal to	   int numTries=1<<(tryHor+tryVert);
			   
			   int nbv,nbh,nh,nv,nb;
			   if (debugLevel>1) System.out.println("selection.x="+selection.x+" selection.y="+selection.y+" selection.width="+selection.width+" selection.height="+selection.height);
			   if (debugLevel>1) System.out.println("numTries="+numTries+" tryHor="+tryHor+" tryVert="+tryVert);
//			   double [][] node=null;
//			   double [][][] nodes=null;
			   boolean oldMode=false; //true; // false;
/*			   
			   if (startScanIndex[0]==0) startScanIndex[0]=3;
			   if (startScanIndex[0]<0){
				   System.out.println("distortions(): BUG - startScanIndex[0]="+startScanIndex[0]+" <0");
				   this.PATTERN_GRID=null;   
				   return 0;
			   }
*/			   
			   if (oldMode) { // old (single-threaded) mode
//				   for (int n=startScanIndex[0];n<numTries;n++) {
				   //			   final boolean [] triedIndices,
//				   nodes = new double [1][][];
//				   nodes[0]=null;

				   for (int startScanIndex=3;startScanIndex<=numTries;startScanIndex++) if (!triedIndices[startScanIndex]){
					   if (startScanIndex==numTries){
						   triedIndices[startScanIndex]=true; // all done
						   break;
					   }
					   nbh=tryHor-1;
					   nbv=tryVert-1;
					   nh=0;
					   nv=0;
					   nb=0;
					   while (nb<(tryHor+tryVert)) {
						   if (nbh>=0) {
							   if ((startScanIndex & (1<<nb))!=0) nh |= 1<<nbh;
							   nbh--;
							   nb++;
						   }
						   if (nbv>=0) {
							   if ((startScanIndex & (1<<nb))!=0) nv |= 1<<nbv;
							   nbv--;
							   nb++;
						   }
					   }
					   if (debugLevel>2) System.out.println("Searching, n="+startScanIndex+", nv="+nv+", nh="+nh+", nb="+nb );
					   if ((nv>0) && (nh>0)) {
						   point[0]=(selection.x+nh*selection.width/(1<<tryHor)) & ~1;
						   point[1]=(selection.y+nv*selection.height/(1<<tryVert)) & ~1;
						   if (debugLevel>2) System.out.println("trying xc="+point[0]+", yc="+point[1]+"(nv="+nv+", nh="+nh+")");
//						   System.out.println("### trying xc="+point[0]+", yc="+point[1]+"(nv="+nv+", nh="+nh+")");
						   if ((debugLevel>2) && (startScanIndex==3)) debugLevel=3; // show debug images for the first point only
						   double [][] node=tryPattern (
								   point, // xy to try
								   thisDistortionParameters, //no control of the displacement
								   patternDetectParameters,
								   thisSimulParameters,
								   matchSimulatedPattern,
								   matchSimulatedPatternCorr,
								   simulationPattern,
								   equalizeGreens,
								   imp, // image to process
								   bPattern,
								   windowFunction,
								   windowFunctionCorr,
								   windowFunctionCorr2,
								   windowFunctionCorr4,
								   locsNeib, // which neibors to try (here - just the center)
								   null // dbgStr
						   );
						   debugLevel=was_debug_level;
						   if ((node!=null) && (node[0]!=null)) {
							   nodeQueue.add(new GridNode(node));
							   break;
						   }
					   }
					   triedIndices[startScanIndex]=true;
				   }
			   } else { // new multithreaded mode
				   int startScanIndex=3;
				   for (;(startScanIndex<numTries) && triedIndices[startScanIndex];startScanIndex++); // skip already tried indices
				   if ((global_debug_level>0) && (startScanIndex>3)) System.out.println("distortions(): startScanIndex="+startScanIndex+" > 3 ####");				   
				   
				   if (startScanIndex<numTries) {
					   nodeQueue =  findPatternCandidates(
							   triedIndices,
							   startScanIndex, // [0] will be updated 
							   tryHor,
							   tryVert,
							   //						   numTries,
							   selection,
							   thisDistortionParameters, //no control of the displacement
							   patternDetectParameters,
							   thisSimulParameters,
							   matchSimulatedPattern,
							   matchSimulatedPatternCorr,
							   simulationPattern,
							   equalizeGreens,
							   imp, // image to process
							   bPattern,
							   windowFunction,
							   windowFunctionCorr,
							   windowFunctionCorr2,
							   windowFunctionCorr4,
							   locsNeib, // which neibors to try (here - just the center)
							   threadsMax,
							   updateStatus,
							   this.debugLevel
					   );
					   if (nodeQueue.isEmpty()) { // nodes==null){ 
//						   if (debugLevel>1) System.out.println("All start points tried");
						   if (global_debug_level>1) {
							   System.out.println("All start points tried");
							   int numLeft=0;
							   for (boolean b:triedIndices) if (!b) numLeft++;
							   System.out.println("nodeQueue.isEmpty(), startScanIndex="+startScanIndex+" numTries="+numTries+" numLeft="+numLeft);
						   }
						   triedIndices[numTries]=true; // all tried
					   } else {
//						   if (debugLevel>1) System.out.println("Found "+nodes.length+" candidates");
//						   if (debugLevel>1) System.out.println("distortions: Found "+nodeQueue.size()+" candidates");
						   if (global_debug_level>0){
							   System.out.println("distortions: Found "+nodeQueue.size()+" candidates");
						   }
					   }
				   } else {
					   System.out.println("All start points tried before - should not get here");
					   triedIndices[numTries]=true; // all tried
				   }
			   }
//				   if (global_debug_level>0) System.out.println("distortions(): startScanIndex="+startScanIndex);				   
			   
//			   if (startScanIndex[0]>=numTries) startScanIndex[0]=-1; // all indices used
//			   if ((nodes==null) || (nodes[0]==null) || (nodes[0][0]==null)) {
			   if ((nodeQueue.isEmpty()) || (nodeQueue.peek().getNode()[0]==null)) {
				   if (debugLevel>1) System.out.println("*** Pattern not found");
				   this.PATTERN_GRID=null;   
				   return 0;
			   }
			   if (global_debug_level>1) {
				   System.out.println("distortions(): found "+nodeQueue.size()+" grid candidates");
//				   System.out.println("*** distortions: Center x="+IJ.d2s(centerXY[0],3)+" y="+ 	IJ.d2s(centerXY[1],3));
			   }

			   debugLevel=debug_level; // ????
			   
		   } else { // create initial wave from the border nodes of existent grid
			   // start with clearing all invalid nodes 
			   for (iUV[1]=0;iUV[1]<this.PATTERN_GRID.length;iUV[1]++) for (iUV[0]=0;iUV[0]<this.PATTERN_GRID[0].length;iUV[0]++)
				   if (!isCellDefined(this.PATTERN_GRID,iUV))clearPatternGridCell(this.PATTERN_GRID,iUV);
			   //		   int [] iUV=  new int [2];
			   // probably start with clearing all invalid nodes
			   //// 	public boolean [] focusMask=null; // array matching image pixels, used with focusing (false outside sample areas)
			   int [] iUV1=new int[2];
			   for (iUV[1]=0;iUV[1]<this.PATTERN_GRID.length;iUV[1]++) for (iUV[0]=0;iUV[0]<this.PATTERN_GRID[0].length;iUV[0]++)
				   if (isCellDefined(this.PATTERN_GRID,iUV)){ // see if it has any new undefined neighbors
					   boolean hasNewNeib=false;
					   for (int dir=0;dir<directionsUV.length;dir++) {
						   iUV1[0]=iUV[0]+directionsUV[dir][0];
						   iUV1[1]=iUV[1]+directionsUV[dir][1];
						   if (isCellNew(this.PATTERN_GRID,iUV1)){
							   hasNewNeib=true;
							   break;
						   }
					   }
					   if (hasNewNeib) {
						   putInWaveList(waveFrontList, iUV, 0);
					   }
				   }
			   double [][] node={null};
			   nodeQueue.add(new GridNode(node)); // will not be used, any element
		   }
		   int numDefinedCells=0;
		   int debug_left=nodeQueue.size();
		   for (GridNode gn:nodeQueue){ // trying candidates as grid seeds - until found or nothing left
			   debug_left--;
			   if (global_debug_level>1) {
				   System.out.println("distortions: nodeQueue has "+(debug_left)+" candidates left (excluding this one)");
			   }
			   if (!updating){
				   double [][] node=gn.getNode();
				   double [] centerXY=node[0];
				   if (global_debug_level>0) {
//					   System.out.println("distortions: node X/Y are "+centerXY[0]+"/"+centerXY[1]);
					   System.out.println("distortions: nodeQueue has "+(debug_left)+" candidates left (excluding this one) :node X/Y are "+centerXY[0]+"/"+centerXY[1]);
					   
				   }
				   //				   if (debugLevel>1) {
				   if (global_debug_level>1) {
					   System.out.println("*** distortions: Center x="+IJ.d2s(centerXY[0],3)+" y="+ 	IJ.d2s(centerXY[1],3));
					   System.out.println("*** distortions: setting debugX="+IJ.d2s(centerXY[0],3)+" debugY="+ 	IJ.d2s(centerXY[1],3));
					   patternDetectParameters.debugX=centerXY[0]; // Change debug coordinates to the initial node
					   patternDetectParameters.debugY=centerXY[1]; //patternDetectParameters.debugRadius);
				   }
				   debugLevel=debug_level;
				   // Reset pattern grid		
				   this.PATTERN_GRID=setPatternGridArray(distortionParameters.gridSize); // global to be used with threads?
				   setPatternGridCell(
						   this.PATTERN_GRID,
						   centerUV,
						   centerXY, // contrast OK?
						   node[1],
						   node[2]);
				   waveFrontList.clear();
				   putInWaveList(waveFrontList, centerUV, 0);
				   if (global_debug_level>1) {
					   System.out.println("putInWaveList(waveFrontList, {"+centerUV[0]+","+centerUV[1]+"}, 0);");
				   }
			   }

			   // Each layer processing may be multi-threaded, they join before going to the next layer
			   // When looking for the next cells, the position is estimated knowing the neighbor that has wave vectors defined
			   // after the layer pass is over, the wave vectors are calculated from the distances to neighbors (one or both vectors may have
			   // to use those from the neighbor?
			   if (debugLevel>1) System.out.println("-->centerUV= "+centerUV[0]+",  "+centerUV[1]+",  0");
			   int [] uvdir; // (u,v,direction}
			   int layer=0;
			   int dir;
			   //			int [] iUV0= new int [2];
			   //			double [][] uv12t=new double[2][2];
			   //			double [][] xy12t=new double[2][2];
			   //			double [][][] cells =new double [3][][]; //0 - this cell, 1 - parent, 2 - other non co-linear
			   double [][][] neibors=new double [8][][]; // uv and xy vectors to 8 neibors (some may be null
			   double [][] thisCell;
			   double [][] otherCell;
			   final int debugThreshold=2;
			   final double [][] extrapolationWeights=generateWeights (
					   distortionParameters.correlationWeightSigma,
					   distortionParameters.correlationRadiusScale); //  if 0 - use sigma as radius, inside - 1.0, outside 0.0. If >0 - size of array n*sigma

			   int umax,vmax,vmin,umin;
			   final AtomicInteger addedCells = new AtomicInteger(0); // cells added at cleanup stage
			   final AtomicBoolean cleanup=new AtomicBoolean(false); // after the wave dies, it will be restored for all cells with defined neigbors to try again. maybe - try w/o threads?

			   final AtomicInteger debugCellSet= new AtomicInteger(0); // cells added at cleanup stage
			   // special case (most common, actually) when initial wave has 1  node. Remove it after processing
			   //first cell(s) will need large correction and so may fail during "refine", so trying to recalculate it right after the first layer)
			   ArrayList<Integer> initialWave=new ArrayList<Integer>();
			   for (Integer I:waveFrontList) initialWave.add(I); 
			   while (waveFrontList.size()>0) {
				   // process current list, add new wave layer (moving in one of the 4 directions)
				   // proceed until the entry is undefined on the grid (or list is empty
				   while (waveFrontList.size()>0) { // will normally break out of the cycle
					   uvdir= getWaveList (waveFrontList,0);
					   if (this.debugLevel>1) System.out.println("<--uvdir= "+uvdir[0]+",  "+uvdir[1]+",  "+uvdir[2]);
					   if (this.PATTERN_GRID[uvdir[1]][uvdir[0]]==null) break; // finished adding new layer
					   if (!isCellDefined(this.PATTERN_GRID,uvdir)) break; // finished adding new layer, hit one of the newely added
					   boolean hasNeededNeighbor=true;
					   if (useFocusMask) {
						   int ix=(int) Math.round(this.PATTERN_GRID[uvdir[1]][uvdir[0]][0][0]);
						   int iy=(int) Math.round(this.PATTERN_GRID[uvdir[1]][uvdir[0]][0][1]);
						   int indx=iy*getImageWidth()+ix;
						   if ((indx<0) || (indx>focusMask.length)){
							   System.out.println("distortions(): this.PATTERN_GRID["+uvdir[1]+"]["+uvdir[0]+"][0][0]="+this.PATTERN_GRID[uvdir[1]][uvdir[0]][0][0]);
							   System.out.println("distortions(): this.PATTERN_GRID["+uvdir[1]+"]["+uvdir[0]+"][0][1]="+this.PATTERN_GRID[uvdir[1]][uvdir[0]][0][1]);
							   System.out.println("distortions(): ix="+ix);
							   System.out.println("distortions(): iy="+iy);
							   System.out.println("distortions(): focusMask.length="+focusMask.length);
						   }
						   // TODO: find how it could get negative coordinates
						   if ((ix<0) || (iy<0) || (ix>=distortionParameters.gridSize) || (iy>=distortionParameters.gridSize)) hasNeededNeighbor=false; //???
						   else hasNeededNeighbor=focusMask[iy*getImageWidth()+ix]; //* OOB -1624 java.lang.ArrayIndexOutOfBoundsException: -1624,  at MatchSimulatedPattern.distortions(MatchSimulatedPattern.java:3063),  at LensAdjustment.updateFocusGrid(LensAdjustment.java:121),  at Aberration_Calibration.measurePSFMetrics(Aberration_Calibration.java:5994)
					   }
					   for (dir=0;dir<directionsUV.length;dir++) {
						   iUV[0]=uvdir[0]+directionsUV[dir][0];
						   iUV[1]=uvdir[1]+directionsUV[dir][1];

						   if ((iUV[0]<0) || (iUV[1]<0) ||
								   (iUV[0]>=distortionParameters.gridSize) || (iUV[1]>=distortionParameters.gridSize)) continue; // don't fit into UV grid
						   if (!isCellNew(PATTERN_GRID,iUV)) continue; // already processed (or deleted!)
						   // add uv and dir to the list
						   // 	public boolean [] focusMask=null; // array matching image pixels, used with focusing (false outside sample areas)
						   // New: if it is updating the grid and focusMask is defined - do not go more than 1 step away from the needed image area
						   if (hasNeededNeighbor) {
							   putInWaveList (waveFrontList, iUV, (dir+(directionsUV.length/2))%directionsUV.length); // opposite direction
							   initPatternGridCell(PATTERN_GRID, iUV);					
							   if (debugLevel>1) System.out.println("-->iUV= "+iUV[0]+",  "+iUV[1]+",  "+((dir+(directionsUV.length/2))%directionsUV.length));
						   }
					   }
					   waveFrontList.remove(0);  // remove first element from the list
					   if (debugLevel>1) System.out.println("xx> remove(0), (waveFrontList.size()="+(waveFrontList.size()));
				   }
				   if (waveFrontList.size()==0) break; // not really needed?
				   layer++;
				   if (updateStatus) IJ.showStatus("Correlating patterns, layer "+layer+(cleanup.get()?"(cleanup)":"")+", length "+waveFrontList.size());
//				   if (debugLevel>1) System.out.println("Correlating patterns, layer "+layer+", length "+waveFrontList.size());
				   if (global_debug_level>2) System.out.println("Correlating patterns, layer "+layer+", length "+waveFrontList.size());
				   // starting layer
				   cellNum.set(0);
				   for (int ithread = 0; ithread < threads.length; ithread++) {
					   threads[ithread] = new Thread() {
						   public void run() {
							   SimulationPattern simulationPattern= new SimulationPattern(bPattern);
							   MatchSimulatedPattern matchSimulatedPatternCorr=new MatchSimulatedPattern(distortionParameters.correlationSize);
							   DoubleFHT fht_instance =new DoubleFHT(); // provide DoubleFHT instance to save on initializations (or null)
							   String dbgStr="";
							   for (int ncell=cellNum.getAndIncrement(); ncell<waveFrontList.size();ncell=cellNum.getAndIncrement()){
								   int [] iUVdir=getWaveList (waveFrontList,ncell);
								   if (debugLevel>debugThreshold) {
									   dbgStr="";
									   dbgStr+="<--iUVdir= "+iUVdir[0]+",  "+iUVdir[1]+",  "+iUVdir[2];
								   }
								   int [] iUVRef=new int[2];
								   iUVRef[0]=iUVdir[0]+directionsUV[iUVdir[2]][0];
								   iUVRef[1]=iUVdir[1]+directionsUV[iUVdir[2]][1];
								   // refCell - is where it came from, but if the initials are disabled, it is null 

								   double [][] refCell=PATTERN_GRID[iUVRef[1]][iUVRef[0]]; // should never be null as it is an old one
								   if (refCell==null){ 
									   System.out.println("**** refCell==null - what does it mean?**** u="+iUVRef[0]+" v="+iUVRef[1]+
											   " current="+iUVdir[0]+"/"+iUVdir[1]+" len="+iUVdir.length);
									   continue;
								   } else if ((refCell[0]!=null) && (refCell[0].length>3)){
									   double dbg_contrast=(refCell[0].length>2)?refCell[0][2]:Double.NaN;
									   System.out.println("**** refCell was deleted **** u="+iUVRef[0]+" v="+iUVRef[1]+
											   " current="+iUVdir[0]+"/"+iUVdir[1]+
											   " ncell="+ncell+" waveFrontList.size()="+waveFrontList.size()+
											   " ref_x="+IJ.d2s(refCell[0][0],3)+" ref_y="+IJ.d2s(refCell[0][1],3)+
											   " contrast="+IJ.d2s(dbg_contrast,3));
								   }
								   //found reference cell, calculate x/y, make sure it is inside the selection w/o borders
								   double [][] wv=new double [2][];
								   wv[0]=refCell[1];
								   wv[1]=refCell[2];
								   double [][]uv2xy=matrix2x2_invert(wv);

								   double [] dUV = new double [2];
								   dUV[0]=0.5*(iUVdir[0]-iUVRef[0]);
								   dUV[1]=0.5*(iUVdir[1]-iUVRef[1]);
								   double []dXY=matrix2x2_mul(uv2xy,dUV);
								   double [] expectedXY=matrix2x2_add(refCell[0],dXY);

								   // Try new extrapolation, debug print both	    						 
								   //extrapolationWeights
								   double [][] estimatedCell=estimateCell(
										   PATTERN_GRID,
										   iUVdir,
										   extrapolationWeights, // quadrant of sample weights
										   true, // useContrast
										   !distortionParameters.useQuadratic,  // use linear approximation (instead of quadratic)
										   1.0E-10,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
										   1.0E-20  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
										   );
								   double [][] simulPars=null;
								   if (debugLevel>debugThreshold) {
									   dbgStr+=" ExpectedXY(old)= "+IJ.d2s(expectedXY[0],3)+" / "+IJ.d2s(expectedXY[1],3)+",  "+
											   " vw00="+IJ.d2s(wv[0][0],5)+" vw01="+IJ.d2s(wv[0][1],5)+
											   " vw10="+IJ.d2s(wv[1][0],5)+" vw11="+IJ.d2s(wv[1][1],5);
									   if (estimatedCell==null) {
										   dbgStr+=" -- ExpectedXY(new)= ***** NULL **** ";
									   } else {
										   dbgStr+=" -- ExpectedXY(new)= "+IJ.d2s(estimatedCell[0][0],3)+" / "+IJ.d2s(estimatedCell[0][1],3)+",  "+
												   " vw00="+IJ.d2s(estimatedCell[1][0],5)+" vw01="+IJ.d2s(estimatedCell[1][1],5)+
												   " vw10="+IJ.d2s(estimatedCell[2][0],5)+" vw11="+IJ.d2s(estimatedCell[2][1],5);
									   }


								   }
								   if (estimatedCell!=null) {
									   expectedXY=estimatedCell[0];
									   wv[0]=estimatedCell[1];
									   wv[1]=estimatedCell[2];

									   simulPars=getSimulationParametersFromGrid(
											   PATTERN_GRID,
											   iUVdir,          // U,V of the center point (for which the simulation pattern should be built
											   expectedXY,          // x,y of the center point (or null to use grid)
											   extrapolationWeights, // quadrant of sample weights
											   !distortionParameters.useQuadratic,  // use linear approximation (instead of quadratic)
											   1.0E-10,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
											   1.0E-20  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
											   );
									   if (debugLevel>debugThreshold) {
										   dbgStr+=" {"+IJ.d2s(simulPars[0][0],5)+"/"+IJ.d2s(simulPars[0][1],5)+"/"+IJ.d2s(simulPars[0][2],5);
										   if (simulPars[0].length>3) dbgStr+="/"+IJ.d2s(simulPars[0][3],7)+"/"+IJ.d2s(simulPars[0][4],7)+"/"+IJ.d2s(simulPars[0][5],7)+"}";
										   dbgStr+=" {"+IJ.d2s(simulPars[1][0],5)+"/"+IJ.d2s(simulPars[1][1],5)+"/"+IJ.d2s(simulPars[1][2],5);
										   if (simulPars[1].length>3) dbgStr+="/"+IJ.d2s(simulPars[1][3],7)+"/"+IJ.d2s(simulPars[1][4],7)+"/"+IJ.d2s(simulPars[1][5],7)+"}";
									   }                                   
								   }
								   if (!selection.contains((int) Math.round(expectedXY[0]),(int) Math.round(expectedXY[1]))) { // just the center point
									   invalidatePatternGridCell(
											   PATTERN_GRID,
											   iUVdir);
									   if (debugLevel>debugThreshold) {
										   dbgStr+=" -- not in selection ";
										   System.out.println(dbgStr);
									   }
									   continue; // the correlation selection does not fit into WOI selection
								   }
								   //Proceed with correlation	
								   //TODO: add contrast verification ? Maximal distance from expected? (return null if failed)		    						 
								   double [] centerXY=correctedPatternCrossLocation(
										   expectedXY, // initial coordinates of the pattern cross point
										   wv[0][0],
										   wv[0][1],
										   wv[1][0],
										   wv[1][1],
										   simulPars,
										   imp,      // image data (Bayer mosaic)
										   distortionParameters, //
										   patternDetectParameters,
										   matchSimulatedPatternCorr, // correlationSize
										   thisSimulParameters,
										   equalizeGreens,			
										   windowFunctionCorr,
										   windowFunctionCorr2,
										   windowFunctionCorr4,
										   simulationPattern,
										   ((iUVdir[0]^iUVdir[1])&1)!=0, // if true - invert pattern
										   fht_instance,
										   distortionParameters.fastCorrelationOnFirstPass,
										   locsNeib,
										   debugLevel,
										   null);
								   //	    						 System.out.println("*+*debugLevel="+debugLevel);
								   if (centerXY==null){
									   invalidatePatternGridCell(
											   PATTERN_GRID,
											   iUVdir);
									   if (debugLevel>debugThreshold) {
										   dbgStr+=" -- FAILED";
										   System.out.println(dbgStr);
									   }
									   continue; // failed to find pattern in the cell TODO: implement
								   }
								   if (debugCellSet.getAndIncrement()==0){ // First cell
									   if (passNumber==1) {
										   debugUV[0]=iUVdir[0];
										   debugUV[1]=iUVdir[1];
										   if (debugLevel>debugThreshold) System.out.println("debugUV[] set to {"+debugUV[0]+","+debugUV[1]+"}");
										   passNumber=2; // global passNumber
									   }
								   }
								   //Found new cell, save info and increment counter		    						 
								   setPatternGridCell(
										   PATTERN_GRID,
										   iUVdir,
										   centerXY,
										   // specify wave vectors from the parent cell, will recalculate (if possible)
										   wv[0], //null, //  double [] wv1,
										   wv[1]); //null); //  double [] wv2);
								   if (cleanup.get()) addedCells.getAndIncrement();
								   if (debugLevel>debugThreshold) {
									   dbgStr+="==>added"+iUVdir[0]+"/"+iUVdir[1]+", dir"+iUVdir[2];
									   System.out.println(dbgStr);
								   }

							   }
						   }
					   };
				   }
				   startAndJoin(threads);
				   // remove invalid cells from the list
				   for (int i=waveFrontList.size()-1;i>=0;i--) {
					   if (!isCellValid(PATTERN_GRID,getWaveList (waveFrontList,i))) {
						   // Make that cell "new", so it will be tried again, until wave will not touch it. So when more neigbors will be defined, previously failed
						   // cell will be retried			    		
						   clearPatternGridCell(PATTERN_GRID,getWaveList (waveFrontList,i));
						   waveFrontList.remove(i);
						   if (debugLevel>1) System.out.println("XXX->clear invalid ("+i+")");
					   }
				   }
				   //If anything was added during the layer - calculate and fill in wave vectors here (they are set to the same as in the parent cell)
				   // this code is not needed now, the wave vectors are recalculated from x/y locations, the stored ones are not used    			
				   if (waveFrontList.size()>0) {
					   for (int listIndex=0;listIndex<waveFrontList.size();listIndex++) {
						   uvdir= getWaveList (waveFrontList,listIndex);
						   if (debugLevel>1) System.out.println("<---= uvdir= "+uvdir[0]+",  "+uvdir[1]+",  "+uvdir[2]);
						   thisCell=PATTERN_GRID[uvdir[1]][uvdir[0]];
						   neibBits=0;
						   for (dir=0;dir<directionsUV8.length;dir++) {
							   neibors[dir]=null;
							   iUV[0]=uvdir[0]+directionsUV8[dir][0];
							   iUV[1]=uvdir[1]+directionsUV8[dir][1];
							   if ((iUV[0]<0) || (iUV[1]<0) ||
									   (iUV[0]>=distortionParameters.gridSize) || (iUV[1]>=distortionParameters.gridSize)) continue; // don't fit into UV grid
							   if (isCellValid(PATTERN_GRID,iUV)) {
								   neibors[dir]= new double [2][2];
								   otherCell=PATTERN_GRID[iUV[1]][iUV[0]];
								   neibors[dir][0][0]=0.5*directionsUV8[dir][0];  // u
								   neibors[dir][0][1]=0.5*directionsUV8[dir][1];  // v
								   neibors[dir][1][0]=otherCell[0][0]-thisCell[0][0];  // x
								   neibors[dir][1][1]=otherCell[0][1]-thisCell[0][1];  // y
								   neibBits |= directionsBits8[dir];
							   }
						   }
						   int i=Integer.bitCount(neibBits); 
						   if (debugLevel>1) System.out.println("neibBits="+neibBits+", number of bits= "+i);
						   if (i>1) {
							   double[][] wv= waveVectorsFromNeib(neibors);
							   setPatternGridCell(
									   PATTERN_GRID,
									   uvdir,
									   null, // XY already set
									   wv[0], 
									   wv[1]);

							   if (debugLevel>1) System.out.println("==+> number of bits:"+i+
									   " vw00="+IJ.d2s(wv[0][0],5)+" vw01="+IJ.d2s(wv[0][1],5)+
									   " vw10="+IJ.d2s(wv[1][0],5)+" vw11="+IJ.d2s(wv[1][1],5));
							   //				    	wv= WaveVectorsFromNeib(neibors);
							   //
							   // 		   vectors: [num_vector][0][0] - U
							   //                  [num_vector][0][1] - V
							   //                  [num_vector][1][0] - X
							   //                  [num_vector][1][1] - Y
							   //                  [num_vector] == null - skip
							   //
						   }

					   }
				   } else if (initialWave!=null){
					   if ((global_debug_level>0) && (initialWave!=null)) {
						   System.out.println("No sense to initiate clenaup during first layer"); // problems heer?
					   }
				   } else if (!cleanup.get() || (addedCells.get()>0)) { // create list of the defined cells on the border (if wave died)
					   cleanup.set(true);
					  // debug
//					   if ((global_debug_level>0) && (initialWave!=null)) {
//						   System.out.println("clenaup during first layer"); // problems heer?
//						   System.out.println("Added "+addedCells.get()+" during border cleanup on first layer");
//					   }
					   if ((debugLevel>1) && !cleanup.get())  System.out.println("Added "+addedCells.get()+" during border cleanup"); // can not get here
					   addedCells.set(0);
					   umax=0;
					   vmax=0;
					   vmin=PATTERN_GRID.length;
					   umin=PATTERN_GRID[0].length;
					   for (int i=0;i<PATTERN_GRID.length;i++) for (int j=0;j<PATTERN_GRID[i].length;j++) {
						   if ((PATTERN_GRID[i][j]!=null) && (PATTERN_GRID[i][j][0]!=null)) {
							   if (vmin > i) vmin = i;
							   if (vmax < i) vmax = i;
							   if (umin > j) umin = j;
							   if (umax < j) umax = j;
						   }
					   }
					   int [] uvNew=new int [2];
					   for (uvNew[1]=vmin;uvNew[1]<=vmax;uvNew[1]++) for (uvNew[0]=umin;uvNew[0]<=umax;uvNew[0]++) if (isCellDefined(PATTERN_GRID,uvNew)){
						   for (dir=0;dir<directionsUV.length;dir++) {
							   iUV[0]=uvNew[0]+directionsUV[dir][0];
							   iUV[1]=uvNew[1]+directionsUV[dir][1];
							   if (!isCellDefined(PATTERN_GRID,iUV) && !isCellDeleted(PATTERN_GRID,iUV)){
								   putInWaveList (waveFrontList, uvNew, dir); // direction does not matter here
								   break;
							   }
						   }
					   }
					   if (global_debug_level>1) System.out.println("***** Starting cleanup, wave length="+waveFrontList.size()); //????
				   }
				   // end of layer - it is a hack below, marking initial wave to recalculate it from neighbors	
				   if (initialWave!=null){ // just after the first layer (usually one cell) - delete it and add next time - otherwise first one needs large correction
					   if (global_debug_level>1) {
						   System.out.println("Removing "+initialWave.size()+" initial wave cells, waveFrontList.size()="+waveFrontList.size());
						   for (int listIndex=0;listIndex<waveFrontList.size();listIndex++) {
							   int [] dbg_uvdir= getWaveList (waveFrontList,listIndex);
							   System.out.println("waveFrontList["+listIndex+"]: "+dbg_uvdir[0]+"/"+dbg_uvdir[1]+" dir="+dbg_uvdir[2]);
						   }
					   }
					   
					   while (initialWave.size()>0){
						   uvdir= getWaveList (initialWave,0);
//						   clearPatternGridCell(PATTERN_GRID, uvdir);
						   if (global_debug_level>1)
							   System.out.println("Removing x="+uvdir[0]+" y="+uvdir[1]+" dir="+uvdir[2]);
						   markDeletedPatternGridCell(PATTERN_GRID, uvdir);
						   initialWave.remove(0);
					   }
					   initialWave=null;
				   }
			   }//while (waveFrontList.size()>0)
			   debugLevel=was_debug_level;
			   /*		   
		   if (updating){
			   return PATTERN_GRID; // no need to crop the array, it should not change
		   }
			    */		   
			   umax=0;
			   vmax=0;
			   vmin=PATTERN_GRID.length;
			   umin=PATTERN_GRID[0].length;
			   numDefinedCells=0;
			   for (int i=0;i<PATTERN_GRID.length;i++) for (int j=0;j<PATTERN_GRID[i].length;j++) {
				   if ((PATTERN_GRID[i][j]!=null) && (PATTERN_GRID[i][j][0]!=null)) {
					   if (vmin > i) vmin = i;
					   if (vmax < i) vmax = i;
					   if (umin > j) umin = j;
					   if (umax < j) umax = j;
					   numDefinedCells++;
				   }
			   }

//			   if (updating){
//				   return numDefinedCells; // no need to crop the array, it should not change
//			   }
			   if (!updating){
				   if (vmin>vmax){
					   this.PATTERN_GRID=null;
					   continue; // try next in queue if available
//					   return 0; // null; // nothing found
				   }
				   // Add extra margins for future extrapolation
				   int extra=distortionParameters.numberExtrapolated -((distortionParameters.removeLast)?1:0);
				   vmin-=extra;
				   if (vmin<0) vmin=0;
				   umin-=extra;
				   if (umin<0) umin=0;
				   vmax+=extra;
				   if (vmax>=PATTERN_GRID.length) vmax=PATTERN_GRID.length-1;
				   umax+=extra;
				   if (umax>=PATTERN_GRID[0].length) umax=PATTERN_GRID[0].length-1;

				   // make sure the odd/even uv does not change (and so the cross phases defined by U & V)
				   vmin &= ~1;
				   umin &= ~1;
				   // make width/height even (not needed)
				   umax |=1;
				   vmax |=1;
				   // remove  margins 		 
				   double [][][][] result = new double [vmax-vmin+1][umax-umin+1][][];
				   for (int i=vmin;i<=vmax;i++) for (int j=umin;j<=umax;j++) {
					   if ((PATTERN_GRID[i][j]!=null) && (PATTERN_GRID[i][j][0]!=null)) {
						   result[i-vmin][j-umin]=PATTERN_GRID[i][j];
					   } else result[i-vmin][j-umin]=null;
				   }
				   this.debugUV[0]-=umin;
				   this.debugUV[1]-=umin;
				   if (debugLevel>2) System.out.println("debugUV[] updated to {"+this.debugUV[0]+","+this.debugUV[1]+"}");

				   if (debugLevel>1) System.out.println("Total number of defined cells="+numDefinedCells);
				   this.PATTERN_GRID=result;
			   }
			   // more tests here (moved from the caller) that result is good
			   double averageGridPeriod=Double.NaN;
			   double [] gridPeriods={Double.NaN,Double.NaN};
			   if (this.PATTERN_GRID!=null) {
				   averageGridPeriod=averageGridPeriod(this.PATTERN_GRID);
				   gridPeriods=averageGridPeriods(this.PATTERN_GRID); // {min,max}
			   }
			   if (debugLevel>0){
				   System.out.println("Pattern period="+averageGridPeriod+" {"+gridPeriods[0]+","+gridPeriods[1]+"}"+
			   						   " limits are set to :"+patternDetectParameters.minGridPeriod+","+patternDetectParameters.maxGridPeriod);
			   }
			   if (!Double.isNaN(averageGridPeriod)) {
				   if (!Double.isNaN(patternDetectParameters.minGridPeriod) &&
						   (patternDetectParameters.minGridPeriod>0.0) &&
						   (averageGridPeriod<patternDetectParameters.minGridPeriod)){
					   if (debugLevel>0){
						   System.out.println("Pattern is too small, period="+averageGridPeriod+
							   	   " minimal="+patternDetectParameters.minGridPeriod);
					   }
					   continue; // bad grid
				   }
				   if (!Double.isNaN(patternDetectParameters.maxGridPeriod) &&
						   (patternDetectParameters.maxGridPeriod>0.0) &&
						   (averageGridPeriod>patternDetectParameters.maxGridPeriod)){
					   if (debugLevel>0){
						   System.out.println("Pattern is too large, period="+averageGridPeriod+
							   	   " maximal="+patternDetectParameters.maxGridPeriod);
					   }
					   continue; // bad grid
				   }
			   }
			   if (    
					   (distortionParameters.minimalPatternCluster<=0) || // minimal cluster size is disabled
					   (distortionParameters.scaleMinimalInitialContrast<=0) || // minimal cluster size is disabled
					   ((numDefinedCells==0) && fromVeryBeginning)|| // no cells detected at all, starting from the very beginning
					   (numDefinedCells>=distortionParameters.minimalPatternCluster)  // detected enough cells
					   )  {
				   return numDefinedCells;
			   }
			   if (roi!=null){ // don't use this feature with ROI as it can be small
				   if (global_debug_level>0) System.out.println("Initial pattern cluster is small ("+numDefinedCells+"), but ROI is set - no retries");
				   {
					   return numDefinedCells;
				   }
			   }
		   } // next node in queue
// failed to find - deal in the caller
		   
		   /*
		   boolean someLeft=false;
		   int startScanIndex=0;
		   for (startScanIndex=3;startScanIndex<triedIndices.length;startScanIndex++) if (!triedIndices[startScanIndex]){
			   someLeft=true;
			   break;
		   }
		   
		   if (someLeft) {
			   if (global_debug_level>0){
				   System.out.println("Initial pattern cluster is too small ("+numDefinedCells+
						   "), continuing scanning from index "+startScanIndex);
			   }
		   } else {
			   //							   startScanIndex[0]=0;
			   System.out.println("Last pattern cluster was too small, adjusting the minimal contrast from "+
					   IJ.d2s(distortionParameters.correlationMinInitialContrast,3)+
					   " to "+IJ.d2s(distortionParameters.correlationMinInitialContrast*distortionParameters.scaleMinimalInitialContrast,3));
			   distortionParameters.correlationMinInitialContrast*=distortionParameters.scaleMinimalInitialContrast;
			   for (int i=0;i<triedIndices.length;i++) triedIndices[i]=(i<3); // mark first 3 as if they are already used
			   fromVeryBeginning=true;
		   }
		   */
		   return 0; // none
	   }
	   
	   public double [][] findPatternCandidate_old(
//			   final int [] startScanIndex, // [0] will be updated
			   final boolean [] triedIndices, // which indices are already tried
			   final int startScanIndex,
			   final int tryHor,
			   final int tryVert,
//			   final int numTries,
			   final Rectangle selection,
			   final DistortionParameters distortionParameters, //
			   final MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
			   final SimulationPattern.SimulParameters  thisSimulParameters,
			   final MatchSimulatedPattern matchSimulatedPattern,
			   final MatchSimulatedPattern matchSimulatedPatternCorr,
			   final SimulationPattern simulationPattern,
			   final boolean equalizeGreens,
			   final ImagePlus imp, // image to process
			   final double [] bPattern,
			   final double [] windowFunction,
			   final double [] windowFunctionCorr,
			   final double [] windowFunctionCorr2,
			   final double [] windowFunctionCorr4,
			   final double[][] locsNeib, // which neibors to try (here - just the center)
			   final int threadsMax,
			   final boolean updateStatus,
			   final int debugLevel
			   ){
		   final Thread[] threads = newThreadArray(threadsMax);
//		   final AtomicInteger seqNumber = new AtomicInteger(3);
//		   final AtomicInteger seqNumber = new AtomicInteger(startScanIndex[0]);
		   final AtomicInteger seqNumber = new AtomicInteger(startScanIndex);
//		   startScanIndex
		   final AtomicBoolean nodeSet=new AtomicBoolean(false);
		   final double [][][] nodeRef= new double[1][][];
		   nodeRef[0]=null;
//		   System.out.println("===== findPatternCandidate(): startScanIndex="+startScanIndex);
//		   for (int i=0;i<triedIndices.length;i++) System.out.print(triedIndices[i]?"+":"-");
//		   System.out.println();
		   for (int ithread = 0; ithread < threads.length; ithread++) {
			   threads[ithread] = new Thread() {
				   public void run() {
					   int nbh, nbv, nh, nv, nb;
					   double [] point = new double[2];
//					   for (int n=seqNumber.getAndIncrement(); n< numTries;n=seqNumber.getAndIncrement()){
					   for (int n=seqNumber.getAndIncrement(); n<(triedIndices.length-1); n=seqNumber.getAndIncrement()) if (!triedIndices[n]){
						   if (nodeSet.get()) break; // already set
						   nbh=tryHor-1;
						   nbv=tryVert-1;
						   nh=0;
						   nv=0;
						   nb=0;
						   while (nb<(tryHor+tryVert)) {
							   if (nbh>=0) {
								   if ((n & (1<<nb))!=0) nh |= 1<<nbh;
								   nbh--;
								   nb++;
							   }
							   if (nbv>=0) {
								   if ((n & (1<<nb))!=0) nv |= 1<<nbv;
								   nbv--;
								   nb++;
							   }
						   }
						   if (debugLevel>2) System.out.println("Searching, n="+n+", nv="+nv+", nh="+nh+", nb="+nb);
						   if ((nv>0) && (nh>0)) {
							   point[0]=(selection.x+nh*selection.width/(1<<tryHor)) & ~1;
							   point[1]=(selection.y+nv*selection.height/(1<<tryVert)) & ~1;
							   if (debugLevel>2) System.out.println("trying xc="+point[0]+", yc="+point[1]+"(nv="+nv+", nh="+nh+")");
//							   if ((debugLevel>2) && (n==3)) debugLevel=3; // show debug images for the first point
							   double [][] node=tryPattern (
									   point, // xy to try
									   distortionParameters, //no control of the displacement
									   patternDetectParameters,
									   thisSimulParameters,
									   matchSimulatedPattern,
									   matchSimulatedPatternCorr,
									   simulationPattern,
									   equalizeGreens,
									   imp, // image to process
									   bPattern,
									   windowFunction,
									   windowFunctionCorr,
									   windowFunctionCorr2,
									   windowFunctionCorr4,
									   locsNeib, // which neibors to try (here - just the center)
									   null // dbgStr
							   );
							   if ((node!=null) && (node[0]!=null)) {
								   if (nodeSet.compareAndSet(false,true)) {
									   nodeRef[0]=node;
//									   startScanIndex[0]=seqNumber.get();
									   triedIndices[n]=true; //found and will be processed
									   if (debugLevel>1)  System.out.println("probing "+n);
									   break;
								   } else {
									   if (debugLevel>1)  System.out.println("missed "+n);
								   }
							   } else {
								   triedIndices[n]=true; // tried, but nothing found
//								   System.out.println("empty "+n);
							   }
						   } else {
							   triedIndices[n]=true; // tried, but nothing found
//							   System.out.println("wrong "+n);
						   }
					   }
				   }
			   };
		   }
		   startAndJoin(threads);
//		   if (nodeRef[0]==null) startScanIndex[0]=numTries; // all used
		   return nodeRef[0];
	   }

	   class GridNode {
		   double [][] node;
		   public GridNode(double [][] node){
			   this.node=node;
		   }
		   public double [][] getNode(){
			   return this.node;
		   }
	   }

//	   private double [][][] findPatternCandidates(
	   private  Queue<GridNode> findPatternCandidates(
			  			   
//			   final int [] startScanIndex, // [0] will be updated
			   final boolean [] triedIndices, // which indices are already tried
			   final int startScanIndex,
			   final int tryHor,
			   final int tryVert,
//			   final int numTries,
			   final Rectangle selection,
			   final DistortionParameters distortionParameters, //
			   final MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
			   final SimulationPattern.SimulParameters  thisSimulParameters,
			   final MatchSimulatedPattern matchSimulatedPattern,
			   final MatchSimulatedPattern matchSimulatedPatternCorr,
			   final SimulationPattern simulationPattern,
			   final boolean equalizeGreens,
			   final ImagePlus imp, // image to process
			   final double [] bPattern,
			   final double [] windowFunction,
			   final double [] windowFunctionCorr,
			   final double [] windowFunctionCorr2,
			   final double [] windowFunctionCorr4,
			   final double[][] locsNeib, // which neibors to try (here - just the center)
			   final int threadsMax,
			   final boolean updateStatus,
			   final int debugLevel
			   ){
		   final int debugThreshold=1;
		   if ((debugLevel>debugThreshold) && ((debugLevel>1) || (startScanIndex>3))) {
			   int debugNumLeft=0;
			   for (boolean b:triedIndices) if (!b) debugNumLeft++;
			   System.out.println("findPatternCandidates(), startScanIndex= "+startScanIndex+",triedIndices.length="+triedIndices.length+" debugNumLeft="+debugNumLeft);
		   }

		   final Thread[] threads = newThreadArray(threadsMax);
		   final AtomicInteger seqNumber = new AtomicInteger(startScanIndex);
//		   final AtomicBoolean nodeSet=new AtomicBoolean(false);
//		   final double [][][] nodeRef= new double[1][][];
//		   nodeRef[0]=null;
		   
		   final AtomicInteger debugNumThreadAtomic= new AtomicInteger(0);

		   final Queue<GridNode> nodeQueue = new ConcurrentLinkedQueue<GridNode>();		   
		   for (int ithread = 0; ithread < threads.length; ithread++) {
			   threads[ithread] = new Thread() {
				   public void run() {
					   int nbh, nbv, nh, nv, nb;
					   double [] point = new double[2];
//					   SimulationPattern simulationPatternClone=simulationPattern.clone();
					   int debugNumThread=debugNumThreadAtomic.getAndIncrement();
					   for (int n=seqNumber.getAndIncrement(); n<(triedIndices.length-1); n=seqNumber.getAndIncrement()) if (!triedIndices[n]){
						   if (!nodeQueue.isEmpty()) break; // already set at least one element - does it work?
						   nbh=tryHor-1;
						   nbv=tryVert-1;
						   nh=0;
						   nv=0;
						   nb=0;
						   while (nb<(tryHor+tryVert)) {
							   if (nbh>=0) {
								   if ((n & (1<<nb))!=0) nh |= 1<<nbh;
								   nbh--;
								   nb++;
							   }
							   if (nbv>=0) {
								   if ((n & (1<<nb))!=0) nv |= 1<<nbv;
								   nbv--;
								   nb++;
							   }
						   }
						   if (debugLevel>2) System.out.println("Searching, n="+n+", nv="+nv+", nh="+nh+", nb="+nb );
						   if ((nv>0) && (nh>0)) {
							   point[0]=(selection.x+nh*selection.width/(1<<tryHor)) & ~1;
							   point[1]=(selection.y+nv*selection.height/(1<<tryVert)) & ~1;
							   if (debugLevel>2) System.out.println("trying xc="+point[0]+", yc="+point[1]+"(nv="+nv+", nh="+nh+")");
//							   if ((debugLevel>2) && (n==3)) debugLevel=3; // show debug images for the first point
							   if (debugLevel>2) System.out.println(debugNumThread+":"+n+" >> ");
							   double [][] node=tryPattern (
									   point, // xy to try
									   distortionParameters, //no control of the displacement
									   patternDetectParameters,
									   thisSimulParameters,
									   matchSimulatedPattern,
									   matchSimulatedPatternCorr,
									   simulationPattern,
									   equalizeGreens,
									   imp, // image to process
									   bPattern,
									   windowFunction,
									   windowFunctionCorr,
									   windowFunctionCorr2,
									   windowFunctionCorr4,
									   locsNeib, // which neibors to try (here - just the center)
									   (debugLevel>debugThreshold)?(""+debugNumThread+":"+n+", nv="+nv+", nh="+nh+", nb="+nb+" "+point[0]+"/"+point[1]):null
							   );
//							   if (debugLevel>0) System.out.println(debugNumThread+":"+n+", nv="+nv+", nh="+nh+", nb="+nb+" "+point[0]+"/"+point[1]+" "+(node!=null));
							   if ((node!=null) && (node[0]!=null)) {
								   nodeQueue.add(new GridNode(node));
//								   if (debugLevel>1)  System.out.println("adding candidate "+n+" x0="+point[0]+" y0="+point[1]+" -> "+ node[0][0]+"/"+node[0][1]);
								   if (debugLevel>debugThreshold)  System.out.println("adding candidate "+n+" x0="+point[0]+" y0="+point[1]+" -> "+ node[0][0]+"/"+node[0][1]+" seqNumber.get()="+seqNumber.get()+" n="+n);
							   }
						   } else {
							   if (debugLevel>debugThreshold) System.out.println("-----"+debugNumThread+":"+n+", nv="+nv+", nh="+nh);
						   }
						   triedIndices[n]=true; // regardless - good or bad
					   }
				   }
			   };
		   }
		   startAndJoin(threads);
//		   if (nodeQueue.isEmpty()) return null;
		   if (debugLevel>debugThreshold){
			   System.out.println("seqNumber after join is "+seqNumber.get());
		   }
		   if (seqNumber.get()>=(triedIndices.length-1) ) triedIndices[triedIndices.length-1]=true; // all tried
		   return nodeQueue; // never null, may be empty
//		   double [][][] nodes = new double [nodeQueue.size()][][];
//		   for (int i=0;i<nodes.length;i++) nodes[i]=nodeQueue.poll().getNode();
//		   return nodes;
	   }
	   
/* ================================================================*/
	   public void scaleContrast(double scale){
		   for (double [][][] patternRow:this.PATTERN_GRID){
			   if (patternRow!=null) for (double [][] node:patternRow){
				   if ((node!=null) && (node.length>0) && (node[0]!=null) && (node[0].length>2)) {
					   node[0][2]*=scale; 
				   }
			   }
		   }
	   }
 /* ================================================================*/
	   public double refineDistortionCorrelation (
			   final DistortionParameters distortionParameters, //
			   final MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
			   final SimulationPattern.SimulParameters  simulParameters,
			   final boolean equalizeGreens,
			   final ImagePlus imp, // image to process
			   final double maxCorr, // maximal allowed correction, in pixels (0.0) - any
			   final int threadsMax,
			   final boolean updateStatus,
			   final int debug_level){// debug level used inside loops
		    scaleContrast(distortionParameters.scaleFirstPassContrast);
		    final double [][][][] patternGrid=this.PATTERN_GRID;
			final int debugThreshold=1;
			final Rectangle selection=new Rectangle(0, 0, imp.getWidth(), imp.getHeight());
			MatchSimulatedPattern matchSimulatedPatternCorr=new MatchSimulatedPattern(distortionParameters.correlationSize);
			matchSimulatedPatternCorr.debugLevel=debugLevel;
			SimulationPattern simulationPattern= new SimulationPattern();
			final SimulationPattern.SimulParameters  thisSimulParameters=simulParameters.clone();
			thisSimulParameters.subdiv=distortionParameters.patternSubdiv;
			final double [] bPattern= simulationPattern.patternGenerator(simulParameters); // reuse pattern for next time
			/*
			final double [] windowFunctionCorr= initWindowFunction(  distortionParameters.correlationSize,distortionParameters.correlationGaussWidth);
			final double [] windowFunctionCorr2=initWindowFunction(2*distortionParameters.correlationSize,
					 (distortionParameters.absoluteCorrelationGaussWidth?0.5:1.0)*distortionParameters.correlationGaussWidth);
			final double [] windowFunctionCorr4=initWindowFunction(4*distortionParameters.correlationSize,
					 (distortionParameters.absoluteCorrelationGaussWidth?0.25:1.0)*distortionParameters.correlationGaussWidth);
					 */
			 final double [] windowFunctionCorr= initWindowFunction(
					 distortionParameters.correlationSize,
					 distortionParameters.correlationGaussWidth,
					 distortionParameters.zeros);
			 final double [] windowFunctionCorr2=initWindowFunction(
					 2*distortionParameters.correlationSize,
					 (distortionParameters.absoluteCorrelationGaussWidth?0.5:1.0)*distortionParameters.correlationGaussWidth,
					 distortionParameters.zeros);
			 final double [] windowFunctionCorr4=initWindowFunction(
					 4*distortionParameters.correlationSize,
					 (distortionParameters.absoluteCorrelationGaussWidth?0.25:1.0)*distortionParameters.correlationGaussWidth,
					 distortionParameters.zeros);
			final int height=patternGrid.length;
			final int width=(height>0)?patternGrid[0].length:0; // oob 0??
			final Thread[] threads = newThreadArray(threadsMax);
			int was_debug_level=debugLevel;
			final int debugOnLevel=(debug_level>0)?3:0;
			final double [][] locsNeib=calcNeibLocsWeights (
					   distortionParameters,
					   distortionParameters.correlationAverageOnRefine);
			debugLevel=debug_level;
			if (debugLevel>1)  System.out.println("Refining correlations, width= "+width+", height= "+height);
			final double [][] extrapolationWeights=generateWeights (
					 distortionParameters.correlationWeightSigma,
					 distortionParameters.correlationRadiusScale); //  if 0 - use sigma as radius, inside - 1.0, outside 0.0. If >0 - size of array n*sigma
            int i=-1;
			int [] iUV=  new int [2];
			
            for (i=0;i<(width*height);i++) {
				iUV[0]=i % width;
				iUV[1]=i / width;
            	if (isCellDefined(patternGrid,iUV)) break;
            }
            if (i<0) return Double.NaN; // no defined nodes at all
            final int startCell=i;
			final AtomicInteger cellNum = new AtomicInteger(startCell);
			final AtomicInteger cellNumDoneAtomic = new AtomicInteger(startCell);
			int dc=i;
			//Debug only
            if (debugOnLevel>debugThreshold) {
    		    final int [][] directionsUV8= {{1,0},{0,1},{-1,0},{0,-1},{1,1},{-1,1},{-1,-1},{1,-1}}; // first 8 should be the same as in directionsUV
            	for (i=dc;i<(width*height);i++) {
            		iUV[0]=i % width;
            		iUV[1]=i / width;
            		if ((iUV[0]>0) && (iUV[1]>0) && (iUV[0]<(width-1)) && (iUV[1]<(height-1)) &&
                     		 isCellDefined(patternGrid,iUV) &&
                    		 isCellDefined(patternGrid,matrix2x2_add(iUV,directionsUV8[0])) &&
                    		 isCellDefined(patternGrid,matrix2x2_add(iUV,directionsUV8[1])) &&
                    		 isCellDefined(patternGrid,matrix2x2_add(iUV,directionsUV8[2])) &&
                    		 isCellDefined(patternGrid,matrix2x2_add(iUV,directionsUV8[3])) &&
                    		 isCellDefined(patternGrid,matrix2x2_add(iUV,directionsUV8[4])) &&
                    		 isCellDefined(patternGrid,matrix2x2_add(iUV,directionsUV8[5])) &&
                    		 isCellDefined(patternGrid,matrix2x2_add(iUV,directionsUV8[6])) &&
                    		 isCellDefined(patternGrid,matrix2x2_add(iUV,directionsUV8[7]))) {
            			dc=i;
            			System.out.println("not used: debug U="+iUV[0]+" V="+iUV[1]+" index="+dc);
            			break;
            		 }
            	}
            }
           
           final int debugCell=debugUV[0]+debugUV[1]*width;
           /**
            * That was wrong to update currently calculated grid, so the newGrid will be calculated instead, then copied altogether
            */
           final double [][][] newGrid=new double [height][width][];
           final boolean refineInPlace=distortionParameters.refineInPlace;
           for (int v=0;v<height;v++) for (int u=0;u<width;u++) newGrid[v][u]=null;    
           IJ.showProgress(0);
		   if (updateStatus) IJ.showStatus("Refining correlations");
           //           MinMaxSync  minMaxSync=new MinMaxSync();
           for (int ithread = 0; ithread < threads.length; ithread++) {
        	   threads[ithread] = new Thread() {
        		   public void run() {
        			   SimulationPattern simulationPattern= new SimulationPattern(bPattern);
        			   MatchSimulatedPattern matchSimulatedPatternCorr=new MatchSimulatedPattern(distortionParameters.correlationSize);
        			   DoubleFHT fht_instance =new DoubleFHT(); // provide DoubleFHT instance to save on initializations (or null)
        			   int [] iUV=  new int [2];
        			   boolean nowDebugCell=false;
        			   for (int ncell=cellNum.getAndIncrement(); ncell<(width*height);ncell=cellNum.getAndIncrement()){
        				   nowDebugCell=(ncell==debugCell);
        				   int thisDebug= (nowDebugCell)?debugOnLevel:debugLevel;
        				   iUV[0]=ncell % width;
        				   iUV[1]=ncell / width;

        				   if (nowDebugCell && (thisDebug>1))  System.out.println(">>>>>>>>>>> Debug cell, thisDebug="+thisDebug+", iUV={"+iUV[0]+","+iUV[1]+"}");
//        				   if ((updateStatus) && (iUV[0]==0)) IJ.showStatus("Refining correlations, row "+(iUV[1]+1)+" of "+height);
        				   if ((thisDebug>1) && ((iUV[0]==0) || (nowDebugCell))) System.out.println("Refining correlations, row "+(iUV[1]+1)+" of "+height);
        				   if (!isCellDefined(patternGrid,iUV)) {
        					   cellNumDoneAtomic.getAndIncrement();
        					   continue;
        				   }
        				   Rectangle centerCross=correlationSelection(
        						   patternGrid[iUV[1]][iUV[0]][0], // initial coordinates of the pattern cross point
        						   distortionParameters.correlationSize);
        				   if (!selection.contains(centerCross)) {
        					   cellNumDoneAtomic.getAndIncrement();
        					   continue; // the correlation selection does not fit into WOI selection ??? WOI is now full image
        				   }
        				   //Proceed with correlation	
        				   //TODO: add contrast verification ? Maximal distance from expected? (return null if failed)

        				   double [][] simulPars=getSimulationParametersFromGrid(
        						   PATTERN_GRID,
        						   iUV,          // U,V of the center point (for which the simulation pattern should be built
        						   null,          // x,y of the center point (or null to use grid)
        						   extrapolationWeights, // quadrant of sample weights
        						   !distortionParameters.useQuadratic,  // use linear approximation (instead of quadratic)
        						   1.0E-10,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
        						   1.0E-20  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
        				   );
        				   if ((thisDebug>debugThreshold) && (simulPars!=null)) {
        					   String dbgStr="";
        					   dbgStr+=" {"+IJ.d2s(simulPars[0][0],5)+"/"+IJ.d2s(simulPars[0][1],5)+"/"+IJ.d2s(simulPars[0][2],5);
        					   if (simulPars[0].length>3) dbgStr+="/"+IJ.d2s(simulPars[0][3],7)+"/"+IJ.d2s(simulPars[0][4],7)+"/"+IJ.d2s(simulPars[0][5],7)+"}";
        					   dbgStr+=" {"+IJ.d2s(simulPars[1][0],5)+"/"+IJ.d2s(simulPars[1][1],5)+"/"+IJ.d2s(simulPars[1][2],5);
        					   if (simulPars[1].length>3) dbgStr+="/"+IJ.d2s(simulPars[1][3],7)+"/"+IJ.d2s(simulPars[1][4],7)+"/"+IJ.d2s(simulPars[1][5],7)+"}";
        					   System.out.println(dbgStr);
        					   if (nowDebugCell && (thisDebug>3)) {
        						   double [] XY={PATTERN_GRID[iUV[1]][iUV[0]][0][0]-32.0,PATTERN_GRID[iUV[1]][iUV[0]][0][1]-32.0};
        						   System.out.println("iUV[0]="+iUV[0]+"iUV[1]="+iUV[1]);
        						   System.out.println("CC : "+IJ.d2s(PATTERN_GRID[iUV[1]  ][iUV[0]  ][0][0]-XY[0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]  ][iUV[0]  ][0][1]-XY[1],3));
        						   System.out.println("TL : "+IJ.d2s(PATTERN_GRID[iUV[1]-1][iUV[0]-1][0][0]-XY[0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]-1][iUV[0]-1][0][1]-XY[1],3)); // sometimes throws
        						   System.out.println("TC : "+IJ.d2s(PATTERN_GRID[iUV[1]-1][iUV[0]  ][0][0]-XY[0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]-1][iUV[0]  ][0][1]-XY[1],3));
        						   System.out.println("TR : "+IJ.d2s(PATTERN_GRID[iUV[1]-1][iUV[0]+1][0][0]-XY[0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]-1][iUV[0]+1][0][1]-XY[1],3));
        						   System.out.println("CR : "+IJ.d2s(PATTERN_GRID[iUV[1]  ][iUV[0]+1][0][0]-XY[0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]  ][iUV[0]+1][0][1]-XY[1],3));
        						   System.out.println("BR : "+IJ.d2s(PATTERN_GRID[iUV[1]+1][iUV[0]+1][0][0]-XY[0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]+1][iUV[0]+1][0][1]-XY[1],3));
        						   System.out.println("BC : "+IJ.d2s(PATTERN_GRID[iUV[1]+1][iUV[0]  ][0][0]-XY[0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]+1][iUV[0]  ][0][1]-XY[1],3));
        						   System.out.println("BL : "+IJ.d2s(PATTERN_GRID[iUV[1]+1][iUV[0]-1][0][0]-XY[0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]+1][iUV[0]-1][0][1]-XY[1],3));
        						   System.out.println("CL : "+IJ.d2s(PATTERN_GRID[iUV[1]  ][iUV[0]-1][0][0]-XY[0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]  ][iUV[0]-1][0][1]-XY[1],3));

        						   System.out.println("CC : "+IJ.d2s(PATTERN_GRID[iUV[1]  ][iUV[0]  ][0][0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]  ][iUV[0]  ][0][1],3));
        						   System.out.println("TL : "+IJ.d2s(PATTERN_GRID[iUV[1]-1][iUV[0]-1][0][0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]-1][iUV[0]-1][0][1],3));
        						   System.out.println("TC : "+IJ.d2s(PATTERN_GRID[iUV[1]-1][iUV[0]  ][0][0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]-1][iUV[0]  ][0][1],3));
        						   System.out.println("TR : "+IJ.d2s(PATTERN_GRID[iUV[1]-1][iUV[0]+1][0][0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]-1][iUV[0]+1][0][1],3));
        						   System.out.println("CR : "+IJ.d2s(PATTERN_GRID[iUV[1]  ][iUV[0]+1][0][0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]  ][iUV[0]+1][0][1],3));
        						   System.out.println("BR : "+IJ.d2s(PATTERN_GRID[iUV[1]+1][iUV[0]+1][0][0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]+1][iUV[0]+1][0][1],3));
        						   System.out.println("BC : "+IJ.d2s(PATTERN_GRID[iUV[1]+1][iUV[0]  ][0][0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]+1][iUV[0]  ][0][1],3));
        						   System.out.println("BL : "+IJ.d2s(PATTERN_GRID[iUV[1]+1][iUV[0]-1][0][0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]+1][iUV[0]-1][0][1],3));
        						   System.out.println("CL : "+IJ.d2s(PATTERN_GRID[iUV[1]  ][iUV[0]-1][0][0],3)+"/"+IJ.d2s(PATTERN_GRID[iUV[1]  ][iUV[0]-1][0][1],3));
        					   }
        				   }                                   

        				   //							if (nowDebugCell)correctedPatternCrossLocationAverage4(

        				   double [] centerXY=correctedPatternCrossLocation(
        						   patternGrid[iUV[1]][iUV[0]][0], // initial coordinates of the pattern cross point
        						   patternGrid[iUV[1]][iUV[0]][1][0],
        						   patternGrid[iUV[1]][iUV[0]][1][1],
        						   patternGrid[iUV[1]][iUV[0]][2][0],
        						   patternGrid[iUV[1]][iUV[0]][2][1],
        						   simulPars,
        						   imp,      // image data (Bayer mosaic)
        						   distortionParameters, //
        						   patternDetectParameters,
        						   matchSimulatedPatternCorr, // correlationSize
        						   thisSimulParameters,
        						   equalizeGreens,			
        						   windowFunctionCorr,
        						   windowFunctionCorr2,
        						   windowFunctionCorr4,
        						   simulationPattern,
        						   ((iUV[0]^iUV[1])&1)!=0, // if true - invert pattern
        						   fht_instance,
        						   distortionParameters.fastCorrelationOnFinalPass, //
        						   locsNeib,
        						   thisDebug, //thisDebug
        						   null);


        				   if (centerXY!=null){
        					   if (thisDebug>2) System.out.println("==>iUV={"+iUV[0]+",  "+iUV[1]+
        							   "}. "+patternGrid[iUV[1]][iUV[0]][0][0]+" / "+patternGrid[iUV[1]][iUV[0]][0][1]+" -> "+centerXY[0]+" / "+centerXY[1]);
        					   if (refineInPlace)	setPatternGridCell(
        							   patternGrid,
        							   iUV,
        							   centerXY,
        							   null, //  double [] wv1,
        							   null); //  double [] wv2);
        					   else newGrid[iUV[1]][iUV[0]]=centerXY.clone();          

        				   } else {
        					   if (debug_level>0){
        						   System.out.println("refineDistortionCorrelation(): failed to refine grid for U="+iUV[0]+" V="+iUV[1]+
        								   " X="+patternGrid[iUV[1]][iUV[0]][0][0]+" Y="+patternGrid[iUV[1]][iUV[0]][0][1]);
        					   }

        				   }
       					final int numFinished=cellNumDoneAtomic.getAndIncrement();
    					SwingUtilities.invokeLater(new Runnable() {
    						public void run() {
    							// Here, we can safely update the GUI
    							// because we'll be called from the
    							// event dispatch thread
    							IJ.showProgress(numFinished,width*height-1);
    						}
    					});
        			   }
        		   }
        	   };
           }
           startAndJoin(threads);
			IJ.showProgress(1.0); // turn off
           double maxActualCorr=0.0;
           double dx,dy,dist;
           if (!refineInPlace) {
        	   //maxCorr
        	   if (maxCorr>0.0) {
        		   // make sure there are no new undefined cells that were initially defined and no correction more than the limit
        		   int numUndefined=0,numFar=0;
        		   //					double maxCorr2=maxCorr*maxCorr;
        		   for (iUV[1]=0;iUV[1]<height;iUV[1]++) for (iUV[0]=0;iUV[0]<width;iUV[0]++) if (isCellDefined(patternGrid, iUV)){
        			   if (newGrid[iUV[1]][iUV[0]]==null){
        				   numUndefined++;
        			   } else {
        				   dx=newGrid[iUV[1]][iUV[0]][0] - patternGrid[iUV[1]][iUV[0]][0][0];
        				   dy=newGrid[iUV[1]][iUV[0]][1] - patternGrid[iUV[1]][iUV[0]][0][1];
        				   dist=Math.sqrt(dx*dx+dy*dy);
        				   if (dist>maxActualCorr) maxActualCorr=dist;
        				   if (dist>maxCorr) {
        					   numFar++;
        					   newGrid[iUV[1]][iUV[0]]=null;
        				   }
        			   }
        		   }
        		   if ((numUndefined>0) || (numFar>0)) {
        			   if (debug_level>0){
        				   System.out.println("refineDistortionCorrelation(): failed, number of undefined cells="+numUndefined+", number of too far cells="+numFar+" maxActualCorr="+maxActualCorr );
        			   }
        			   if (numUndefined>0) return -numUndefined; // negative - some cells undefined, no info about maximal correction returned
        			   return maxActualCorr; // no correction performed
        		   }

        	   } else {
        		   // only calculate maximal distance
        		   for (iUV[1]=0;iUV[1]<height;iUV[1]++) for (iUV[0]=0;iUV[0]<width;iUV[0]++) if (isCellDefined(patternGrid, iUV) && (newGrid[iUV[1]][iUV[0]]!=null)){
        			   dx=newGrid[iUV[1]][iUV[0]][0] - patternGrid[iUV[1]][iUV[0]][0][0];
        			   dy=newGrid[iUV[1]][iUV[0]][1] - patternGrid[iUV[1]][iUV[0]][0][1];
        			   dist=Math.sqrt(dx*dx+dy*dy);
        			   if (dist>maxActualCorr) maxActualCorr=dist;
        		   }

        	   }
        	   // Copy new values for the grid cells
        	   for (iUV[1]=0;iUV[1]<height;iUV[1]++) for (iUV[0]=0;iUV[0]<width;iUV[0]++) if (newGrid[iUV[1]][iUV[0]]!=null){
        		   setPatternGridCell(
        				   patternGrid,
        				   iUV,
        				   newGrid[iUV[1]][iUV[0]],
        				   null, //  double [] wv1,
        				   null); //  double [] wv2);

        	   }
        	   // correction is only calculated for simultaneous update (not for in-place)				
        	   if (debug_level>1){
        		   System.out.println("refineDistortionCorrelation(): maximal correction="+ maxActualCorr+" pixels");
        	   }
           }
           debugLevel=was_debug_level;
           return maxActualCorr;
	   }

	   
	   public class MinMaxSync {
    	  private double min;
    	  private double max;
    	  private boolean defined;
    	  public MinMaxSync(){
    		  defined=false;
			  min=Double.NaN;;
			  max=Double.NaN;;
    	  }
    	  public void reset(){
    		  defined=false;
    	  }
    	  public synchronized void minMax(double d){
    		  if (!defined){
    			  min=d;
    			  max=d;
    			  defined=true;
    		  } else {
    			  if (d>max) max=d;
    			  else if (d<min) min=d;
    		  }
    	  }
          public double getMin() {return min;}
          public double getMax() {return max;}
          public boolean isDefined() {return defined;}
      }
/* ================================================================*/
/*
        public boolean flatFieldCorrection=true; // compensate grid uneven intensity (vignetting, illumination)
        public double flatFieldExtarpolate=1.0;  // extrapolate flat field intensity map (relative to the average grid period)
        public double flatFieldBlur=1.0;   // blur the intensity map (relative to the average grid period)
	   
 */
	   public ImagePlus equalizeGridIntensity(
			   ImagePlus imp,
			   double [][][][] patternGrid,
			   DistortionParameters distortionParameters, //
			   boolean equalizeGreens,
			   int debugLevel,
			   boolean updateStatus,
			   int threadsMax	
	   ){
		 int dbgThreshold=1;
		   double [][] gridIntensity=calcGridIntensity(
				   4, //bayerComponent 
				   distortionParameters.correlationSize, // size
				   distortionParameters, //
				   equalizeGreens,
				   imp, // image to process
				   patternGrid,
				   threadsMax);// debug level used inside loops
		   if (debugLevel>(dbgThreshold+2)){
		     double [] testGI=new double [gridIntensity.length*gridIntensity[0].length];
		     int index=0;
		     for (int v=0;v<gridIntensity.length;v++) for (int u=0;u<gridIntensity[0].length;u++)testGI[index++]=gridIntensity[v][u];
		     this.SDFA_INSTANCE.showArrays(testGI, gridIntensity[0].length, gridIntensity.length, imp.getTitle()+"-GI");
		   }
           double [] fffg=calcFlatFieldForGrid(
				   gridIntensity,
				   patternGrid,
				   imp.getWidth(),
				   imp.getHeight());
           
           double averageGridPeriod=averageGridPeriod( patternGrid);
           
    	   int preShrink= (int) (averageGridPeriod * distortionParameters.flatFieldShrink);
    	   int expand=    (int) (averageGridPeriod * distortionParameters.flatFieldExpand);
    	   double extrapolateSigma=averageGridPeriod * distortionParameters.flatFieldSigmaRadius;
    	   double extrapolateKSigma=distortionParameters.flatFieldExtraRadius;
		   if (debugLevel>=(dbgThreshold+2)){
			     this.SDFA_INSTANCE.showArrays(fffg.clone(), imp.getWidth(), imp.getHeight(), imp.getTitle()+"-fffg");
		   }
    	   extrapolatePatternFlatFieldCorrection(
    			   fffg, //fieldXY,
    			   imp.getWidth(),
    			   preShrink,
    			   expand,
    			   extrapolateSigma,
    			   extrapolateKSigma,
    			   threadsMax,     //   100; // testing multi-threading, limit maximal number of threads
    			   updateStatus);

		   if (debugLevel>(dbgThreshold+2)){
			     this.SDFA_INSTANCE.showArrays(fffg.clone(), imp.getWidth(), imp.getHeight(), imp.getTitle()+"-extrapolated");
		   }
           if (distortionParameters.flatFieldBlur>0.0) {
        	   DoubleGaussianBlur gb=new DoubleGaussianBlur();
        	   gb.blurDouble(
        			   fffg,
        			   imp.getWidth(),
        			   imp.getHeight(),
        			   distortionParameters.flatFieldBlur*averageGridPeriod,
        			   distortionParameters.flatFieldBlur*averageGridPeriod,
        			   0.01);
           }
           
    	   double max=0.0;
    	   for (int i=0;i<fffg.length;i++) if (max<fffg[i]) max =fffg[i];
    	   double k=1.0/max;
    	   
    	   for (int i=0;i<fffg.length;i++) {
    		   fffg[i]*=k;
    		   if (fffg[i]<distortionParameters.flatFieldMin)fffg[i]=0.0;
    	   }

    	   if (debugLevel>1) System.out.println("averageGridPeriod="+averageGridPeriod);

    	   if (debugLevel>(dbgThreshold+1)){
    		   this.SDFA_INSTANCE.showArrays(fffg, imp.getWidth(), imp.getHeight(), imp.getTitle()+"-blured");
    	   }

    	   this.flatFieldForGrid=fffg;

    	   ImagePlus imp_eq=  applyFlatField (imp, fffg);
    	   if (debugLevel>dbgThreshold) imp_eq.show();
    	   return imp_eq;
	   }
	   
	   public ImagePlus applyFlatField (ImagePlus imp){
		   if (this.PATTERN_GRID==null) return imp;
		   if ((getImageHeight()!=imp.getHeight()) || (getImageWidth()!=imp.getWidth())){
			   String msg="applyFlatField (): Supplied image does not match in dimensions "+imp.getWidth()+"x"+imp.getHeight()+
			   " the one for wich grid was calculated ("+getImageWidth()+"x"+getImageHeight()+")";
//			   IJ.showMessage("Error",msg);
			   throw new IllegalArgumentException (msg);
		   }
		   return applyFlatField (imp, this.flatFieldForGrid);
		   
	   }
	   public ImagePlus applyFlatField (ImagePlus imp, double [] ff){
		   if (ff==null) return imp; // nothing to apply
    	   float [] pixels=(float []) imp.getProcessor().getPixels();
    	   
		   if (pixels.length!=ff.length){
			   String msg="Supplied image does not match in dimensions "+(pixels.length)+
			   " the one for wich grid was calculated ("+(ff.length)+")";
//			   IJ.showMessage("Error",msg);
			   throw new IllegalArgumentException (msg);
		   }
    	   
    	   
    	   
    	   float [] eqPixels=new float [pixels.length];
    	   for (int i=0;i<pixels.length;i++) if (ff[i]>0) eqPixels[i]=(float) (pixels[i]/ff[i]);
    	   else eqPixels[i]=0.0f;

    	   ImageProcessor ip=new FloatProcessor(imp.getWidth(), imp.getHeight());
    	   ip.setPixels(eqPixels);
    	   ip.resetMinAndMax();
    	   ImagePlus imp_eq=  new ImagePlus(imp.getTitle()+"-flat", ip);
    	   return imp_eq;
	   }

	   public double [][][] calcGridIntensities(
			   final DistortionParameters distortionParameters, //
			   final boolean equalizeGreens,
			   final ImagePlus imp, // image to process
			   final int threadsMax){
		   double dSize=averageGridPeriod(this.PATTERN_GRID)*distortionParameters.averagingAreaScale;
		   int size=  ~1 & ((int) Math.round(dSize)); // should be even
		   int size4= ~1 & ((int) Math.round(dSize*Math.sqrt(2.0))); // larger when using diagonal greens (component 4)
		   int [] bayerIndices={-1,1,4,2}; // -1 - contrast, 1 - R, 4 - G, 2 - B
		   this.gridContrastBrightness=new double [bayerIndices.length][][];
		   if (this.debugLevel>1) System.out.println("Calculating grid intensities, average period="+IJ.d2s(averageGridPeriod(this.PATTERN_GRID),2)+
				   " pixels, using square sample "+size+"x"+size+" for all colors ,but (diagonal) greens, for greens - "+size4+"x"+size4);
		   for (int i=0;i<bayerIndices.length;i++){
			   this.gridContrastBrightness[i]=calcGridIntensity (
					   bayerIndices[i], //final int bayerComponent,
					   ((bayerIndices[i]==4)?size4:size), //final int size,
					   distortionParameters, //final DistortionParameters distortionParameters, //
					   equalizeGreens,
					   imp, // image to process
					   this.PATTERN_GRID,
					   threadsMax);
		   }
		   return this.gridContrastBrightness;
	   }
	   
	   
	   public double [][] calcGridIntensity (
			   final int bayerComponent,
			   final int size,
			   final DistortionParameters distortionParameters, //
			   final boolean equalizeGreens,
			   final ImagePlus imp, // image to process
			   final double [][][][] patternGrid,
			   final int threadsMax){// debug level used inside loops
		   final double [][] gridIntensity=new double[patternGrid.length][patternGrid[0].length];
		   for (int i=0;i<gridIntensity.length;i++) for (int j=0;j<gridIntensity[0].length;j++)gridIntensity[i][j]=(bayerComponent>=0)?-1.0:0.0; // undefined
		   MatchSimulatedPattern matchSimulatedPatternCorr=new MatchSimulatedPattern(distortionParameters.correlationSize);
		   matchSimulatedPatternCorr.debugLevel=debugLevel;
		   final double [] windowFunctionCorr= initWindowFunction(
				   size, // distortionParameters.correlationSize,
				   distortionParameters.correlationGaussWidth,
				   distortionParameters.zeros);
		   final int width=patternGrid[0].length;
		   final int height=patternGrid.length;
		   final Thread[] threads = newThreadArray(threadsMax);
		   if (debugLevel>1)  System.out.println("Calculating average intensity at grid nodes, width= "+width+", height= "+height+ ", bayerComponent="+bayerComponent+", size="+size);
		   int [] iUV=  new int [2];
		   int i;
		   for (i=0;i<(width*height);i++) {
			   iUV[0]=i % width;
			   iUV[1]=i / width;
			   if (isCellDefined(patternGrid,iUV)) break;
		   }
		   final int startCell=i;
		   final AtomicInteger cellNum = new AtomicInteger(startCell);
		   final double [][][] newGrid=new double [height][width][];
		   for (int v=0;v<height;v++) for (int u=0;u<width;u++) newGrid[v][u]=null;          
		   for (int ithread = 0; ithread < threads.length; ithread++) {
			   threads[ithread] = new Thread() {
				   public void run() {
					   int [] iUV=  new int [2];
					   for (int ncell=cellNum.getAndIncrement(); ncell<(width*height);ncell=cellNum.getAndIncrement()){
						   iUV[0]=ncell % width;
						   iUV[1]=ncell / width;
						   if (!isCellDefined(patternGrid,iUV)) continue;
						   if (bayerComponent>=0){
							   Rectangle centerCross=correlationSelection(
									   patternGrid[iUV[1]][iUV[0]][0], // initial coordinates of the pattern cross point
									   size); // distortionParameters.correlationSize);
							   double[][] input_bayer=splitBayer (imp,centerCross,equalizeGreens);
							   double sum=0.0, sumW=0.0;
							   for (int i=0;i<input_bayer[bayerComponent].length;i++){
								   sum+= input_bayer[bayerComponent][i]*windowFunctionCorr[i];
								   sumW+=                  windowFunctionCorr[i];
							   }
							   gridIntensity[iUV[1]][iUV[0]]=sum/sumW;
						   } else {
							   // trying alternative
//							   double [][][][] patternGrid_same=patternGrid;
							   gridIntensity[iUV[1]][iUV[0]]=Double.NaN;
							   if (isCellDefined(patternGrid,iUV[0],iUV[1])) {
								   double [][] patternCell=patternGrid[iUV[1]][iUV[0]];
								   if (patternCell[0].length>2) gridIntensity[iUV[1]][iUV[0]]=patternCell[0][2];
							   }
/*							   
							   gridIntensity[iUV[1]][iUV[0]]=localGridContrast(
									   imp,
									   equalizeGreens,
									   patternGrid,
									   iUV[0],
									   iUV[1]);
									   */
						   }
					   }
				   }
			   };
		   }
		   startAndJoin(threads);
		   return gridIntensity;
	   }
/* ======================================================================== */

	   public double localGridContrast(
			   ImagePlus imp,
			   boolean equalizeGreens,
			   final double [][][][] patternGrid,
			   int u,
			   int v){
		   if (!isCellDefined(patternGrid,u,v))   return 0.0;
		   if (!isCellDefined(patternGrid,u+1,v)) return 0.0;
		   if (!isCellDefined(patternGrid,u,v+1)) return 0.0;
		   if (!isCellDefined(patternGrid,u-1,v)) return 0.0;
		   if (!isCellDefined(patternGrid,u,v-1)) return 0.0;
		   double [][] deltas={
				   {0.25*(patternGrid[v+1][u][0][0]-patternGrid[v-1][u][0][0]),
					   0.25*(patternGrid[v+1][u][0][1]-patternGrid[v-1][u][0][1])},
					   {0.25*(patternGrid[v][u+1][0][0]-patternGrid[v][u-1][0][0]),
						   0.25*(patternGrid[v][u+1][0][1]-patternGrid[v][u-1][0][1])} };
		   double delta=Math.sqrt(0.5*(deltas[0][0]*deltas[0][0]+
				   deltas[0][1]*deltas[0][1]+
				   deltas[1][0]*deltas[1][0]+
				   deltas[1][1]*deltas[1][1]));
		   int range=(int) Math.round(0.25*delta); // center of the white/black;
		   int [][] iDeltas={
				   {(int) Math.round(deltas[0][0]),
					   (int) Math.round(deltas[0][1])},
					   {(int) Math.round(deltas[1][0]),
						   (int) Math.round(deltas[1][1])}};
		   
		   int [][] centersOnBayer4=
		   {
				   { iDeltas[0][0], iDeltas[0][1]},
				   {-iDeltas[0][0],-iDeltas[0][1]},
				   { iDeltas[1][0], iDeltas[1][1]},
				   {-iDeltas[1][0],-iDeltas[1][1]} };

		   double diff=0.0;
		   double sum=0.0;
		   int maxDxy=0;
		   for (int n=0;n<4;n++) for (int i=0;i<2;i++) if (centersOnBayer4[n][i]>maxDxy) maxDxy=centersOnBayer4[n][i];
		   int size=2*(maxDxy+range+1); // this will include all needed pixels
//		   size+=2;
		   int hSize=size/2;
		   boolean debug=false; //((u==30) && (v==30));
		   Rectangle centerCross=correlationSelection(
				   patternGrid[v][u][0], // initial coordinates of the pattern cross point
				   size); // distortionParameters.correlationSize);

//		   Rectangle thisSel=new Rectangle(centerCross.x,centerCross.y,2*size,2*size); // "2" - sensor pixels, befor split to components
		   double[][] input_bayer=splitBayer (imp,centerCross,equalizeGreens);
		   if (debug) this.SDFA_INSTANCE.showArrays(input_bayer, size,size, true, imp.getTitle()+"-bayer");
 
		   double [] bayer4=input_bayer[4];
		   for (int dv=-range;dv<=range;dv++) for (int du=-range;du<=range;du++) {
			   int [] indices= new int[4];
			   for (int n=0;n<4;n++) indices[n]=size*(centersOnBayer4[n][1]+dv+hSize)+(centersOnBayer4[n][0]+du+hSize);
			   if ((indices[0]>bayer4.length) || (indices[1]>bayer4.length) || (indices[2]>bayer4.length) || (indices[3]>bayer4.length)
					   || (indices[0]<0) || (indices[1]<0) || (indices[2]<0) || (indices[3]<0)||
					   debug){
				   System.out.println("centersOnBayer4[0]={"+centersOnBayer4[0][0]+", "+centersOnBayer4[0][1]+"}");
				   System.out.println("centersOnBayer4[1]={"+centersOnBayer4[1][0]+", "+centersOnBayer4[1][1]+"}");
				   System.out.println("centersOnBayer4[2]={"+centersOnBayer4[2][0]+", "+centersOnBayer4[2][1]+"}");
				   System.out.println("centersOnBayer4[3]={"+centersOnBayer4[3][0]+", "+centersOnBayer4[3][1]+"}");
				   System.out.println("range="+range);
				   System.out.println("dv="+dv+" du="+du);
				   System.out.println("maxDxy="+maxDxy+" size="+size+" hSize="+hSize);
				   System.out.println("indices=={"+indices[0]+", "+indices[1]+", "+indices[2]+", "+indices[3]+"}, bayer4.length="+bayer4.length);
			   }
			   
			   
			   sum+= bayer4[indices[0]]+bayer4[indices[1]]+bayer4[indices[2]]+bayer4[indices[3]];
			   diff+=bayer4[indices[0]]+bayer4[indices[1]]-bayer4[indices[2]]-bayer4[indices[3]];

		   }
		   if (((u ^ v) & 1)!=0) diff=-diff;
		   if (sum==0.0) return 0.0;
		   return diff/sum;
	   }

/* ======================================================================== */
	   public double averageGridPeriod(
			   double [][][][] patternGrid){
		   int n=0;
		   double sum=0.0;
		   int [] iUV=new int[2];
		   int [] iUV1=new int[2];
		   int [][]dirs={{0,1},{1,0}};
		   double dx,dy;
		   for (iUV[1]=0;iUV[1]<patternGrid.length-1;iUV[1]++) for (iUV[0]=0;iUV[0]<patternGrid[0].length-1;iUV[0]++) if (isCellDefined(patternGrid,iUV)){
			   for (int dir=0;dir<dirs.length;dir++){
				   iUV1[0]=iUV[0]+dirs[dir][0];
				   iUV1[1]=iUV[1]+dirs[dir][1];
				   if (isCellDefined(patternGrid,iUV1)){
//					   dx=patternGrid[iUV1[1]][iUV1[0]][0][0]-patternGrid[iUV1[1]][iUV[0]][0][0]; // old bug, skewed period!
//					   dy=patternGrid[iUV1[1]][iUV1[0]][0][1]-patternGrid[iUV1[1]][iUV[0]][0][1]; // old bug, skewed period!
					   dx=patternGrid[iUV1[1]][iUV1[0]][0][0]-patternGrid[iUV[1]][iUV[0]][0][0];
					   dy=patternGrid[iUV1[1]][iUV1[0]][0][1]-patternGrid[iUV[1]][iUV[0]][0][1];
					   sum+=dx*dx+dy*dy;
					   n++;
				   }
			   }
		   }
		   if (n>0) sum/=n;
		   return Math.sqrt (sum);
	   }
/* ======================================================================== */
	   public double [] averageGridPeriods( // min,max for u,v
			   double [][][][] patternGrid){
		   double [] result={Double.NaN,Double.NaN};
//		   int n=0;
		   double [] sum={0.0,0.0};
		   int [] numSamples={0,0};
		   int [] iUV=new int[2];
		   int [] iUV1=new int[2];
		   int [][]dirs={{0,1},{1,0}};
		   double dx,dy;
		   for (iUV[1]=0;iUV[1]<patternGrid.length-1;iUV[1]++) for (iUV[0]=0;iUV[0]<patternGrid[0].length-1;iUV[0]++) if (isCellDefined(patternGrid,iUV)){
			   for (int dir=0;dir<dirs.length;dir++){
				   iUV1[0]=iUV[0]+dirs[dir][0];
				   iUV1[1]=iUV[1]+dirs[dir][1];
				   if (isCellDefined(patternGrid,iUV1)){
					   dx=patternGrid[iUV1[1]][iUV1[0]][0][0]-patternGrid[iUV[1]][iUV[0]][0][0];
					   dy=patternGrid[iUV1[1]][iUV1[0]][0][1]-patternGrid[iUV[1]][iUV[0]][0][1];
					   sum[dir]+=dx*dx+dy*dy;
					   numSamples[dir]++;
				   }
			   }
		   }
		   for (int dir=0;dir<dirs.length;dir++){
			   if (numSamples[dir]>0) result[dir]=Math.sqrt (sum[dir]/numSamples[dir]);
		   }
		   if (result[0]>result[1]){
			   double tmp=result[0];
			   result[0]=result[1];
			   result[1]=tmp;
		   }
		   return result;
	   }

/* ======================================================================== */
	   public double [] calcFlatFieldForGrid(
			   double [][] gridIntensity,
			   double [][][][] patternGrid,
			   int sWidth,
			   int sHeight){
			int width=patternGrid[0].length;
			int height=patternGrid.length;
			int [][] uvInc={{0,0},{1,0},{0,1},{1,1}}; // four corners as u,v pair
			int [][] cycles={ // counter-clockwise corners bounding the area  (only orthogonal sides?)
					{1,0,2},
					{2,3,1},
					{0,2,3},
					{3,1,0}};
			double [] fffg=   new double [sWidth*sHeight];
			int    [] fffgNum=new int [sWidth*sHeight];
			for (int i=0;i<fffg.length;i++){
				fffg[i]=0.0;
				fffgNum[i]=0;
			}
			int [] iUV=new int[2];
			for (int v=0;v<(height-1); v++) for (int u=0; u<(width-1);u++){
                double [][] cornerXY =new double[4][];
                for (int i=0;i<uvInc.length;i++){
                	iUV[0]=u+uvInc[i][0];
                	iUV[1]=v+uvInc[i][1];
            		
                	if (isCellDefined(patternGrid,iUV)){
                		cornerXY[i]=new double[3];
                		cornerXY[i][0]=patternGrid[iUV[1]][iUV[0]][0][0];
                		cornerXY[i][1]=patternGrid[iUV[1]][iUV[0]][0][1];
                		cornerXY[i][2]=gridIntensity[iUV[1]][iUV[0]];
                	} else cornerXY[i]=null;
                }
                boolean [] cycleFits=new boolean[cycles.length];              
                for (int i=0;i<cycles.length;i++){
                	cycleFits[i]=true;
                	for (int j=0;j<cycles[i].length;j++) if (cornerXY[cycles[i][j]]==null) {
                		cycleFits[i]=false;
                		break;
                	}
                }
                if (cycleFits[0]&&cycleFits[1]){ // remove overlaps
                	cycleFits[2]=false;
                	cycleFits[3]=false;
                }
                boolean minMaxUndefined=true;
				double minX=0,maxX=0,minY=0,maxY=0;
				// find bounding rectangle;
				for (int nCycle=0;nCycle<cycles.length;nCycle++) if (cycleFits[nCycle]){
					int [] cycle=cycles[nCycle];
					for (int corner=0; corner<cycle.length;corner++){
						if (minMaxUndefined || (minX>cornerXY[cycle[corner]][0])) minX=cornerXY[cycle[corner]][0];
						if (minMaxUndefined || (maxX<cornerXY[cycle[corner]][0])) maxX=cornerXY[cycle[corner]][0];
						if (minMaxUndefined || (minY>cornerXY[cycle[corner]][1])) minY=cornerXY[cycle[corner]][1];
						if (minMaxUndefined || (maxY<cornerXY[cycle[corner]][1])) maxY=cornerXY[cycle[corner]][1];
						minMaxUndefined=false;
					}
				}
				int iMinX=(int) Math.floor(minX);
				int iMinY=(int) Math.floor(minY);
				int iMaxX=(int) Math.ceil(maxX);
				int iMaxY=(int) Math.ceil(maxY);
				if (iMinX<0) iMinX=0;
				if (iMinY<0) iMinY=0;
				if (iMaxX>=sWidth)  iMaxX=sWidth-1;
				if (iMaxY>=sHeight) iMaxY=sHeight-1;
				double [] originXY=new double [2];
				double [] endXY=new double [2];
				for (int idY=iMinY; idY<=iMaxY;idY++){
					double pY=idY; // in sensor pixels
					for (int idX=iMinX; idX<=iMaxX;idX++){
						double pX=idX; // in sensor pixels
						// scan allowed triangles, usually 2
						for (int nCycle=0;nCycle<cycles.length;nCycle++) if (cycleFits[nCycle]){
							int [] cycle=cycles[nCycle];
							// is this point inside?
							boolean inside=true;
							for (int nEdge=0;nEdge<cycle.length;nEdge++){
								int nextNEdge=(nEdge==(cycle.length-1))?0:(nEdge+1);

								originXY[0]=patternGrid[v+uvInc[cycle[nEdge]][1]][u+uvInc[cycle[nEdge]][0]][0][0];
								originXY[1]=patternGrid[v+uvInc[cycle[nEdge]][1]][u+uvInc[cycle[nEdge]][0]][0][1];
								endXY[0]=   patternGrid[v+uvInc[cycle[nextNEdge]][1]][u+uvInc[cycle[nextNEdge]][0]][0][0];
								endXY[1]=   patternGrid[v+uvInc[cycle[nextNEdge]][1]][u+uvInc[cycle[nextNEdge]][0]][0][1];
								if (((pX-originXY[0])*(endXY[1]-originXY[1]) - (pY-originXY[1])*(endXY[0]-originXY[0]))<0.0){
									inside=false;
									break;
								}
							}
							if (!inside) continue; // point is outside of the interpolation area, try next triangle (if any)
							/* interpolate: 
							1. taking cycles[0] as origin and two (non co-linear) edge vectors - V1:from 0 to 1 and V2 from 1 to 2
							    find a1 and a2  so that vector V  (from 0  to pXY) = a1*V1+ a2*V2
							2. if F0 is the value of the interpolated function at cycles[0], F1 and F2 - at cycles[1] and cycles2
							   then F=F0+(F1-F0)*a1 +(F2-F1)*a2    
							 */

							double [] XY0={patternGrid[v+uvInc[cycle[0]][1]][u+uvInc[cycle[0]][0]][0][0],patternGrid[v+uvInc[cycle[0]][1]][u+uvInc[cycle[0]][0]][0][1]};
							double [] XY1={patternGrid[v+uvInc[cycle[1]][1]][u+uvInc[cycle[1]][0]][0][0],patternGrid[v+uvInc[cycle[1]][1]][u+uvInc[cycle[1]][0]][0][1]};
							double [] XY2={patternGrid[v+uvInc[cycle[2]][1]][u+uvInc[cycle[2]][0]][0][0],patternGrid[v+uvInc[cycle[2]][1]][u+uvInc[cycle[2]][0]][0][1]};
							
							double [] V= {pX-XY0[0],pY-XY0[1]};
							double [][] M={
									{XY1[0]-XY0[0],XY2[0]-XY1[0]},
									{XY1[1]-XY0[1],XY2[1]-XY1[1]}};
							double det=M[0][0]*M[1][1]-M[1][0]*M[0][1];
							double [][] MInverse={
									{ M[1][1]/det,-M[0][1]/det},
									{-M[1][0]/det, M[0][0]/det}};
							double [] a12={
									MInverse[0][0]*V[0]+MInverse[0][1]*V[1],
									MInverse[1][0]*V[0]+MInverse[1][1]*V[1]};
							int pCorrIndex=idY*sWidth+idX;
// some points may be accumulated multiple times - thisPCorr[3] will take care of this
							if (this.debugLevel>3) {
								System.out.println("XY0="+IJ.d2s(XY0[0],3)+":"+IJ.d2s(XY0[1],3));
								System.out.println("XY1="+IJ.d2s(XY1[0],3)+":"+IJ.d2s(XY1[1],3));
								System.out.println("XY2="+IJ.d2s(XY2[0],3)+":"+IJ.d2s(XY2[1],3));
								System.out.println("M00="+IJ.d2s(M[0][0],3)+" M01="+IJ.d2s(M[0][1],3));
								System.out.println("M10="+IJ.d2s(M[1][0],3)+" M11="+IJ.d2s(M[1][1],3));
								System.out.println("MInverse00="+IJ.d2s(MInverse[0][0],5)+" MInverse01="+IJ.d2s(MInverse[0][1],5));
								System.out.println("MInverse10="+IJ.d2s(MInverse[1][0],5)+" MInverse11="+IJ.d2s(MInverse[1][1],5));
								System.out.println("a12="+IJ.d2s(a12[0],3)+":"+IJ.d2s(a12[1],3));
								System.out.println("gridIntensity[v+uvInc[cycle[0]][1]][u+uvInc[cycle[0]][0]]="+
										IJ.d2s(gridIntensity[v+uvInc[cycle[0]][1]][u+uvInc[cycle[0]][0]],3));
								System.out.println("gridIntensity[v+uvInc[cycle[1]][1]][u+uvInc[cycle[1]][0]]="+
										IJ.d2s(gridIntensity[v+uvInc[cycle[1]][1]][u+uvInc[cycle[1]][0]],3));
								System.out.println("gridIntensity[v+uvInc[cycle[2]][1]][u+uvInc[cycle[2]][0]]="+
										IJ.d2s(gridIntensity[v+uvInc[cycle[2]][1]][u+uvInc[cycle[2]][0]],3));
							}
							double val=
								gridIntensity[v+uvInc[cycle[0]][1]][u+uvInc[cycle[0]][0]]+
								(gridIntensity[v+uvInc[cycle[1]][1]][u+uvInc[cycle[1]][0]]-gridIntensity[v+uvInc[cycle[0]][1]][u+uvInc[cycle[0]][0]])*a12[0]+
								(gridIntensity[v+uvInc[cycle[2]][1]][u+uvInc[cycle[2]][0]]-gridIntensity[v+uvInc[cycle[1]][1]][u+uvInc[cycle[1]][0]])*a12[1];
							if (this.debugLevel>3) {
								System.out.println("val="+IJ.d2s(val,3));
							}
							fffg[pCorrIndex]+=val;// error in /data/focus/grid3d/center/1317924548_967543-00.tiff OOB: 5019002
							fffgNum[pCorrIndex]+=1;
						}
					} // idX
					// use same order in calculations, make sure no gaps
				} // idY
			} // finished image
			for (int i=0;i<fffg.length;i++) if (fffgNum[i]>0){
				fffg[i]/=fffgNum[i];
			}
			return fffg;
	   }
/* ======================================================================== */

	   /**
	    *  Extrapolates flat-field correction
	    * @param data [nPixels] data to extrapolate
	    * @param sWidth data width
	    * @param preShrink shrink the non-zero data by this number of pixels before extrapolating
	    * @param expand expand the (pre-shrank) data by up to this number of pixels  
	    * @param sigma when fitting plane through new point use Gaussian weight function for the neighbors
	    *  (normalized to non-decimated points)
	    * @param ksigma Process pixels in a square with the side 2*sigma*ksigma
	    */
// TODO: Use threads	   
	   public boolean extrapolatePatternFlatFieldCorrection(
			   final double [] data, //fieldXY,
			   final int sWidth,
			   final int preShrink,
			   final int expand,
			   final double sigma,
			   final double ksigma,
			   int    threadsMax,     //   100; // testing multi-threading, limit maximal number of threads
			   boolean updateStatus){
		   int dbgThreshold=1;
		   final int length=data.length;
		   final int sHeight=length/sWidth;
		   // create mask
		   final boolean [] fMask=new boolean[data.length];
		   for (int i=0;i<length;i++) fMask[i]= data[i]>0.0;

		   final int len= (int) Math.ceil(sigma*ksigma);

		   final double [] gaussian=new double[len+1];
		   double k=0.5/sigma/sigma;
		   for (int i=0;i<=len;i++) gaussian[i]=Math.exp(-i*i*k);
		   int [][] dirs={{-1,0},{1,0},{0,-1},{0,1}}; // order matters
		   final List <Integer> extList=new ArrayList<Integer>(1000);
		   Integer Index, Index2;
		   extList.clear();
		   // create initial wave
		   if (this.debugLevel>2) System.out.println("extrapolatePatternFlatFieldCorrection() sWidth="+sWidth+" sHeight="+sHeight);

		   for (int iy=0;iy<sHeight;iy++) for (int ix=0;ix<sWidth;ix++) {
			   Index=iy*sWidth+ix;
			   if (fMask[Index]) {
				   int numNew=0;
				   for (int dir=0;dir<dirs.length;dir++){
					   int ix1=ix+dirs[dir][0];
					   int iy1=iy+dirs[dir][1];
					   if ((ix1>=0) && (iy1>=0) && (ix1<sWidth) && (iy1<sHeight)) {
						   if (!fMask[iy1*sWidth+ix1]) numNew++;
					   }
					   if (numNew>0) extList.add(Index); // neighbor will have non-singular matrix
				   }
			   }
		   }
		   // now shrink 
		   // unmask current wave
		   for (int i=extList.size()-1; i>=0;i--) fMask[extList.get(i)]=false;
		   if (extList.size()==0) return false; // no points
		   for (int nShrink=0;nShrink<preShrink;nShrink++){
			   int size=extList.size();
			   if (size==0) return false; // no points
			   // wave step, unmasking
			   for (int i=0; i<size;i++) {
				   Index=extList.get(0);
				   extList.remove(0);
				   int iy=Index/sWidth;
				   int ix=Index%sWidth;
				   for (int dir=0;dir<dirs.length;dir++){
					   int ix1=ix+dirs[dir][0];
					   int iy1=iy+dirs[dir][1];
					   if ((ix1>=0) && (iy1>=0) && (ix1<sWidth) && (iy1<sHeight)){ 
						   Index=iy1*sWidth+ix1;
						   if (fMask[Index]){
							   extList.add(Index);
							   fMask[Index]=false; // restore later?
						   }
					   }
				   }
			   }
		   }
		   // restore mask on the front
		   for (int i=extList.size()-1; i>=0;i--) fMask[extList.get(i)]=true;

		   if (this.debugLevel>dbgThreshold+1){
			   for (int i=0;i<length;i++) if (!fMask[i]) data[i]=0.0;
			   
			     this.SDFA_INSTANCE.showArrays(data, sWidth,sHeight, "shrank");
		   }

		   
		   // repeat with the wave until there is place to move, but not more than "expand" steps
		   
			final Thread[] threads = newThreadArray(threadsMax);
			final AtomicInteger pixInWaveNum = new AtomicInteger();
		   
		   
		   int [] dirs2=new int [2];
		   for (int n=0; (n<expand) && (extList.size()>0); n++ ){
			   if (updateStatus) IJ.showStatus("Expanding, step="+(n+1)+" (of "+expand+"), extList.size()="+extList.size());
			   if (this.debugLevel>2) System.out.println("Expanding, step="+n+", extList.size()="+extList.size());
			   // move wave front 1 pixel hor/vert        	
			   for (int i=extList.size();i>0;i--){ // repeat current size times
				   Index=extList.get(0);
				   extList.remove(0);
				   int iy=Index/sWidth;
				   int ix=Index%sWidth;
				   for (int dir=0;dir<dirs.length;dir++){
					   int ix1=ix+dirs[dir][0];
					   int iy1=iy+dirs[dir][1];
					   if ((ix1>=0) && (iy1>=0) && (ix1<sWidth) && (iy1<sHeight)){ 
						   Index=iy1*sWidth+ix1;
						   if (!fMask[Index]){
							   // verify it has neighbors in the perpendicular direction to dir
							   dirs2[0]=(dir+2) & 3;
							   dirs2[1]=dirs2[0] ^ 1;
							   for (int dir2=0;dir2<dirs2.length;dir2++){
								   int ix2=ix+dirs[dirs2[dir2]][0]; // from the old, not the new point!
								   int iy2=iy+dirs[dirs2[dir2]][1];
								   if ((ix2>=0) && (iy2>=0) && (ix2<sWidth) && (iy2<sHeight)){ 
									   Index2=iy2*sWidth+ix2;
									   if (fMask[Index2]){ // has orthogonal neighbor, OK to add
										   extList.add(Index);
										   fMask[Index]=true; // remove later
										   break;
									   }
								   }
							   }
						   }
					   }
				   }
			   }
			   // now un-mask the pixels in new list new
			   for (int i =0;i<extList.size();i++){
				   Index=extList.get(i);
				   fMask[Index]=false; // now mask is only set for known pixels
			   }
	// Calculate values (extrapolate) for the pixels in the list
				/*
	Err = sum (W(x,y)*(f(x,y)-F0-Ax*(x-X0)-Ay*(y-Y0))^2)=
	sum (Wxy*(Fxy^2+F0^2+Ax^2*(x-X0)^2+Ay^2*(y-Y0)^2
	-2*Fxy*F0 -2*Fxy*Ax*(x-X0) - 2*Fxy*Ay*(y-Y0)
	+2*F0*Ax*(x-X0) + 2*F0*Ay*(y-Y0) 
	+2*Ax*(x-X0)*Ay*(y-Y0))
	(1)0=dErr/dF0= 2*sum (Wxy*(F0-Fxy+Ax*(x-X0)+Ay(y-Y0)))
	(2)0=dErr/dAx= 2*sum (Wxy*(Ax*(x-X0)^2-Fxy*(x-X0) +F0*(x-X0)+Ay*(x-x0)*(y-Y0)))
	(3)0=dErr/dAy= 2*sum (Wxy*(Ay*(y-y0)^2-Fxy*(y-Y0) +F0*(y-Y0)+Ax*(x-x0)*(y-Y0)))

	S0 = sum(Wxy)
	SF=  sum(Wxy*Fxy)
	SX=  sum(Wxy*(x-X0)
	SY=  sum(Wxy*(y-Y0)
	SFX= sum(Wxy*Fxy*(x-X0)
	SFY= sum(Wxy*Fxy*(y-Y0)
	SX2= sum(Wxy*(x-X0)^2
	SY2= sum(Wxy*(y-Y0)^2
	SXY= sum(Wxy*(x-X0)*(y-Y0)

	(1) F0*S0 - SF + Ax*SX +Ay*Sy = 0
	(2) Ax*SX2-SFX+F0*SX+Ay*SXY = 0
	(3) Ay*Sy2 -SFY + F0*SY +Ax*SXY = 0

	(1) F0*S0  + Ax*SX +Ay*SY = SF
	(2) Ax*SX2+F0*SX+Ay*SXY = SFX
	(3) Ay*Sy2  + F0*SY +Ax*SXY = SFY


	   | F0 |
	V= | Ax |
	   | Ay |

	     | SF  |
	B =  | SFX |
	     | SFY |

	     | S0  SX   SY  |
	M =  | SX  SX2  SXY | 
	     | SY  SXY  SY2 |

	M * V = B
				 */
			   pixInWaveNum.set(0);
			   for (int ithread = 0; ithread < threads.length; ithread++) {
				   threads[ithread] = new Thread() {
					   public void run() {

						   //			   for (int i =0;i<extList.size();i++){

						   for (int i=pixInWaveNum.getAndIncrement(); i<extList.size();i=pixInWaveNum.getAndIncrement()){

							   Integer Indx=extList.get(i);
							   int iy=Indx/sWidth;
							   int ix=Indx%sWidth;
							   double  S0= 0.0;
							   double  SF= 0.0;
							   double  SX= 0.0;
							   double  SY= 0.0;
							   double  SFX=0.0;
							   double  SFY=0.0;
							   double  SX2=0.0;
							   double  SY2=0.0;
							   double  SXY=0.0;
							   int iYmin=iy-len; if (iYmin<0) iYmin=0;
							   int iYmax=iy+len; if (iYmax>=sHeight) iYmax=sHeight-1;
							   int iXmin=ix-len; if (iXmin<0) iXmin=0;
							   int iXmax=ix+len; if (iXmax>=sWidth) iXmax=sWidth-1;
							   for (int iy1=iYmin;iy1<=iYmax;iy1++) for (int ix1=iXmin;ix1<=iXmax;ix1++) {
								   int ind=ix1+iy1*sWidth;
								   if (fMask[ind]){
									   double w=gaussian[(iy1>=iy)?(iy1-iy):(iy-iy1)]*gaussian[(ix1>=ix)?(ix1-ix):(ix-ix1)];
									   S0+= w;
									   SF+= w*data[ind];
									   SX+= w*(ix1-ix);
									   SY+= w*(iy1-iy);
									   SFX+=w*data[ind]*(ix1-ix);
									   SFY+=w*data[ind]*(iy1-iy);
									   SX2+=w*(ix1-ix)*(ix1-ix);
									   SY2+=w*(iy1-iy)*(iy1-iy);
									   SXY+=w*(ix1-ix)*(iy1-iy);
								   }

							   }
							   double [][] aB={{SF},{SFX},{SFY}};
							   double [][] aM={
									   {S0,SX, SY},
									   {SX,SX2,SXY},
									   {SY,SXY,SY2}
							   };
							   Matrix B=new Matrix(aB);
							   Matrix M=new Matrix(aM);
							   if (!(new LUDecomposition(M)).isNonsingular() && (S0!=0.0)){
								   data[Indx]=SF/S0;   
							   } else {
							     Matrix V=M.solve(B);  // sometimes singular
							     data[Indx]=V.get(0,0);
							   }

						   }
					   }
				   };
				}
				startAndJoin(threads);
			   

			   // set mask again for the new calculated layer of pixels			
			   for (int i =0;i<extList.size();i++){
				   Index=extList.get(i);
				   fMask[Index]=true;
			   }
			   IJ.showProgress(n+1,expand);
		   }
		   IJ.showProgress(1.0);

		   return true;	
	   }



	   
/* ======================================================================== */
	   private double [][] calcNeibLocsWeights (
			   DistortionParameters distortionParameters,
			   boolean useNeib){
		    double [][] locsNeib={{0.0,0.0,1.0}};
		    if (!useNeib) return locsNeib;
		    locsNeib= new double [9][3];
		    double [][]dirs={{ 0.0, 0.0},
		    		         { 1.0, 0.0},
		    		         { 0.0, 1.0},
		    		         {-1.0, 0.0},
		    		         { 0.0,-1.0},
		    		         { 1.0, 1.0},
		    		         { 1.0,-1.0},
		    		         {-1.0, 1.0},
		    		         {-1.0,-1.0}};
		    int i;
		    locsNeib[0][2]=1.0-distortionParameters.averageOrthoWeight-distortionParameters.averageOrthoWeight;
		    for (i=0;i<4;i++) {
			    locsNeib[i+1][2]=0.25*distortionParameters.averageOrthoWeight;
			    locsNeib[i+5][2]=0.25*distortionParameters.averageDiagWeight;
		    }
		    double k=1.0;
		    for (i=0;i<9;i++) {
		    	if (i>0) k=distortionParameters.averageOrthoDist;
		    	if (i>4) k=distortionParameters.averageDiagDist;
		    	locsNeib[i][0]=k*dirs[i][0];
		    	locsNeib[i][1]=k*dirs[i][1];
		    }
		    return locsNeib;
	   }
/* ======================================================================== */
	   public void zeroNaNContrast(){
		   for (double [][][]  row:this.PATTERN_GRID){
			   for (double [][] node:row){
				   if ((node!=null) && (node.length>0) && (node[0]!=null) && (node[0].length>2)){
					   if (Double.isNaN(node[0][2])) node[0][2]=0.0;
				   }
			   }
		   }
	   }
	   
	   
	   public double[][][][] recalculateWaveVectors (
//			   double[][][][] patternGrid,
			   final boolean updateStatus,
			   final int debug_level){// debug level used inside loops
//		   double[][][][] patternGrid=this.PATTERN_GRID;
		   int i;
		   int [] iuv=new int [2];
		    final int [][] directionsUV8= {{1,0},{0,1},{-1,0},{0,-1},{1,1},{-1,1},{-1,-1},{1,-1}}; // first 8 should be the same as in directionsUV
		    final int [] directionsBits8= {1,4,1,4,2,8,2,8}; // should match directionsUV8
		    int neibBits;
			int dir;
			int [] iUV=  new int [2];
			double [][][] neibors=new double [8][][]; // uv and xy vectors to 8 neibors (some may be null
			double [][] thisCell;
			double [][] otherCell;
			int was_debug_level=debugLevel;
			debugLevel=debug_level;
			if (debugLevel>1) System.out.println("Recalculating wave vectors from coordinates...");

		   for (iuv[1]=0;iuv[1]<this.PATTERN_GRID.length;iuv[1]++) for (iuv[0]=0;iuv[0]<this.PATTERN_GRID[0].length;iuv[0]++) if (isCellValid(this.PATTERN_GRID,iuv)){
				if (debugLevel>2) System.out.println("<---= iuv= "+iuv[0]+",  "+iuv[1]);
		    	thisCell=this.PATTERN_GRID[iuv[1]][iuv[0]];
		    	neibBits=0;
		    	for (dir=0;dir<directionsUV8.length;dir++) {
		    		neibors[dir]=null;
		    		iUV[0]=iuv[0]+directionsUV8[dir][0];
		    		iUV[1]=iuv[1]+directionsUV8[dir][1];
					if ((iUV[0]<0) || (iUV[1]<0) ||
							 (iUV[0]>=this.PATTERN_GRID[0].length) || (iUV[1]>=this.PATTERN_GRID.length)) continue; // don't fit into UV grid
					if (isCellValid(this.PATTERN_GRID,iUV)) {
						neibors[dir]= new double [2][2];
				    	otherCell=this.PATTERN_GRID[iUV[1]][iUV[0]];
				    	neibors[dir][0][0]=0.5*directionsUV8[dir][0];  // u
				    	neibors[dir][0][1]=0.5*directionsUV8[dir][1];  // v
				    	neibors[dir][1][0]=otherCell[0][0]-thisCell[0][0];  // x
				    	neibors[dir][1][1]=otherCell[0][1]-thisCell[0][1];  // y
				    	neibBits |= directionsBits8[dir];
					}
		    	}
				i=Integer.bitCount(neibBits); 
				if (debugLevel>2) System.out.println("neibBits="+neibBits+", number of bits= "+i);
				if (i>1) {
		    	 double[][] wv= waveVectorsFromNeib(neibors);
					 setPatternGridCell(
							 this.PATTERN_GRID,
						 iuv,
						 null, // XY already set
						 wv[0], 
						 wv[1]);
		    	   
				   if (debugLevel>2) System.out.println("==+> number of bits:"+i+
						" vw00="+IJ.d2s(wv[0][0],5)+" vw01="+IJ.d2s(wv[0][1],5)+
						" vw10="+IJ.d2s(wv[1][0],5)+" vw11="+IJ.d2s(wv[1][1],5));
//		    	wv= WaveVectorsFromNeib(neibors);
//
//	   vectors: [num_vector][0][0] - U
//          [num_vector][0][1] - V
//          [num_vector][1][0] - X
//          [num_vector][1][1] - Y
//          [num_vector] == null - skip
//
				}
		   }
		   debugLevel=was_debug_level;
		   return this.PATTERN_GRID;
	   }
	   
/* ======================================================================== */
	   private double [][] waveVectorsFromNeib(double [][][] vectors){
/*
 * 		   vectors: [num_vector][0][0] - U
 *                  [num_vector][0][1] - V
 *                  [num_vector][1][0] - X
 *                  [num_vector][1][1] - Y
 *                  [num_vector] == null - skip
 *    minimizing sum of squared errors.
 *    
      Ui=Xi*Wv00+Yi*Wv01
      Vi=Xi*Wv10+Yi*Wv11

           sum(Xi^2) *Wv00 +sum(Xi*Yi)*Wv01- sum(Xi*Ui) =0
           sum(Xi*Yi)*Wv00 +sum(Yi^2) *Wv01- sum(Yi*Ui) =0


           sum(Xi^2) *Wv10 +sum(Xi*Yi)*Wv11- sum(Xi*Vi) =0
           sum(Xi*Yi)*Wv10 +sum(Yi^2) *Wv11- sum(Yi*Vi) =0

     S= |  sum(Xi^2)   sum(Xi*Yi) |
        |  sum(Xi*Yi)  sum(Yi^2)  |

     SU= | sum(Xi*Ui) |
         | sum(Yi*Ui) |

     SV= | sum(Xi*Vi) |
         | sum(Yi*Vi) |

     Wv0=| Wv00 |
         | Wv01 |

     Wv1=| Wv10 |
         | Wv11 |
     
     S * Wv0 = SU
     S * Wv1 = SV

     Wv0 = inv(S) * SU 
     Wv1 = inv(S) * SV
 *                  
 */
		   int i;
		   double [][] S={{0.0,0.0},{0.0,0.0}};
		   double []   SU={0.0,0.0};
		   double []   SV={0.0,0.0};
		   double [][] WV=new double [2][];
		   for (i=0;i<vectors.length;i++) if (vectors[i]!=null) {
//			   if (debugLevel>1) System.out.println("waveVectorsFromNeib: i="+i +": "+
//					   vectors[i][0][0]+" "+vectors[i][0][1]+" "+vectors[i][1][0]+" "+vectors[i][1][1]+" ");
			   S[0][0]+= vectors[i][1][0]*vectors[i][1][0]; // sum(Xi^2)
			   S[0][1]+= vectors[i][1][0]*vectors[i][1][1]; // sum(Xi*Yi)
			   S[1][1]+= vectors[i][1][1]*vectors[i][1][1]; // sum(Yi^2) 
			   SU[0]+=   vectors[i][1][0]*vectors[i][0][0]; // sum(Xi*Ui)
			   SU[1]+=   vectors[i][1][1]*vectors[i][0][0]; // sum(Yi*Ui)
			   SV[0]+=   vectors[i][1][0]*vectors[i][0][1]; // sum(Xi*Vi)
			   SV[1]+=   vectors[i][1][1]*vectors[i][0][1]; // sum(Yi*Vi)
		   }
		   S[1][0]=S[0][1];
//		   if (debugLevel>1) System.out.println("waveVectorsFromNeib: S00="+S[0][0]+" S01="+S[0][1]);
//		   if (debugLevel>1) System.out.println("waveVectorsFromNeib: S10="+S[1][0]+" S11="+S[1][1]);
//		   if (debugLevel>1) System.out.println("waveVectorsFromNeib: SU0="+SU[0]+  " SU1="+SU[1]);
//		   if (debugLevel>1) System.out.println("waveVectorsFromNeib: SV0="+SV[0]+  " SV1="+SV[1]);

		   S=matrix2x2_invert(S);
//		   if (debugLevel>1) System.out.println("waveVectorsFromNeib: S00="+S[0][0]+" S01="+S[0][1]);
//		   if (debugLevel>1) System.out.println("waveVectorsFromNeib: S10="+S[1][0]+" S11="+S[1][1]);
		   
		   WV[0]=matrix2x2_mul(S,SU);
		   WV[1]=matrix2x2_mul(S,SV);
		   return WV;
	   }
	   private void putInWaveList (
			   List <Integer> list,
			   int [] uv,
			   int dir) {
		   int l=(Integer.SIZE-2)/2;
		   int mask =(1<<l)-1;
		   
		   list.add(new Integer((dir & 3) | ((uv[0] & mask) << 2) | ((uv[1] & mask) << (2+l)))); 
	   }
	   private int [] getWaveList (
			   List <Integer> list,
			   int index) {
		   int l=(Integer.SIZE-2)/2;
		   int mask =(1<<l)-1;
		   
		   int d=list.get(index);
		   int [] result=new int[3];
		   result[2]=(d & 3);
		   result[0]=(d >>2 ) & mask;
		   result[1]=(d >> (2+l)) & mask;
		   return result;
	   }
	   
	
/* ======================================================================== */
	// set XY coordinates and (optionally) wave vectors of the pattern grid cell
	/*	   
		   cell==null - new cell, not yet defined
		   cell.length==1 - invalid cell
		   cell.length>1 - initialized:
		   cell[0]==null - undefined 
		   cell[0]!=null - defined
	*/	   
		   private void setPatternGridCell(
				   double [][][][] grid,
				   int [] uv,
				   double [] xy, // may be a 3-element, with contrast
				   double [] wv1,
				   double [] wv2){
			   int i;
			   initPatternGridCell(grid,uv);
			   if (xy!=null) {
//				  double [] grid_xy= new double[2];
//				  for (i=0;i<2;i++) grid_xy[i]=xy[i];
				  grid[uv[1]][uv[0]][0]= xy.clone(); // grid_xy;
			   }
			   if (wv1!=null) {
				  double [] grid_wv1= new double[2];
				  for (i=0;i<2;i++) grid_wv1[i]=wv1[i];
				  grid[uv[1]][uv[0]][1]= grid_wv1;
			   }
			   if (wv2!=null) {
				  double [] grid_wv2= new double[2];
				  for (i=0;i<2;i++) grid_wv2[i]=wv2[i];
				  grid[uv[1]][uv[0]][2]= grid_wv2;
			   }
		   }
		   private void initPatternGridCell(
				   double [][][][] grid,
				   int [] uv){
			   int i;
			   if (grid[uv[1]][uv[0]]==null) {
				  double [][] grid_cell= new double [3][];
				  for (i=0;i<3;i++) grid_cell[i]=null;
				  grid[uv[1]][uv[0]]=grid_cell;
			   }
		   }
		   
	// mark the grid cell as invalid	   
		   private void invalidatePatternGridCell(
				   double [][][][] grid,
				   int [] uv){
			   double [][] cell = new double [1][];
			   cell[0]=null;
			   grid[uv[1]][uv[0]]=cell;
		   }
		   private void clearPatternGridCell(
				   double [][][][] grid,
				   int [] uv){
			   grid[uv[1]][uv[0]]=null;
		   }

		   private void markDeletedPatternGridCell(
				   double [][][][] grid,
				   int [] uv){
			   if ((grid[uv[1]][uv[0]]!=null) && (grid[uv[1]][uv[0]][0]!=null)) {
				   double [] newXYC=new double[4];
				   for (int i=0;i<newXYC.length;i++){
					   if (i<grid[uv[1]][uv[0]][0].length)
						   newXYC[i]=grid[uv[1]][uv[0]][0][i];
					   else
						   newXYC[i]=Double.NaN;
				   }
				   grid[uv[1]][uv[0]][0]=newXYC;
			   }
//			   grid[uv[1]][uv[0]]=null;

		   }
		   
		   private boolean isCellDeleted(
	    		   double [][][][] grid,
	    		   int [] uv){
	    	   return ((uv[1]>=0) && (uv[0]>=0) && (uv[1]<grid.length) && (uv[0]<grid[uv[1]].length) &&
	    			   (grid[uv[1]][uv[0]]!=null) && (grid[uv[1]][uv[0]][0]!=null) && (grid[uv[1]][uv[0]][0].length>3));    	   
	       }
		   private boolean isCellNew( //modified, for invalid uv will return "not new"
				   double [][][][] grid,
				   int [] uv){
//	           return (uv[1]>=0) && (uv[0]>=0) && (uv[1]<grid.length) && (uv[0]<grid[uv[1]].length) && (grid[uv[1]][uv[0]]==null);
			   // 4-th element is added to mark that the cell is dleted, but keep coordinates
	           return (uv[1]>=0) && (uv[0]>=0) && (uv[1]<grid.length) && (uv[0]<grid[uv[1]].length) && ((grid[uv[1]][uv[0]]==null) || (grid[uv[1]][uv[0]].length>3));    	   
	       }
		   private boolean isCellValid(
	    		   double [][][][] grid,
	    		   int [] uv){
	    	   if ((uv[1]>=0) && (uv[0]>=0) && (uv[1]<grid.length) && (uv[0]<grid[uv[1]].length)) { 
	    		   double [][] cell = grid[uv[1]][uv[0]];
	    		   return ((cell!=null) && (cell.length>1));    	   
	    	   }
	    	   return false;
	       }
		   private boolean isCellDefined(
	    		   double [][][][] grid,
	    		   int [] uv){
	    	   return ((uv[1]>=0) && (uv[0]>=0) && (uv[1]<grid.length) && (uv[0]<grid[uv[1]].length) &&
	    			   (grid[uv[1]][uv[0]]!=null) && (grid[uv[1]][uv[0]][0]!=null));    	   
	       }
		   private boolean isCellDefined(
	    		   double [][][][] grid,
	    		   int u,
	    		   int v){
	    	   return ((v>=0) && (u>=0) && (v<grid.length) && (u<grid[v].length) &&
	    			   (grid[v][u]!=null) && (grid[v][u][0]!=null));    	   
	       }
		   private boolean isCellDefined(
	    		   int u,
	    		   int v){
	    	   return isCellDefined(this.PATTERN_GRID,u,v);    	   
	       }
		   private boolean isCellDefined(
	    		   int [] uv){
	    	   return isCellDefined(this.PATTERN_GRID,uv);    	   
	       }

// with contrast
		   private double getCellContrast(double [][][][] grid,
	    		   int [] uv){
			   if  ((uv[1]>=0) && (uv[0]>=0) && (uv[1]<grid.length) && (uv[0]<grid[uv[1]].length) &&
	    			   (grid[uv[1]][uv[0]]!=null) && (grid[uv[1]][uv[0]][0]!=null) && (grid[uv[1]][uv[0]][0].length>2)) {
				   return grid[uv[1]][uv[0]][0][2];
			   } else {
				   return Double.NaN;
			   }
		   }
		   private double getCellContrast(double [][][][] grid,
	    		   int u,
	    		   int v){
			   if  ((v>=0) && (u>=0) && (v<grid.length) && (u<grid[v].length) &&
	    			   (grid[v][u]!=null) && (grid[v][u][0]!=null) && (grid[v][u][0].length>2)) {
				   return grid[v][u][0][2];
			   } else {
				   return Double.NaN;
			   }
		   }
		   public double getCellContrast(int [] uv){
			   return getCellContrast(this.PATTERN_GRID,uv);
		   }
		   public double getCellContrast(int u, int v){
			   return getCellContrast(this.PATTERN_GRID,u,v);
		   }
		   private boolean isCellDefinedC(
	    		   double [][][][] grid,
	    		   int [] uv){
	    	   return ((uv[1]>=0) && (uv[0]>=0) && (uv[1]<grid.length) && (uv[0]<grid[uv[1]].length) &&
	    			   (grid[uv[1]][uv[0]]!=null) && (grid[uv[1]][uv[0]][0]!=null) && (grid[uv[1]][uv[0]][0].length>2) && !Double.isNaN(grid[uv[1]][uv[0]][0][2]));    	   
	       }
		   private boolean isCellDefinedC(
	    		   double [][][][] grid,
	    		   int u,
	    		   int v){
	    	   return ((v>=0) && (u>=0) && (v<grid.length) && (u<grid[v].length) &&
	    			   (grid[v][u]!=null) && (grid[v][u][0]!=null) && (grid[v][u][0].length>2) && !Double.isNaN(grid[v][u][0][2]));    	   
	       }
		   public boolean isCellDefinedC(
	    		   int u,
	    		   int v){
	    	   return isCellDefinedC(this.PATTERN_GRID,u,v);    	   
	       }
		   public boolean isCellDefinedC(
	    		   int [] uv){
	    	   return isCellDefinedC(this.PATTERN_GRID,uv);    	   
	       }
		   
		   
		   
		   /*
		   private double [] cellXY(int u, int v){
			   if (!isCellDefined(u,v)) return null;
			   return this.PATTERN_GRID[v][u][0];
		   }
		   private double [] cellXY(int [] uv){
			   if (!isCellDefined(uv)) return null;
			   return this.PATTERN_GRID[uv[1]][uv[0]][0];
		   }
*/
		   private double [] cellXYC(int u, int v){
			   if (!isCellDefined(u,v)) return null;
			   double [] xyc={
					   this.PATTERN_GRID[v][u][0][0],
					   this.PATTERN_GRID[v][u][0][1],
					   (this.PATTERN_GRID[v][u][0].length>2)?this.PATTERN_GRID[v][u][0][2]:
					   ((this.gridContrastBrightness==null)?1.0:this.gridContrastBrightness[0][v][u])
			   };
			   return xyc; // this.PATTERN_GRID[uv[1]][uv[0]][0];
		   }
		   private double [] cellXYC(int [] uv){
			   if (!isCellDefined(uv)) return null;
			   double [] xyc={
					   this.PATTERN_GRID[uv[1]][uv[0]][0][0],
					   this.PATTERN_GRID[uv[1]][uv[0]][0][1],
					   (this.PATTERN_GRID[uv[1]][uv[0]][0].length>2)?this.PATTERN_GRID[uv[1]][uv[0]][0][2]:
					   ((this.gridContrastBrightness==null)?1.0:this.gridContrastBrightness[0][uv[1]][uv[0]])
			   };
			   return xyc; // this.PATTERN_GRID[uv[1]][uv[0]][0];
		   }

		   
		   
		   public int numDefinedCells()  {
			   return numDefinedCells(this.PATTERN_GRID);
		   }

		   public int numDefinedCells(double [][][][] grid)  { // calulate/print number of defined nodes in a grid
			   int [] iUV=new int [2];
			   int numDefinedCells=0;
			   for (iUV[1]=0;iUV[1]<grid.length;iUV[1]++) for (iUV[0]=0;iUV[0]<grid[0].length;iUV[0]++)
				   if (this.isCellDefined(grid,iUV)) numDefinedCells++;
			   return numDefinedCells;
		   }
		   public int gridUVWidth(){return ((this.PATTERN_GRID==null) || (this.PATTERN_GRID.length==0l) ||(this.PATTERN_GRID[0]==null))?0:this.PATTERN_GRID[0].length;}		   
		   public int gridUVHeight(){return ((this.PATTERN_GRID==null) || (this.PATTERN_GRID.length==0l) ||(this.PATTERN_GRID[0]==null))?0:this.PATTERN_GRID.length;}		   
/* ======================================================================== */
		   /**
		    * returns number of laser pointers matched (or negative error)
		    * if (this.flatFieldForGrid!=null) it should already be applied !!
		    */
		   public int calculateDistortions(
				   MatchSimulatedPattern.DistortionParameters distortionParameters, //
				   MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
				   SimulationPattern.SimulParameters  simulParameters,
				   boolean equalizeGreens,
				   ImagePlus imp, // image to process
				   LaserPointer laserPointer, // LaserPointer object or null
				   boolean removeOutOfGridPointers, //
				   double [][][] hintGrid, // predicted grid array (or null)
				   double        hintGridTolerance, // alllowed mismatch (fraction of period) or 0 - orientation only
				   int threadsMax,
				   boolean updateStatus,
				   int global_debug_level, // DEBUG_LEVEL
				   int debug_level, // debug level used inside loops
				   boolean noMessageBoxes ){
			   if (imp==null){
				   IJ.showMessage("Error","There are no images open\nProcess canceled");
				   return 0;
			   }
			   long 	  startTime=System.nanoTime();
			   // start from scratch
//			   this.PATTERN_GRID=null;
//			   invalidateCalibration();
//			   invalidateFlatFieldForGrid(); will keep it!
//			   invalidateFocusMask();
			   Roi roi= imp.getRoi();
			   Rectangle selection;
			   if (roi==null){
				   setWOI(0, 0, imp.getWidth(), imp.getHeight());
				   selection=new Rectangle(0, 0, imp.getWidth(), imp.getHeight());
			   } else {
				   setWOI(roi.getBounds());
				   selection=roi.getBounds();
			   }
			   this.debugLevel=global_debug_level;
			   int patternCells=0;
			   // save initial distortionParameters.correlationMinInitialContrast
			   double savedCorrelationMinInitialContrast=distortionParameters.correlationMinInitialContrast;
			   int reTries= 10; // bail out after these attempts
//			   int [] startScanIndex={0}; // scan for pattern will update this index to continue next time (<0 - nothing left)
			   boolean foundGoodCluster=false;
			   
			   int tryHor=0,tryVert=0;
//				distortionParameters.searchOverlap=goniometerParameters.searchOverlap;
//with distortionParameters.searchOverlap==0.5 (default) step will be FFTSize original pixels, so half of the (2xFFTSize) square processed simultaneously			   
			   if (distortionParameters.searchOverlap<0.1) distortionParameters.searchOverlap=0.1;
			   int effectiveWidth=(int) (selection.width*0.5/distortionParameters.searchOverlap);
			   int effectiveHeight=(int) (selection.height*0.5/distortionParameters.searchOverlap);

			   for (int i=distortionParameters.FFTSize;i<effectiveWidth;i*=2) tryHor++;
			   for (int i=distortionParameters.FFTSize;i<effectiveHeight;i*=2) tryVert++;
			   
			   int numTries=1<<(tryHor+tryVert);
			   boolean [] triedIndices=new boolean[numTries+1]; // last set - all used
			   for (int i=0;i<triedIndices.length;i++) triedIndices[i]=(i<3); // mark first 3 as if they are already used
			   // =========  Removing adjustment of contrast ==============
//			   boolean fromVeryBeginning=true;

			   
			   
			   while (reTries-->0) {
				   this.PATTERN_GRID=null;
				   invalidateCalibration();

				   patternCells=distortions( // calculates matchSimulatedPattern.DIST_ARRAY // invalidates calibration, flatFieldForGrid, resets this.PATTERN_GRID
						   triedIndices,
//						   startScanIndex, // [0] will be updated 
						   distortionParameters, //
						   patternDetectParameters,
						   simulParameters,
						   equalizeGreens,
						   imp,
						   threadsMax,
						   updateStatus,
						   debug_level,
						   global_debug_level); // debug level
				   if (global_debug_level>0) System.out.println("Pattern correlation done at "+ IJ.d2s(0.000000001*(System.nanoTime()-startTime),3)+
						   " found "+patternCells+" cells, reTries left: "+reTries);
				   if (patternCells>0) {
					   foundGoodCluster=true;
					   break; // new distortions() code - returns non-zero only if passed other tests
				   }
				   
				   // =========  Removing adjustment of contrast ==============
//				   if (fromVeryBeginning){ 
//					  if (global_debug_level>0) System.out.println("--- Nothing found at all --- at "+ IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
//					  break; // or maybe - still try to adjust threshold?
//				   }
/*
				   double averageGridPeriod=Double.NaN;
				   if (this.PATTERN_GRID!=null) averageGridPeriod=averageGridPeriod(this.PATTERN_GRID);
				   if (global_debug_level>0){
					   System.out.println("Pattern period="+averageGridPeriod+
				   						   " limits are set to :"+patternDetectParameters.minGridPeriod+","+patternDetectParameters.maxGridPeriod);
				   }
				   if (!Double.isNaN(averageGridPeriod)) {
					   if (!Double.isNaN(patternDetectParameters.minGridPeriod) &&
							   (patternDetectParameters.minGridPeriod>0.0) &&
							   (averageGridPeriod<patternDetectParameters.minGridPeriod)){
						   if (global_debug_level>0){
							   System.out.println("Pattern is too small, period="+averageGridPeriod+
								   	   " minimal="+patternDetectParameters.minGridPeriod);
						   }
						   continue; //?
					   }
					   if (!Double.isNaN(patternDetectParameters.maxGridPeriod) &&
							   (patternDetectParameters.maxGridPeriod>0.0) &&
							   (averageGridPeriod>patternDetectParameters.maxGridPeriod)){
						   if (global_debug_level>0){
							   System.out.println("Pattern is too large, period="+averageGridPeriod+
								   	   " maximal="+patternDetectParameters.maxGridPeriod);
						   }
						   continue; //?
					   }
				   }
				   if (    
						   (distortionParameters.minimalPatternCluster<=0) || // minimal cluster size is disabled
						   (distortionParameters.scaleMinimalInitialContrast<=0) || // minimal cluster size is disabled
						   ((patternCells==0) && fromVeryBeginning)|| // no cells detected at all, starting from the very beginning
						   (patternCells>=distortionParameters.minimalPatternCluster)  // detected enough cells
				   ){
					   foundGoodCluster=true;
					   break;
				   }
				   fromVeryBeginning=false;

				   if (roi!=null){ // don't use this feature with ROI as it can be small
					   if (global_debug_level>0) System.out.println("Initial pattern cluster is small ("+patternCells+"), but ROI is set - no retries");
					   {
						   foundGoodCluster=true;
						   break;
					   }
				   } else {
					   //					   if (global_debug_level>0){
					   //						   if (startScanIndex[0]>=0) {
					   if (!triedIndices[triedIndices.length-1]) {
						   if (global_debug_level>0){
							   int startScanIndex=3;
							   for (;(startScanIndex<triedIndices.length) && triedIndices[startScanIndex];startScanIndex++); // skip tried indices 
							   System.out.println("Initial pattern cluster is too small ("+patternCells+
									   "), continuing scanning from index "+startScanIndex);
						   }
					   } else {
						   //							   startScanIndex[0]=0;
						   System.out.println("Last pattern cluster was too small, adjusting the minimal contrast from "+
								   IJ.d2s(distortionParameters.correlationMinInitialContrast,3)+
								   " to "+IJ.d2s(distortionParameters.correlationMinInitialContrast*distortionParameters.scaleMinimalInitialContrast,3));
						   distortionParameters.correlationMinInitialContrast*=distortionParameters.scaleMinimalInitialContrast;
						   for (int i=0;i<triedIndices.length;i++) triedIndices[i]=(i<3); // mark first 3 as if they are already used
						   fromVeryBeginning=true;
					   }
				   }
//				   distortionParameters.correlationMinInitialContrast*=distortionParameters.scaleMinimalInitialContrast;
				   //				   }
				   
*/				   
				   boolean someLeft=false;
				   int startScanIndex=0;
				   for (startScanIndex=3;startScanIndex<triedIndices.length;startScanIndex++) if (!triedIndices[startScanIndex]){
					   someLeft=true;
					   break;
				   }
				   
				   if (someLeft) {
//				   if (!triedIndices[triedIndices.length-1]) {
					   if (global_debug_level>0){
//						   int startScanIndex=3;
//						   for (;(startScanIndex<triedIndices.length) && triedIndices[startScanIndex];startScanIndex++); // skip tried indices 
						   System.out.println("Initial pattern cluster is too small ("+patternCells+
								   "), continuing scanning from index "+startScanIndex);
					   }
				   } else {
					  if (global_debug_level>0) System.out.println("--- Tried all - nothing found --- at "+ IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
					  break;
					   // =========  Removing adjustment of contrast ==============
/*					   
					   //							   startScanIndex[0]=0;
					   System.out.println("Last pattern cluster was too small, adjusting the minimal contrast from "+
							   IJ.d2s(distortionParameters.correlationMinInitialContrast,3)+
							   " to "+IJ.d2s(distortionParameters.correlationMinInitialContrast*distortionParameters.scaleMinimalInitialContrast,3));
					   distortionParameters.correlationMinInitialContrast*=distortionParameters.scaleMinimalInitialContrast;
					   for (int i=0;i<triedIndices.length;i++) triedIndices[i]=(i<3); // mark first 3 as if they are already used
					   fromVeryBeginning=true;
*/					   
				   }
			   }
			   
			   
			   
			   // restore initial distortionParameters.correlationMinInitialContrast
			   distortionParameters.correlationMinInitialContrast=savedCorrelationMinInitialContrast;
			   if (!foundGoodCluster){
				   System.out.println("calculateDistortions(): Pattern too small, initial cluster had "+patternCells+" cells");
				   if (global_debug_level>2) IJ.showMessage("Error","Pattern too small: "+patternCells);
				   return distortionParameters.errPatternNotFound;
			   }
			   if (!patternOK()) {
				   System.out.println("Pattern not found");
				   if (global_debug_level>2) IJ.showMessage("Error","Pattern not found");
				   return distortionParameters.errPatternNotFound;
			   } else {
				   if (global_debug_level>1) System.out.println("Initial pattern cluster has "+patternCells+" cells"); 
			   }
			   if (global_debug_level>1) System.out.println("Wave vectors recalculated at "+ IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
			   recalculateWaveVectors (
					   updateStatus,
					   debug_level);// debug level used inside loops
			   ImagePlus imp_eq;
			   if (distortionParameters.flatFieldCorrection && (this.flatFieldForGrid==null)) // if it is not null it is already supposed to be applied!
				   imp_eq=equalizeGridIntensity(
					   imp,
					   this.PATTERN_GRID,
					   distortionParameters, //
					   equalizeGreens,
					   global_debug_level,
					   updateStatus,
					   threadsMax);
			    else imp_eq=imp;

			   if (distortionParameters.refineCorrelations) {
				   refineDistortionCorrelation (
						   distortionParameters, //
						   patternDetectParameters,
						   simulParameters,
						   equalizeGreens,
						   imp_eq,
						   0.0, //final double maxCorr, // maximal allowed correction, in pixels (0.0) - any
						   threadsMax,
						   updateStatus,
						   debug_level); // debug level

				   recalculateWaveVectors (
						   updateStatus,
						   debug_level);// debug level used inside loops
				   if (global_debug_level>1) System.out.println("Second pass over at "+ IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
			   }
			 //hack gridSize
			   if ((distortionParameters.gridSize & 1)!=0) {
				   refineDistortionCorrelation (
						   distortionParameters, //
						   patternDetectParameters,
						   simulParameters,
						   equalizeGreens,
						   imp_eq,
						   0.0, //final double maxCorr, // maximal allowed correction, in pixels (0.0) - any
						   threadsMax,
						   updateStatus,
						   debug_level); // debug level

				   recalculateWaveVectors (
						   updateStatus,
						   debug_level);// debug level used inside loops
				   if (global_debug_level>0) System.out.println("Third pass over at "+ IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
//hack gridSize
			   }
			   patternCells=numDefinedCells();
			   if ((roi!=null) && (patternCells<distortionParameters.minimalPatternCluster)){
				   if (global_debug_level>0) System.out.println("Detected pattern is too small: "+patternCells+
						   ", minimum is set to "+distortionParameters.minimalPatternCluster);
				   return distortionParameters.errTooFewCells; // -10
			   }
			   
			   double [] xy0={simulParameters.offsetX,simulParameters.offsetY} ; //debug
		       createUV_INDEX(
		        		   imp, // or null - just to determine WOI (when getWOI matches image size) 
		        		   xy0, // add to patterGrid xy, null OK
						   threadsMax,
						   updateStatus,
						   global_debug_level, // DEBUG_LEVEL
						   debug_level); // debug level used inside loops
			   finalizeDistortionsBorder (
					   distortionParameters, //
					   updateStatus,
					   debug_level);// debug level used inside loops
			   //Wave vectors are used when calculating PSF
			   recalculateWaveVectors (
					   updateStatus,
					   debug_level);// debug level used inside loops

			   int numDifferentFFT=0;
			   int maxLn2=0;
			   for (int i=0;i<getCorrelationSizesUsed().length;i++) if (getCorrelationSizesUsed()[i]) {
				   numDifferentFFT++;
				   maxLn2=i;
			   }
			   if (numDifferentFFT>1){
				   String sizesUsed="";
				   for (int i=0;i<getCorrelationSizesUsed().length;i++) if (getCorrelationSizesUsed()[i]) sizesUsed+=" "+(1<<i);
				   String msg="Different correlation FFT sizes used:"+sizesUsed+". You may consider increasing \"Correlation size\" setting to "+(1<<maxLn2)+" to reduce artifacts";
				   if (global_debug_level>0){
					   System.out.println(msg);
					   if (global_debug_level>1) IJ.showMessage(msg);
				   }
				   
			   } else if (numDifferentFFT>0){
				   String msg="Single correlation FFT size used: "+(1<<maxLn2);
				   if (global_debug_level>0) System.out.println(msg);
			   }
			   zeroNaNContrast(); // replace grid NaN with 0
			   
			   int numPointers=(laserPointer!=null)?laserPointer.laserUVMap.length:0;
			   double [][] pointersXY=(numPointers>0)?getPointersXY(imp, numPointers):null;
			   if (global_debug_level>1){
				   if (pointersXY!=null){
				   System.out.println("calculateDistortions() numPointers="+numPointers+" pointersXY.length="+pointersXY.length);
				   for (int ii=0;ii<pointersXY.length;ii++) {
					   if (pointersXY[ii]!=null){
						   System.out.println("calculateDistortions()  pointersXY["+ii+"][0]="+pointersXY[ii][0]);
						   System.out.println("                        pointersXY["+ii+"][1]="+pointersXY[ii][1]);
					   } else{
						   System.out.println("calculateDistortions()  pointersXY["+ii+"]=NULL");
					   }
				   }
				   System.out.println("                                     hintGrid="+((hintGrid==null)?"NULL":"not NULL"));
				   System.out.println("                            hintGridTolerance="+hintGridTolerance);
				   } else {
					   System.out.println("pointersXY == null");
				   }
			   }
			   return combineGridCalibration(
					  laserPointer, // LaserPointer object or null
					  pointersXY,
					  removeOutOfGridPointers, //
					  hintGrid, // predicted grid array (or null)
					  hintGridTolerance, // alllowed mismatch (fraction of period) or 0 - orientation only
					  global_debug_level, // DEBUG_LEVEL
					  noMessageBoxes );
		   }
		   
//====================================================
		   /**
		    * Approximate function z(x,y) as a second degree polynomial
		    * f(x,y)=A*x^2+B*y^2+C*x*y+D*x+E*y+F
		    * data array consists of lines of either 2 or 3 vectors:
		    *  2-element vector x,y
		    *  variable length vector z (should be the same for all samples)
		    *  optional 1- element vector w (weight of the sample)
		    * 
		    * returns array of vectors or null
		    * each vector (one per each z component) is either 6-element-  (A,B,C,D,E,F) if quadratic is possible and enabled
		    * or 3-element - (D,E,F) if linear is possible and quadratic is not possible or disbled
		    * returns null if not enough data even for the linear approximation
		    */

		   public double [][] approximatePSFQuadratic(
				   double []       psf,     // PSF function, square array, nominally positive
				   double cutoffEnergy,     // fraction of energy in the pixels to be used
				   double cutoffLevel,      // minimal level as a fraction of maximal
				   int         minArea,      // minimal selected area in pixels
				   double      blurSigma,    // optionally blur the selection
				   double      maskCutOff,
				   int           debugLevel, // debug level
				   String        title) {    // prefix used for debug images
			   double [] mask=findClusterOnPSF(
					   psf,
					   cutoffEnergy,
					   cutoffLevel,
					   minArea,
					   blurSigma,
					   debugLevel,
					   title);
			   int numPix=0;
			   for (int i=0;i<mask.length;i++)
				   if (mask[i]<maskCutOff) mask[i]=0.0;
			   for (int i=0;i<mask.length;i++)
				   if (mask[i]>0.0) numPix++;
			   double [][][]data = new double[numPix][3][];
			   numPix=0;
			   int size = (int) Math.sqrt(psf.length);
			   int hsize=size/2;
			   for (int i=0;i<mask.length;i++)  if (mask[i]>0.0) {
				   data[numPix][0]=new double[2];
				   data[numPix][0][0]=(i % size) - hsize;
				   data[numPix][0][1]=(i / size) - hsize;
				   data[numPix][1]=new double[1];
				   data[numPix][1][0]=psf[i];
				   data[numPix][2]=new double[1];
				   data[numPix][2][0]=mask[i];
				   numPix++;
			   }
			   return  new PolynomialApproximation(debugLevel).quadraticApproximation(
					   data,
					   false,  // use linear approximation (instead of quadratic)
					   1.0E-10,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
					   1.0E-20);  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
		   }

//====================================================
		   public double [] tangetRadialSizes(
				   double ca, // cosine of the center to sample vector
				   double sa, // sine of the center to sample vector
				   double []       psf,     // PSF function, square array, nominally positive
				   double cutoffEnergy,     // fraction of energy in the pixels to be used
				   double cutoffLevel,      // minimal level as a fraction of maximal
				   int         minArea,      // minimal selected area in pixels
				   double      blurSigma,    // optionally blur the selection
				   double      maskCutOff,
				   int           debugLevel, // debug level
				   String        title) {    // prefix used for debug images
			   double [] mask=findClusterOnPSF(
					   psf,
					   cutoffEnergy,
					   cutoffLevel,
					   minArea,
					   blurSigma,
					   debugLevel,
					   title);
			   for (int i=0;i<mask.length;i++)
				   if (mask[i]<maskCutOff) mask[i]=0.0;
			   int size = (int) Math.sqrt(psf.length);
			   int hsize=size/2;
//			   int nn=0;
			   double S0=0.0, SR=0.0, ST=0.0,SR2=0.0,ST2=0.0; //,SRT=0.0;
			   for (int i=0;i<mask.length;i++)  if (mask[i]>0.0) {
				   double x=(i % size) - hsize;
				   double y=(i / size) - hsize;
				   double rc= x*ca+ y*sa;
				   double tc=-x*sa+ y*ca;
				   double d=psf[i]*mask[i];
				   S0+=d;
				   SR+=d*rc;
				   ST+=d*tc;
				   SR2+=d*rc*rc;
				   ST2+=d*tc*tc;
//				   nn++;
			   }
			   if (S0==0.0) return null; // make sure it is OK
			   double  [] result={ Math.sqrt(ST2*S0 - ST*ST)/S0, Math.sqrt(SR2*S0 - SR*SR)/S0};
//			   System.out.println(" mask.length="+mask.length+" nn="+nn+" S0="+S0+" SR="+SR+" ST="+ST+" SR2="+SR2+" ST2="+ST2+
//					   " result={"+result[0]+","+result[1]+"}");
			   return result;
		   }

		 //====================================================
		   public double [] x2y2xySizes(
				   double []       psf,     // PSF function, square array, nominally positive
				   double cutoffEnergy,     // fraction of energy in the pixels to be used
				   double cutoffLevel,      // minimal level as a fraction of maximal
				   int         minArea,      // minimal selected area in pixels
				   double      blurSigma,    // optionally blur the selection
				   double      maskCutOff,
				   int           debugLevel, // debug level
				   String        title) {    // prefix used for debug images
			   double [] mask=findClusterOnPSF(
					   psf,
					   cutoffEnergy,
					   cutoffLevel,
					   minArea,
					   blurSigma,
					   debugLevel,
					   title);
			   for (int i=0;i<mask.length;i++)
				   if (mask[i]<maskCutOff) mask[i]=0.0;
			   int size = (int) Math.sqrt(psf.length);
			   int hsize=size/2;
//			   int nn=0;
			   double S0=0.0, SX=0.0, SY=0.0,SX2=0.0,SY2=0.0,SXY=0.0;
			   for (int i=0;i<mask.length;i++)  if (mask[i]>0.0) {
				   double x=(i % size) - hsize;
				   double y=(i / size) - hsize;
				   double d=psf[i]*mask[i];
				   S0+=d;
				   SX+=d*x;
				   SY+=d*y;
				   SX2+=d*x*x;
				   SY2+=d*y*y;
				   SXY+=d*x*y;
//				   nn++;
			   }
			   if (S0==0.0) return null; // make sure it is OK
			   double  [] result={
					   (SX2*S0 - SX*SX)/S0/S0,
					   (SY2*S0 - SY*SY)/S0/S0,
					   (SXY*S0 - SX*SY)/S0/S0}; // this may be negative
//			   System.out.println(" mask.length="+mask.length+" nn="+nn+" S0="+S0+" SX="+SX+" SY="+SY+" SX2="+SXR2+" SY2="+SY2+" SXY="+SXY+
//					   " result={"+result[0]+","+result[1]+","+result[2]+"}");
			   return result;
		   }
		   
		   
//====================================================
		   
			public double [] findClusterOnPSF(
					double []        psf,     // PSF function, square array, nominally positive
					double cutoffEnergy,     // fraction of energy in the pixels to be used
					double cutoffLevel,      // minimal level as a fraction of maximal
					int         minArea,      // minimal selected area in pixels
					double      blurSigma,    // optionally blur the selection
					int           debugLevel, // debug level
					String        title) {    // prefix used for debhug images
//				int i,j;
				int ix,iy,ix1,iy1;
				List <Integer> pixelList=new ArrayList<Integer>(100);
				Integer Index=0, Index1,IndexMax;
				int size=(int) Math.sqrt(psf.length);
//				int [][]clusterMap=new int[size][size];
				int [][] dirs={{-1,0},{-1,-1},{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1}};
				int len=size*size;
				double [] clusterMap=new double[len];
				double full_energy=0.0;
				double maxValue=0;
				for (int i=0;i<len;i++) {
					clusterMap[i]=0.0;
					if (psf[i]>0.0) full_energy+=psf[i];
					if (maxValue<psf[i]) {
						maxValue=psf[i];
						Index=i;
					}
				}
				if (maxValue<=0.0){
	    		  String msg="psf array does not contain any positive values";
//	    		  IJ.showMessage("Error",msg);
	    		  System.out.println("Error "+msg);
	    		  throw new IllegalArgumentException (msg);
				}
				ix=Index % size;
				iy=Index / size;
				double theresholdLevel=maxValue*cutoffLevel;
				double theresholdEnergy=full_energy*cutoffEnergy;
				double cluster_energy=0.0;
				int clusterSize=0;

				boolean noNew=true;
		if (debugLevel>1)		System.out.println("findClusterOnPSF(): full_energy="+full_energy+" theresholdEnergy="+theresholdEnergy+
				" maxValue="+maxValue+ " theresholdLevel="+theresholdLevel);
		if (debugLevel>1)		System.out.println("findClusterOnPSF(): ix="+ix+" iy="+iy);
				IndexMax=0;
				int listIndex;
				pixelList.clear();
				pixelList.add (Index);
				clusterSize++;
				clusterMap[Index]=1.0;
				cluster_energy+=psf[Index];
				noNew=true;
				while ((pixelList.size()>0) &&
						((clusterSize<minArea) || (cluster_energy<theresholdEnergy))) { // will break from the loop if  (psf[Index] <theresholdLevel)
		/* Find maximal new neighbor */
					maxValue=0.0;
					listIndex=0;
					while (listIndex<pixelList.size()) {
						Index=pixelList.get(listIndex);
						iy=Index/size;
						ix=Index%size;
						noNew=true;
						for (int j=0;j<8;j++) if (((iy > 0 ) || (dirs[j][1]>=0)) && ((iy < (size-1) ) || (dirs[j][1]<=0))){
							ix1=(ix+dirs[j][0]+size) % size;
							iy1= iy+dirs[j][1];
							Index1=iy1*size+ix1;
							if (clusterMap[Index1]==0.0) {
								noNew=false;
								if (psf[Index1]>maxValue) {
									maxValue= psf[Index1];
									IndexMax=Index1;
								}
							}
						}
						if (noNew) pixelList.remove(listIndex);  //  remove current list element
						else       listIndex++;     // increase list index
					}
					if (maxValue==0.0) 	break; // no positive points left 
					if ((clusterSize>=minArea) && (psf[IndexMax]<theresholdLevel)) break; // level is below thershold, minimal size condition met 
		/* Add this new point to the list */
					pixelList.add (IndexMax);
					clusterSize++;
					clusterMap[IndexMax]=1.0;
					cluster_energy+=psf[IndexMax];
				} // end of while ((pixelList.size()>0) &&  ...)
				if (debugLevel>3)   System.out.println("findClusterOnPSF: cluster size is "+clusterSize);
				if (debugLevel>3) {
					SDFA_INSTANCE.showArrays(psf, size, size, title+"-psf");
				}
				if (debugLevel>2) {
					SDFA_INSTANCE.showArrays(clusterMap, size, size, title+"-clusterMap");
				}
				if (blurSigma>0.0){
		        	   DoubleGaussianBlur gb=new DoubleGaussianBlur();
		        	   gb.blurDouble(
		        			   clusterMap,
		        			   size,
		        			   size,
		        			   blurSigma,
		        			   blurSigma,
		        			   0.01);
						if (debugLevel>2) {
							SDFA_INSTANCE.showArrays(clusterMap, size, size, title+"-clusterMap-blured");
						}
				}
				return clusterMap;
			}
		   
		   
/* ======================================================================== */
		   /**
		    * Mask (assignes zero) to the flat-field array outside of the sample squares,]
		    * Clears grid nodes that do not have neighbors inside the sample squares.
		    * If (this.flatFieldForGrid==null) - creates mask of 1.0/0.0
		    * @param focusMeasurementParameters - parameters specifying probe points
		    */
		   public void maskFocus(
	    			double x0,   // lens center on the sensor
	    			double y0,  // lens center on the sensor
	    			LensAdjustment.FocusMeasurementParameters focusMeasurementParameters){
			   if (this.PATTERN_GRID==null) {
				   String msg="PATTERN_GRID array does not exist, exiting";
				   IJ.showMessage("Error",msg);
				   throw new IllegalArgumentException (msg);
			   }
			   if (this.flatFieldForGrid==null) {
				   String msg="Flat field for grid array does not exist, exiting";
				   IJ.showMessage("Error",msg);
				   throw new IllegalArgumentException (msg);
			   }
			   int [][] dirs= {{1,0},{0,1},{-1,0},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}};
			   int width=getImageWidth();
			   int height=getImageHeight();
			   int halfSize=focusMeasurementParameters.sampleSize/2;
// System.out.println("maskFocus(): width="+width+" height="+height+" _halfSize="+halfSize);				   
			   
		    	double [][][] sampleCoord= focusMeasurementParameters.sampleCoordinates(
		    			x0,   // lens center on the sensor
		    			y0);  // lens center on the sensor
			   this.focusMask =new boolean[this.flatFieldForGrid.length];
			   for (int i=0;i<this.focusMask.length;i++) this.focusMask[i]=false;
			   for (int i=0;i<focusMeasurementParameters.numSamples[1];i++){
//System.out.println(i+": y0="+y0);				   
				   for (int j=0;j<focusMeasurementParameters.numSamples[0];j++){
					   int xs=(int) sampleCoord[i][j][0];
					   int ys=(int) sampleCoord[i][j][1];
//System.out.println(j+": xs="+xs);				   
					   for (int y=ys-halfSize;y<(ys+halfSize);y++) if ((y>=0) && (y<height)) {
						   for (int x=xs-halfSize;x<(xs+halfSize);x++) if ((x>=0) && (x<width)) {
							   this.focusMask[y*width+x]=true;
						   }
					   }
				   }
				   
			   }
			   // Do we really need to zero the this.flatFieldForGrid where this.focusMask is false? We may need
			   // the pixels out of WOI to update grid around the needed nodes
//			   for (int i=0;i<this.focusMask.length;i++) if (!this.focusMask[i]) this.flatFieldForGrid[i]=0.0;
			   if (this.PATTERN_GRID.length==0) return;
			   boolean [][] maskUV=new boolean[this.PATTERN_GRID.length][this.PATTERN_GRID[0].length];
			   int [] iUV={0,0};
			   for (iUV[1]=0;iUV[1]<maskUV.length;iUV[1]++) for (iUV[0]=0;iUV[0]<maskUV[0].length;iUV[0]++) {
				   maskUV[iUV[1]][iUV[0]]=false;
				   if (isCellDefined(this.PATTERN_GRID, iUV)){
					   int x= (int) Math.round(this.PATTERN_GRID[iUV[1]][iUV[0]][0][0]);
					   int y= (int) Math.round(this.PATTERN_GRID[iUV[1]][iUV[0]][0][1]);
					   if ((x>=0) && (x<width) && (y>=0) && (y<height) && this.focusMask[y*width+x]) maskUV[iUV[1]][iUV[0]]=true;
				   }
			   }
			   for (iUV[1]=0;iUV[1]<maskUV.length;iUV[1]++) for (iUV[0]=0;iUV[0]<maskUV[0].length;iUV[0]++) if (!maskUV[iUV[1]][iUV[0]]){
				   boolean neibExists=false;
				   for (int d=0;d<dirs.length;d++) {
					   int [] iUV1={iUV[0]+dirs[d][0],iUV[1]+dirs[d][1]};
					   if (isCellDefined(this.PATTERN_GRID, iUV1) && maskUV[iUV1[1]][iUV1[0]]){
						   neibExists=true;
						   break;
					   }
				   }
				   if (!neibExists) clearPatternGridCell(this.PATTERN_GRID,iUV);
				   
			   }			   
		   }
/* ======================================================================== */
		   
		   public double[][] getPointersXY(ImagePlus imp, int numPointers){
			   // try absolute grid calibratrion
			   // read image info to properties (if it was not done yet - should it?
			   if ((imp.getProperty("timestamp")==null) || (((String) imp.getProperty("timestamp")).length()==0)) {
				   JP46_Reader_camera jp4_instance= new JP46_Reader_camera(false);
				   jp4_instance.decodeProperiesFromInfo(imp);
			   }
			   double [][] pointersXY=new double[numPointers][];
			   int numPointerDetected=0;
			   for (int i=0;i<pointersXY.length;i++) {
				   pointersXY[i]=null;
				   if ((imp.getProperty("POINTER_X_"+i)!=null) && (imp.getProperty("POINTER_Y_"+i)!=null)) {
					   pointersXY[i]=new double[2];
					   pointersXY[i][0]=Double.parseDouble((String) imp.getProperty("POINTER_X_"+i));
					   pointersXY[i][1]=Double.parseDouble((String) imp.getProperty("POINTER_Y_"+i));
					   numPointerDetected++;
				   }
			   }
			   if (numPointerDetected>0) return pointersXY;
			   else return null;
		   }
/* ======================================================================== */

		   public String getChannel(ImagePlus imp){
			   // read image info to properties (if it was not done yet - should it?
			   if ((imp.getProperty("timestamp")==null) || (((String) imp.getProperty("timestamp")).length()==0)) {
				   JP46_Reader_camera jp4_instance= new JP46_Reader_camera(false);
				   jp4_instance.decodeProperiesFromInfo(imp);
			   }
			   if (imp.getProperty("channel")==null) return null;
			   return (String) imp.getProperty("channel");
		   }

/* ======================================================================== */
		   public void showFlatFieldForGrid(){
			  if (this.flatFieldForGrid!=null) this.SDFA_INSTANCE.showArrays(this.flatFieldForGrid, getImageWidth(), getImageHeight(), "Flat_field_for_grid");
		   }
		   public void showFFCorrectedGrid(){
			   if (this.gridFFCorr!=null) this.SDFA_INSTANCE.showArrays(this.gridFFCorr, getImageWidth(), getImageHeight(), "Flat_field_corrected_grid");
		   }
		   public void showFocusMask(){
			   if (this.focusMask!=null){
				   double [] dfm=new double [this.focusMask.length];
				   for (int i=0;i<dfm.length;i++) dfm[i]=this.focusMask[i]?1.0:0.0;
				   this.SDFA_INSTANCE.showArrays(dfm, getImageWidth(), getImageHeight(), "Focus_mask");
			   }
		   }
		   public void showUVIndex(){
			   if (this.UV_INDEX!=null){
				   double [] uv=new double [this.UV_INDEX.length];
				   for (int i=0;i<uv.length;i++) uv[i]=this.UV_INDEX[i];
				   this.SDFA_INSTANCE.showArrays(uv, getImageWidth(), getImageHeight(), "UV_INDEX");
			   }
		   }
		   public boolean patternOK(){
			   return (this.PATTERN_GRID!=null);
		   }
		   public double [][][][] getDArray(){
			   return this.PATTERN_GRID;
		   }
		   public double [][][] getDArray(int v){
			   return this.PATTERN_GRID[v];
		   }
		   public double [][] getDArray(int v, int u){
			   return this.PATTERN_GRID[v][u];
		   }
		   public double [] getDArray(int v, int u, int n){
			   return this.PATTERN_GRID[v][u][n];
		   }
		   public double getDArray(int v, int u, int n, int k){
			   return this.PATTERN_GRID[v][u][n][k];
		   }
		   public double [] getXY (int v, int u){
			   return this.PATTERN_GRID[v][u][0];
		   }
		   public int getDArrayHeight(){
			   if (this.PATTERN_GRID==null) return 0;
			   return this.PATTERN_GRID.length;
		   }
		   public int getDArrayWidth(){
			   if (this.PATTERN_GRID==null) return 0;
			   return this.PATTERN_GRID[0].length;
		   }
//			matchSimulatedPattern.DIST_SELECTION.width, // image (mask) width
		   public Rectangle getWOI(){
			   return this.DIST_SELECTION;
		   }
		   public int getImageWidth(){
			   return this.UV_INDEX_WIDTH;
		   }
		   public int getImageHeight(){
			   if (this.UV_INDEX==null) return 0;
			   return this.UV_INDEX.length/this.UV_INDEX_WIDTH;
		   }
		   public void setWOI(Rectangle woi){
			   this.DIST_SELECTION=new Rectangle(woi) ;
		   }
		   public void setWOI(int x, int y, int w, int h){
			   this.DIST_SELECTION=new Rectangle(x, y, w, h) ;
		   }
           public int [] getUVIndex(){
        	   return this.UV_INDEX;
           }
           public int getUVIndex(int i){
        	   if ((i<0) || (i>=this.UV_INDEX.length)) return -1; // let it throw exception
        	   return this.UV_INDEX[i];
           }
           public int getUVIndex(int x, int y){
        	   if ((x<0) || (y<0) || (x>=this.PATTERN_GRID[0].length)  || (y>=this.PATTERN_GRID.length)) return -1;
        	   return this.UV_INDEX[x+UV_INDEX_WIDTH*y];
           }
/**
 * Returns a pair of U,V from xy, integer result (does not interpolate 
 * @param x - pixel coordinat X 
 * @param y - pixel coordinat Y
 * @return {u,v} pair
 */
           public int [] getUV(int x, int y)    {
        	   if ((x<0) || (y<0) || (x>=this.UV_INDEX_WIDTH) || (y>=(this.UV_INDEX.length/this.UV_INDEX_WIDTH))) return null; // out of UV_INDEX bounds
        	   if ((x+this.UV_INDEX_WIDTH*y)>this.UV_INDEX.length) {
        		   if (this.debugLevel>0){
        			   System.out.println("getUV("+x+","+y+"): this.UV_INDEX.length="+this.UV_INDEX.length+", this.UV_INDEX_WIDTH="+this.UV_INDEX_WIDTH);
        		   }
        		   return null;
        	   }
        	   int index=this.UV_INDEX[x+this.UV_INDEX_WIDTH*y];
        	   if (index<0) return null; // <0 - undefined
        	   int width=this.PATTERN_GRID[0].length;
        	   int [] uv={index % width,index / width};
        	   return uv;
           }
           /**
            * Map estimated grid hintGrid  to measured 
            * @param hintGrid [v][u][ 0-x, 1-y, 2 - u, 3- v]
            * @param searchAround  how far (in pixels) to look for the nearest defined one if the specified is undefined
            * @return array [v][u] [0 - measured u, 1 - measured v, 2 - measured contrast] ([v][u]==null no measured grid there 
            */
		
           public double [][][] mapEstimatedToMeasured(double [][][] hintGrid, double searchAround){
			   double [][][] measuredUV=new double[hintGrid.length][hintGrid[0].length][];
			   for (int v=0;v<measuredUV.length;v++) for (int u=0;u<measuredUV[0].length;u++)
				   if (hintGrid[v][u]!=null) measuredUV[v][u]=getUVLinear(hintGrid[v][u][0],hintGrid[v][u][1],searchAround);
			  else measuredUV[v][u]=null;
			   return measuredUV;
		   }
           
           /**
            * Calculate linear matrix (2x3) parameters measured grid UV from estimated grid UV
            * @param hintGrid  [v][u][ 0-x, 1-y, 2 - u, 3- v]
            * @param searchAround how far (in pixels) to look for the nearest defined one if the specified is undefined
            * @return {{Au, Bu, Cu}.{Av, Bc, Cv}}; where Umeas=Au*Uhint+Bu*Vhint+Cu, Vmeas=Av*Uhint+Bv*Vhint+Cv
            */
           public double [][] calcGridMatchMatrix (double [][][] hintGrid, double searchAround){
        	   double [][][] measuredUV=mapEstimatedToMeasured(hintGrid, searchAround); // contrast - minimal of the ones around
        	   if (this.debugLevel>2){
        		   double [][] pixels=new double[9][measuredUV.length*measuredUV[0].length];
        		   int index=0;
        		   String [] titles={"grid-U","grid-V","contrast","hint-X","hint-Y","hint-U","hint-V","grid-hint-U","grid-hint-V"}; // last 2 only valid for rotation==0
        		   for (int v=0; v<measuredUV.length;v++) for (int u=0;u<measuredUV[v].length;u++){
        			   if (measuredUV[v][u]!=null){
        				   for (int i=0; i<3;i++)	pixels[i][index]=measuredUV[v][u][i];
        				   for (int i=0; i<4;i++)	pixels[i+3][index]=hintGrid[v][u][i];
        				   for (int i=0; i<2;i++)	pixels[i+7][index]=measuredUV[v][u][i]-hintGrid[v][u][i+2];
        			   } else {
        				   for (int i=0; i<9;i++)	pixels[i][index]=Double.NaN;
        			   }
        			   index++;
        		   }
        		   (new showDoubleFloatArrays()).showArrays(pixels, measuredUV[0].length, measuredUV.length,  true, "measuredUV", titles);

        	   }

        	   int numDefined=0;
        	   for (int v=0;v<measuredUV.length;v++) for (int u=0;u<measuredUV[v].length;u++) if (measuredUV[v][u]!=null) numDefined++;
        	   double [][][] data =new double [numDefined][3][];//        	   double [][][] data =new double [numDefined][2][2];
        	   int index=0;
        	   for (int v=0;v<measuredUV.length;v++) for (int u=0;u<measuredUV[v].length;u++) if (measuredUV[v][u]!=null) {
        		   data[index][0]=new double[2];
        		   data[index][1]=new double[2];
        		   data[index][2]=new double[1];
        		   data [index][0][0]=hintGrid[v][u][2]; // hinted U
        		   data [index][0][1]=hintGrid[v][u][3]; // hinted V
        		   data [index][1][0]=measuredUV[v][u][0]; // measured U
        		   data [index][1][1]=measuredUV[v][u][1]; // measured V
        		   data [index][2][0]=measuredUV[v][u][2]; // contrast
        		   index++;
        	   }
    		   if (this.debugLevel>0) {
    			   System.out.println("calcGridMatchMatrix(), data.length="+data.length+" measuredUV.length="+measuredUV.length+
    				   ((measuredUV.length>0)?(", measuredUV[0].length="+measuredUV[0].length):""));
    			   if (this.debugLevel>3) for (index=0;index<data.length;index++){
    				   System.out.println(data[index][0][0]+","+data[index][0][1]+","+data[index][1][0]+","+data[index][1][1]+","+data[index][2][0]);
    			   }
    		   }
//			   int gridRotation=-1; //undefined
        	   if (data.length<3){
///        		   if (this.debugLevel>0) System.out.println("calcGridMatchMatrix(), data.length="+data.length+" measuredUV.length="+measuredUV.length+
///        				   ((measuredUV.length>0)?(", measuredUV[0].length="+measuredUV[0].length):""));
        		   return null;
        	   }
        	   double [][] coeff=new PolynomialApproximation(this.debugLevel).quadraticApproximation(data, true);
        	   if (coeff!=null) {
//				   gridRotation=matrixToRot(coeff);
	        	   int rot=matrixToRot(coeff);
	        	   boolean [] flips=rotToFlips(rot);
	        	   double [][] aI={{1,0},{0,1}};
	    		   double [][] aSwap= {{ 0,1},{1,0}};
	    		   double [][] aFlipU={{-1,0},{0,1}};
	    		   double [][] aFlipV={{ 1,0},{0,-1}};
	        	   Matrix M=new Matrix(aI);
	        	   if (flips[0]) M=M.times((new Matrix(aSwap))); 
	        	   if (flips[1]) M=M.times((new Matrix(aFlipU))); 
	        	   if (flips[2]) M=M.times((new Matrix(aFlipV)));
	        	   // now M reconstructs coeff
	        	   double [][]aM=M.getArray();
				   double SMU=0.0,SMV=0.0,SW=0.0;
				   for (int i=0;i<data.length;i++){
					   SMU+=data[i][2][0]*(data[i][1][0]-(aM[0][0]*data[i][0][0]+aM[0][1]*data[i][0][1]));
					   SMV+=data[i][2][0]*(data[i][1][1]-(aM[1][0]*data[i][0][0]+aM[1][1]*data[i][0][1]));
					   SW+=data[i][2][0];
				   }
				   SMU/=SW;
				   SMV/=SW;
				   if (this.debugLevel>0) {
					   System.out.println("coeff[0][0]="+coeff[0][0]+" coeff[0][1]="+coeff[0][1]+" coeff[0][2]="+coeff[0][2]);
					   System.out.println("coeff[1][0]="+coeff[1][0]+" coeff[1][1]="+coeff[1][1]+" coeff[1][2]="+coeff[1][2]);
					   System.out.println("aM[0][0]="+aM[0][0]+" aM[0][1]="+aM[0][1]+" SMU="+SMU);
					   System.out.println("aM[1][0]="+aM[1][0]+" aM[1][1]="+aM[1][1]+" SMV="+SMV);
				   }
				   coeff[0][0]=aM[0][0];
				   coeff[0][1]=aM[0][1];
				   coeff[1][0]=aM[1][0];
				   coeff[1][1]=aM[1][1];
				   coeff[0][2]=SMU;
				   coeff[1][2]=SMV;
				   
        	   }
        	   return coeff;
           }
           public int matrixToRot(double [][] coeff){
        	   boolean [] flips = {false,false,false};
        	   double  [][] aR={{coeff[0][0],coeff[0][1]},{coeff[1][0],coeff[1][1]}};
    		   double  [][] aSwap={{0,1},{1,0}};
        	   Matrix R = new Matrix (aR);
        	   if ((aR[0][0]*aR[0][0]+aR[1][1]*aR[1][1]) < (aR[1][0]*aR[1][0]+aR[0][1]*aR[0][1])){
        		   flips[0]=true;
        		   R=(new Matrix(aSwap)).times(R);
        	   }
        	   flips[1]=R.getArray()[0][0]<0;
        	   flips[2]=R.getArray()[1][1]<0;
        	   return flipsToRot(flips[0],flips[1],flips[2]);
           }
           
           public int [][] gridMatrixApproximate(double [][] coeff){
        	   int rot=matrixToRot(coeff);
        	   boolean [] flips=rotToFlips(rot);
        	   double [][] aI={{1,0},{0,1}};
    		   double [][] aSwap= {{ 0,1},{1,0}};
    		   double [][] aFlipU={{-1,0},{0,1}};
    		   double [][] aFlipV={{ 1,0},{0,-1}};
        	   Matrix M=new Matrix(aI);
        	   if (flips[0]) M=M.times((new Matrix(aSwap))); 
        	   if (flips[1]) M=M.times((new Matrix(aFlipU))); 
        	   if (flips[2]) M=M.times((new Matrix(aFlipV)));
        	   // now M reconstructs coeff
        	   double [][]aM=M.getArray();
        	   // Black/white cells have to be flipped if flipU XOR flipW, regardless of swapUV
        	   int flipForWhite=(flips[1]^flips[2])?1:0;
        	   int [][] shifts={
        			   {2*((int) Math.round(0.5*(coeff[0][2]+0))),   2*((int) Math.round(0.5*(coeff[1][2]+flipForWhite)))    -flipForWhite},
        			   {2*((int) Math.round(0.5*(coeff[0][2]-1)))+1, 2*((int) Math.round(0.5*(coeff[1][2]+flipForWhite-1)))+1-flipForWhite}
        	   };
        	   int shiftSelect=(
        			   ((shifts[0][0]-coeff[0][2])*(shifts[0][0]-coeff[0][2]) + (shifts[0][1]-coeff[1][2])*(shifts[0][1]-coeff[1][2]))>
        			   ((shifts[1][0]-coeff[0][2])*(shifts[1][0]-coeff[0][2]) + (shifts[1][1]-coeff[1][2])*(shifts[1][1]-coeff[1][2])))?1:0;
        	   if (this.debugLevel>1){
        		   double d1=Math.sqrt((shifts[0][0]-coeff[0][2])*(shifts[0][0]-coeff[0][2]) + (shifts[0][1]-coeff[1][2])*(shifts[0][1]-coeff[1][2]));
        		   double d2=Math.sqrt((shifts[1][0]-coeff[0][2])*(shifts[1][0]-coeff[0][2]) + (shifts[1][1]-coeff[1][2])*(shifts[1][1]-coeff[1][2]));
        	   System.out.println("gridMatrixApproximate(): shifts[0][0]="+shifts[0][0]+" shifts[0][1]="+shifts[0][1]+
        			   " shifts[1][0]="+shifts[1][0]+" shifts[1][1]="+shifts[1][1]+ " shiftSelect="+shiftSelect+ " d1="+d1+" d2="+d2);
        	   }
        	   int [][] iCoeff={
//        			   {(int) Math.round(aM[0][0]), (int) Math.round(aM[0][1]),(int) Math.round(coeff[0][2])},
//        			   {(int) Math.round(aM[1][0]), (int) Math.round(aM[1][1]),(int) Math.round(coeff[1][2])}};
			   {(int) Math.round(aM[0][0]), (int) Math.round(aM[0][1]),shifts[shiftSelect][0]},
			   {(int) Math.round(aM[1][0]), (int) Math.round(aM[1][1]),shifts[shiftSelect][1]}};
        	   return iCoeff;
           }
// old version, no distinction between B and W           
           public int [][] gridMatrixApproximateNoBW(double [][] coeff){
        	   int rot=matrixToRot(coeff);
        	   boolean [] flips=rotToFlips(rot);
        	   double [][] aI={{1,0},{0,1}};
    		   double [][] aSwap= {{ 0,1},{1,0}};
    		   double [][] aFlipU={{-1,0},{0,1}};
    		   double [][] aFlipV={{ 1,0},{0,-1}};
        	   Matrix M=new Matrix(aI);
        	   if (flips[0]) M=M.times((new Matrix(aSwap))); 
        	   if (flips[1]) M=M.times((new Matrix(aFlipU))); 
        	   if (flips[2]) M=M.times((new Matrix(aFlipV)));
        	   // now M reconstructs coeff
        	   double [][]aM=M.getArray();
        	   int [][] iCoeff={
        			   {(int) Math.round(aM[0][0]), (int) Math.round(aM[0][1]),(int) Math.round(coeff[0][2])},
        			   {(int) Math.round(aM[1][0]), (int) Math.round(aM[1][1]),(int) Math.round(coeff[1][2])}};
        	   return iCoeff;
           }

           public double worstGridMatchRotSkew(double [][] coeff){
        	   int [][] iCoeff=gridMatrixApproximate(coeff);
        	   double worst=0;
        	   for (int i=0;i<2;i++) for (int j=0;j<2;j++){
        		   double d=Math.abs(coeff[i][j]-iCoeff[i][j]);
        		   if (d>worst) worst=d;
        	   }
        	   return worst;
           }
           public double worstGridMatchTranslate(double [][] coeff){ // in grids half-periods, not pixels!
        	   int [][] iCoeff=gridMatrixApproximate(coeff);
        	   double worst=0;
        	   for (int i=0;i<2;i++){
        		   double d=Math.abs(coeff[i][2]-iCoeff[i][2]);
        		   if (d>worst) worst=d;
        	   }
        	   return worst;
           }
//searchAround           

/**
 * Find double u,v from double x,y by linear interpolation from neighbor cells. Requires  this.UV_INDEX to be calculated and
 * matching this.PATTERN_GRID           
 * @param x pixel coordinate
 * @param y  pixel coordinate
 * @param searchAround how far (in pixels) to look for the nearest defined one if the specified is undefined
 * @return uv pair or null - modified - triplet, last - contrast
 */
           public double [] getUVLinear(double x, double y, double searchAround){
        	   int ix0= (int) Math.round(x);
        	   int iy0= (int) Math.round(y);
        	   int    ix=ix0,iy=iy0;
        	   int [] uv0=getUV(ix, iy);
        	   double best2=searchAround*searchAround+1;
        	   // if the point is slightly out of grid - find the one near 
        	   if (uv0==null) {
        		   for (int iy1=iy-((int) Math.round(searchAround));iy1>=iy+searchAround;iy1++)
        			   for (int ix1=ix-((int) Math.round(searchAround));ix1>=ix+searchAround;ix1++) {
        				   double d= (ix1-ix)*(ix1-ix)+(iy1-iy)*(iy1-iy);
        				   if (d<best2)  {
        					   uv0=getUV(ix1, iy1);
        					   if (uv0!=null){
        						   best2=d;
        						   ix=ix1;
        						   iy=iy1;
        					   }
        				   }
        			   }

        	   }
        	   if (uv0==null) return null; // no grid points near
        	   int [][] dirDiffs={{1,1},{-1,1},{1,-1},{-1,-1}};
        	   double [] xy0= cellXYC(uv0);
        	   double [] xy1=null;
        	   double [] xy2=null;
        	   int [] deltaUV=null;
        	   for (int dir=0;dir<dirDiffs.length;dir++) {
        		   xy1=cellXYC(uv0[0]+dirDiffs[dir][0],uv0[1]);
        		   xy2=cellXYC(uv0[0]                 ,uv0[1]+dirDiffs[dir][1]);
        		   if ((xy1!=null) && (xy2!=null)) {
        			   deltaUV=new int[2];
        			   deltaUV[0]=dirDiffs[dir][0];
        			   deltaUV[1]=dirDiffs[dir][1];
        			   break;
        		   }
        	   }
        	   double minContrast=Math.min(Math.min(xy1[2], xy2[2]),xy0[2]); // minimal contrast off all 3 points
        	   if (deltaUV==null) return null; // could not find 2 orthogonal neighbors to interpolate
/*
x=xy0[0] + dU*deltaUV[0]*(xy1[0]-xy0[0])+dV*deltaUV[1]*(xy2[0]-xy0[0])
y=xy0[1] + dU*deltaUV[0]*(xy1[1]-xy0[1])+dV*deltaUV[1]*(xy2[1]-xy0[1])

 */
        	   double [][] aM={
        			   {deltaUV[0]*(xy1[0]-xy0[0]),deltaUV[1]*(xy2[0]-xy0[0])},
        			   {deltaUV[0]*(xy1[1]-xy0[1]),deltaUV[1]*(xy2[1]-xy0[1])}};
        	   Matrix M=new Matrix(aM);
        	   double [][] aB={{x-xy0[0]},{y-xy0[1]}};
        	   Matrix B=new Matrix(aB);
        	   if (!(new LUDecomposition(M)).isNonsingular()){
        		   System.out.println("getUVLinear("+x+","+y+"): Matix is singular:");
        		   M.print(10, 6);
        		   return null;
        	   }
        	   double [] dUV=M.solve(B).getRowPackedCopy();
        	   double [] result={uv0[0]+dUV[0],uv0[1]+dUV[1],minContrast};
        	   return result;
           }
           public void invalidateAll(){
        	   invalidateCalibration();
        	   invalidateFlatFieldForGrid();
        	   invalidateFocusMask();
           }
           private void invalidateCalibration(){
        	   this.reMap=null;    // invalidate if any
        	   this.targetUV=null; // invalidate if any
               this.pXYUV=null; // invalidate if any
               this.passNumber=1;
               resetCorrelationSizesUsed(); // reset which FFT sizes where used in correlation
           }
           public void invalidateFlatFieldForGrid(){
               this.flatFieldForGrid=null; // reset flat field for grid
               this.gridContrastBrightness=null;
           }
           public void invalidateFocusMask(){
               this.focusMask=null;
           }
           /* get height and width of the measured pattern array applies to PATTERN_GRID, targetUV and pixelsUV */
 
		   public int getHeight(){
			   if (this.pXYUV==null) return 0;
			   return this.pXYUV.length;
		   }
		   public int getWidth(){
			   if (this.pXYUV==null) return 0;
			   return this.pXYUV[0].length;
		   }
		   /* Get physical target UV pair from measured pattern. Requires absolute mapping (by laser spots)
		    *  Pair may be null if no pattern is detected for this node in the image
		    */
           public int [][][] getTargetUV(){
        	   return this.targetUV;
           }
           /* Get pixel X,Y pair for each node in the measured pattern. Calculated during absolute mapping (by laser spots)
            *  Pair may be null if no pattern is detected for this node in the image
            */
           public double [][][] getPXYUV(){
        	   return this.pXYUV;
           }
           
           public int restorePatternGridFromGridList(
        		   double [][][] pixelsXYSet,
        		   int [][][] pixelsUVSet,
        		   double [] intensityRange){
        	   double maxX=0,maxY=0;
        	   for (int n=0;n<pixelsXYSet.length;n++){
        		   for (int i=0;i<pixelsXYSet[n].length;i++){
        			   if (pixelsXYSet[n][i][0]>maxX) maxX=pixelsXYSet[n][i][0];
        			   if (pixelsXYSet[n][i][1]>maxY) maxY=pixelsXYSet[n][i][1];
        		   }
        	   }
        	   int width=(int) Math.ceil(maxX)+1;
        	   int height=(int) Math.ceil(maxY)+1;
        	   return restorePatternGridFromGridList(pixelsXYSet, pixelsUVSet, width,height,intensityRange);
           }
           
           /**
            * Restore this.PATTERN_GRID array (no wave vectors) from the lists use in Distortions class
            * @param pixelsXYSet list of the {x,y} pairs for each grid node (now - a pair of lists used pixels and those that did not fit into physical target)
            * @param pixelsUVSet list of {u,v} pairs for each grid node (now - a pair of lists)
            * @param width sensor (image) width
            * @param height sensor (image) height
            * @return number of cells in the grid
            */
           
           public int restorePatternGridFromGridList(
        		   double [][][] pixelsXYSet,
        		   int [][][] pixelsUVSet,
        		   int width,
        		   int height,
        		   double [] intensityRange){
        	   int numCells=0;
        	   int minU=0,minV=0,maxU=0,maxV=0;
        	   this.PATTERN_GRID=null;
        	   setWOI(0, 0, 0, 0);
        	   if ((pixelsXYSet!=null) && (pixelsUVSet!=null ) &&  (pixelsXYSet.length>0)){ 
        		   setWOI(0, 0, width, height);//  set WOI for the current image
        		   for (int n=0;n<pixelsXYSet.length;n++)
        			   for (int i=0;i<pixelsXYSet[n].length;i++) if ((pixelsXYSet[n][i]!=null)&&(pixelsUVSet[n][i]!=null)) {
        				   if (numCells==0){
        					   minV=pixelsUVSet[n][i][1];
        					   maxV=pixelsUVSet[n][i][1];
        					   minU=pixelsUVSet[n][i][0];
        					   maxU=pixelsUVSet[n][i][0];
        				   } else {
        					   if (minV>pixelsUVSet[n][i][1])  minV=pixelsUVSet[n][i][1];
        					   else if (maxV<pixelsUVSet[n][i][1])  maxV=pixelsUVSet[n][i][1];
        					   if (minU>pixelsUVSet[n][i][0])  minU=pixelsUVSet[n][i][0];
        					   else if (maxU<pixelsUVSet[n][i][0])  maxU=pixelsUVSet[n][i][0];
        				   }
        				   numCells++;
        			   }
        		   if (numCells>0) {
        			   // do not break black/white correspondence, always move by even number of cells
        			   if ((minU & 1)!=0 )minU--;
        			   if ((minV & 1)!=0 )minV--;
        			   this.minUV[0]=minU;
        			   this.minUV[1]=minV; // save shift to restore later
        			   this.PATTERN_GRID=setPatternGridArray(maxU-minU+1,maxV-minV+1);
        			   this.gridContrastBrightness=new double[4][this.PATTERN_GRID.length][this.PATTERN_GRID[0].length]; //{grid contrast, grid intensity red, grid intensity green, grid intensity blue}[v][u]
        			   for (int n=0;n<4;n++)
        				   for (int v=0;v<this.gridContrastBrightness[0].length;v++)
        					   for (int u=0;u<this.gridContrastBrightness[0][0].length;u++)
        						   this.gridContrastBrightness[n][v][u]=0.0;

        			   for (int n=0;n<pixelsXYSet.length;n++)
        				   for (int i=0;i<pixelsXYSet[n].length;i++) if ((pixelsXYSet[n][i]!=null)&&(pixelsUVSet[n][i]!=null)) {
        					   int [] shiftedUV={pixelsUVSet[n][i][0]-minU,pixelsUVSet[n][i][1]-minV};
        					   setPatternGridCell(
        							   this.PATTERN_GRID,
        							   shiftedUV, //pixelsUV[i],
        							   pixelsXYSet[n][i], // will have extra data
        							   null,
        							   null);
        					   this.gridContrastBrightness[0][shiftedUV[1]][shiftedUV[0]]=pixelsXYSet[n][i][2];
        					   this.gridContrastBrightness[1][shiftedUV[1]][shiftedUV[0]]=pixelsXYSet[n][i][3]*intensityRange[0];
        					   this.gridContrastBrightness[2][shiftedUV[1]][shiftedUV[0]]=pixelsXYSet[n][i][4]*intensityRange[1];
        					   this.gridContrastBrightness[3][shiftedUV[1]][shiftedUV[0]]=pixelsXYSet[n][i][5]*intensityRange[2];
        					   
        				   }
        		   }
        	   }
        	   return numCells;
           }
           /**
            * Create PATTERN_GRID for calculated grid (for sensor parameters, orientation) for debugging purposes
            * @param hintGrid  grid array [v][u][0- x,  1 - y, 2 - u, 3 - v] (u,v - not used here) 
            * @param width     image width, in pixels
            * @param height    image height, in pixels
            * @return          number of non-empty cells   
            */
           public int restoreSimulatedPatternGridFromHint(double [][][] hintGrid, int width, int height){
        	   int numCells=0;
        	   this.PATTERN_GRID=null;
        	   if (hintGrid==null) return 0;
    		   setWOI(0, 0, width, height);//  set WOI for the current image
//        	   this.PATTERN_GRID=setPatternGridArray(hintGrid[0].length+1,hintGrid.length+1);
        	   this.PATTERN_GRID=setPatternGridArray(hintGrid[0].length,hintGrid.length);
        	   for (int v=0;v<hintGrid.length;v++) for (int u=0;u<hintGrid[v].length;u++) if (hintGrid[v][u]!=null){
        		   double [] xy={hintGrid[v][u][0],hintGrid[v][u][1]};
        		   if ((xy[0]>=0) && (xy[1]>=0) && (xy[0]<width) && (xy[1]<height)) {  
        			   int    [] uv={u,v};
//            		   if ((xy[0]==0) && (xy[1]==0)) {
//            			   System.out.println("x==0,y==0 for u="+u+", v="+v+", hintGrid[v][u][2]="+hintGrid[v][u][2]+", hintGrid[v][u][3]="+hintGrid[v][u][3]);
//            		   }
        			   setPatternGridCell(
        					   this.PATTERN_GRID,
        					   uv,
        					   xy,
        					   null,
        					   null);
        			   numCells++;
        		   }
        	   }
        	   return numCells;
           }
           
           
           /**
            * restore grid parameters - this.PATTERN_GRID (no wave vectors) only
            * absolute calibration (if any) is lost, only orientation is preserved
            * @param imp_grid - grid encoded as image 
            * @return array of laser pointers coordinates - no separate, return number of grid cells
            */
           public int restorePatternGridFromImage(ImagePlus imp_grid){
        	   int numCells=0;
        	   this.PATTERN_GRID=null;
    		   setWOI(0, 0, 0, 0);
        	   if (imp_grid!=null){
        		   setWOI(0, 0, imp_grid.getWidth(), imp_grid.getHeight());//  set WOI for the current image
        		   ImageStack stack=imp_grid.getStack();
        		   float [][] pixels=new float[4][];
        		   if ((stack==null) || (stack.getSize()!=4)) {
        			   String msg="Expected a 4-slice stack in "+imp_grid.getTitle();
        			   IJ.showMessage("Error",msg);
        			   throw new IllegalArgumentException (msg);
        		   }
        		   for (int i=0;i<4;i++) pixels[i]= (float[]) stack.getPixels(i+1); // pixel X : negative - no grid here
        		   int minU=0,minV=0,maxU=0,maxV=0;
        		   for (int i=0;i<pixels[0].length;i++) if (pixels[0][i]>=0) {
        			   int u=(int) Math.round(pixels[2][i]);
        			   int v=(int) Math.round(pixels[3][i]);
        			   if (numCells==0){
        				   minV=v;
        				   maxV=v;
        				   minU=u;
        				   maxU=u;
        			   } else {
        				   if (minV>v)  minV=v;
        				   else if (maxV<v)  maxV=v;
        				   if (minU>u)  minU=u;
        				   else if (maxU<u)  maxU=u;
        			   }
        			   numCells++;
        		   }
        		   if (numCells>0) {
        			   setPatternGridArray(maxU-minU+1,maxV-minV+1);
        			   double [] xy=new double[2];
        			   int [] uv=new int[2];
        			   for (int i=0;i<pixels[0].length;i++) if (pixels[0][i]>=0) {
        				   uv[0]=((int) Math.round(pixels[2][i]))-minU;
        				   uv[1]=((int) Math.round(pixels[3][i]))-minV;
        				   xy[0]=pixels[0][i];
        				   xy[1]=pixels[1][i];
        				   setPatternGridCell(
        						   this.PATTERN_GRID,
        						   uv,
        						   xy,
        						   null,
        						   null);
        			   }
        		   }
        	   }
        	   return numCells;
           }
           /**
            * Calculate this.UV_INDEX (and this.UV_INDEX_WIDTH) - map from image pixel to U,V (U*this.UV_INDEX_WIDTH*V). this.DIST_SELECTION should be set
            * @param imp source image (just to find image size, null - use this.DIST_SELECTION.width, this.DIST_SELECTION.height)
            * @param shiftXY pattern shift (from debug), null - use {0,0}
            * @param threadsMax limit on threads to use
            * @param updateStatus update ImageJ status bar
            * @param global_debug_level global debug level
            * @param debug_level loop debug level
            * @return true - OK, false - failure
            */
           public boolean createUV_INDEX(
        		   ImagePlus imp, // or null - just to determine WOI (when getWOI matches image size) 
        		   double [] shiftXY, // add to patterGrid xy, null OK
				   int threadsMax,
				   boolean updateStatus,
				   int global_debug_level, // DEBUG_LEVEL
				   int debug_level // debug level used inside loops
        		   ){
			   SimulationPattern simulationPattern=new SimulationPattern(); // do not need bitmap array here
			   float [] UV_float0= simulationPattern.simulateGrid (
					   getDArray(),
					   2, // gridFrac, // number of grid steps per pattern full period
					   null, //simulParameters,
					   getWOI(),
					   1, // SIMUL.subdiv/2,
					   shiftXY,    // add to patterGrid xy, null OK
					   threadsMax,
					   updateStatus,
					   debug_level); // debug level
			   if (UV_float0==null) {
				   System.out.println ("BUG: createUV_INDEX(): simulationPattern.simulateGrid() returnerd null");
				   System.out.println ("BUG: createUV_INDEX(): getDArray() returnerd "+((getDArray()==null)?"null":"noy null"));
				   return false;
			   }
			   float [] UV_float= simulationPattern.combineWithCanvas(
					   -1.0,
					   ((imp==null)?(getWOI().width):imp.getWidth()),
					   ((imp==null)?(getWOI().height):imp.getHeight()),
					   getWOI(),
					   UV_float0 );
			   if (global_debug_level>3) SDFA_INSTANCE.showArrays(UV_float0,getWOI().width, getWOI().height, "UV_float0"); // all -1
			   if (global_debug_level>2) SDFA_INSTANCE.showArrays(UV_float, ((imp==null)?(getWOI().width):imp.getWidth()), ((imp==null)?(getWOI().height):imp.getHeight()), "UV_float");

			   this.UV_INDEX=new int [UV_float.length];
			   this.UV_INDEX_WIDTH=((imp==null)?(getWOI().width):imp.getWidth());
			   for (int i=0;i<this.UV_INDEX.length;i++) this.UV_INDEX[i]=(int) UV_float[i];
			   return true;
           }
           /**
            * Apply hint grid and laser pointer calibration to the grid. If (hintGrid!= null) && (hintGridTolerance>0) than any result >=0 means match
            * Create this.pixelsUV, this.targetUV
            * @param laserPointer Laser pointer parameters
            * @param pointersXY   pairs of detected pointers x,y (or nulls)
            * @param removeOutOfGridPointers if true - remove pointers if they are outside of the pattern grid
            * @param hintGrid predicted grid array (or null)
            * @param hintGridTolerance allowed mismatch (fraction of period) or 0 - orientation only
            * @param global_debug_level debug level
            * @param noMessageBoxes do not open (and wait for) dialog boxes
            * @return >=0 - number of laser pointers used for calibration, <0 - different errors
            */
           
           public int combineGridCalibration(
				   LaserPointer laserPointer, // LaserPointer object or null
				   double [][] pointersXY,
				   boolean removeOutOfGridPointers, //
				   double [][][] hintGrid, // predicted grid array (or null)
				   double        hintGridTolerance, // alllowed mismatch (fraction of period) or 0 - orientation only
				   int global_debug_level, // DEBUG_LEVEL
				   boolean noMessageBoxes 
        		   ){
			   int acalibrated=0;
			   double [][] gridMatchCoeff=null;
			   double searchAround=20.0; // how far to look for the grid node
			   int gridRotation=-1; //undefined
			   int [] iGridTranslateUV=null; // translate UV grid by these integer numbers
			   if (hintGrid!=null){
				   gridMatchCoeff=calcGridMatchMatrix (hintGrid, searchAround);
				   // now already rounds rotate and re-replaces gridMatchCoeff with approximated, refines gridMatchCoeff[0][2] and gridMatchCoeff[1][2]
				   if (gridMatchCoeff!=null) {
					   gridRotation=matrixToRot(gridMatchCoeff);
					   this.debugLevel=global_debug_level;
					   int [][] iGridMatchCoeff=gridMatrixApproximate(gridMatchCoeff);
					   if (global_debug_level>1){
						   System.out.println("gridMatchCoeff[0]={"+IJ.d2s(gridMatchCoeff[0][0],5)+", "+IJ.d2s(gridMatchCoeff[0][1],5)+", "+IJ.d2s(gridMatchCoeff[0][2],5)+"}");
						   System.out.println("gridMatchCoeff[1]={"+IJ.d2s(gridMatchCoeff[1][0],5)+", "+IJ.d2s(gridMatchCoeff[1][1],5)+", "+IJ.d2s(gridMatchCoeff[1][2],5)+"}");
						   System.out.println("gridRotation="+gridRotation);
						   System.out.println("iGridMatchCoeff[0]={"+iGridMatchCoeff[0][0]+", "+iGridMatchCoeff[0][1]+", "+iGridMatchCoeff[0][2]+"}");
						   System.out.println("iGridMatchCoeff[1]={"+iGridMatchCoeff[1][0]+", "+iGridMatchCoeff[1][1]+", "+iGridMatchCoeff[1][2]+"}");
						   System.out.println("worstGridMatchRotSkew()="+IJ.d2s(worstGridMatchRotSkew(gridMatchCoeff),5));
						   System.out.println("worstGridMatchTranslate()="+IJ.d2s(worstGridMatchTranslate(gridMatchCoeff),5));
					   }
					   // hintGridTolerance==0 - do not try to determine shift from the hint (not reliable yet)
					   if (hintGridTolerance>0) {
						   if (worstGridMatchTranslate(gridMatchCoeff)<=hintGridTolerance){ // convert to pixels from halfperiods (or just chnage definition of hintGridTolerance)
							   if (global_debug_level>1) System.out.println("worstGridMatchTranslate(gridMatchCoeff)= "+worstGridMatchTranslate(gridMatchCoeff)+", hintGridTolerance="+hintGridTolerance);
							   iGridTranslateUV=new int[2];
							   iGridTranslateUV[0]=iGridMatchCoeff[0][2];
							   iGridTranslateUV[1]=iGridMatchCoeff[1][2];
						   } else {
							   if (global_debug_level>1) System.out.println("*** Warning: combineGridCalibration() failed,  worstGridMatchTranslate(gridMatchCoeff)= "+worstGridMatchTranslate(gridMatchCoeff)+", hintGridTolerance="+hintGridTolerance);
							   return -1;
						   }
					   }
					   if (global_debug_level>0){
						   System.out.println((((iGridMatchCoeff[0][2]+iGridMatchCoeff[1][2])&1)==0)?"EVEN shift":"ODD shift");
					   }
				   } else {
					   if (global_debug_level>0) System.out.println("*** Warning: combineGridCalibration(): gridMatchCoeff() failed");
					   return -1;
				   }
			   }
			   if (((laserPointer!=null) && (laserPointer.laserUVMap.length>0)) ||
					   ((iGridTranslateUV!=null) && (gridRotation>=0))){ // no laser pointers, but hint grid with specified tolerance
				   if ((global_debug_level>1) && (pointersXY==null)) System.out.println("This image does not contain any laser pointer data");
				   acalibrated=calibrateGrid( // now should work without laser pointers too
						   laserPointer,
						   pointersXY,
						   removeOutOfGridPointers,
						   gridRotation,
						   iGridTranslateUV,
						   noMessageBoxes,
						   global_debug_level);
				   if (global_debug_level>1) {
					   System.out.println("matchSimulatedPattern.laserCalibrateGrid() returned "+acalibrated+
							   ((acalibrated>0)?" laser points used.":(((iGridTranslateUV==null) || (acalibrated<0))?" - error code":"none")));
				   }
			   }
			   if (global_debug_level>0) System.out.println("Pattern size is "+getDArrayWidth()+" x "+ getDArrayHeight());
			   return acalibrated;
        	   
           }
           
           public int replaceGridXYWithProjected(double [][][] projectedGrid, String debugTitle){
        	   int minU=0,minV=0,maxU=0,maxV=0;
        	   boolean notYetSet=true;
        	   for (double [][]row:projectedGrid) for (double [] cell:row) if (cell!=null){
        		   int u = (int) cell[2];
        		   int v = (int) cell[3];
        		   if (notYetSet){
        			   minU=u;
        			   maxU=u;
        			   minV=v;
        			   maxV=v;
        			   notYetSet=false;
        		   } else {
        			   if (minU>u) minU=u;
        			   if (maxU<u) maxU=u;
        			   if (minV>v) minV=v;
        			   if (maxV<v) maxV=v;
        		   }
        	   }
        	   double [][][] grid=new double [maxV-minV+1][maxU-minU+1][];
//        	   for (double [][]row:grid) for (double [] cell:row) cell=null; // See if this works with "enhanced for loop"
        	   for (double [][]row:grid) for (int u=0;u<row.length;u++) row[u]=null; // See if this works with "enhanced for loop"
        	   
        	   for (double [][] row:projectedGrid) for (double [] cell:row) if (cell!=null){
        		   int u = (int) cell[2];
        		   int v = (int) cell[3];
        		   double [] xy={cell[0],cell[1]};
        		   grid[v-minV][u-minU]=xy;
        	   }
        	   int numNewDefined=0;
//        	   System.out.println("this.PATTERN_GRID.length="+this.PATTERN_GRID.length+"this.PATTERN_GRID[0.length="+this.PATTERN_GRID[0].length);
//        	   System.out.println("this.targetUV.length="+this.targetUV.length+"this.targetUV[0.length="+this.targetUV[0].length);
        	   if ((debugTitle!=null) && (this.debugLevel>0)){
            	   double [][] debugReplace=null;
            	   String [] debugTiltes={"deltaX","deltaY","Contrast", "measX","measY", "targetU","targetV"};
        		   debugReplace=new double[7][this.PATTERN_GRID.length*this.PATTERN_GRID[0].length];
        		   for (int i=0;i<debugReplace.length;i++) Arrays.fill(debugReplace[i],Double.NaN);
        		   for (int v=0;v<this.PATTERN_GRID.length;v++) for (int u=0;u<this.PATTERN_GRID[v].length;u++) {
        			   double [][] cell=this.PATTERN_GRID[v][u];
        			   if ((cell !=null) && (cell.length>0) &&(cell[0] !=null) && (cell[0].length>1)){
        				   int tu=this.targetUV[v][u][0]-minU;
        				   int tv=this.targetUV[v][u][1]-minV;
        				   if ((tu>=0) && (tv>=0) && (tv<grid.length) && (tu<grid[tv].length) && (grid[tv][tu]!=null)) {
        					   int index=v*this.PATTERN_GRID[0].length+u;
        					   debugReplace[0][index]=grid[tv][tu][0]-this.PATTERN_GRID[v][u][0][0];
        					   debugReplace[1][index]=grid[tv][tu][1]-this.PATTERN_GRID[v][u][0][1];
        					   debugReplace[2][index]=this.PATTERN_GRID[v][u][0][2];
        					   debugReplace[3][index]=this.PATTERN_GRID[v][u][0][0];
        					   debugReplace[4][index]=this.PATTERN_GRID[v][u][0][1];
        					   debugReplace[5][index]=tu;
        					   debugReplace[6][index]=tu;
        				   }
        			   }
        		   }
        		   SDFA_INSTANCE.showArrays(debugReplace, this.PATTERN_GRID[0].length, this.PATTERN_GRID.length, true, "replaceGridXYWithProjected-"+debugTitle, debugTiltes);
        	   }
        	   
        	   for (int v=0;v<this.PATTERN_GRID.length;v++) for (int u=0;u<this.PATTERN_GRID[v].length;u++) {
        		   double [][] cell=this.PATTERN_GRID[v][u];
        		   if ((cell !=null) && (cell.length>0) &&(cell[0] !=null) && (cell[0].length>1)){
//                	   System.out.print("v="+v+" u="+u);
        			   int tu=this.targetUV[v][u][0]-minU;
        			   int tv=this.targetUV[v][u][1]-minV;
//                	   System.out.println("  tv="+tv+" tu="+tu);
        			   if ((tu>=0) && (tv>=0) && (tv<grid.length) && (tu<grid[tv].length) && (grid[tv][tu]!=null)) {
        				   cell[0][0]=grid[tv][tu][0]; // -81 -.-1
        				   cell[0][1]=grid[tv][tu][1];
        				   if (Double.isNaN(cell[0][0]) || Double.isNaN(cell[0][1])){
        					   this.PATTERN_GRID[v][u]=null; // make it undefined
        				   } else {
        					   numNewDefined++;
        				   }
        			   } else {
        				   this.PATTERN_GRID[v][u]=null; // make it undefined
        			   }
        		   }
        	   }
        	   return numNewDefined;
           }
           
           /* Get calibrated pattern as a 8-slice image (can be saved as TIFF)
            * first   slice - pixel X or -1 for undefined
            * second  slice - pixel Y or -1 for undefined
            * third   slice - target U (may be negative)
            * fourth  slice - target V (may be negative)
            *  other slices - if present
            * fifth   slice -  local grid contrast (looks for 2  white and 2 blacks around) - can be used to filter
            * sixth   slice - red intensity of the grid (avaraged around the grid node) 
            * seventh slice - green intensity of the grid (avaraged around the grid node) 
            * eighth  slice - blue intensity of the grid (avaraged around the grid node) 
            */
           public ImagePlus getCalibratedPatternAsImage(String title, int numUsedPointers){
        	   if ((this.targetUV==null) ||(this.pXYUV==null)) {
        		   System.out.println("getCalibratedPatternAsImage(): this.targetUV="+((this.targetUV==null)?"null":"not null")+", this.pixelsUV="+((this.pXYUV==null)?"null":"not null"));
        		   return null;
        	   }
        	   int numSlices=(this.gridContrastBrightness==null)?4:8;
        	   float [][] pixels=new float [numSlices][getWidth()*getHeight()];
        	   ImageStack stack=new ImageStack(getWidth(),getHeight());
        	   int index=0;
        	   for (int v=0;v<getHeight();v++) for (int u=0;u<getWidth();u++) {
        		   if ((this.targetUV[v][u]==null) ||(this.pXYUV[v][u]==null)||
        				   (this.pXYUV[v][u][0]<0.0) || (this.pXYUV[v][u][1]<0.0)) { // disregard negative sensor pixels
        			   pixels [0][index]=-1.0f;
        			   pixels [1][index]=-1.0f;
        			   pixels [2][index]= 0.0f;
        			   pixels [3][index]= 0.0f;
        			   if (numSlices>4){
            			   pixels [4][index]= 0.0f; // contrast
            			   pixels [5][index]=-1.0f; // red, undefined
            			   pixels [6][index]=-1.0f; // green, undefined
            			   pixels [7][index]=-1.0f; // blue, undefined
        				   
        			   }
        		   } else {
        			   pixels [0][index]=(float) this.pXYUV[v][u][0];
        			   pixels [1][index]=(float) this.pXYUV[v][u][1];
        			   pixels [2][index]=(float) this.targetUV[v][u][0];
        			   pixels [3][index]=(float) this.targetUV[v][u][1];
        			   if (numSlices>4){
            			   pixels [4][index]=(float) this.gridContrastBrightness[0][v][u]; // grid contrast
            			   pixels [5][index]=(float) this.gridContrastBrightness[1][v][u]; // red
            			   pixels [6][index]=(float) this.gridContrastBrightness[2][v][u]; // green
            			   pixels [7][index]=(float) this.gridContrastBrightness[3][v][u]; // blue
        			   }
        		   }
        		   index++;
        	   }
        	   stack.addSlice("pixel-X",  pixels[0]);
        	   stack.addSlice("pixel-Y",  pixels[1]);
        	   stack.addSlice("target-U", pixels[2]);
        	   stack.addSlice("target-V", pixels[3]);
			   if (numSlices>4){
	        	   stack.addSlice("contrast",  pixels[4]);
	        	   stack.addSlice("red",       pixels[5]);
	        	   stack.addSlice("green",     pixels[6]);
	        	   stack.addSlice("blue",      pixels[7]);
				   
			   }
        	   ImagePlus imp = new ImagePlus(title, stack);
				imp.setProperty("USED_POINTERS",((numUsedPointers>=0)?numUsedPointers:0)+"");
//				System.out.println("getCalibratedPatternAsImage(): numUsedPointers="+numUsedPointers+" getProperty(\"USED_POINTERS\")="+imp.getProperty("USED_POINTERS"));
        	   return imp;
           }
// searching for single-pixel errors (program bug)           
           public ImagePlus getCalibratedPatternCurvatureAsImage(String title){
        	   if ((this.targetUV==null) ||(this.pXYUV==null)) {
        		   String msg="this.targetUV="+((this.targetUV==null)?"null":"not null")+", this.pixelsUV="+((this.pXYUV==null)?"null":"not null");
        		   IJ.showMessage("Error",msg);
        		   throw new IllegalArgumentException (msg);
        	   }
        	   double [][][] curves=     new double [this.targetUV.length][this.targetUV[0].length][2];
        	   double [][][] diff_curves=new double [this.targetUV.length][this.targetUV[0].length][2];
        	   boolean [][]   mask_curves=new boolean [this.targetUV.length][this.targetUV[0].length];
        	   boolean [][]   mask_diff_curves=new boolean [this.targetUV.length][this.targetUV[0].length];
        	   for (int v=0;v<curves.length;v++) for (int u=0;u<curves[0].length;u++){
        		   curves[v][u][0]=0.0;
        		   curves[v][u][1]=0.0;
        		   diff_curves[v][u][0]=0.0;
        		   diff_curves[v][u][1]=0.0;
        		   mask_curves[v][u]=false;
        		   mask_diff_curves[v][u]=false;
        	   }
        	   ImageStack stack=new ImageStack(getWidth(),getHeight());
        	   int [][] dirs=   {{0,0},{-1,0},{0,-1},{1,0},{0,1},{-1,-1},{-1,1},{1,-1},{1,1}};
        	   double [] weights={1.0, -0.15,- 0.15, -.15, -.15,   -0.1,  -0.1,  -0.1, -0.1};
        	   // first pass - calculate "curvature" - difference between pixel dx, dy values and those average (weighted) of 8 neigbors
        	   for (int v=1;v<getHeight()-1;v++) for (int u=1;u<getWidth()-1;u++) {
        		   double [] avrg={0.0,0.0};
        		   boolean valid=true;
        		   for (int d=0;d<dirs.length;d++){
            		   int u1=u+dirs[d][0];
            		   int v1=v+dirs[d][1];
        			   if ((this.targetUV[v1][u1]==null) ||(this.pXYUV[v1][u1]==null)||
            				   (this.pXYUV[v1][u1][0]<0.0) || (this.pXYUV[v1][u1][0]<0.0)) { // disregard negative sensor pixels
        				   valid=false;
        				   break;
        			   } else {
        				   avrg[0]+=weights[d]*this.pXYUV[v1][u1][0];
        				   avrg[1]+=weights[d]*this.pXYUV[v1][u1][1];
        			   }
        		   }
        		   if (valid) {
        			   curves[v][u][0]=avrg[0];
        			   curves[v][u][1]=avrg[1];
        			   mask_curves[v][u]=true;
        		   }
        	   }
        	   // second pass - calculate difference between curvatives of each node and and those average (weighted) of 8 neigbors
        	   // This will mostly eliminate the distortion shift, and can be used as a measure of the "noise"
        	   for (int v=2;v<getHeight()-2;v++) for (int u=2;u<getWidth()-2;u++) {
        		   double [] avrg={0.0,0.0};
        		   boolean valid=true;
        		   for (int d=0;d<dirs.length;d++){
            		   int u1=u+dirs[d][0];
            		   int v1=v+dirs[d][1];
        			   if (!mask_curves[v1][u1]) {
        				   valid=false;
        				   break;
        			   } else {
        				   avrg[0]+=weights[d]*curves[v1][u1][0];
        				   avrg[1]+=weights[d]*curves[v1][u1][1];
        			   }
        		   }
        		   if (valid) {
        			   diff_curves[v][u][0]=avrg[0];
        			   diff_curves[v][u][1]=avrg[1];
        			   mask_diff_curves[v][u]=true;
        		   }
        	   }
        	   
        	   float [][] pixels=new float [7][getWidth()*getHeight()];
        	   int numPix=0;
        	   double sum=0.0;
        	   int index=0;
        	   double curvature, diff_curvature;
        	   for (int v=0;v<getHeight();v++) for (int u=0;u<getWidth();u++) {
        		   if (mask_curves[v][u]){
        			   curvature=Math.sqrt(curves[v][u][0]*curves[v][u][0]+curves[v][u][1]*curves[v][u][1]);
        			   pixels[0][index]=(float) curvature;
        			   pixels[1][index]=(float) curves[v][u][0];
        			   pixels[2][index]=(float) curves[v][u][1];
        			   if (mask_diff_curves[v][u]){
        				   diff_curvature=Math.sqrt(diff_curves[v][u][0]*diff_curves[v][u][0]+diff_curves[v][u][1]*diff_curves[v][u][1]);
            			   pixels[3][index]=(float) diff_curvature;
            			   pixels[4][index]=(float) diff_curves[v][u][0];
            			   pixels[5][index]=(float) diff_curves[v][u][1];
            			   pixels[6][index]=1.0f;
            			   sum+=diff_curvature*diff_curvature;
            			   numPix++;
            		   } else {
            			   pixels[3][index]=0.0f;
            			   pixels[4][index]=0.0f;
            			   pixels[5][index]=0.0f;
            			   pixels[6][index]=0.0f;
            		   }
        		   } else {
        			   pixels[0][index]=0.0f;
        			   pixels[1][index]=0.0f;
        			   pixels[2][index]=0.0f;
        			   
        		   }
        		   index++;
        	   }
        	   stack.addSlice("curvature",  pixels[0]);
        	   stack.addSlice("X-diff",     pixels[1]);
        	   stack.addSlice("Y-diff",     pixels[2]);
        	   stack.addSlice("error",      pixels[3]);
        	   stack.addSlice("X-error",    pixels[4]);
        	   stack.addSlice("Y-error",    pixels[5]);
        	   stack.addSlice("mask",       pixels[6]);
        	   if (numPix>0){
        		   double rms=Math.sqrt(sum/numPix);
        		   String msg="Deviation calculated for "+numPix+" grid nodes. RMS="+rms;
        		   System.out.println(msg);
        		   IJ.showMessage(msg);
        	   } else {
        		   String msg="Zero points to calculate deviation";
        		   System.out.println(msg);
        		   IJ.showMessage(msg);
        		   
        	   }
        	   ImagePlus imp = new ImagePlus(title, stack);
        	   return imp;
           }
           public ImagePlus getCalibratedPatternAsImage(
        		   ImagePlus imp_src,
        		   String prefix, int numUsedPointers){
//        	   ImagePlus imp_result=getCalibratedPatternAsImage("grid-"+imp_src.getTitle(), numUsedPointers);
        	   ImagePlus imp_result=getCalibratedPatternAsImage(prefix+imp_src.getTitle(), numUsedPointers);
// copy all the properties to the new image
    		JP46_Reader_camera jp4_instance= new JP46_Reader_camera(false);
    		jp4_instance.copyProperties (imp_src,imp_result);
    		jp4_instance.encodeProperiesToInfo(imp_result);
      	     return imp_result;
           }
           
           public boolean [] rotToFlips(int rot){
        	   boolean[][] rot2flips={      // swapUV,flipU,flipV for different rotations above
        			   {false,false,false},
        			   {true, true, false},
        			   {false,true, true },
        			   {true, false,true },
        			   {false,false,true },
        			   {true, false,false},
        			   {false,true, false},
        			   {true, true, true }};
        	   return rot2flips[rot];
           }
           public int flipsToRot(boolean swapUV, boolean flipU, boolean flipV) {
        	   for (int i=0;i<8;i++) if (
        			   (rotToFlips(i)[0]==swapUV) &&
        			   (rotToFlips(i)[1]==flipU) &&
        			   (rotToFlips(i)[2]==flipV)) return i;
        	   return -1; // never
           }
//		   
// move elsewhere?
           /**
            * Create this.targetUV and this.pixelsUV for the grid that does not have any laser pointer references
            */
           public void unCalibrateGrid(){
        	// calculate targetUV that maps PATTERN_GRID cells to the target (absolute) UV
        	   this.targetUV=new int    [this.PATTERN_GRID.length][this.PATTERN_GRID[0].length][];
        	   this.pXYUV=new double [this.PATTERN_GRID.length][this.PATTERN_GRID[0].length][];
// Or set it back to original 9do not touch, rotate/shift in the end?        	   
        	   Arrays.fill(this.UVShiftRot, 0);
        	   for (int v=0;v<this.PATTERN_GRID.length;v++) for (int u=0;u<this.PATTERN_GRID[v].length;u++){
        		   if ((this.PATTERN_GRID[v][u]==null) || (this.PATTERN_GRID[v][u][0]==null)) {
        			   this.targetUV[v][u]=null;
        			   this.pXYUV[v][u]=null;
        		   } else {
        			   this.targetUV[v][u]=new int [2];
        			   this.targetUV[v][u][0]=u;
        			   this.targetUV[v][u][1]=v;
        			   this.pXYUV[v][u]=new double [2];
        			   this.pXYUV[v][u][0]=PATTERN_GRID[v][u][0][0];
        			   this.pXYUV[v][u][1]=PATTERN_GRID[v][u][0][1];
        		   }
        	   }
        	   
           }
// returns -1 - failure, otherwise - number of points used for calibration array (move default orientation to laserPointer paramerters
           public int calibrateGrid(
        		   LaserPointer laserPointer,
				   double [][] xy, // null and zero length OK
				   boolean removeOutOfGridPointers,
				   int hintRotation, // rotation (0..7) found from hintGrid, -1 - undefined
				   int [] hintTranslateUV, // found from hintGrid: translate UV by this vector or null if undefined
//				   double [][][] hintGrid, // predicted grid array (or null) - use just direction
//				   double        hintGridTolerance, // alllowed mismatch (fraction of period) or 0 - orientation only
				   boolean noMessageBoxes,
				   int debugLevel
				   )
           {
        	   if (xy==null) xy=new double[0][];
        	   invalidateCalibration();
        	   double [][]uv=uvFromXY(xy,removeOutOfGridPointers?2.0:-1);
//        	   if (uv==null) return -1;
        	   int numPointesLeft=0;
    		   for (int i=0;i<xy.length;i++) if ((xy[i]!=null) && (uv[i]!=null)) numPointesLeft++;
        	   if (debugLevel>1){
        		   int numRemoved=0;
        		   for (int i=0;i<xy.length;i++) if ((xy[i]!=null) && (uv[i]==null)) numRemoved++;
        		   System.out.println("Removed "+numRemoved+" out-of-grid pointers, "+numPointesLeft+" pointers remain.");
        	   }
// Now remove pointers that are not on white cells        	   
        	   
        	   if ((laserPointer!=null) && laserPointer.whiteOnly){
        		   int numBad=0;
        		   for (int i=0;i<uv.length;i++) if (uv[i]!=null) {
        			   // Verify that laser spots are on the white cells (sum of uv is even)
        			   if ((((int)(Math.floor(uv[i][0])+Math.floor(uv[i][1]))) & 1)!=0){
        				   String msg="Laser point "+i+" is not on the white pattern cell, and this check is enforced in the configuration";
        				   System.out.println("Warning:"+msg);
        				   if (!noMessageBoxes) IJ.showMessage("Warning",msg);
        				   uv[i]=null;
        				   numBad++;
        				   continue;
        			   }
        		   }
        		   if (numBad>0){
				   String msg="Removed "+numBad+" pointers on black cells";
				   System.out.println("Warning:"+msg);
        		   }
        	   }
        	   // Later some pointers may be removed even if they are used to determine orientation/shift. But that should not lead
        	   // to white/black confusion
/*        	   
        	   int [][][] rotations={
        			   {{ 1, 0},{ 0, 1}}, // not mirrored
        			   {{ 0, 1},{-1, 0}},
        			   {{-1, 0},{ 0,-1}},
        			   {{ 0,-1},{ 1, 0}},
        			   
        			   {{ 1, 0},{ 0,-1}}, // mirrored
        			   {{ 0, 1},{ 1, 0}},
        			   {{-1, 0},{ 0, 1}},
        			   {{ 0,-1},{-1, 0}}};
        	   // shifts when rotating around unknown center (make it white)
        	   int [][] dfltShifts={
        			   {0,0},
        			   {0,1},
        			   {0,0},
        			   {1,0},
        			   {0,1},
        			   {0,0},
        			   {1,0},
        			   {0,0}};
*/        			   
        	   boolean [] possibleRotations={true,true,true,true,true,true,true,true};
// If orientation is hinted, remove all other ones from the list of possible ones        	   
        	   if (hintRotation>=0){ // defind from the hintGrid
        		   for (int i=0;i<possibleRotations.length;i++) possibleRotations[i]=(i==hintRotation); 
        	   }
 //       	   boolean [] partialPossibleRotations=new boolean [possibleRotations.length];
        	   boolean pairMatch,allMatch;
        	   int [] diffUVTable=new int [2]; // difference between points specified in the table
        	   int [] diffUVMeas= new int [2]; // measured difference (PATTERN_GRID U,V
        	   int [] rotUVTable=  new int [2]; // rotated 'laser' coordinates difference (should match measured)
        	   int [] belongsToGoodPair=new int[uv.length];
        	   int [] belongsToBadPair=new int[uv.length];
        	   for (int i=0;i<uv.length;i++){
        		   belongsToGoodPair[i]=0;
        		   belongsToBadPair[i]=0;
        	   }
        	   // pass 0 - process good/bad pairs, do not disable directions if does not match
        	   // if at least 1 good pair exists - remove all that do not match
        	   // if no good pairs - remove all bad
        	   // second pass: if more than 1 good pair - should match all (or error)
        	   
        	   
        	   //TODO: When hinted position, remove far pointers before matching pairs
        	   
        	   for (int pass=0;pass<2;pass++) {
        		   for (int i=0;i<uv.length;i++) if (uv[i]!=null) for (int j=i+1;j<uv.length;j++) if (uv[j]!=null) {
        			   pairMatch=false;
        			   allMatch=false;
        			   diffUVTable[0]=(int) Math.round(laserPointer.laserUVMap[j][0]-laserPointer.laserUVMap[i][0]); // should not get here if uv is {}
        			   diffUVTable[1]=(int) Math.round(laserPointer.laserUVMap[j][1]-laserPointer.laserUVMap[i][1]);
        			   diffUVMeas[0]= (int) Math.round(uv[j][0]-uv[i][0]);
        			   diffUVMeas[1]= (int) Math.round(uv[j][1]-uv[i][1]);
        			   // see which rotations are possible for this pair of points        		   
        			   if (debugLevel>2){
        				   System.out.println("pass="+pass+" i="+i+" j="+j);
        				   System.out.println("diffUVTable=["+diffUVTable[0]+","+diffUVTable[1]+"]");
        				   System.out.println("diffUVMeas= ["+diffUVMeas[0]+ ","+diffUVMeas[1]+"]");
        			   }
        			   for (int dir=0; dir<rotations.length;dir++) {
        				   rotUVTable[0]=rotations[dir][0][0]*diffUVTable[0]+rotations[dir][0][1]*diffUVTable[1]; 
        				   rotUVTable[1]=rotations[dir][1][0]*diffUVTable[0]+rotations[dir][1][1]*diffUVTable[1];
        				   if (debugLevel>2){
        					   System.out.println(dir+": rotUVTable= ["+rotUVTable[0]+ ","+rotUVTable[1]+"]");
        				   }
        				   if ((rotUVTable[0]==diffUVMeas[0]) && (rotUVTable[1]==diffUVMeas[1])) {
        					   pairMatch=true;
        					   if (possibleRotations[dir]) allMatch=true; // this and hinted direction
        				   } else {
        					   if (pass>0){ // do not disable rotation on the first pass
        						   possibleRotations[dir]=false;
        					   }
        				   }
        			   }
        			   // TODO: Find maximal number of matching pointers?

        			   if (pass==0) {
        				   //        				   if (allMatch) {
        				   if (pairMatch) {
        					   belongsToGoodPair[i]++;
        					   belongsToGoodPair[j]++;
        				   } else {
        					   belongsToBadPair[i]++;
        					   belongsToBadPair[j]++;
        				   }
        			   } else { // second pass
        				   if (!pairMatch) {
        					   String msg="Laser points\n"+i+" ["+IJ.d2s(laserPointer.laserUVMap[i][0],2)+":"+IJ.d2s(laserPointer.laserUVMap[i][1],2)+
        					   "] -> ["+IJ.d2s(uv[i][0],2)+":"+IJ.d2s(uv[i][1],2)+"] and \n"+
        					   j +" ["+IJ.d2s(laserPointer.laserUVMap[j][0],2)+":"+IJ.d2s(laserPointer.laserUVMap[j][1],2)+
        					   "] -> ["+IJ.d2s(uv[j][0],2)+":"+IJ.d2s(uv[j][1],2)+"] do not match";
        					   System.out.println(msg);
        					   if (!noMessageBoxes) IJ.showMessage("Error",msg);
        					   unCalibrateGrid();
        					   return -2;
        				   } else if (!allMatch){
        					   String msg="Following laser pointers can not be mapped simultaneously:\n";
        					   for (int k=0;k<=j;k++) if (uv[k]!=null) {
        						   msg+=k+" ["+IJ.d2s(laserPointer.laserUVMap[k][0],2)+":"+IJ.d2s(laserPointer.laserUVMap[k][1],2)+
        						   "] -> ["+IJ.d2s(uv[k][0],2)+":"+IJ.d2s(uv[k][1],2)+"]\n";
        					   }
        					   System.out.println(msg);
        					   if (!noMessageBoxes) IJ.showMessage("Error",msg);
        					   unCalibrateGrid();
        					   return -1;
        				   }
        			   }
        		   }
        		   if (pass==0){
        			  int numInGood=0;  
        			  int numInBad=0;
        			  for (int i=0;i<uv.length;i++){
        				  if (belongsToGoodPair[i]>0) numInGood++;
        				  if (belongsToBadPair[i]>0) numInBad++;
        			  }
        			  if (numInBad>0){
        				  if (numInGood==0){
        					  String msg="No matching laser points pairs exist, and "+numInBad+" points do not match";
        					  System.out.println(msg);
        					  if (!noMessageBoxes) IJ.showMessage("Error",msg);
        					  /// will report error on the second pass
        				  } else if (numInBad>0){
        					  String msg="Matching laser points pair(s) exist(s), but other:";
        					  for (int i=0;i<uv.length;i++) if ((belongsToBadPair[i]>0) && (belongsToGoodPair[i]==0)){
        						  msg+=" #"+i+" ("+(i+1)+" of "+uv.length+")";
        						  uv[i]=null; // remove it from consideration
        					  }
        					  msg+=" do not match and will be removed.";
        					  System.out.println(msg);
        					  if (!noMessageBoxes) IJ.showMessage("Error",msg);
        				  }
        			  }
        		   }
        	   }
//TODO: here at least some rotations match all points. If there ere more than two - try to use closest to the default/previous
        	   int rotation=(laserPointer!=null)?(flipsToRot(laserPointer.swapUV,laserPointer.flipU,laserPointer.flipV)):0;
        	   if (!possibleRotations[rotation]) { // current rotation value defined by laserPointer.{swapUV,flipU,flipV} does not match
        		   // find a new one (first - without mirroring)
        		   for (int i=0; i<8;i++) if (possibleRotations[(((rotation ^ i) & 4)) | ((rotation+i) & 3)]) {
        			   rotation=(((rotation ^ i) & 4)) | ((rotation+i) & 3); // first tried in the same half, then - the next one
        			   break;
        		   }
            	   if (!possibleRotations[rotation]) { // Program bug - should not happen
        			   String msg="Program error - could not find laser point mapping while it should exist\n";
        			   System.out.println(msg);
   					   if (!noMessageBoxes) IJ.showMessage("Error",msg);
   					   unCalibrateGrid();
        			   return -3;
            	   }
        	   }
// now rotation is the correct one, update laserPointer.{swapUV,flipU,flipV};
        	   if (laserPointer!=null){
        		   laserPointer.swapUV=rotToFlips(rotation)[0];
        		   laserPointer.flipU =rotToFlips(rotation)[1];
        		   laserPointer.flipV =rotToFlips(rotation)[2];
        	   }
//calculate shift
        	   int [] uvShift=dfltShifts[rotation].clone(); //{0,0};
        	   for (int i=0;i<uv.length;i++) if (uv[i]!=null) { // laserPointer -> uv=={}
        		   uvShift[0]=(int) Math.round(uv[i][0]-
        				   (rotations[rotation][0][0]*laserPointer.laserUVMap[i][0]+
        				    rotations[rotation][0][1]*laserPointer.laserUVMap[i][1]));
        		   uvShift[1]=(int) Math.round(uv[i][1]-
        				   (rotations[rotation][1][0]*laserPointer.laserUVMap[i][0]+
        				    rotations[rotation][1][1]*laserPointer.laserUVMap[i][1]));
        		   break;
        	   }
        	   
// Hinted shift will only be used if no laser pointers are available, otherwise - only verify/warn
        	   if (hintTranslateUV!=null) {
//        		   if ((uv.length==0) || (numPointesLeft==0)){
            	   if (numPointesLeft==0){
        			   uvShift[0]=hintTranslateUV[0];
        			   uvShift[1]=hintTranslateUV[1];
        			   if (debugLevel>1){
        				   System.out.println("No laser pointers available, using hinted translation");
        			   }
        		   } else {
        			   if ((uvShift[0]==hintTranslateUV[0]) && (uvShift[0]==hintTranslateUV[0])){
        				   if (debugLevel>1){
            				   System.out.println("Translation from the laser pointers matches the hinted one");
            			   }
        			   } else {
        				   if (debugLevel>1){
            				   System.out.println("Translation from the laser pointers does not match the hinted one:");
            				   System.out.println("Hinted: delta U="+hintTranslateUV[0]+", V="+hintTranslateUV[1]);
            				   System.out.println("Lasers: delta U="+uvShift[0]+", V="+uvShift[1]);
            				   System.out.println("Trusting lasers");
            			   }
        			   }
        		   }
        	   }
// calculate remap array (rotation+translation) from the target UV to the measured grid UV.        	   
        	   this.reMap=new int[2][3]; // seems it is never used?
        	   this.reMap[0][0]= rotations[rotation][0][0];
        	   this.reMap[0][1]= rotations[rotation][0][1];
        	   this.reMap[0][2]= uvShift[0];
        	   this.reMap[1][0]= rotations[rotation][1][0];
        	   this.reMap[1][1]= rotations[rotation][1][1];
        	   this.reMap[1][2]= uvShift[1];
// calculate reverse remap array (rotation+translation) from the the measured grid UV to the target UV        	   
        	   int reRot=(rotation>=4)?rotation:((4-rotation) & 3); // number of reverse mirror-rotation
//        	   int [] UVRot={uvShift[0],uvShift[1],reRot};
        	   int [] UVRot={
        			   -(rotations[reRot][0][0]*uvShift[0] +rotations[reRot][0][1]*uvShift[1]),
        			   -(rotations[reRot][1][0]*uvShift[0] +rotations[reRot][1][1]*uvShift[1]),
        			   reRot};
        	   return  applyUVShiftRot(
        			   UVRot, // int [] UVShiftRot,
            		   uv,   // double [][]uv,
            		   laserPointer,
            		   noMessageBoxes);
/* 
        	   int [][] reReMap={
        			   {rotations[reRot][0][0],rotations[reRot][0][1],
        		       -(rotations[reRot][0][0]*uvShift[0] +rotations[reRot][0][1]*uvShift[1])},
        		       {rotations[reRot][1][0],rotations[reRot][1][1],
            		       -(rotations[reRot][1][0]*uvShift[0] +rotations[reRot][1][1]*uvShift[1])}};
        	   
        	   if (debugLevel>1){
        		   System.out.println("rotation="+rotation+", reMap= [["+this.reMap[0][0]+","+this.reMap[0][1]+","+this.reMap[0][2]+"]["+
        				   +this.reMap[1][0]+","+this.reMap[1][1]+","+this.reMap[1][2]+"]]");      	   
        		   System.out.println("reRot="+   reRot+",  reReMap= [["+reReMap[0][0]+","+reReMap[0][1]+","+reReMap[0][2]+"]["+
        				   +reReMap[1][0]+","+reReMap[1][1]+","+reReMap[1][2]+"]]");      	   
        	   }
// calculate targetUV that maps PATTERN_GRID cells to the target (absolute) UV
        	   this.targetUV=new int    [this.PATTERN_GRID.length][this.PATTERN_GRID[0].length][];
        	   this.pXYUV=new double [this.PATTERN_GRID.length][this.PATTERN_GRID[0].length][];
        	   for (int v=0;v<this.PATTERN_GRID.length;v++) for (int u=0;u<this.PATTERN_GRID[v].length;u++){
        		   if ((this.PATTERN_GRID[v][u]==null) || (this.PATTERN_GRID[v][u][0]==null)) {
        			   this.targetUV[v][u]=null;
        			   this.pXYUV[v][u]=null;
        		   } else {
        			   this.targetUV[v][u]=new int [2];
        			   this.targetUV[v][u][0]=reReMap[0][0]*u+reReMap[0][1]*v+reReMap[0][2];
        			   this.targetUV[v][u][1]=reReMap[1][0]*u+reReMap[1][1]*v+reReMap[1][2];
//        			   System.out.println("v="+v+", u="+u+", PATTERN_GRID.length="+PATTERN_GRID.length+", PATTERN_GRID[v].length="+PATTERN_GRID[v].length);
//        			   System.out.println("this.pixelsUV.length="+this.pixelsUV.length);
//        			   System.out.println("this.pixelsUV["+v+"].length="+this.pixelsUV[v].length);
        			   this.pXYUV[v][u]=new double [2];
        			   this.pXYUV[v][u][0]=PATTERN_GRID[v][u][0][0];
        			   this.pXYUV[v][u][1]=PATTERN_GRID[v][u][0][1];
        		   }
        	   }
        	   int numGood=0;
        	   int numBad=0;
        	   double [] distUV=new double[2];
        	   double dist;
        	   for (int i=0;i<uv.length;i++) if (uv[i]!=null) { //laserPointer == null > uv={}
        		// Verify that laser spots are inside specified distance from the cell centers        	   
        		   distUV[0]=reReMap[0][0]*uv[i][0]+reReMap[0][1]*uv[i][1]+reReMap[0][2]-laserPointer.laserUVMap[i][0];
        		   distUV[1]=reReMap[1][0]*uv[i][0]+reReMap[1][1]*uv[i][1]+reReMap[1][2]-laserPointer.laserUVMap[i][1];
        		   dist=Math.sqrt(distUV[0]*distUV[0]+distUV[1]*distUV[1]);
            	   if (debugLevel>1){
            		   System.out.println("Laser spot #"+i+", distance from predicted ="+ IJ.d2s(dist,3)+" ("+IJ.d2s(200*dist,3)+
            				   "% of cell radius), du="+IJ.d2s(distUV[0],3)+", dv="+IJ.d2s(distUV[1],3));
            	   }
            	   if ((2*dist)> laserPointer.maxOffsetFromCenter){
        			   String msg="Laser point "+(i+1)+"(of "+uv.length+") is too far from the specified location, and this check is enforced in the configuration\n"+
        			              "measured distance is "+ IJ.d2s(200*dist,1)+"% of the cell radius, specified is "+ IJ.d2s(100*laserPointer.maxOffsetFromCenter,1)+"%";
        			   System.out.println("Warning:"+msg);
        			   if (!noMessageBoxes) IJ.showMessage("Warning",msg);
        			   numBad++;
        			   uv[i]=null;
        			   continue;
            	   }
        		   numGood++;
        	   }
        	   if ((debugLevel>0) && (numBad>0)){
        		   System.out.println("Removed "+numBad+" pointers that are too far from the predicted locations");
        	   }
        	   return numGood;
*/        	   
           }
           
           
           public int applyUVShiftRot(
        		   int [] UVShiftRot,
        		   double [][]uv,
        		   LaserPointer laserPointer,
        		   boolean noMessageBoxes
        		   ){
        	   if (UVShiftRot!=null) this.UVShiftRot=UVShiftRot.clone();
//        	   int reRot=(rotation>=4)?rotation:((4-rotation) & 3); // number of reverse mirror-rotation
/*        	   int [][] reReMap={
        			   {rotations[UVShiftRot[2]][0][0],rotations[UVShiftRot[2]][0][1],
        		       -(rotations[UVShiftRot[2]][0][0]*UVShiftRot[0] +rotations[UVShiftRot[2]][0][1]*UVShiftRot[1])},
        		       {rotations[UVShiftRot[2]][1][0],rotations[UVShiftRot[2]][1][1],
            		       -(rotations[UVShiftRot[2]][1][0]*UVShiftRot[0] +rotations[UVShiftRot[2]][1][1]*UVShiftRot[1])}}; */
        	   int [][] reReMap=getRemapMatrix(UVShiftRot);
        	   if (debugLevel>1){
//        		   System.out.println("rotation="+rotation+", reMap= [["+this.reMap[0][0]+","+this.reMap[0][1]+","+this.reMap[0][2]+"]["+
//        				   +this.reMap[1][0]+","+this.reMap[1][1]+","+this.reMap[1][2]+"]]");      	   
        		   System.out.println("reRot="+   UVShiftRot[2]+",  reReMap= [["+reReMap[0][0]+","+reReMap[0][1]+","+reReMap[0][2]+"]["+
        				   +reReMap[1][0]+","+reReMap[1][1]+","+reReMap[1][2]+"]]");      	   
        	   }
// calculate targetUV that maps PATTERN_GRID cells to the target (absolute) UV
        	   this.targetUV=new int    [this.PATTERN_GRID.length][this.PATTERN_GRID[0].length][];
        	   this.pXYUV=new double [this.PATTERN_GRID.length][this.PATTERN_GRID[0].length][];
        	   for (int v=0;v<this.PATTERN_GRID.length;v++) for (int u=0;u<this.PATTERN_GRID[v].length;u++){
        		   if ((this.PATTERN_GRID[v][u]==null) || (this.PATTERN_GRID[v][u][0]==null)) {
        			   this.targetUV[v][u]=null;
        			   this.pXYUV[v][u]=null;
        		   } else {
        			   this.targetUV[v][u]=new int [2];
        			   this.targetUV[v][u][0]=reReMap[0][0]*u+reReMap[0][1]*v+reReMap[0][2];
        			   this.targetUV[v][u][1]=reReMap[1][0]*u+reReMap[1][1]*v+reReMap[1][2];
//        			   System.out.println("v="+v+", u="+u+", PATTERN_GRID.length="+PATTERN_GRID.length+", PATTERN_GRID[v].length="+PATTERN_GRID[v].length);
//        			   System.out.println("this.pixelsUV.length="+this.pixelsUV.length);
//        			   System.out.println("this.pixelsUV["+v+"].length="+this.pixelsUV[v].length);
        			   this.pXYUV[v][u]=new double [2];
        			   this.pXYUV[v][u][0]=PATTERN_GRID[v][u][0][0];
        			   this.pXYUV[v][u][1]=PATTERN_GRID[v][u][0][1];
        		   }
        	   }
        	   int numGood=0;
        	   int numBad=0;
        	   double [] distUV=new double[2];
        	   double dist;
        	   if (laserPointer!=null) {
        		   for (int i=0;i<uv.length;i++) if (uv[i]!=null) { //laserPointer == null > uv={}
        			   // Verify that laser spots are inside specified distance from the cell centers        	   
        			   distUV[0]=reReMap[0][0]*uv[i][0]+reReMap[0][1]*uv[i][1]+reReMap[0][2]-laserPointer.laserUVMap[i][0];
        			   distUV[1]=reReMap[1][0]*uv[i][0]+reReMap[1][1]*uv[i][1]+reReMap[1][2]-laserPointer.laserUVMap[i][1];
        			   dist=Math.sqrt(distUV[0]*distUV[0]+distUV[1]*distUV[1]);
        			   if (debugLevel>1){
        				   System.out.println("Laser spot #"+i+", distance from predicted ="+ IJ.d2s(dist,3)+" ("+IJ.d2s(200*dist,3)+
        						   "% of cell radius), du="+IJ.d2s(distUV[0],3)+", dv="+IJ.d2s(distUV[1],3));
        			   }
        			   if ((2*dist)> laserPointer.maxOffsetFromCenter){
        				   String msg="Laser point "+(i+1)+"(of "+uv.length+") is too far from the specified location, and this check is enforced in the configuration\n"+
        						   "measured distance is "+ IJ.d2s(200*dist,1)+"% of the cell radius, specified is "+ IJ.d2s(100*laserPointer.maxOffsetFromCenter,1)+"%";
        				   System.out.println("Warning:"+msg);
        				   if (!noMessageBoxes) IJ.showMessage("Warning",msg);
        				   numBad++;
        				   uv[i]=null;
        				   continue;
        			   }
        			   numGood++;
        		   }
        	   }
        	   if ((debugLevel>0) && (numBad>0)){
        		   System.out.println("Removed "+numBad+" pointers that are too far from the predicted locations");
        	   }
        	   return numGood;        	   
           }

           
           /**
            * Rotate/flip PATTERN_GRID to match expected
            * @param hintGrid [v][u][0 - pixel X, 1 - pixel Y, 2 - targetU, 3 - targetV
            * @return true if possible, false - if not
            */
 /*          
           public boolean applyHintToGrid(double [][][] hintGrid){
        	   
           }
*/           
		   /**
		    * Calculate grid fractional UV from x,y and grid array
		    * using bi-linear interpolation from the nearest points.
		    * Iterates through all grid points, so it is not optimal
		    * for processing each pixel.
		    */
		   public double [][] uvFromXY(
				   double [][] xy,
				   double maxDist
		   ){
			   if (xy==null) {
				   double[][] uv=new double[0][];
				   return uv;
			   }
			   double[][] uv=new double[xy.length][];
			   for (int i=0;i<xy.length;i++) {
				   uv[i]= uvFromXY(xy[i],maxDist);
//				   if (uv[i]==null) return null;
			   }
			   return uv;
		   }
		   public double [] uvFromXY(
				   double [] xy,
				   double maxDist
		   ){
//			   double [][][][] grid= this.PATTERN_GRID;
			   //			   double [] gXY=new double [2];
			   //			   int [] iUV=new int [2];
			   if (xy==null) return null;
			   int [][] iUV=new int[3][2];
			   int width=this.PATTERN_GRID[0].length;
			   // find closest point to xy
			   double dist2,dx,dy,minDist2=-1.0;
			   double []dist2Array=new double [this.PATTERN_GRID.length*width];
			   for (int v=0;v< this.PATTERN_GRID.length; v++) for (int u=0;u< this.PATTERN_GRID[v].length; u++)
				   if ((this.PATTERN_GRID[v][u]!=null) && (this.PATTERN_GRID[v][u][0]!=null)){
					   dx=this.PATTERN_GRID[v][u][0][0]-xy[0];
					   dy=this.PATTERN_GRID[v][u][0][1]-xy[1];
					   dist2=dx*dx+dy*dy;
					   dist2Array[v*width+u]=dist2;
					   if ((minDist2<0.0) || (minDist2>dist2)) {
						   minDist2=dist2;
						   iUV[0][0]=u;
						   iUV[0][1]=v;
					   }
				   } else {
					   dist2Array[v*width+u]=-1.0;
				   }
			   // now find two other closest points (not on the same line
			   dist2Array[iUV[0][1]*width+iUV[0][0]]=-1.0; // mark used point
			   int indx=0;
			   minDist2=-1.0;
			   for (int i=0; i<dist2Array.length;i++) if ((dist2Array[i]>=0.0) && ((minDist2<0.0) || (minDist2>dist2Array[i]))){
				   minDist2=dist2Array[i];
				   indx=i;
			   }
			   iUV[1][0]=indx%width;
			   iUV[1][1]=indx/width;
			   // mark all points on the same line as iUV[0] and iUV[1]
			   // find closest of the remaining points
			   indx=0;
			   minDist2=-1.0;
			   int dU1=iUV[1][0]-iUV[0][0];
			   int dV1=iUV[1][1]-iUV[0][1];
			   int dU2,dV2;
			   for (int i=0; i<dist2Array.length;i++)
				   if ((dist2Array[i]>=0.0) && ((minDist2<0.0) || (minDist2>dist2Array[i]))) {
					   dU2=i%width-iUV[0][0];
					   dV2=i/width-iUV[0][1];
					   if (dU1*dV2!=dV1*dU2) {
						   minDist2=dist2Array[i];
						   indx=i;
					   }
				   }
			   iUV[2][0]=indx%width;
			   iUV[2][1]=indx/width;
			   // now there are 3 (not co-linear) points to interpolate u,v
			   double [][] aMuv={
					   {iUV[1][0]-iUV[0][0],iUV[2][0]-iUV[0][0]},
					   {iUV[1][1]-iUV[0][1],iUV[2][1]-iUV[0][1]}
			   };
			   Matrix Muv=new Matrix(aMuv);
			   if (
					   (this.PATTERN_GRID==null)||
					   (this.PATTERN_GRID[iUV[0][1]][iUV[0][0]]==null)||
					   (this.PATTERN_GRID[iUV[1][1]][iUV[1][0]]==null)||
					   (this.PATTERN_GRID[iUV[2][1]][iUV[2][0]]==null)||
					   (this.PATTERN_GRID[iUV[0][1]][iUV[0][0]][0]==null)||
					   (this.PATTERN_GRID[iUV[1][1]][iUV[1][0]][0]==null)||
					   (this.PATTERN_GRID[iUV[2][1]][iUV[2][0]][0]==null)) return null;
			   double [][] aMxy={
					   {   this.PATTERN_GRID[iUV[1][1]][iUV[1][0]][0][0]-this.PATTERN_GRID[iUV[0][1]][iUV[0][0]][0][0],
						   this.PATTERN_GRID[iUV[2][1]][iUV[2][0]][0][0]-this.PATTERN_GRID[iUV[0][1]][iUV[0][0]][0][0]},
						   {   this.PATTERN_GRID[iUV[1][1]][iUV[1][0]][0][1]-this.PATTERN_GRID[iUV[0][1]][iUV[0][0]][0][1],
							   this.PATTERN_GRID[iUV[2][1]][iUV[2][0]][0][1]-this.PATTERN_GRID[iUV[0][1]][iUV[0][0]][0][1]}
			   };
			   Matrix Mxy=new Matrix(aMxy);
			   double [][] aVxy={
					   {xy[0]-this.PATTERN_GRID[iUV[0][1]][iUV[0][0]][0][0]},
					   {xy[1]-this.PATTERN_GRID[iUV[0][1]][iUV[0][0]][0][1]}
			   };
			   Matrix Vxy=new Matrix(aVxy);
			   double [][] aVuv0={
					   {iUV[0][0]},
					   {iUV[0][1]}
			   };
			   Matrix Vuv0=new Matrix(aVuv0);
			   Matrix Vuv=Vuv0.plus(Muv.times(Mxy.inverse()).times(Vxy));
			   double [] result=Vuv.getRowPackedCopy();
			   if (this.debugLevel>1) System.out.println("X="+IJ.d2s(xy[0],3)+" Y="+IJ.d2s(xy[1],3));
			   if (this.debugLevel>2) System.out.println(" "+
					   "Grid["+iUV[0][1]+"]["+iUV[0][0]+"]X="+IJ.d2s(this.PATTERN_GRID[iUV[0][1]][iUV[0][0]][0][0],3)+" "+
					   "Grid["+iUV[0][1]+"]["+iUV[0][0]+"]Y="+IJ.d2s(this.PATTERN_GRID[iUV[0][1]][iUV[0][0]][0][1],3)+"\n "+
					   "Grid["+iUV[1][1]+"]["+iUV[1][0]+"]X="+IJ.d2s(this.PATTERN_GRID[iUV[1][1]][iUV[1][0]][0][0],3)+" "+
					   "Grid["+iUV[1][1]+"]["+iUV[1][0]+"]Y="+IJ.d2s(this.PATTERN_GRID[iUV[1][1]][iUV[1][0]][0][1],3)+"\n "+
					   "Grid["+iUV[2][1]+"]["+iUV[2][0]+"]X="+IJ.d2s(this.PATTERN_GRID[iUV[2][1]][iUV[2][0]][0][0],3)+" "+
					   "Grid["+iUV[2][1]+"]["+iUV[2][0]+"]Y="+IJ.d2s(this.PATTERN_GRID[iUV[2][1]][iUV[2][0]][0][1],3));
			   if (this.debugLevel>1) System.out.println("U="+IJ.d2s(result[0],3)+" V="+IJ.d2s(result[1],3)+"\n");
			   minDist2=(result[0]-iUV[0][0])*(result[0]-iUV[0][0])+(result[1]-iUV[0][1])*(result[1]-iUV[0][1]);
			   if ((maxDist>0.0) && (minDist2>maxDist*maxDist)) {
				   if (this.debugLevel>0) System.out.println("minDist2="+minDist2+" (maxDist="+maxDist+") - pointer too far (x="+xy[0]+" y="+xy[1]+")");
				   return null; // pointer too far from the grid (outside of the grid)
			   }
// change test (make sure that all 4 grid points around the result are defined
			   int uFloor=(int) Math.floor(result[0]);
			   int vFloor=(int) Math.floor(result[1]);
			   int extra=(int) Math.round(maxDist)-1;
			   if (extra<0) extra=0;
			   for (int v=vFloor-extra; v<= vFloor+extra+1;v++)  for (int u=uFloor-extra; u<= uFloor+extra+1;u++) 
				   if ((v<0) || (u<0) || (v>=this.PATTERN_GRID.length) || (u>=this.PATTERN_GRID[v].length) ||
						   (this.PATTERN_GRID[v][u]==null) || (this.PATTERN_GRID[v][u][0]==null)) {
					   if (this.debugLevel>1) System.out.println("pointer="+result[0]+":"+result[1]+
							   ", no grid at "+u+":"+v+" - pointer does not have grid around (x="+xy[0]+" y="+xy[1]+"), extra="+extra+" vFloor="+vFloor+" uFloor="+uFloor);
					   for (int iiv=-2;iiv<3;iiv++) {
						   for (int iiu=-2;iiu<3;iiu++) {
							   boolean iinValid=
								   ((iiv+vFloor)<0)||
								   ((iiv+vFloor)>=this.PATTERN_GRID.length) ||
								   ((iiu+uFloor)<0)||
								   ((iiu+uFloor)>=this.PATTERN_GRID[0].length) ||
								   (this.PATTERN_GRID[iiv+vFloor][iiu+uFloor]==null) ||
								   (this.PATTERN_GRID[iiv+vFloor][iiu+uFloor][0]==null);
							   if (this.debugLevel>1)  System.out.println((iiu+uFloor)+":"+
									   (iiv+vFloor)+ "  "+(iinValid?"---":(IJ.d2s(this.PATTERN_GRID[iiv+vFloor][iiu+uFloor][0][0],1)+":"+IJ.d2s(this.PATTERN_GRID[iiv+vFloor][iiu+uFloor][0][1],1))));
						   }
					   }
					   return null; // pointer too far from the grid (outside of the grid)
			   }
			   return result;
		   }
		   
/* ======================================================================== */
/*
    public static MatchSimulatedPattern.LaserPointer LASER_POINTERS= new MatchSimulatedPattern.LaserPointer (
		   
 */
//		   
/* ======================================================================== */
		   private  double [] correctedPatternCrossLocation(
					double [] beforeXY, // initial coordinates of the pattern cross point
					double wv0x,
					double wv0y,
					double wv1x,
					double wv1y,
					double [][] correction,
					ImagePlus imp,      // image data (Bayer mosaic)
					DistortionParameters distortionParameters, //
					MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
					MatchSimulatedPattern matchSimulatedPattern, // correlationSize
					SimulationPattern.SimulParameters  thisSimulParameters,
					boolean equalizeGreens,			
					double [] window,   // window function
					double [] window2,   // window function - twice FFT size (or null)
					double [] window4,   // window function - 4x FFT size (or null)
					SimulationPattern simulationPattern,
					boolean negative, // invert cross phase
					DoubleFHT fht_instance,
					boolean fast, // use fast measuring of the maximum on the correlation
					double [][] locsNeib, // locations and weights of neighbors to average
					int debug_level,
					String dbgStr){
			   if (distortionParameters.legacyMode)
				   return correctedPatternCrossLocationOld(
						beforeXY, // initial coordinates of the pattern cross point
						wv0x,
						wv0y,
						wv1x,
						wv1y,
						correction,
						imp,      // image data (Bayer mosaic)
						distortionParameters, //
						patternDetectParameters,
						matchSimulatedPattern, // correlationSize
						thisSimulParameters,
						equalizeGreens,			
						window,   // window function
						window2,   // window function - twice FFT size (or null)
						window4,   // window function - 4x FFT size (or null)
						simulationPattern,
						negative, // invert cross phase
						fht_instance,
						fast, // use fast measuring of the maximum on the correlation
						locsNeib, // locations and weights of neighbors to average
						debug_level);
			   else
				   return correctedPatternCrossLocationAverage4(
							beforeXY, // initial coordinates of the pattern cross point
							wv0x,
							wv0y,
							wv1x,
							wv1y,
							correction,
							imp,      // image data (Bayer mosaic)
							distortionParameters, //
							patternDetectParameters,
							matchSimulatedPattern, // correlationSize
							thisSimulParameters,
							equalizeGreens,			
							window,   // window function
							window2,   // window function - twice FFT size (or null)
							window4,   // window function - 4x FFT size (or null)
							simulationPattern,
							negative, // invert cross phase
							fht_instance,
							fast, // use fast measuring of the maximum on the correlation
							locsNeib, // locations and weights of neighbors to average
							debug_level,
							dbgStr);
		   }
		   private  double [] correctedPatternCrossLocationOld(
				double [] beforeXY, // initial coordinates of the pattern cross point
				double wv0x,
				double wv0y,
				double wv1x,
				double wv1y,
				double [][] correction,
				ImagePlus imp,      // image data (Bayer mosaic)
				DistortionParameters distortionParameters, //
				MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
				MatchSimulatedPattern matchSimulatedPattern, // correlationSize
				SimulationPattern.SimulParameters  thisSimulParameters,
				boolean equalizeGreens,			
				double [] window,   // window function
				double [] window2,   // window function - twice FFT size (or null)
				double [] window4,   // window function - 4x FFT size (or null)
				SimulationPattern simulationPattern,
				boolean negative, // invert cross phase
				DoubleFHT fht_instance,
				boolean fast, // use fast measuring of the maximum on the correlation
				double [][] locsNeib, // locations and weights of neighbors to average
				int debug_level){
		   
// Just for testing
			   
		   beforeXY[0]+=distortionParameters.correlationDx;  // offset, X (in pixels)
		   beforeXY[1]+=distortionParameters.correlationDy; // offset y (in pixels)
		   
		   
			double [][] convMatrix= {{1.0,-1.0},{1.0,1.0}}; // from greens2 to pixel WV
			double [][] invConvMatrix= matrix2x2_scale(matrix2x2_invert(convMatrix),2.0);
		   
		   	double [] result=new double [3];
		   	result[0]=beforeXY[0];
		   	result[1]=beforeXY[1];
		   	result[2]=0.0; // contrast
		   	
		   	
			if (fht_instance==null) fht_instance=new DoubleFHT(); // move upstream to reduce number of initializations
		   	 

		//create diagonal green selection around ixc,iyc

			 double [][]wv={{wv0x, wv0y},
					        {wv1x, wv1y}};
			 double [][] WVgreens=matrix2x2_mul(wv,invConvMatrix);
			 if (debug_level>2) System.out.println("WVgreens[0][0]="+IJ.d2s(WVgreens[0][0],3)+
					 " WVgreens[0][1]="+IJ.d2s(WVgreens[0][1],3)+
					 " WVgreens[1][0]="+IJ.d2s(WVgreens[1][0],3)+
					 " WVgreens[1][1]="+IJ.d2s(WVgreens[1][1],3));
			 double [] dUV;
			 double[][] sim_pix;
			 double [] simGreensCentered;
			 double [] modelCorr;
			 double [] xyCorr={0.0,0.0};
			 double [] centerXY;
			 double 	contrast;
			 int numNeib;
	    	 double []corr=null;
		     double [] neibCenter=new double[2];	
		     if (correction!=null) { // overwrite wave vectors
				 wv[0][0]=correction[0][0];
				 wv[0][1]=correction[0][1];
				 wv[1][0]=correction[1][0];
				 wv[1][1]=correction[1][1];
		    	 if (correction[0].length>3) { // enough data for quadratic approximation
		    		 corr=new double[10];
			    	 corr[0]=correction[0][3]/4;
			    	 corr[1]=correction[0][4]/4;
			    	 corr[2]=correction[0][5]/4;
			    	 corr[3]=correction[1][3]/4;
			    	 corr[4]=correction[1][4]/4;
			    	 corr[5]=correction[1][5]/4;
			    	 corr[6]=0.0;
			    	 corr[7]=0.0;
			    	 corr[9]=0.0;
			    	 corr[9]=0.0;
		    	 }
		     }
		     double u_span=Math.sqrt(wv0x*wv0x+wv0y*wv0y)*distortionParameters.correlationSize;
		     double v_span=Math.sqrt(wv1x*wv1x+wv1y*wv1y)*distortionParameters.correlationSize;
		     double min_span=Math.min(u_span, v_span);
		     int thisCorrelationSize=distortionParameters.correlationSize;
		     double [] thisWindow=window;
		     double uv_threshold=distortionParameters.minUVSpan*0.25*Math.sqrt(2.0); 

		     if (
		    		 (min_span<uv_threshold) &&
		    		 (window2!=null) &&
		    		 (thisCorrelationSize<distortionParameters.maximalCorrelationSize)) { // trying to increase only twice
		    	 thisCorrelationSize*=2;
		    	 min_span*=2;
		    	 thisWindow=window2;
		    	 if (
		    			 (min_span<uv_threshold) &&
		    			 (window4!=null) &&
		    			 (thisCorrelationSize<distortionParameters.maximalCorrelationSize)) {
		    		 thisCorrelationSize*=2;
			    	 min_span*=2;
			    	 thisWindow=window4;
		    	 }
		     }
		     setCorrelationSizesUsed(thisCorrelationSize);
//		     if (thisCorrelationSize>distortionParameters.correlationSize) System.out.println("**** u/v span too small, increasing FFT size to "+thisCorrelationSize);
		     if ((debug_level>0)&&(thisCorrelationSize>distortionParameters.correlationSize)) System.out.println("**** u/v span too small, increasing FFT size to "+thisCorrelationSize);
			   	Rectangle centerCross=correlationSelection(
						beforeXY, // initial coordinates of the pattern cross point
//						distortionParameters.correlationSize);
						thisCorrelationSize);

			 int ixc=centerCross.x+centerCross.width/2;
			 int iyc=centerCross.y+centerCross.height/2;
			 double [] diffBeforeXY={beforeXY[0]-ixc, beforeXY[1]-iyc};
			 double[][] input_bayer=splitBayer (imp,centerCross,equalizeGreens);

			 if (debug_level>3) SDFA_INSTANCE.showArrays(input_bayer,  true, "centered");
			 if (debug_level>2) SDFA_INSTANCE.showArrays(input_bayer[4], "greens");
			 if (debug_level>2) System.out.println("ixc="+ixc+" iyc="+iyc);
			 double [] greens=normalizeAndWindow (input_bayer[4], thisWindow);

		     if (debug_level>2) {
		    	 System.out.println(" wv0x="+IJ.d2s(wv0x,5)+" wv0y="+IJ.d2s(wv0y,5));
		    	 System.out.println(" wv1x="+IJ.d2s(wv1x,5)+" wv1y="+IJ.d2s(wv1y,5));
		    	 System.out.println(" u-span="+IJ.d2s(u_span,3)+"  v-span="+IJ.d2s(v_span,3)+" threshold="+IJ.d2s(uv_threshold,3)+" ("+IJ.d2s(distortionParameters.minUVSpan,3)+")");
		    	 if (corr!=null) {
		    		 System.out.println(" Ax="+IJ.d2s(corr[0],8)+" Bx="+IJ.d2s(corr[1],8)+" Cx="+IJ.d2s(corr[2],8)+" Dx="+IJ.d2s(corr[6],8)+" Ex="+IJ.d2s(corr[7],8));
		    		 System.out.println(" Ay="+IJ.d2s(corr[3],8)+" By="+IJ.d2s(corr[4],8)+" Cy="+IJ.d2s(corr[5],8)+" Dy="+IJ.d2s(corr[8],8)+" Ey="+IJ.d2s(corr[9],8));
		    	 }
		     }
			 for (numNeib=0;numNeib<locsNeib.length;numNeib++) if (locsNeib[numNeib][2]!=0.0) {
				 neibCenter[0]=diffBeforeXY[0]+locsNeib[numNeib][0];
				 neibCenter[1]=diffBeforeXY[1]+locsNeib[numNeib][1];
//			 dUV=matrix2x2_scale(matrix2x2_mul(wv,diffBeforeXY),-2*Math.PI);

				 dUV=matrix2x2_scale(matrix2x2_mul(wv,neibCenter),-2*Math.PI);
				 simulationPattern.simulatePatternFullPattern( // not thread safe
						 wv0x,
						 wv0y,
						 dUV[0]+(negative?(-Math.PI/2):Math.PI/2), // negative?(-Math.PI/2):Math.PI/2,
						 wv1x,
						 wv1y,
						 dUV[1]+Math.PI/2, //Math.PI/2,
						 corr, //null, // no mesh distortion here
						 thisSimulParameters.subdiv,// SIMUL.subdiv, - do not need high quality here
						 thisCorrelationSize,
				         true); // center for greens
				 sim_pix= simulationPattern.extractSimulPatterns (
				                             thisSimulParameters,
				                             1,       // subdivide output pixels  
				                             thisCorrelationSize,    // number of Bayer cells in width of the square selection (half number of pixels)
				        					 0,
				        					 0);
				 if ((debug_level>2) && (numNeib==0)){
//				 if (debug_level>2){
					 System.out.println("==========Showing simul"+ixc+":"+iyc);					 
					 SDFA_INSTANCE.showArrays(sim_pix[4].clone(),  "simul"+ixc+":"+iyc);
				 }

				 simGreensCentered= normalizeAndWindow (sim_pix[4], thisWindow);
//				 if ((debug_level>2) && (numNeib==0)){
				 if (debug_level>2){
					 System.out.println("==========Showing simGreensCentered"+ixc+":"+iyc);					 

					 SDFA_INSTANCE.showArrays(simGreensCentered.clone(),  "simGreensCentered"+ixc+":"+iyc);
					 SDFA_INSTANCE.showArrays(greens.clone(),  "greensWidowed"+ixc+":"+iyc);
//					 System.out.println("debug_level="+debug_level+" *** Remove next line ***");
//					 sim_pix[14]=null; // make it crash here
				 }
				 modelCorr=fht_instance.correlate (greens.clone(),  // measured pixel array
						 //						 modelCorr=fht_instance.correlate (greens,  // measured pixel array
						 simGreensCentered,  // simulated (model) pixel array)
						 //	                     distortionParameters.correlationHighPassSigma);
						 distortionParameters.correlationHighPassSigma,
						 distortionParameters.correlationLowPassSigma,
						 distortionParameters.phaseCorrelationFraction);				 

//				 if ((debug_level>2) && (numNeib==0)){
				 if (debug_level>2){
					 System.out.println("==========Showing modelCorr"+ixc+":"+iyc);					 
					 SDFA_INSTANCE.showArrays(modelCorr, "modelCorr"+ixc+":"+iyc);
				 }
//				 xyCorr=new double[2]; //????????????????????
	// Use fast, but less precise method here ?		
//				 if (numNeib==0) System.out.println ("correctedPatternCrossLocation(): debugLevel="+debugLevel+" fast="+fast);
				 if (fast) centerXY= correlationMaximum(
						 modelCorr,
						 distortionParameters.correlationMaxOffset,
						 (debug_level>2) && (numNeib==0));
				 else      centerXY= correlationMaximum(modelCorr,
						 distortionParameters.correlationRadius,
						 distortionParameters.correlationThreshold,	//double threshold, // fraction of maximum (slightly less than 1.0) to limit the top part of the maximum for centroid

						 distortionParameters.correlationSubdiv,
						 distortionParameters.correlationFFTSubdiv,
						 fht_instance,
						 distortionParameters.correlationMaxOffset,
						 0.0, // lowpass filtering already done
						 (debug_level>2) && ((numNeib==0) || (passNumber>1)));

		   	   	if (centerXY==null) {
						if (debug_level>0) System.out.println("Too far from the center0 ("+beforeXY[0]+"/"+beforeXY[1]+")");
		   	   			return null;
		   	   	}
	// Verify contrast (if specified) - only for the center sample (numNeib==0) 
		   	   	if (numNeib==0) {
		   	   		double [] contrasts= correlationContrast(
							modelCorr,
							greens,							
							WVgreens,    // wave vectors (same units as the pixels array)
//							distortionParameters.correlationRingWidth,   // ring (around r=0.5 dist to opposite corr) width
							distortionParameters.contrastSelectSigma, // Gaussian sigma to select correlation centers (fraction of UV period), 0.1
							distortionParameters.contrastAverageSigma,
	//TODO: verify that displacement is correct here (sign, direction)						
							centerXY[0],    //  x0,              // center coordinates
							centerXY[1],    //y0,
							"test-contrast");   // title base for optional plots names
		   	   		contrast=contrasts[0];

					 result[2]=contrast;

					 if (Double.isNaN(contrasts[0]) || ((distortionParameters.correlationMinContrast>0) && (contrasts[0]<distortionParameters.correlationMinContrast))) {
						 if (debug_level>1) System.out.println("Center contrast too low - "+contrasts[0]+"<"+distortionParameters.correlationMinContrast);
						 if (debug_level>1) System.out.println("Center contrast "+IJ.d2s(contrasts[0],3)+" ("+distortionParameters.correlationMinContrast+")"+
								 " is too low ("+IJ.d2s(beforeXY[0],3)+"/"+IJ.d2s(beforeXY[1],3)+")->"+
								 IJ.d2s(centerXY[0],3)+"/"+IJ.d2s(centerXY[1],3));
						 return null;
					 } else {
						 if (debug_level>1) System.out.println("Contrast "+IJ.d2s(contrasts[0],3)+" ("+distortionParameters.correlationMinContrast+")"+
								 " is good ("+IJ.d2s(beforeXY[0],3)+"/"+IJ.d2s(beforeXY[1],3)+")->"+
								 IJ.d2s(centerXY[0],3)+"/"+IJ.d2s(centerXY[1],3));
					 }


					 if (Double.isNaN(contrasts[1]) || ((distortionParameters.correlationMinAbsoluteContrast>0) && (contrasts[1]<distortionParameters.correlationMinAbsoluteContrast))) {
						 if (debug_level>1) System.out.println("Absolute contrast too low - "+contrasts[1]+"<"+distortionParameters.correlationMinAbsoluteContrast);
						 if (debug_level>1) System.out.println("Absolute contrast "+IJ.d2s(contrasts[1],3)+" ("+distortionParameters.correlationMinAbsoluteContrast+")"+
								 " is too low ("+IJ.d2s(beforeXY[0],3)+"/"+IJ.d2s(beforeXY[1],3)+")->"+
								 IJ.d2s(centerXY[0],3)+"/"+IJ.d2s(centerXY[1],3));
						 return null;
					 }


					 if (debug_level>2) System.out.println("Contarst="+contrast+" (legacy)");
		   	   	}
				 if (debug_level>2) System.out.println("correctedPatternCrossLocation: Center x="+IJ.d2s(centerXY[0],3)+" y="+ 	IJ.d2s(centerXY[1],3));
	// convert from diagonal greens coordinates to sensor pixel coordinates			 
				 xyCorr[0]+=(-centerXY[0]-centerXY[1])*locsNeib[numNeib][2];
				 xyCorr[1]+=( centerXY[0]-centerXY[1])*locsNeib[numNeib][2];/*
				 if (debug_array!=null) {
					 debug_array[9][0]+=(-centerXY[0]-centerXY[1])*locsNeib[numNeib][2];
					 debug_array[9][1]+=( centerXY[0]-centerXY[1])*locsNeib[numNeib][2];
				 }
*/
				 if (debug_level>1) System.out.println("correctedPatternCrossLocation: dist="+IJ.d2s(Math.sqrt(xyCorr[0]*xyCorr[0]+xyCorr[1]*xyCorr[1]),4)+" xyCorr[0]="+IJ.d2s(xyCorr[0],4)+" xyCorr[1]="+ 	IJ.d2s(xyCorr[1],4));
			 }
// average 	xyCorr[]		 
			 
//			 result[0]=ixc-xyCorr[0];
//			 result[1]=iyc-xyCorr[1];
			 
// disabling correction !!!!!!!!!!!!!!!!!!!!!!!			 
			 result[0]=ixc-xyCorr[0]+diffBeforeXY[0];
			 result[1]=iyc-xyCorr[1]+diffBeforeXY[1];
//			 result[0]=ixc+diffBeforeXY[0];
//			 result[1]=iyc+diffBeforeXY[1];
			 
			 if (debug_level>2) System.out.println("---correctedPatternCrossLocation: before x="+IJ.d2s(beforeXY[0],3)+" y="+IJ.d2s(beforeXY[1],3));
			 if (debug_level>2) System.out.println("+++correctedPatternCrossLocation: after  x="+IJ.d2s(result[0],3)+" y="+IJ.d2s(result[1],3));
//			 if (debug_level>0) System.out.println("---correctedPatternCrossLocation: before x="+IJ.d2s(beforeXY[0],3)+" y="+IJ.d2s(beforeXY[1],3));
//			 if (debug_level>0) System.out.println("+++correctedPatternCrossLocation: after  x="+IJ.d2s(result[0],3)+" y="+IJ.d2s(result[1],3));
		  return result;		
		}
		   private  double [] correctedPatternCrossLocationAverage4(
				   double [] beforeXY, // initial coordinates of the pattern cross point
				   double wv0x,
				   double wv0y,
				   double wv1x,
				   double wv1y,
				   double [][] correction,
				   ImagePlus imp,      // image data (Bayer mosaic)
				   DistortionParameters distortionParameters, //distortionParameters.refineCorrelations
				   MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
				   MatchSimulatedPattern matchSimulatedPattern, // correlationSize
				   SimulationPattern.SimulParameters  thisSimulParameters,
				   boolean equalizeGreens,			
				   double [] window,   // window function
				   double [] window2,   // window function - twice FFT size (or null)
				   double [] window4,   // window function - 4x FFT size (or null)
				   SimulationPattern simulationPattern,
				   boolean negative, // invert cross phase
				   DoubleFHT fht_instance,
				   boolean fast, // use fast measuring of the maximum on the correlation
				   double [][] locsNeib, // locations and weights of neighbors to average
				   int debug_level,
				   String dbgStr
				   ){
// next print - same for good and bad, correction==null
			   if (dbgStr!=null) System.out.println(dbgStr+ ": wv0x="+wv0x+" wv0y="+wv0y+ " wv1x="+wv1x+" wv1y="+wv1y+
					   " beforeXY[0]="+beforeXY[0]+", beforeXY[1]="+beforeXY[1]+" correction is "+((correction==null)?"null":"not null"));
			   
			   
			   boolean dbgThis=
					   (Math.abs(beforeXY[0]-patternDetectParameters.debugX)<patternDetectParameters.debugRadius) &&
					   (Math.abs(beforeXY[1]-patternDetectParameters.debugY)<patternDetectParameters.debugRadius);
			   if (dbgThis) {
				   System.out.println("correctedPatternCrossLocationAverage4(), beforeXY[0]="+beforeXY[0]+", beforeXY[1]="+beforeXY[1]);
				   debug_level+=3;
			   }
			   //	System.out.println("correctedPatternCrossLocationAverage4(): beforeXY[0]="+beforeXY[0]+". beforeXY[1]="+beforeXY[1]);		   
			   // Just for testing
			   beforeXY[0]+=distortionParameters.correlationDx;  // offset, X (in pixels)
			   beforeXY[1]+=distortionParameters.correlationDy; // offset y (in pixels)


			   double [][] convMatrix= {{1.0,-1.0},{1.0,1.0}}; // from greens2 to pixel WV
			   double [][] invConvMatrix= matrix2x2_scale(matrix2x2_invert(convMatrix),2.0);

			   double [] result=new double [3];
			   result[0]=beforeXY[0];
			   result[1]=beforeXY[1];
			   result[2]=0.0; // contrast


			   if (fht_instance==null) fht_instance=new DoubleFHT(); // move upstream to reduce number of initializations


			   //create diagonal green selection around ixc,iyc

			   double [][]wv={{wv0x, wv0y},
					   {wv1x, wv1y}};
			   double [][] WVgreens=matrix2x2_mul(wv,invConvMatrix);
			   if (debug_level>2) System.out.println("WVgreens[0][0]="+IJ.d2s(WVgreens[0][0],3)+
					   " WVgreens[0][1]="+IJ.d2s(WVgreens[0][1],3)+
					   " WVgreens[1][0]="+IJ.d2s(WVgreens[1][0],3)+
					   " WVgreens[1][1]="+IJ.d2s(WVgreens[1][1],3));
			   double [] dUV;
			   double[][] sim_pix;
			   double [] simGreensCentered;
			   //				 double [] modelCorr;

			   double [] centerXY;
			   double 	contrast;
			   int numNeib;
			   double []corr=null;
			   double [] neibCenter=new double[2];	
			   if (correction!=null) { // overwrite wave vectors
				   wv[0][0]=correction[0][0];
				   wv[0][1]=correction[0][1];
				   wv[1][0]=correction[1][0];
				   wv[1][1]=correction[1][1];
				   if (correction[0].length>3) { // enough data for quadratic approximation
					   corr=new double[10];
					   corr[0]=correction[0][3]/4;
					   corr[1]=correction[0][4]/4;
					   corr[2]=correction[0][5]/4;
					   corr[3]=correction[1][3]/4;
					   corr[4]=correction[1][4]/4;
					   corr[5]=correction[1][5]/4;
					   corr[6]=0.0;
					   corr[7]=0.0;
					   corr[9]=0.0;
					   corr[9]=0.0;
				   }
			   }
			   double u_span=Math.sqrt(wv0x*wv0x+wv0y*wv0y)*distortionParameters.correlationSize;
			   double v_span=Math.sqrt(wv1x*wv1x+wv1y*wv1y)*distortionParameters.correlationSize;
			   double min_span=Math.min(u_span, v_span);
			   int thisCorrelationSize=distortionParameters.correlationSize;
			   double [] thisWindow=window;
			   double uv_threshold=distortionParameters.minUVSpan*0.25*Math.sqrt(2.0); 

			   if (
					   (min_span<uv_threshold) &&
					   (window2!=null) &&
					   (thisCorrelationSize<distortionParameters.maximalCorrelationSize)) { // trying to increase only twice
				   thisCorrelationSize*=2;
				   min_span*=2;
				   thisWindow=window2;
				   if (
						   (min_span<uv_threshold) &&
						   (window4!=null) &&
						   (thisCorrelationSize<distortionParameters.maximalCorrelationSize)) {
					   thisCorrelationSize*=2;
					   min_span*=2;
					   thisWindow=window4;
				   }
			   }
			   
			   /*			     
			     if ((min_span<uv_threshold) && (window2!=null)) { // trying to increase only twice
			    	 thisCorrelationSize*=2;
			    	 min_span*=2;
			    	 thisWindow=window2;
			    	 if ((min_span<uv_threshold) && (window4!=null)) {
			    		 thisCorrelationSize*=2;
				    	 min_span*=2;
				    	 thisWindow=window4;
			    	 }
			     }
			    */
			   setCorrelationSizesUsed(thisCorrelationSize);
			   if ((debug_level>0)&&(thisCorrelationSize>distortionParameters.correlationSize)) System.out.println("**** u/v span too small, increasing FFT size to "+thisCorrelationSize);
			   Rectangle centerCross=correlationSelection(
					   beforeXY, // initial coordinates of the pattern cross point
					   thisCorrelationSize);

			   int ixc=centerCross.x+centerCross.width/2;
			   int iyc=centerCross.y+centerCross.height/2;
			   double [] diffBeforeXY={beforeXY[0]-ixc, beforeXY[1]-iyc};
			   double[][] input_bayer=splitBayer (imp,centerCross,equalizeGreens);

			   if (debug_level>3) SDFA_INSTANCE.showArrays(input_bayer,  true, "centered");
			   if (debug_level>2) SDFA_INSTANCE.showArrays(input_bayer[4], "greens");
			   if (debug_level>2) System.out.println("ixc="+ixc+" iyc="+iyc);
			   double [] greens=normalizeAndWindow (input_bayer[4], thisWindow);
			   if (debug_level>2) SDFA_INSTANCE.showArrays(greens, "greensWindowed");
			   // average is not zero - probably				 

			   if (debug_level>2) {
				   System.out.println(" wv0x="+IJ.d2s(wv0x,5)+" wv0y="+IJ.d2s(wv0y,5));
				   System.out.println(" wv1x="+IJ.d2s(wv1x,5)+" wv1y="+IJ.d2s(wv1y,5));
				   System.out.println(" u-span="+IJ.d2s(u_span,3)+"  v-span="+IJ.d2s(v_span,3)+" threshold="+IJ.d2s(uv_threshold,3)+" ("+IJ.d2s(distortionParameters.minUVSpan,3)+")");
				   if (corr!=null) {
					   System.out.println(" Ax="+IJ.d2s(corr[0],8)+" Bx="+IJ.d2s(corr[1],8)+" Cx="+IJ.d2s(corr[2],8)+" Dx="+IJ.d2s(corr[6],8)+" Ex="+IJ.d2s(corr[7],8));
					   System.out.println(" Ay="+IJ.d2s(corr[3],8)+" By="+IJ.d2s(corr[4],8)+" Cy="+IJ.d2s(corr[5],8)+" Dy="+IJ.d2s(corr[8],8)+" Ey="+IJ.d2s(corr[9],8));
				   }
			   }
			   int [][] greenNeib={{0,0},{0,1},{1,0},{1,1}};
			   int numOfNeib=distortionParameters.correlationAverageOnRefine?greenNeib.length:1;
			   if (debug_level>2) {
				   System.out.println(" numOfNeib="+numOfNeib+" (distortionParameters.correlationAverageOnRefine="+distortionParameters.correlationAverageOnRefine);
			   }
			   if (locsNeib.length==1) {
				   numOfNeib=1; // on the first pass, from legacy
				   if (debug_level>2) {
					   System.out.println("Reduced numOfNeib to "+numOfNeib+" as locsNeib.length="+locsNeib.length);
				   }
			   }
			   if (dbgStr!=null) {
				   double dbgSumWindow=0.0;
				   for (double dbgD:thisWindow) dbgSumWindow+=dbgD; 
				   // All he same - good/bad
				   System.out.println(dbgStr+ ": thisCorrelationSize="+thisCorrelationSize+" min_span="+min_span+ " dbgSumWindow="+dbgSumWindow+
						   "locsNeib.length="+locsNeib.length+" fast="+fast+
						   " numOfNeib="+numOfNeib+" (distortionParameters.correlationAverageOnRefine="+distortionParameters.correlationAverageOnRefine);
			   }

			   double [][] modelCorrs=new double[numOfNeib][];
			   double [][] debugGreens=new double[numOfNeib][0];          
			   for (numNeib=0;numNeib<numOfNeib;numNeib++) {
				   neibCenter[0]=diffBeforeXY[0]+0.5*(greenNeib[numNeib][0]+greenNeib[numNeib][1]);
				   neibCenter[1]=diffBeforeXY[1]+0.5*(greenNeib[numNeib][0]-greenNeib[numNeib][1]);
				   dUV=matrix2x2_scale(matrix2x2_mul(wv,neibCenter),-2*Math.PI);
				   double [] barray= simulationPattern.simulatePatternFullPatternSafe( // Is it the most time-consuming part? should it be done once and then only extraction separate?
						   wv0x,
						   wv0y,
						   dUV[0]+(negative?(-Math.PI/2):Math.PI/2), // negative?(-Math.PI/2):Math.PI/2,
						   wv1x,
						   wv1y,
						   dUV[1]+Math.PI/2, //Math.PI/2,
						   corr, //null, // no mesh distortion here
						   thisSimulParameters.subdiv,// SIMUL.subdiv, - do not need high quality here
						   thisCorrelationSize,
						   true); // center for greens
				   sim_pix= simulationPattern.extractSimulPatterns (
						   barray,
						   thisSimulParameters,
						   1,       // subdivide output pixels  
						   thisCorrelationSize,    // number of Bayer cells in width of the square selection (half number of pixels)
						   0,
						   0);
				   if (sim_pix==null){
					   System.out.println("***** BUG: extractSimulPatterns() FAILED *****");
					   return null;
				   }
				   if (dbgStr!=null) {
					   double dbgSumWindow=0.0;
					   for (double[] dbgSlice:sim_pix) for (double dbgD:dbgSlice) dbgSumWindow+=dbgD; 
					   System.out.println(dbgStr+ ": SUM of sim_pix="+dbgSumWindow); // First difference good/bad
					   
					   
				   }

				   simGreensCentered= normalizeAndWindow (sim_pix[4], thisWindow);

				   if (dbgStr!=null) {
					   double dbgSumWindow=0.0;
					   for (double dbgD:simGreensCentered) dbgSumWindow+=dbgD; 
					   System.out.println(dbgStr+ ": SUM of simGreensCentered="+dbgSumWindow);
				   }

				   debugGreens[numNeib]=simGreensCentered.clone();

				   // testing if phase reversal would exactly inverse result pattern - tested, perfect

				   modelCorrs[numNeib]=fht_instance.correlate (greens.clone(),  // measured pixel array
						   //						 modelCorr=fht_instance.correlate (greens,  // measured pixel array
						   simGreensCentered,  // simulated (model) pixel array)
						   //	                     distortionParameters.correlationHighPassSigma);
						   distortionParameters.correlationHighPassSigma,
						   fast?distortionParameters.correlationLowPassSigma:0.0,// moved to decimation via FFT
								   distortionParameters.phaseCorrelationFraction);
				   if (dbgStr!=null) {
					   double dbgSumWindow=0.0;
					   for (double[] dbgSlice:modelCorrs) for (double dbgD:dbgSlice) dbgSumWindow+=dbgD; 
					   System.out.println(dbgStr+ ": SUM of modelCorrs="+dbgSumWindow);
				   }
				   

			   }
			   if (debug_level>2){
				   System.out.println(">=========Showing simGreensCentered"+ixc+":"+iyc);					 
				   SDFA_INSTANCE.showArrays(debugGreens, true, "simGreensCentered"+ixc+":"+iyc);
			   }

			   if (debug_level>2){
				   System.out.println(">=========Showing modelCorrs, passNumber="+passNumber);					 
				   SDFA_INSTANCE.showArrays(modelCorrs, true, "modelCorrs:"+numOfNeib);
			   }

			   // combine 4 correlations into the double resolution, same output size (so half input size) array				 
			   int halfSize=thisCorrelationSize/2;
			   int qSize=thisCorrelationSize/4;
			   double [] modelCorr;
			   int thisFFTSubdiv=distortionParameters.correlationFFTSubdiv;
			   double thisLowpass=distortionParameters.correlationLowPassSigma;
			   if (numOfNeib>1) {
				   modelCorr=new double [thisCorrelationSize*thisCorrelationSize];
				   for (int i=0;i<modelCorr.length;i++) modelCorr[i]=0.0;
				   for (int dy=0;dy<2;dy++) for (int dx=0;dx<2;dx++)  {
					   for (int y=0;y<halfSize;y++) for (int x=0;x<halfSize;x++) {
						   modelCorr[(2*y+dy)*thisCorrelationSize+(2*x+dx)]+=
								   modelCorrs[2*dy+dx][(qSize+y)*thisCorrelationSize+(qSize+x)];
					   }
				   }
				   thisLowpass/=2.0; // the lower the value, the more filtering.  Decimated twice,so low pass filtering - accordingly 
				   thisFFTSubdiv=(thisFFTSubdiv>1)?(thisFFTSubdiv/2):1;
			   } else {
				   modelCorr=modelCorrs[0]; // also - different size
			   }
			   if (debug_level>2){
				   System.out.println(">==========Showing modelCorr");					 
				   SDFA_INSTANCE.showArrays(modelCorr, thisCorrelationSize,thisCorrelationSize, "modelCorr");
			   }
			   if (fast) centerXY= correlationMaximum( // maybe twice actual size if 
					   modelCorr,
					   distortionParameters.correlationMaxOffset,
					   (debug_level>2) && (numNeib==0));  // low-pass filtering should already be done
			   else      centerXY= correlationMaximum(modelCorr,
					   distortionParameters.correlationRadius,
					   distortionParameters.correlationThreshold,	//double threshold, // fraction of maximum (slightly less than 1.0) to limit the top part of the maximum for centroid

					   distortionParameters.correlationSubdiv,
					   thisFFTSubdiv,
					   fht_instance,
					   distortionParameters.correlationMaxOffset,
					   thisLowpass, //distortionParameters.correlationLowPassSigma
					   //						 (debug_level>2) && (passNumber>1));
					   (debug_level>2));
			   if (centerXY==null) {
				   if (debug_level>1) System.out.println("Too far from the center01 ("+beforeXY[0]+"/"+beforeXY[1]+")");
				   if (dbgStr!=null) System.out.println(dbgStr+ "- Too far from the center01 ("+beforeXY[0]+"/"+beforeXY[1]+")");
				   return null;
			   }

			   //				 debug_level=3;				 

			   if (numNeib>1){
				   centerXY[0]*=0.5;
				   centerXY[1]*=0.5;

				   for (int i=0;i<2;i++) for (int j=0;j<2;j++) WVgreens[i][j]*=0.5;
			   }
			   /*				 
				 contrast= correlationContrast1(
						 modelCorr,
						 WVgreens,    // wave vectors (same units as the pixels array)
						 distortionParameters.contrastSelectSigma, // Gaussian sigma to select correlation centers (fraction of UV period), 0.1
						 //TODO: verify that displacement is correct here (sign, direction)						
						 centerXY[0],    //  x0,              // center coordinates
						 centerXY[1],    //y0,
						 "test-contrast",   // title base for optional plots names
						 debug_level);
				 result[2]=contrast;

				 if ((distortionParameters.correlationMinContrast>0) && (contrast<distortionParameters.correlationMinContrast)) {
//					 if (debug_level>1) System.out.println("Contrast too low - "+contrast+"<"+distortionParameters.correlationMinContrast);
					 if (debug_level>1) System.out.println("Contrast "+IJ.d2s(contrast,3)+" ("+distortionParameters.correlationMinContrast+")"+
							 " is too low ( probed around "+IJ.d2s(beforeXY[0],3)+"/"+IJ.d2s(beforeXY[1],3)+")->"+
							 IJ.d2s(centerXY[0],3)+"/"+IJ.d2s(centerXY[1],3));
					 return null;
				 }
			    */
			   double [] contrasts= correlationContrast(
					   modelCorr,
					   greens,							
					   WVgreens,    // wave vectors (same units as the pixels array)
					   distortionParameters.contrastSelectSigma, // Gaussian sigma to select correlation centers (fraction of UV period), 0.1
					   distortionParameters.contrastAverageSigma,
					   centerXY[0],    //  x0,              // center coordinates
					   centerXY[1],    //y0,
					   "test-contrast");   // title base for optional plots names
			   contrast=contrasts[0];
			   result[2]=contrast;
			   if (Double.isNaN(contrasts[0]) || ((distortionParameters.correlationMinContrast>0) && (contrasts[0]<distortionParameters.correlationMinContrast))) {
				   if (debug_level>1) System.out.println("Contrast too low - "+contrasts[0]+"<"+distortionParameters.correlationMinContrast);
				   if (debug_level>1) System.out.println("Contrast "+IJ.d2s(contrasts[0],3)+" ("+distortionParameters.correlationMinContrast+")"+
						   " is TOO LOW ("+IJ.d2s(beforeXY[0],3)+"/"+IJ.d2s(beforeXY[1],3)+")->"+
						   IJ.d2s(centerXY[0],3)+"/"+IJ.d2s(centerXY[1],3));
				   if (dbgStr!=null) System.out.println(dbgStr+ " - Contrast "+IJ.d2s(contrasts[0],3)+" ("+distortionParameters.correlationMinContrast+")"+
						   " is TOO LOW ("+IJ.d2s(beforeXY[0],3)+"/"+IJ.d2s(beforeXY[1],3)+")->"+
						   IJ.d2s(centerXY[0],3)+"/"+IJ.d2s(centerXY[1],3));

				   return null;
			   } else {
				   if (debug_level>1) System.out.println("Contrast "+IJ.d2s(contrasts[0],3)+" ("+distortionParameters.correlationMinContrast+")"+
						   " is GOOD ("+IJ.d2s(beforeXY[0],3)+"/"+IJ.d2s(beforeXY[1],3)+")->"+
						   IJ.d2s(centerXY[0],3)+"/"+IJ.d2s(centerXY[1],3));
				   if (dbgStr!=null) System.out.println(dbgStr+ " - Contrast "+IJ.d2s(contrasts[0],3)+" ("+distortionParameters.correlationMinContrast+")"+
						   " is GOOD ("+IJ.d2s(beforeXY[0],3)+"/"+IJ.d2s(beforeXY[1],3)+")->"+
						   IJ.d2s(centerXY[0],3)+"/"+IJ.d2s(centerXY[1],3));
			   }


			   if (Double.isNaN(contrasts[1]) || ((distortionParameters.correlationMinAbsoluteContrast>0) && (contrasts[1]<distortionParameters.correlationMinAbsoluteContrast))) {
				   if (debug_level>1) System.out.println("Absolute contrast too low - "+contrasts[1]+"<"+distortionParameters.correlationMinAbsoluteContrast);
				   if (debug_level>1) System.out.println("Absolute contrast "+IJ.d2s(contrasts[1],3)+" ("+distortionParameters.correlationMinAbsoluteContrast+")"+
						   " is too low ("+IJ.d2s(beforeXY[0],3)+"/"+IJ.d2s(beforeXY[1],3)+")->"+
						   IJ.d2s(centerXY[0],3)+"/"+IJ.d2s(centerXY[1],3));
				   if (dbgStr!=null) System.out.println(dbgStr+ " - Absolute contrast "+IJ.d2s(contrasts[1],3)+" ("+distortionParameters.correlationMinAbsoluteContrast+")"+
						   " is too low ("+IJ.d2s(beforeXY[0],3)+"/"+IJ.d2s(beforeXY[1],3)+")->"+
						   IJ.d2s(centerXY[0],3)+"/"+IJ.d2s(centerXY[1],3));
				   return null;
			   } else {
				   if (dbgStr!=null) System.out.println(dbgStr+ " - Absolute contrast "+IJ.d2s(contrasts[1],3)+" ("+distortionParameters.correlationMinAbsoluteContrast+")"+
						   " is GOOD ("+IJ.d2s(beforeXY[0],3)+"/"+IJ.d2s(beforeXY[1],3)+")->"+
						   IJ.d2s(centerXY[0],3)+"/"+IJ.d2s(centerXY[1],3));
			   }

			   if (debug_level>1) System.out.println(">>>Contrast="+contrasts[0]+"/"+contrasts[1]+" ("+IJ.d2s(beforeXY[0],3)+":"+IJ.d2s(beforeXY[1],3)+")->"+IJ.d2s(result[0],3)+":"+IJ.d2s(result[1],3));
			   result[0]=ixc-(-centerXY[0]-centerXY[1])+diffBeforeXY[0];
			   result[1]=iyc-( centerXY[0]-centerXY[1])+diffBeforeXY[1];

			   if (debug_level>2) System.out.println(">---correctedPatternCrossLocation: before x="+IJ.d2s(beforeXY[0],3)+" y="+IJ.d2s(beforeXY[1],3));
			   if (debug_level>2) System.out.println(">+++correctedPatternCrossLocation: after  x="+IJ.d2s(result[0],3)+" y="+IJ.d2s(result[1],3));

			   //				 if (debug_level>0) System.out.println(">---correctedPatternCrossLocation: before x="+IJ.d2s(beforeXY[0],3)+" y="+IJ.d2s(beforeXY[1],3));
			   //				 if (debug_level>0) System.out.println(">+++correctedPatternCrossLocation: after  x="+IJ.d2s(result[0],3)+" y="+IJ.d2s(result[1],3));


			   return result;		
		   }

/* ======= Debugging only - returns 2-d array of x,y as a function of initial estimation =================== */
	   public  double [][][] scanPatternCrossLocation( 
			    double range, // size of the scanning square
			    int    size,  // number of scan points in each direction (total size*size)
				double [] beforeCenterXY, // initial coordinates of the pattern cross point
				double wv0x,
				double wv0y,
				double wv1x,
				double wv1y,
				ImagePlus imp,      // image data (Bayer mosaic)
				DistortionParameters distortionParameters, //
				MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
				MatchSimulatedPattern matchSimulatedPattern, // correlationSize
				SimulationPattern.SimulParameters  thisSimulParameters,
				boolean equalizeGreens,			
				double [] window,   // window function
				SimulationPattern simulationPattern,
				boolean negative, // invert cross phase
				DoubleFHT fht_instance
				){
//		   	double [] result=beforeXY.clone();
		   	double [][][] result=new double [size][size][4];
			if (fht_instance==null) fht_instance=new DoubleFHT(); // move upstream to reduce number of initializations
			double [] beforeXY=new double[2];
			double [] filter=fht_instance.createFrequencyFilter(
					new double [distortionParameters.correlationSize*distortionParameters.correlationSize], //distortionParameters.correlationSize,
					distortionParameters.correlationHighPassSigma,
					distortionParameters.correlationLowPassSigma);
			if (debugLevel>2){
				double [] maskFull = new double [distortionParameters.correlationSize*distortionParameters.correlationSize];
				for (int i=0;i<maskFull.length;i++) {
					if (i<filter.length) maskFull[i]=filter[i];
					else {
						int rowMod = (distortionParameters.correlationSize - (i/distortionParameters.correlationSize)) % distortionParameters.correlationSize;
						int colMod = (distortionParameters.correlationSize - (i%distortionParameters.correlationSize)) % distortionParameters.correlationSize;
						maskFull[i]=filter[rowMod*distortionParameters.correlationSize+colMod];
					}
					
				}
				SDFA_INSTANCE.showArrays(maskFull, "filter");
			}
			
			
			for (int i=0;i<size;i++) for (int j=0;j<size;j++) {
		    	beforeXY[1]=beforeCenterXY[1]-range/2+ (range*i)/(size-1);
		    	beforeXY[0]=beforeCenterXY[0]-range/2+ (range*j)/(size-1);
		   	Rectangle centerCross=correlationSelection(
						beforeXY, // initial coordinates of the pattern cross point
						distortionParameters.correlationSize);

			 int ixc=centerCross.x+centerCross.width/2;
			 int iyc=centerCross.y+centerCross.height/2;
			 double [] diffBeforeXY={beforeXY[0]-ixc, beforeXY[1]-iyc};

		//create diagonal green selection around ixc,iyc
			 double[][] input_bayer=splitBayer (imp,centerCross,equalizeGreens);
/*			 
			 double[][] corrWindow=null;
			 if (distortionParameters.correlationRadiusScale>=0.0) {
				 corrWindow=generateWeights (
						 distortionParameters.correlationWeightSigma,
						 distortionParameters.correlationRadiusScale); //  if 0 - use sigma as radius, inside - 1.0, outside 0.0. If >0 - size of array n*sigma

			 }
			 
*/
			 if (debugLevel>3) SDFA_INSTANCE.showArrays(input_bayer,  true, "centered");
			 if (debugLevel>1) System.out.println(i+"/"+j+": ixc="+ixc+" iyc="+iyc);
// alternative way to  generate shifted pattern
			 double [][]wv={{wv0x, wv0y},
					        {wv1x, wv1y}};
			 double [] dUV=matrix2x2_scale(matrix2x2_mul(wv,diffBeforeXY),-2*Math.PI);
			 
			 //correlationHighPassSigma
			 double [] greens=normalizeAndWindow (input_bayer[4], window);
			 simulationPattern.simulatePatternFullPattern(
					 wv0x,
					 wv0y,
					 dUV[0]+(negative?(-Math.PI/2):Math.PI/2),
					 wv1x,
					 wv1y,
					 dUV[1]+Math.PI/2, //0.0,
					 null, // no mesh distortion here
					 thisSimulParameters.subdiv,// SIMUL.subdiv, - do not need high quality here
					 distortionParameters.correlationSize,
			         true); // center for greens
			 
			 double[][] sim_pix= simulationPattern.extractSimulPatterns (
			                             thisSimulParameters,
			                             1,       // subdivide output pixels  
			                             distortionParameters.correlationSize,    // number of Bayer cells in width of the square selection (half number of pixels)
			        					 0.0, //-diffBeforeXY[0],
			        					 0.0); //-diffBeforeXY[1]);

			 double [] simGreensCentered= normalizeAndWindow (sim_pix[4], window);
			 if (debugLevel>2) SDFA_INSTANCE.showArrays(greens.clone(), "greens-i"+i+"-j"+j);
			 if (debugLevel>2) SDFA_INSTANCE.showArrays(simGreensCentered.clone(), "simGreensCentered-i"+i+"-j"+j);

			 double [] modelCorr=fht_instance.correlate (greens,  // measured pixel array
                     simGreensCentered,  // simulated (model) pixel array)
//                     distortionParameters.correlationHighPassSigma);
                       filter);
			 if (debugLevel>2) SDFA_INSTANCE.showArrays(modelCorr.clone(), "modelCorr-i"+i+"-j"+j);
			 double [] xyCorr=new double[2];
			 double [] centerXY;
//			 if (distortionParameters.correlationRadiusScale>=0.0)  centerXY= correlationMaximum(modelCorr,corrWindow); 
			 if (distortionParameters.correlationRadius>0){
				 centerXY= correlationMaximum(modelCorr,
						 distortionParameters.correlationRadius,
						 distortionParameters.correlationThreshold,
						 distortionParameters.correlationSubdiv,
						 distortionParameters.correlationFFTSubdiv,
						 fht_instance,
						 distortionParameters.correlationMaxOffset,
						 0.0, // low-pass filtering already done
						 (debugLevel>2)
						 ); 
			 } 	 else centerXY= correlationMaximum(modelCorr,distortionParameters.correlationMaxOffset,(debugLevel>2));
			 if (centerXY==null) {
				 centerXY=new double[2];
				 centerXY[0]=0.0;
				 centerXY[1]=0.0;
			 }
			 if (debugLevel>2) System.out.println("correctedPatternCrossLocation: Center x="+IJ.d2s(centerXY[0],3)+" y="+ 	IJ.d2s(centerXY[1],3));
			 xyCorr[0]=-centerXY[0]-centerXY[1];
			 xyCorr[1]= centerXY[0]-centerXY[1];
			 if (debugLevel>1) System.out.println("correctedPatternCrossLocation: "+i+"/"+j+": dist="+IJ.d2s(Math.sqrt(xyCorr[0]*xyCorr[0]+xyCorr[1]*xyCorr[1]),4)+" xyCorr[0]="+IJ.d2s(xyCorr[0],4)+" xyCorr[1]="+ 	IJ.d2s(xyCorr[1],4));
//			 result[0]=ixc-xyCorr[0];
//			 result[1]=iyc-xyCorr[1];
			 result[i][j][0]=ixc-xyCorr[0]+diffBeforeXY[0];
			 result[i][j][1]=iyc-xyCorr[1]+diffBeforeXY[1];
			 result[i][j][2]=beforeXY[0];
			 result[i][j][3]=beforeXY[1];
			 if (debugLevel>1) System.out.println("---correctedPatternCrossLocation: "+i+"/"+j+" before x="+IJ.d2s(beforeXY[0],3)+" y="+IJ.d2s(beforeXY[1],3));
			 if (debugLevel>1) System.out.println("+++correctedPatternCrossLocation: "+i+"/"+j+" after  x="+IJ.d2s(result[i][j][0],3)+" y="+IJ.d2s(result[i][j][1],3));
		    }
		  return result;		
		}
/*
			distortionParameters.correlationWeightSigma=  gd.getNextNumber();
			distortionParameters.correlationRadiusScale=  gd.getNextNumber();
	   
 */

/* ======================================================================== */
/**
 * Interpolate maximum on a square correlation array, return vector from the center 
 */
// one quater of the weights function to be used to approximate maximum on correlation by a second-degree polynominal	   
	   public double [][] generateWeights (double sigma,
			   double n) { //  if 0 - use sigma as radius, inside - 1.0, outside 0.0. If >0 - size of array n*sigma
		   double r0=((n>0)?n:1.0)*sigma;
		   double r2=r0*r0;
		   int size = (int)  Math.ceil(r0);
		   double [][] mask=new double [size][size];
		   int i,j;
		   double [] gaussian=new double [size];
		   if (n>0) {
			   double k=0.5/sigma/sigma;
			   for (i=0;i<size;i++) gaussian[i]=Math.exp(-(k*i*i));
		   }
		   for (i=0;i<size;i++) for (j=0;j<size;j++){
			   if ((i*i+j*j)>r2) mask[i][j]=0.0;
			   else if (n>0)     mask[i][j]=gaussian[i]*gaussian[j];
			   else              mask[i][j]=1.0;
		   }
		   return mask;
	   }
   	   public double [] correlationMaximum(
  	   			double [] corr,   // square (correlation) array to find location of the maximum ([0.0,0.0] in the center of the arrray)
  	   			double [][] weights,
  	   			double maxOffset) {
  	   		if ((corr==null) || (corr.length==0)) return null;
//  	   		double [] corrMax= new double[2];
  	   		int size= (int) Math.sqrt(corr.length);
  	   		int i,j,imax=0,ix,iy, ixc, iyc;
  	   		double max=corr[0];
  	   		for (i=1;i<corr.length;i++) if (max<corr[i]) {
  	   			max=corr[i];
  	   			imax=i;
  	   		}
  	   		iyc=imax/size;
  	   		ixc=imax%size;
   	   		int ixc0=ixc-size/2;
   	   		int iyc0=iyc-size/2;
   	   		if ((maxOffset>0) && (maxOffset*maxOffset<(ixc0*ixc0+iyc0*iyc0))) {
				if (debugLevel>1) System.out.println("Too far from the center1: ixc="+ixc+" iyc="+iyc);
   	   			return null;
   	   		}
			if (debugLevel>1) System.out.println("correlationMaximum: ixc="+ixc+" iyc="+iyc);
  	   		
  	   		
/* ix, iy - the location of the point with maximal value. We'll approximate the vicinity of that maximum using a
 * second degree polunominal:
   Z(x,y)~=A*x^2+B*y^2+C*x*y+D*x+E*y+F
   by minimizing sum of squared differences between the actual (Z(x,uy)) and approximated values. 
   and then find the maximum on the approximated surface. Here is the math:
  	   		
Z(x,y)~=A*x^2+B*y^2+C*x*y+D*x+E*y+F
minimizing squared error, using W(x,y) as weight function

error=Sum(W(x,y)*((A*x^2+B*y^2+C*x*y+D*x+E*y+F)-Z(x,y))^2)

error=Sum(W(x,y)*(A^2*x^4 + 2*A*x^2*(B*y^2+C*x*y+D*x+E*y+F-Z(x,y)) +(...) )
0=derror/dA=Sum(W(x,y)*(2*A*x^4 + 2*x^2*(B*y^2+C*x*y+D*x+E*y+F-Z(x,y)))
0=Sum(W(x,y)*(A*x^4 + x^2*(B*y^2+C*x*y+D*x+E*y+F-Z(x,y)))

SX4=Sum(W(x,y)*x^4), etc

(1) 0=A*SX4 + B*SX2Y2 + C*SX3Y +D*SX3 +E*SX2Y +F*SX2 - SZX2

derror/dB:

error=Sum(W(x,y)*(B^2*y^4 + 2*B*y^2*(A*x^2+C*x*y+D*x+E*y+F-Z(x,y)) +(...) )
0=derror/dB=Sum(W(x,y)*(2*B*y^4 + 2*y^2*(A*x^2+C*x*y+D*x+E*y+F-Z(x,y)))
0=Sum(W(x,y)*(B*y^4 + y^2*(A*x^2+C*x*y+D*x+E*y+F-Z(x,y)))

(2) 0=B*SY4 + A*SX2Y2 + C*SXY3 +D*SXY2 +E*SY3 +F*SY2 - SZY2
(2) 0=A*SX2Y2 + B*SY4 + C*SXY3 +D*SXY2 +E*SY3 +F*SY2 - SZY2

derror/dC:

error=Sum(W(x,y)*(C^2*x^2*y^2 + 2*C*x*y*(A*x^2+B*y^2+D*x+E*y+F-Z(x,y)) +(...) )
0=derror/dC=Sum(W(x,y)*(2*C*x^2*y^2 + 2*x*y*(A*x^2+B*y^2+D*x+E*y+F-Z(x,y)) )
0=Sum(W(x,y)*(C*x^2*y^2 + x*y*(A*x^2+B*y^2+D*x+E*y+F-Z(x,y)) )

(3) 0= A*SX3Y +  B*SXY3 +  C*SX2Y2 + D*SX2Y + E*SXY2 + F*SXY - SZXY

derror/dD:

error=Sum(W(x,y)*(D^2*x^2 + 2*D*x*(A*x^2+B*y^2+C*x*y+E*y+F-Z(x,y)) +(...) )
0=derror/dD=Sum(W(x,y)*(2*D*x^2 + 2*x*(A*x^2+B*y^2+C*x*y+E*y+F-Z(x,y)) )
0=Sum(W(x,y)*(D*x^2 + x*(A*x^2+B*y^2+C*x*y+E*y+F-Z(x,y)) )

(4) 0= A*SX3 +   B*SXY2 +  C*SX2Y + D*SX2  + E*SXY +  F*SX  - SZX

derror/dE:

error=Sum(W(x,y)*(E^2*y^2 + 2*E*y*(A*x^2+B*y^2+C*x*y+D*x+F-Z(x,y)) +(...) )
0=derror/dE=Sum(W(x,y)*(2*E*y^2 + 2*y*(A*x^2+B*y^2+C*x*y+D*x+F-Z(x,y)) )
0=Sum(W(x,y)*(E*y^2 + y*(A*x^2+B*y^2+C*x*y+D*x+F-Z(x,y)) )
(5) 0= A*SX2Y +  B*SY3 +   C*SXY2 + D*SXY +  E*SY2  + F*SY  - SZY

derror/dF:

error=Sum(W(x,y)*(F^2 +  2*F*(A*x^2+B*y^2+C*x*y+D*x+E*y-Z(x,y)) +(...) )
0=derror/dF=Sum(W(x,y)*(2*F +  2*(A*x^2+B*y^2+C*x*y+D*x+E*y-Z(x,y)) )
0=Sum(W(x,y)*(F +  (A*x^2+B*y^2+C*x*y+D*x+E*y-Z(x,y)) )
(6) 0= A*SX2 +   B*SY2 +   C*SXY +  D*SX +   E*SY   + F*S   - SZ




(1) 0= A*SX4 +   B*SX2Y2 + C*SX3Y +  D*SX3 +  E*SX2Y + F*SX2 - SZX2
(2) 0= A*SX2Y2 + B*SY4 +   C*SXY3 +  D*SXY2 + E*SY3  + F*SY2 - SZY2
(3) 0= A*SX3Y +  B*SXY3 +  C*SX2Y2 + D*SX2Y + E*SXY2 + F*SXY - SZXY
(4) 0= A*SX3 +   B*SXY2 +  C*SX2Y +  D*SX2  + E*SXY  + F*SX  - SZX
(5) 0= A*SX2Y +  B*SY3 +   C*SXY2 +  D*SXY +  E*SY2  + F*SY  - SZY
(6) 0= A*SX2 +   B*SY2 +   C*SXY +   D*SX +   E*SY   + F*S   - SZ


(1) 0= A*S40 + B*S22 + C*S31 + D*S30 + E*S21 + F*S20 - SZ20
(2) 0= A*S22 + B*S04 + C*S13 + D*S12 + E*S03 + F*S02 - SZ02
(3) 0= A*S31 + B*S13 + C*S22 + D*S21 + E*S12 + F*S11 - SZ11
(4) 0= A*S30 + B*S12 + C*S21 + D*S20 + E*S11 + F*S10 - SZ10
(5) 0= A*S21 + B*S03 + C*S12 + D*S11 + E*S02 + F*S01 - SZ01
(6) 0= A*S20 + B*S02 + C*S11 + D*S10 + E*S01 + F*S00 - SZ00


we beed x,y of maximum, so
d(A*x^2+B*y^2+C*x*y+D*x+E*y+F)/dx=0
d(A*x^2+B*y^2+C*x*y+D*x+E*y+F)/dy=0
d()/dx=2*A*x+C*y+D=0
d()/dy=C*x+2*B*y+E=0


  | S40 S22 S31 S30 S21 S20 |   | A |   | SZ20 |
  | S22 S04 S13 S12 S03 S02 |   | B |   | SZ02 |
  | S31 S13 S22 S21 S12 S11 |   | C |   | SZ11 |
  | S30 S12 S21 S20 S11 S10 | * | D | = | SZ10 |
  | S21 S03 S12 S11 S02 S01 |   | E |   | SZ01 |
  | S20 S02 S11 S10 S01 S00 |   | F |   | SZ00 |


  | 2*A    C | * | x | = | -D |
  |   C  2*B |   | Y |   | -E |
  	   		
 */ 	   		
  	   		
  	   		double S00=0.0,
  	   		       S10=0.0,S01=0.0,
  	   		       S20=0.0,S11=0.0,S02=0.0,
  	   		       S30=0.0,S21=0.0,S12=0.0,S03=0.0,
  	   		       S40=0.0,S31=0.0,S22=0.0,S13=0.0,S04=0.0,
  	   		       SZ00=0.0,
  	   		       SZ10=0.0,SZ01=0.0,
  	   		       SZ20=0.0,SZ11=0.0,SZ02=0.0;
  	   		int wsize=weights.length;
  	   		double w,z,x,x2,x3,x4,y,y2,y3,y4,wz;
  	   		for (i=iyc-wsize+1;i<iyc+wsize;i++) if ((i>0) && (i<size)) for (j=ixc-wsize+1;j<ixc+wsize;j++) if ((j>0) && (j<size)) {
  	   			iy=i-iyc;
  	   			ix=j-ixc;
  	   			w=weights[(iy>=0)?iy:-iy][(ix>=0)?ix:-ix];
  	   			if (w>0) {
  	   				z=corr[i*size+j];
  	   				wz=w*z;
  	   				x=ix;
  	   				x2=x*x;
  	   				x3=x2*x;
  	   				x4=x3*x;
  	   				y=iy;
  	   				y2=y*y;
  	   				y3=y2*y;
  	   				y4=y3*y;
  	   				S00+=w;
  	   				S10+=w*x;
  	   				S01+=w*y;
  	   				S20+=w*x2;
  	   				S11+=w*x*y;
  	   				S02+=w*y2;
  	   				S30+=w*x3;
  	   				S21+=w*x2*y;
  	   				S12+=w*x*y2;
  	   				S03+=w*y3;
  	   				S40+=w*x4;
  	   				S31+=w*x3*y;
  	   				S22+=w*x2*y2;
  	   				S13+=w*x*y3;
  	   				S04+=w*y4;
  	   				SZ00+=wz;
  	   				SZ10+=wz*x;
  	   				SZ01+=wz*y;
  	   				SZ20+=wz*x2;
  	   				SZ11+=wz*x*y;
  	   				SZ02+=wz*y2;
  	   			}
  	   		}
  	   		/*
  | S40 S22 S31 S30 S21 S20 |   | A |   | SZ20 |
  | S22 S04 S13 S12 S03 S02 |   | B |   | SZ02 |
  | S31 S13 S22 S21 S12 S11 |   | C |   | SZ11 |
  | S30 S12 S21 S20 S11 S10 | * | D | = | SZ10 |
  | S21 S03 S12 S11 S02 S01 |   | E |   | SZ01 |
  | S20 S02 S11 S10 S01 S00 |   | F |   | SZ00 |

  	   		 */
  	   		double [][] mAarray= {
  	   				{S40,S22,S31,S30,S21,S20},
  	   				{S22,S04,S13,S12,S03,S02},
  	   				{S31,S13,S22,S21,S12,S11},
  	   				{S30,S12,S21,S20,S11,S10},
  	   				{S21,S03,S12,S11,S02,S01},
  	   				{S20,S02,S11,S10,S01,S00}};

            double [] zAarray={SZ20,SZ02,SZ11,SZ10,SZ01,SZ00};
  	   		Matrix M=new Matrix (mAarray);
  	   		Matrix Z=new Matrix (zAarray,6);
  	   		double [] ABCDEF= M.solve(Z).getRowPackedCopy();
/*
  | 2*A    C | * | x | = | -D |
  |   C  2*B |   | Y |   | -E |
*/
  	   		double [][] mXYarray= {{2*ABCDEF[0],ABCDEF[2]},{ABCDEF[2],2*ABCDEF[1]}};
  	   		double []   mDEarray={-ABCDEF[3],-ABCDEF[4]};

  	   		Matrix mXY=new Matrix (mXYarray);
  	   		Matrix mDE= new Matrix (mDEarray,2);
  	   		double [] corrMax= mXY.solve(mDE).getRowPackedCopy();
  	      	if (debugLevel>1) System.out.println("correlationMaximum: ixc="+ixc+" iyc="+iyc+" corrMax[0]="+corrMax[0]+" corrMax[1]="+corrMax[1]);
  	   		corrMax[0]+=ixc-size/2;
  	   		corrMax[1]+=iyc-size/2;

  	   		if (debugLevel>2){
  	   			double [] approx=new double [size*size];
  	   			for (i=0;i<approx.length;i++) approx[i]=0.0;
  	  	   		for (i=iyc-wsize+1;i<iyc+wsize;i++) if ((i>0) && (i<size)) for (j=ixc-wsize+1;j<ixc+wsize;j++) if ((j>0) && (j<size)) {
  	  	   			iy=i-iyc;
  	  	   			ix=j-ixc;
  	  	   			x=ix;
  	  	   			y=iy;
  	  	   			
//  	   				z=corr[i*size+j];
  	  	   			approx[i*size+j]=ABCDEF[0]*x*x+
  	  	   		                     ABCDEF[1]*y*y+
  	  	   		                     ABCDEF[2]*x*y+
  	  	   		                     ABCDEF[3]*x+
  	  	   		                     ABCDEF[4]*y+
  	  	   		                     ABCDEF[5];
  	  	   		}
  	  	   		double [][] both= new double[2][];
  	  	   		both[0]=corr;
  	  	   		both[1]=approx;
  	  	   		//corr
  				SDFA_INSTANCE.showArrays(both, true, "corr-approx"); // stack
  	   		}
//  	   		if (debugLevel>2) System.out.println("correlationMaximum: ix="+ix+" iy="+iy);
//  	   		if (debugLevel>2) System.out.println("correlationMaximum: maxInHor[0] ="+maxInHor[0]+ " maxInHor[1]= "+maxInHor[1]+ " maxInHor[2]= "+maxInHor[2]);
//  	   		if (debugLevel>2) System.out.println("correlationMaximum: maxInVert[0]="+maxInVert[0]+" maxInVert[1]="+maxInVert[1]+" maxInVert[2]="+maxInVert[2]);
  	   	    return corrMax;
  	   	}

	   
	   private double [] correlationMaximum(
	   	   			double [] corr,
	   	   			int dist,       // maximal distance from the maximum to consider
	   	   			double threshold, // fraction of maximum (slightly less than 1.0) to limit the top part of the maximum for centroid
	   	   			int decimate, // interpolate to finer grid (both FFT and linear)
	   	   			int decimateFFT, // should be power of 2
					DoubleFHT fht_instance,
					double maxOffset,
					double lowpass, // relative to original corr size (will be scaled for decimation). Will only be applied if decimateFFT >1!
					boolean showDebug
	   	   			){
	   	   		if ((corr==null) || (corr.length==0)) return null;
	   	   		int size= (int) Math.sqrt(corr.length);
	   	   		int i,j,imax=0,ixc,iyc,index;
//	   	   	if (showDebug) System.out.println("correlationMaximum(), decimateFFT="+decimateFFT);
	   	   	/**
	   	   	 * Reduces size of the correlation area (using center part) and simultaneously interpolating pixels, so the result is a
	   	   	 * scaled version of the center (total FFT suize remains the same)
	   	   	 */
	   	   		if (showDebug){
	   	   			System.out.println("correlationMaximum(): decimate="+decimate+" decimateFFT="+decimateFFT);
	   	   		}
	   	   		if (decimateFFT>1){
	   	   			if (fht_instance==null) fht_instance=new DoubleFHT();
	   	   			double scale=decimateFFT*decimateFFT;
	   	   			double [] corr1=new double [corr.length];
	   	   			/**
	   	   			 * As we are interested only in the center part of the image, we'll use flat-top
	   	   			 * window based on Hamming.
	   	   			 */

	   	   			int size1=size/decimateFFT;
	   	   			for (i=0;i<corr1.length;i++) corr1[i]=0.0;
	   	   			double borderAverage=0;
	   	   			index=size*(size+1)*(decimateFFT-1)/decimateFFT/2;
	   	   			int i0=index,i1=index+size1,i2=i1+size1*size,i3=i2-size1; // on right and bottom edge goes 1 pixel outside of the used area
	   	   			for (i=0;i<size1;i++)	{
/*	   	   				if (showDebug){
	   	   					System.out.println(":: size="+size+" size1="+size1+ " i="+i+" i0="+i0+" i1="+i1+" i2="+i2+" i3="+i3+" scale="+scale);
	   	   				}
*/	   	   				
	   	   				borderAverage+=corr[i0++]+corr[i1+=size1]+corr[i2--]+corr[i3-=size1];

	   	   			}
	   	   			borderAverage/=4*size1;
	   	   			double [] preHammingMod=fht_instance.getHamming1d(size1/2);
	   	   			double [] hammingMod=new double [size1];
	   	   			for (i=0;i<size1/4;i++) hammingMod[i]=preHammingMod[i];
	   	   			for (i=1;i<size1/4;i++) hammingMod[size1-i]=preHammingMod[i];
	   	   			for (i=size1/4;i<=(size1-size1/4);i++) hammingMod[i]=1.0;
	   	   			
	   	   		    if (showDebug) System.out.println("scale="+scale+ " borderAverage="+borderAverage);
	   	   			for (i=0;i<size1;i++) for (j=0;j<size1;j++) {
	   	   				corr1[(i*size+j)*decimateFFT] = scale*(corr[index+i*size+j]-borderAverage)*hammingMod[i]*hammingMod[j]+borderAverage;
	   	   			}
	   	   			if (showDebug) SDFA_INSTANCE.showArrays(corr1.clone(), "decimatedForFFT");
	   	   		    fht_instance.swapQuadrants(corr1);
	    	    	if (!fht_instance.transform(corr1,false)) return null; // direct FHT
	   	   			if (showDebug) SDFA_INSTANCE.showArrays(corr1.clone(), "FFT");
	    	    	// zero out aliases
	    	    	for (i=0; i<=size1/2;i++) for (j=size1/2+1;j<size -(size1/2);j++) corr1[i*size+j]=0.0;
	    	    	for (i=size1/2+1;i<size -(size1/2);i++) for (j=0;j<size;j++) corr1[i*size+j]=0.0;
	    	    	for (i=size -(size1/2); i<size;i++) for (j=size1/2+1;j<size -(size1/2);j++) corr1[i*size+j]=0.0;
	    	    	// apply window for now - just
/*	    	    	
	    	    	if (showDebug) {
	    	    		System.out.println("Getting hamming1d ("+size1+")");
	    	    	}
*/	    	    	
	    	    	double [] hamming=fht_instance.getHamming1d(size1).clone();
/*	    	    	
	    	    	if (showDebug) {
	    	    		for (i=0;i<hamming.length;i++) System.out.println("hamming["+i+"]="+hamming[i]); 
	    	    	}
	   	   			if (showDebug) SDFA_INSTANCE.showArrays(corr1.clone(), "NO_ALIAS");
	   	   			// Combine with low-pass Gaussian (if it is >0)
	   	   			if (lowpass>0){
	   	   				double [] gaussian1d=fht_instance.getGaussian1d(lowpass,size1); // no need to divide by /decimateFFT as we use size1, not size
	   	   				for (i=0;i<hammingMod.length;i++) hamming[i]*=gaussian1d[i];
		    	    	if (showDebug) {
		    	    		System.out.println("lowpass="+lowpass);
		    	    		for (i=0;i<gaussian1d.length;i++) System.out.println("gaussian1d["+i+"]="+gaussian1d[i]); 
		    	    	}
	   	   			}

	    	    	if (showDebug) {
	    	    		for (i=0;i<hamming.length;i++) System.out.println("hamming["+i+"]="+hamming[i]); 
	    	    	}
*/	    	    	
	    	    	int halfSize1=size1/2, shiftZero=size-halfSize1;
	    	    	for (i=0;i<=size1;i++) for (j=0;j<=size1;j++){
	    	    		int im=i%size1,jm=j%size1;
	    	    		corr1[((i+shiftZero)%size)*size+((j+shiftZero)%size)]*=hamming[im]*hamming[jm];
	    	    	}
	    	    	
	    	    	
	   	   			if (showDebug) SDFA_INSTANCE.showArrays(corr1.clone(), "FFT-masked");
	    	    	if (!fht_instance.transform(corr1,true)) return null; // inverse FHT
	   	   		    fht_instance.swapQuadrants(corr1);
	   	   			if (showDebug) SDFA_INSTANCE.showArrays(corr1.clone(), "decimatedAfterFFT");
	   	   		    dist*=decimateFFT;
	   	   		    maxOffset*=decimateFFT;
	   	   			decimate/=decimateFFT;
	   	   		    corr=corr1; // replace
	   	   		}

	   	   		
	   	   		double max=corr[0];
	   	   		for (i=1;i<corr.length;i++) if (max<corr[i]) {
	   	   			max=corr[i];
	   	   			imax=i;
	   	   		}
	   	   		iyc=imax/size;
	   	   		ixc=imax%size;
	   	   		int ixc0=ixc-size/2;
	   	   		int iyc0=iyc-size/2;
	   	   		if ((maxOffset>0) && (maxOffset*maxOffset<(ixc0*ixc0+iyc0*iyc0))) {
					if (showDebug || (debugLevel>1)) System.out.println("Too far from the center2: ixc="+ixc+" iyc="+iyc+" ixc0="+ixc0+" iyc0="+iyc0+" maxOffset="+maxOffset);
//					if (showDebug || (debugLevel>0)) System.out.println("Too far from the center2: ixc="+ixc+" iyc="+iyc+" ixc0="+ixc0+" iyc0="+iyc0+" maxOffset="+maxOffset);
	   	   			return null;
	   	   		}
				if (showDebug || (debugLevel>1)) System.out.println("correlationMaximum: ixc="+ixc+" iyc="+iyc+" ixc0="+ixc0+" iyc0="+iyc0+" maxOffset="+maxOffset);

// reduce dist if it hits borders
	   	   		if (dist>iyc) dist=iyc;
	   	   		if (dist>ixc) dist=ixc;
	   	   		if (dist>(size-iyc-1)) dist=(size-iyc-1);
	   	   		if (dist>(size-ixc-1)) dist=(size-ixc-1);
	   	   		int interpSize=2*dist*decimate+1;
	   	   		double [][] cell=new double[2][2];
	   	   		double [] row = new double [2]; 
	   	   		double [] ki= new double [2];
	   	   		double kj;
	   	   		double [] interpCorr=new double[interpSize*interpSize];
	   	   		int i1,j1;
	   	   		int i1Range,j1Range;
	   	   		for (i=0;i<2*dist;i++) for (j=0;j<2*dist;j++) {
	   	   			index=(iyc-dist+i)*size+(ixc-dist+j);
	   	   			cell[0][0]= corr[index];
	   	   			cell[0][1]= corr[index+1];
	   	   			cell[1][0]= corr[index+size];
	   	   			cell[1][1]= corr[index+size+1];
	   	   			i1Range=decimate+((i==(2*dist-1))?1:0);
	   	   			j1Range=decimate+((j==(2*dist-1))?1:0);
	   	   			ki[0]=(cell[1][0]-cell[0][0])/decimate;
	   	   			ki[1]=(cell[1][1]-cell[0][1])/decimate;
	   	   			
	   	   			for (i1=0;i1<i1Range;i1++){
	   	   				row[0]=cell[0][0]+ki[0]*i1;
	   	   				row[1]=cell[0][1]+ki[1]*i1;
	   	   				kj=(row[1]-row[0])/decimate;
	   	   				for (j1=0;j1<j1Range;j1++) {
	   	   					interpCorr[(i*decimate+i1)*interpSize+(j*decimate+j1)]=row[0]+kj*j1;
	   	   			    }
	   	   			}
	   	   		}
// Gaussian blur the after linear interpolation, use sigma = 0.75* decimate ?	   	   		
// now find the maximal value on the border - it will be a threshold for a wave from the center
	   	   		double interpolationBlurSigma=0.75* decimate;
	    		DoubleGaussianBlur gb=new DoubleGaussianBlur();
	    		gb.blurDouble(interpCorr, interpSize, interpSize, interpolationBlurSigma, interpolationBlurSigma, 0.01);

	   	   		double limit=interpCorr[0];
	   	   		for (i=0;i<interpSize;i++) {
	   	   			if (limit<interpCorr[i]) limit=interpCorr[i];
	   	   			if (limit<interpCorr[interpSize*interpSize-i-1])   limit=interpCorr[interpSize*interpSize-i-1];
	   	   			if (limit<interpCorr[interpSize*i])                limit=interpCorr[interpSize*i];
	   	   			if (limit<interpCorr[interpSize*i+(interpSize-1)]) limit=interpCorr[interpSize*i+(interpSize-1)];
	   	   		}
// Now modify the limit if it is below threshold*max (sharp maximum)
	   	   		if (limit <threshold*max) limit =threshold*max;
	   	   		
// run wave from the center, border pixels <=limit, so no need to verify array limits
	   	   		
	   			List <Integer> pixelList=new ArrayList<Integer>(100);
	   			Integer Index, newIndex;
	   			int []clusterMap=new int[interpSize*interpSize];
	   			for (i=0;i<clusterMap.length;i++) clusterMap[i]=0;
	   			int [] dirs={-1,-interpSize-1,-interpSize,-interpSize+1,1,interpSize+1,interpSize,interpSize-1};
	   			Index=dist*decimate*(interpSize+1); // center
	   			pixelList.clear();
	   			pixelList.add (Index);
	  	   		if (showDebug || (debugLevel>1)) System.out.println("correlationMaximum: pixelList.add ("+Index+ "), i="+(Index/interpSize)+ " j= "+(Index%interpSize));
	   			clusterMap[Index]=1;
	   			while (pixelList.size()>0) {
	   				Index=pixelList.remove(0);
	   				for (i=0;i<dirs.length;i++) {
	   					newIndex=Index+dirs[i];
	   					if ((clusterMap[newIndex]==0) && (interpCorr[newIndex]>limit)){
	   			   			pixelList.add (newIndex);
	   			   			clusterMap[newIndex]=1;
	   					}
	   				}
	   			}
// Calculate centroid
	   			double s=0.0,sx=0.0, sy=0.0,x,y,d;
	  	   		if (showDebug || (debugLevel>1)) System.out.println("correlationMaximum: dist ="+dist+ " decimate= "+decimate+ " interpSize= "+interpSize);
	   			for (i=0;i<clusterMap.length;i++) if (clusterMap[i]>0){
	   				x=(i%interpSize-dist*decimate);
	   				y=(i / interpSize-dist*decimate);
	   				d=interpCorr[i]-limit;
	   				s+=d;
	   				sx+=x*d;
	   				sy+=y*d;
	   			}
	   			double [] corrXY={sx/s/decimate+ixc-size/2,sy/s/decimate+iyc-size/2};
	  	   		if (showDebug || (debugLevel>1)) System.out.println("correlationMaximum: s ="+s+ " sx= "+sx+ " sy= "+sy);
	  	   		if (showDebug || (debugLevel>1)) System.out.println("correlationMaximum: sx/s/decimate ="+(sx/s/decimate)+ " sy/s/decimate= "+(sy/s/decimate));
				if (showDebug || (debugLevel>1)) System.out.println("correlationMaximum: dx="+IJ.d2s(corrXY[0],3)+" dy="+IJ.d2s(corrXY[1],3));
	  	   		
//			    if ((debugLevel>1) && (showDebug)) {
			    if (showDebug) {
			    	double [] decimatedMasked=interpCorr.clone();
			    	for (i=0;i<decimatedMasked.length;i++) {
			    		if (clusterMap[i]==0) decimatedMasked[i]=limit;
			    	}
			    	double [][] both={interpCorr,decimatedMasked};
			    	SDFA_INSTANCE.showArrays(both, true, "centerCorr");
			    }
			    if (decimateFFT>1) {
			    	corrXY[0]/=decimateFFT;
			    	corrXY[1]/=decimateFFT;
			    }
	   	   	    return corrXY;
	   	   	}

	   private double [] correlationMaximum(
  	   			double [] corr,
  	   			double maxOffset,
				boolean showDebug) {
  	   		if ((corr==null) || (corr.length==0)) return null;
  	   		double [] corrMax= new double[2];
  	   		int size= (int) Math.sqrt(corr.length);
  	   		int i,imax=0,ix,iy;
  	   		double max=corr[0];
  	   		for (i=1;i<corr.length;i++) if (max<corr[i]) {
  	   			max=corr[i];
  	   			imax=i;
  	   		}
  	   		iy=imax/size;
  	   		ix=imax%size;
 
  	   		corrMax[0]=ix-size/2;
  	   		corrMax[1]=iy-size/2;
   	   		if ((maxOffset>0) && (maxOffset*maxOffset<(corrMax[0]*corrMax[0]+corrMax[1]*corrMax[1]))) {
				if (debugLevel>1) System.out.println("Too far from the center3: corrMax[0]="+corrMax[0]+" corrMax[1]="+corrMax[1]);
   	   			return null;
   	   		}
  	   		
  	   		if ((ix==0) || (iy==0) || (ix==(size-1))  || (iy==(size-1))) return corrMax; // on the border - no interpolation;
  	   		
  	   		double [] maxInHor= new double[3]; // locations of interpolated maximums for each of 3 rows   (iy-1, iy, iy+1)
  	   		double [] maxInVert=new double[3]; // locations of interpolated maximums for each of 3 columns(ix-1, ix, ix+1)
  	   		if (debugLevel>2) System.out.println("correlationMaximum: ix="+ix+" iy="+iy);

  	   		for (i=0;i<3;i++) {
  	   			maxInHor[i]= -0.5+(corr[imax+size*(i-1)]-corr[imax+size*(i-1)-1])/
  	   			                  (2*corr[imax+size*(i-1)]-corr[imax+size*(i-1)-1]-corr[imax+size*(i-1)+1]);
  	   			maxInVert[i]=-0.5+(corr[imax+(i-1)]-corr[imax+     (i-1)-size])/
  	                                 (2*corr[imax+ (i-1)]-corr[imax+(i-1)-size]-corr[imax+(i-1)+size]);
  	   		}
  	   		if (debugLevel>2) System.out.println("correlationMaximum: maxInHor[0] ="+maxInHor[0]+ " maxInHor[1]= "+maxInHor[1]+ " maxInHor[2]= "+maxInHor[2]);
  	   		if (debugLevel>2) System.out.println("correlationMaximum: maxInVert[0]="+maxInVert[0]+" maxInVert[1]="+maxInVert[1]+" maxInVert[2]="+maxInVert[2]);
  	           int maxInHorIndex=0;		
  	           int maxInVertIndex=0;
  	           if ((maxInHor[0] <maxInHor[1] ) && (maxInHor[0] <maxInHor[2]) ) maxInHorIndex=1;
  	           if ((maxInVert[0]<maxInVert[1]) && (maxInVert[0]<maxInVert[2])) maxInVertIndex=1;
  	   		if (debugLevel>2) System.out.println("correlationMaximum: maxInHorIndex="+maxInHorIndex+" maxInVertIndex="+maxInVertIndex);
  	   /*
  	    * y= (y0+x0(y1-y0))/(1-(x1-x0)(y1-y0))
  	    * x= (x0+y0(x1-x0))/(1-(x1-x0)(y1-y0))
  	    * d= (1-(x1-x0)(y1-y0))
  	    * y= (y0+x0(y1-y0))/d
  	    * x= (x0+y0(x1-x0))/d
  	    */
  	   		double d=1-(maxInHor[maxInHorIndex+1]-maxInHor[maxInHorIndex])*(maxInVert[maxInVertIndex+1]-maxInVert[maxInVertIndex]);
  	   		corrMax[0]=(maxInHor [maxInHorIndex]+ maxInVert[maxInVertIndex]*(maxInHor [maxInHorIndex +1]-maxInHor [maxInHorIndex ]))/d;
  	   		corrMax[1]=(maxInVert[maxInVertIndex]+maxInHor [maxInHorIndex ]*(maxInVert[maxInVertIndex+1]-maxInVert[maxInVertIndex]))/d;
  	   		if (debugLevel>2) System.out.println("correlationMaximum: corrMax[0]="+corrMax[0]+" corrMax[1]="+corrMax[1]);
  	   		corrMax[0]+=ix-size/2;
  	   		corrMax[1]+=iy-size/2;
  	   	    return corrMax;
  	   	}
	   
	   
/* ======================================================================== */
	   public Rectangle correlationSelection(
				double [] beforeXY, // initial coordinates of the pattern cross point
				int size){
			 int ixc=2*((int) Math.round(beforeXY[0]/2));
			 int iyc=2*((int) Math.round(beforeXY[1]/2));
			 Rectangle centerCross=new Rectangle(ixc-size,
					                             iyc-size,
		          2*size,2*size);       
           return centerCross;
	   }
/* ======================================================================== */
// Estimate center xy and wave vectors from the neigbors
// returns {{x,y},{wv1x,wv1y},{wv2x,wv2y}}
	   
	   
	   public double [][] estimateCell(
			   double [][][][] grid,
			   int [] uv0,
			   double [][] weights, // quadrant of sample weights
			   boolean useContrast, // do not use cells with undefined contrast
			   boolean forceLinear,  // use linear approximation (instead of quadratic)
			   double thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
			   double thresholdQuad  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
	   ){
		   int dist=weights.length-1;
		   int size=dist*2+1;
		   double [][][] samples0 = new double [size*size][3][];
		   int index=0;
		   int [] uv=new int[2];
		   double w;
		   int maxU=-dist-1,minU=dist+1,maxV=-dist-1,minV=dist+1,maxUpV=-2*dist-1,minUpV=2*dist+1,maxUmV=-2*dist-1,minUmV=2*dist+1;
		   for (int iDv=-dist;iDv<=dist;iDv++) for (int iDu=-dist;iDu<=dist;iDu++) {
			   uv[0]=uv0[0]+iDu;
			   uv[1]=uv0[1]+iDv;
			   if ((!useContrast && isCellDefined(grid,uv)) || isCellDefinedC(grid,uv)) {
				   w=weights[(iDv>=0)?iDv:-iDv][(iDu>=0)?iDu:-iDu];
				   if (w!=0.0){
					   if (maxU<iDu) maxU=iDu;
					   if (minU>iDu) minU=iDu;
					   if (maxV<iDv) maxV=iDv;
					   if (minV>iDv) minV=iDv;
					   if (maxUpV<(iDu+iDv)) maxUpV= iDu+iDv;
					   if (minUpV>(iDu+iDv)) minUpV= iDu+iDv;
					   if (maxUmV<(iDu-iDv)) maxUmV= iDu-iDv;
					   if (minUmV>(iDu-iDv)) minUmV= iDu-iDv;
					   samples0[index][0]=new double[2];
					   samples0[index][1]=new double[useContrast?3:2];
					   samples0[index][2]=new double[1];
					   samples0[index][2][0]=w;
					   samples0[index][0][0]=iDu;
					   samples0[index][0][1]=iDv;
					   samples0[index][1][0]=grid[uv[1]][uv[0]][0][0];
					   samples0[index][1][1]=grid[uv[1]][uv[0]][0][1];
					   if (useContrast){
						   samples0[index][1][2]=grid[uv[1]][uv[0]][0][2]; // contrast
					   }
					   index++;
				   }
			   }
		   }
		   if (debugLevel>3) System.out.println(" maxU-minU="+(maxU-minU)+" maxV-minV="+(maxV-minV));
		   if (debugLevel>3) System.out.println(" maxUpV-minUpV="+(maxUpV-minUpV)+" maxUmV-minUmV="+(maxUmV-minUmV));
		   
		   int diameter=maxU-minU;
		   if (diameter>(maxV-minV)) diameter= maxV-minV;
		   diameter*=2;
		   if (diameter>(maxUpV-minUpV)) diameter= (maxUpV-minUpV);
		   if (diameter>(maxUmV-minUmV)) diameter= (maxUmV-minUmV);
		   if (debugLevel>3) System.out.println(" diameter="+diameter+" number="+index);
		   if (diameter<2) return null;

		   double [][][] samples = new double [index][][];
		   System.arraycopy(samples0, 0, samples, 0, index);
		   double [][] estimatedCell= interpolateQuadraticWithWvAtZero(
				   samples,   // see quadraticApproximation()
				   forceLinear || (diameter<5),  // use linear approximation diameter <4 should be enough, 5 - just to be safe
				   thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
				   thresholdQuad);  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
		   if ((estimatedCell==null) || (estimatedCell[0]==null) || useContrast) return estimatedCell;
		   double contrast=Double.NaN;
		   if (isCellDefined(grid,uv0)) {
			   double [] xycOld=grid[uv0[1]][uv0[0]][0];
			   if (xycOld.length>2) contrast=xycOld[2];
		   }
		   double [] xyc={estimatedCell[0][0],estimatedCell[0][1],contrast};
		   estimatedCell[0]=xyc;
		   return estimatedCell;
	   }
	   
	   public double [] interpolateQuadratic(
			   double [] xy,         // coordinates for which the interpolation is needed 
			   double [][][] data,   // see quadraticApproximation()
			   boolean forceLinear,  // use linear approximation
			   double thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
			   double thresholdQuad){  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
		   double [][] coeff = new PolynomialApproximation(this.debugLevel).quadraticApproximation(
				   data,
				   forceLinear,  // use linear approximation
				   thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
				   thresholdQuad);
		   if (coeff==null) return null;
		   double [] result = new double [coeff.length];
		   int offset=(coeff[0].length>3)?3:0;
		   for (int i=0;i<coeff.length;i++) {
			   result[i]=coeff[i][offset+0]*xy[0]+coeff[i][offset+1]*xy[1]+coeff[i][offset+2];
			   if (offset>0) result[i]+=coeff[i][0]*xy[0]*xy[0]+coeff[i][1]*xy[1]*xy[1]+coeff[i][2]*xy[0]*xy[1];
		   }
		   return result;
	   }

	   // returns {{x,y},{wv1x,wv1y},{wv2x,wv2y}}
	   public double [][] interpolateQuadraticWithWvAtZero(
			   double [][][] data,   // see quadraticApproximation()
			   boolean forceLinear,  // use linear approximation
			   double thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
			   double thresholdQuad){  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
		   double [][] coeff = new PolynomialApproximation(this.debugLevel).quadraticApproximation(
				   data,
				   forceLinear,  // use linear approximation
				   thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
				   thresholdQuad);
		   if (coeff==null) return null;
		   if (coeff.length!=2) return null;
		   double [][] result = new double [3][2];
		   double [][] uv2xy=new double [2][2];
		   int offset=(coeff[0].length>3)?3:0;
		   for (int i=0;i<2;i++) {
			   result[0][i]=coeff[i][offset+2];
			   uv2xy[0][i]=coeff[0][offset+i];
			   uv2xy[1][i]=coeff[1][offset+i];
		   }
		   double[][] wv=matrix2x2_invert(matrix2x2_scale(uv2xy,2.0));
		   for (int i=0;i<2;i++) {
			   result[1][i]=wv[0][i];
			   result[2][i]=wv[1][i];
		   }
			 if ((debugLevel>2) && forceLinear) {
				 System.out.println("*************** interpolateQuadraticWithWvAtZero() linear forced, data.length="+data.length);
			 }
			 if (debugLevel>3) {
				 for (int i=0;i<data.length;i++) {
					 System.out.println(i+": uv=["+IJ.d2s(data[i][0][0],3)+":"+IJ.d2s(data[i][0][1],3)+"]"+
					 " xy=["+IJ.d2s(data[i][1][0],3)+":"+IJ.d2s(data[i][1][1],3)+"]"+
					 " weight="+IJ.d2s(data[i][2][0],3));
				 }
				 String dbgStr="";
				 dbgStr+=" [["+IJ.d2s(coeff[0][0],5)+"/"+IJ.d2s(coeff[0][1],5)+"/"+IJ.d2s(coeff[0][2],5);
				 if (coeff[0].length>3) dbgStr+="/"+IJ.d2s(coeff[0][3],5)+"/"+IJ.d2s(coeff[0][4],5)+"/"+IJ.d2s(coeff[0][5],5)+"]]";
				 dbgStr+=" [["+IJ.d2s(coeff[1][0],5)+"/"+IJ.d2s(coeff[1][1],5)+"/"+IJ.d2s(coeff[1][2],5);
				 if (coeff[1].length>3) dbgStr+="/"+IJ.d2s(coeff[1][3],5)+"/"+IJ.d2s(coeff[1][4],5)+"/"+IJ.d2s(coeff[1][5],5)+"]]";
				 System.out.println(dbgStr);
				 for (int i=0;i<2;i++) {
					 System.out.println(i+": uv2xy="+IJ.d2s(uv2xy[i][0],3)+":"+IJ.d2s(uv2xy[i][1],3));
				 }
				 for (int i=0;i<2;i++) {
					 System.out.println(i+": wv="+IJ.d2s(wv[i][0],3)+":"+IJ.d2s(wv[i][1],3));
				 }
			 }                                   
		   return result;
	   }
// calculate simulation parameters for quadratic distortion of the pattern, compatible with SimulationPattern class
// Returns 2 lines {{wv1x, wv1y, u0, Ax, Bx, Cx},{wv2x, wv2y, v0, Ay, By, Cy}}
// if quadratic is not possible, only {{wv1x, wv1y, u0},{wv2x, wv2y}} will be returned
// or just null if even linear is not possible
// data array consists of lines of either 2 or 3 vectors:
//  2-element vector x,y
//  2 element vector u,v 
//  optional 1- element vector w (weight of the sample)
	   public double [][] getSimulationParametersFromGrid(
			   double [][][][] grid,
			   int [] uv0,          // U,V of the center point (for which the simulation pattern should be built
			   double [] xy0,          // x,y of the center point (or null to use grid)
			   double [][] weights, // quadrant of sample weights
			   boolean forceLinear,  // use linear approximation (instead of quadratic)
			   double thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
			   double thresholdQuad  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
	   ){
		   int dist=weights.length-1;
		   int size=dist*2+1;
		   double [][][] samples0 = new double [size*size][3][];
		   int index=0;
		   int [] uv=new int[2];
		   double w;
		   if (xy0==null) {
			   if (isCellDefined(grid,uv0)) {
				   xy0=new double [2];
				   xy0[0]=grid[uv0[1]][uv0[0]][0][0];
				   xy0[1]=grid[uv0[1]][uv0[0]][0][1];
			   } else {
				   return null; //xy of the center is not known
			   }
		   }
		   int maxU=-dist-1,minU=dist+1,maxV=-dist-1,minV=dist+1,maxUpV=-2*dist-1,minUpV=2*dist+1,maxUmV=-2*dist-1,minUmV=2*dist+1;
		   for (int iDv=-dist;iDv<=dist;iDv++) for (int iDu=-dist;iDu<=dist;iDu++) {
			   uv[0]=uv0[0]+iDu;
			   uv[1]=uv0[1]+iDv;
			   if ((uv[0]>=0) && (uv[1]>=0) && (uv[1]<grid.length) && (uv[0]<grid[uv[1]].length) && (isCellDefined(grid,uv))) {
				   w=weights[(iDv>=0)?iDv:-iDv][(iDu>=0)?iDu:-iDu];
				   if (w!=0.0){
					   if (maxU<iDu) maxU=iDu;
					   if (minU>iDu) minU=iDu;
					   if (maxV<iDv) maxV=iDv;
					   if (minV>iDv) minV=iDv;
					   if (maxUpV<(iDu+iDv)) maxUpV= iDu+iDv;
					   if (minUpV>(iDu+iDv)) minUpV= iDu+iDv;
					   if (maxUmV<(iDu-iDv)) maxUmV= iDu-iDv;
					   if (minUmV>(iDu-iDv)) minUmV= iDu-iDv;
					   samples0[index][0]=new double[2];
					   samples0[index][1]=new double[2];
					   samples0[index][2]=new double[1];
					   samples0[index][2][0]=w;
					   samples0[index][0][0]=grid[uv[1]][uv[0]][0][0]-xy0[0];
					   samples0[index][0][1]=grid[uv[1]][uv[0]][0][1]-xy0[1];
					   samples0[index][1][0]=uv[0];
					   samples0[index][1][1]=uv[1];
					   
						 if (debugLevel>20) {
								 System.out.println("iDu="+iDu+" iDv="+iDv+" "+
								 " uv[0]="+IJ.d2s(uv[0],3)+" uv[1]="+IJ.d2s(uv[1],3)+" "+
								 " samples0["+index+"][0][0]="+IJ.d2s(samples0[index][0][0],3)+" samples0["+index+"][0][1]="+IJ.d2s(samples0[index][0][1],3)+" "+
								 " samples0["+index+"][1][0]="+IJ.d2s(samples0[index][1][0],3)+" samples0["+index+"][1][1]="+IJ.d2s(samples0[index][1][1],3));
						 }					   
					   index++;
				   }
			   }
		   }
		   int diameter=maxU-minU;
		   if (diameter>(maxV-minV)) diameter= maxV-minV;
		   diameter*=2;
		   if (diameter>(maxUpV-minUpV)) diameter= (maxUpV-minUpV);
		   if (diameter>(maxUmV-minUmV)) diameter= (maxUmV-minUmV);
		   if (debugLevel>2) System.out.println(" diameter="+diameter+" number="+index);
		   if (diameter<2) return null;
		   double [][][] samples = new double [index][][];
		   System.arraycopy(samples0, 0, samples, 0, index);
		   double [][] simulParams= getSimulationParametersFromSamples(
				   samples,   // see quadraticApproximation()
				   forceLinear || (diameter<5),  // use linear approximation diameter <4 should be enough, 5 - just to be safe
				   thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
				   thresholdQuad);  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
		   return simulParams;
	   }

	   
	   public double [][] getSimulationParametersFromSamples(
			   double [][][] data,   // see quadraticApproximation()
			   boolean forceLinear,  // use linear approximation
			   double thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
			   double thresholdQuad){  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
		   double [][] coeff = new PolynomialApproximation(this.debugLevel).quadraticApproximation(
				   data,
				   forceLinear,  // use linear approximation
				   thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
				   thresholdQuad);
		   
			 if (debugLevel>2) {
				 for (int i=0;i<data.length;i++) {
					 System.out.println(i+": xy=["+IJ.d2s(data[i][0][0],3)+":"+IJ.d2s(data[i][0][1],3)+"]"+
					 " uv=["+IJ.d2s(data[i][1][0],3)+":"+IJ.d2s(data[i][1][1],3)+"]"+
					 " weight="+IJ.d2s(data[i][2][0],3));
				 }
				 String dbgStr="";
				 dbgStr+=" ["+IJ.d2s(coeff[0][0],5)+"/"+IJ.d2s(coeff[0][1],5)+"/"+IJ.d2s(coeff[0][2],5);
				 if (coeff[0].length>3) dbgStr+="/"+IJ.d2s(coeff[0][3],5)+"/"+IJ.d2s(coeff[0][4],5)+"/"+IJ.d2s(coeff[0][5],5)+"]";
				 dbgStr+=" ["+IJ.d2s(coeff[1][0],5)+"/"+IJ.d2s(coeff[1][1],5)+"/"+IJ.d2s(coeff[1][2],5);
				 if (coeff[1].length>3) dbgStr+="/"+IJ.d2s(coeff[1][3],5)+"/"+IJ.d2s(coeff[1][4],5)+"/"+IJ.d2s(coeff[1][5],5)+"]";
				 System.out.println(dbgStr);
			 }                                   

		   
		   if (coeff==null) return null;
		   if (coeff.length!=2) return null;
		   boolean isQuad=coeff[0].length>3;
		   int offset=isQuad?3:0;
		   double [][] result = new double [2][isQuad?6:3];
		   double [][] xy2uv = new double [2][2];
           for (int i=0;i<2;i++) {
        	   result[i][2]=coeff[i][offset+2]; // F
        	   xy2uv[i][0]=coeff[i][offset+0]; // D
        	   xy2uv[i][1]=coeff[i][offset+1]; // E
        	   result[i][0]=0.5*xy2uv[i][0]; // 0.5 because uv grid is 0.5 (pos/neg)
        	   result[i][1]=0.5*xy2uv[i][1]; //
           }
           if (isQuad) {
        	   double [][] uv2xy=matrix2x2_invert(xy2uv);
//        	   double [][] ABCuv={{coeff[0][0],coeff[0][1],0.5*coeff[0][2]},
//        			              {coeff[1][0],coeff[1][1],0.5*coeff[1][2]}};
        	   double [][] ABCuv={
        			      {4*coeff[0][0],4*coeff[0][1],2*coeff[0][2]}, // correction that uv grid is 0.5
			              {4*coeff[1][0],4*coeff[1][1],2*coeff[1][2]}};
        	   double [][] ABCxy=new double [2][3];
        	   for (int i=0;i<2;i++) for (int j=0;j<3;j++) {
        		   ABCxy[i][j]=0.0;
        		   for (int k=0;k<2;k++) ABCxy[i][j]+=uv2xy[i][k]*ABCuv[k][j];
        	   }
        	   for (int i=0;i<2;i++) for (int j=0;j<3;j++)result[i][j+3]=ABCxy[i][j];
           }
		   return result;
	   }
	   
		public double [][] findPatternFromGrid(
				int x0, // top-left pixel of the square WOI
				int y0,
				int size, // size of square (pixels)
				double[] halfWindow,
				boolean forceLinear,  // use linear approximation (instead of quadratic)
				double thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
				double thresholdQuad  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
				){ // only half-window - half height by half width
			if (this.PATTERN_GRID==null) {
				String msg="PATTERN_GRID is needed, but undefined";
				IJ.showMessage("Error",msg);
				throw new IllegalArgumentException (msg);
			}
			if (this.PATTERN_GRID.length==0) return null;
			double x1=x0,y1=y0;
			double x2=x1+size;
			double y2=y1+size;
			double x,y;
			List <Integer> nodeList=new ArrayList<Integer>(1000);
			Integer Index;
			int len=this.PATTERN_GRID[0].length;
			for (int v=0;v<this.PATTERN_GRID.length;v++) for (int u=0;u<len;u++)
				if ((this.PATTERN_GRID[v][u]!=null) && (this.PATTERN_GRID[v][u][0]!=null)){
					x=this.PATTERN_GRID[v][u][0][0];
					y=this.PATTERN_GRID[v][u][0][1];
					if ((x>=x1) && (x<x2) && (y>=y1) && (y<y2)) {
						Index=v*len+u;
						nodeList.add(Index);
					}

				}
			double [][][] samples =new double [nodeList.size()][3][];
// pattern parameters are referenced to the center of the square
			double xc=x0+size/2;
			double yc=y0+size/2;
			int halfSize=size/2;
			for (int i=0;i<samples.length;i++){
				int uv=nodeList.get(i);
				int v=uv/len;
				int u=uv%len;
				samples[i][0]=new double [2];
				samples[i][0][0]=this.PATTERN_GRID[v][u][0][0]-xc;
				samples[i][0][1]=this.PATTERN_GRID[v][u][0][1]-yc;
				samples[i][1]=new double [2];
				samples[i][1][0]=u;
				samples[i][1][1]=v;
				samples[i][2]=new double [1];
				int iy=((int) Math.round((this.PATTERN_GRID[v][u][0][1]-y1)/2));
				int ix=((int) Math.round((this.PATTERN_GRID[v][u][0][0]-x1)/2));
				samples[i][2][0]=((iy>=0) && (iy<halfSize) && (ix>=0) && (ix<halfSize))?halfWindow[iy*halfSize+ix]:0.0;
			}
			   double [][] simulParams= getSimulationParametersFromSamples(
					   samples,   // see quadraticApproximation()
					   forceLinear || (halfSize<5),  // use linear approximation diameter <4 should be enough, 5 - just to be safe
					   thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
					   thresholdQuad);  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
			   return simulParams;
		}
	   
	   
/**		Moved to PolyninomialApproximation class */
	   
/**
 * Approximate function z(x,y) as a second degree polynomial
 * f(x,y)=A*x^2+B*y^2+C*x*y+D*x+E*y+F
 * data array consists of lines of either 2 or 3 vectors:
 *  2-element vector x,y
 *  variable length vector z (should be the same for all samples)
 *  optional 1- element vector w (weight of the sample)
 * 
 * returns arrrray of vectors or null
 * each vector (one per each z component) is either 6-element-  (A,B,C,D,E,F) if quadratic is possible and enabled
 * or 3-element - (D,E,F) if linear is possible and quadratic is not possible or disbled
 * returns null if not enough data even for the linear approximation
 
 */
	   
/* ======================================================================== */
		/*
	   public double [][] quadraticApproximation(
			   double [][][] data,
			   boolean forceLinear,  // use linear approximation
			   double thresholdLin,  // thershold ratio of matrix determinant to norm for linear approximation (det too low - fail)
			   double thresholdQuad  // thershold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
			   ){
/* ix, iy - the location of the point with maximal value. We'll approximate the vicinity of that maximum using a
 * second degree polynominal:
   Z(x,y)~=A*x^2+B*y^2+C*x*y+D*x+E*y+F
   by minimizing sum of squared differenceS00between the actual (Z(x,uy)) and approximated values. 
   and then find the maximum on the approximated surface. Here iS00the math:
  	   		
Z(x,y)~=A*x^2+B*y^2+C*x*y+D*x+E*y+F
minimizing squared error, using W(x,y) aS00weight function

error=Sum(W(x,y)*((A*x^2+B*y^2+C*x*y+D*x+E*y+F)-Z(x,y))^2)

error=Sum(W(x,y)*(A^2*x^4 + 2*A*x^2*(B*y^2+C*x*y+D*x+E*y+F-Z(x,y)) +(...) )
0=derror/dA=Sum(W(x,y)*(2*A*x^4 + 2*x^2*(B*y^2+C*x*y+D*x+E*y+F-Z(x,y)))
0=Sum(W(x,y)*(A*x^4 + x^2*(B*y^2+C*x*y+D*x+E*y+F-Z(x,y)))

S40=Sum(W(x,y)*x^4), etc

(1) 0=A*S40 + B*S22 + C*S31 +D*S30 +E*S21 +F*S20 - SZ20

derror/dB:

error=Sum(W(x,y)*(B^2*y^4 + 2*B*y^2*(A*x^2+C*x*y+D*x+E*y+F-Z(x,y)) +(...) )
0=derror/dB=Sum(W(x,y)*(2*B*y^4 + 2*y^2*(A*x^2+C*x*y+D*x+E*y+F-Z(x,y)))
0=Sum(W(x,y)*(B*y^4 + y^2*(A*x^2+C*x*y+D*x+E*y+F-Z(x,y)))

(2) 0=B*S04 + A*S22 + C*S13 +D*S12 +E*S03 +F*SY2 - SZ02
(2) 0=A*S22 + B*S04 + C*S13 +D*S12 +E*S03 +F*SY2 - SZ02

derror/dC:

error=Sum(W(x,y)*(C^2*x^2*y^2 + 2*C*x*y*(A*x^2+B*y^2+D*x+E*y+F-Z(x,y)) +(...) )
0=derror/dC=Sum(W(x,y)*(2*C*x^2*y^2 + 2*x*y*(A*x^2+B*y^2+D*x+E*y+F-Z(x,y)) )
0=Sum(W(x,y)*(C*x^2*y^2 + x*y*(A*x^2+B*y^2+D*x+E*y+F-Z(x,y)) )

(3) 0= A*S31 +  B*S13 +  C*S22 + D*S21 + E*S12 + F*S11 - SZ11

derror/dD:

error=Sum(W(x,y)*(D^2*x^2 + 2*D*x*(A*x^2+B*y^2+C*x*y+E*y+F-Z(x,y)) +(...) )
0=derror/dD=Sum(W(x,y)*(2*D*x^2 + 2*x*(A*x^2+B*y^2+C*x*y+E*y+F-Z(x,y)) )
0=Sum(W(x,y)*(D*x^2 + x*(A*x^2+B*y^2+C*x*y+E*y+F-Z(x,y)) )

(4) 0= A*S30 +   B*S12 +  C*S21 + D*S20  + E*S11 +  F*S10  - SZ10

derror/dE:

error=Sum(W(x,y)*(E^2*y^2 + 2*E*y*(A*x^2+B*y^2+C*x*y+D*x+F-Z(x,y)) +(...) )
0=derror/dE=Sum(W(x,y)*(2*E*y^2 + 2*y*(A*x^2+B*y^2+C*x*y+D*x+F-Z(x,y)) )
0=Sum(W(x,y)*(E*y^2 + y*(A*x^2+B*y^2+C*x*y+D*x+F-Z(x,y)) )
(5) 0= A*S21 +  B*S03 +   C*S12 + D*S11 +  E*SY2  + F*SY  - SZ01

derror/dF:

error=Sum(W(x,y)*(F^2 +  2*F*(A*x^2+B*y^2+C*x*y+D*x+E*y-Z(x,y)) +(...) )
0=derror/dF=Sum(W(x,y)*(2*F +  2*(A*x^2+B*y^2+C*x*y+D*x+E*y-Z(x,y)) )
0=Sum(W(x,y)*(F +  (A*x^2+B*y^2+C*x*y+D*x+E*y-Z(x,y)) )
(6) 0= A*S20 +   B*SY2 +   C*S11 +  D*S10 +   E*SY   + F*S00  - SZ00


(1) 0= A*S40 + B*S22 + C*S31 + D*S30 + E*S21 + F*S20 - SZ20
(2) 0= A*S22 + B*S04 + C*S13 + D*S12 + E*S03 + F*S02 - SZ02
(3) 0= A*S31 + B*S13 + C*S22 + D*S21 + E*S12 + F*S11 - SZ11
(4) 0= A*S30 + B*S12 + C*S21 + D*S20 + E*S11 + F*S10 - SZ10
(5) 0= A*S21 + B*S03 + C*S12 + D*S11 + E*S02 + F*S01 - SZ01
(6) 0= A*S20 + B*S02 + C*S11 + D*S10 + E*S01 + F*S00 - SZ00
* /
		   int zDim=data[0][1].length;   

		   double w,z,x,x2,x3,x4,y,y2,y3,y4,wz;
		   int i,j,n=0;
		   double S00=0.0,
		   S10=0.0,S01=0.0,
		   S20=0.0,S11=0.0,S02=0.0,
		   S30=0.0,S21=0.0,S12=0.0,S03=0.0,
		   S40=0.0,S31=0.0,S22=0.0,S13=0.0,S04=0.0;
		   double [] SZ00=new double [zDim];
		   double [] SZ01=new double [zDim];
		   double [] SZ10=new double [zDim];
		   double [] SZ11=new double [zDim];
		   double [] SZ02=new double [zDim];
		   double [] SZ20=new double [zDim];
		   for (i=0;i<zDim;i++) {
			   SZ00[i]=0.0;
			   SZ01[i]=0.0;
			   SZ10[i]=0.0;
			   SZ11[i]=0.0;
			   SZ02[i]=0.0;
			   SZ20[i]=0.0;
		   }
		   for (i=0;i<data.length;i++)  {
			   w=(data[i].length>2)? data[i][2][0]:1.0;
			   if (w>0) {
				   n++;
				   x=data[i][0][0];
				   y=data[i][0][1];
				   x2=x*x;
				   y2=y*y;
				   S00+=w;
				   S10+=w*x;
				   S01+=w*y;
				   S11+=w*x*y;
				   S20+=w*x2;
				   S02+=w*y2;
				   if (!forceLinear) {
					   x3=x2*x;
					   x4=x3*x;
					   y3=y2*y;
					   y4=y3*y;
					   S30+=w*x3;
					   S21+=w*x2*y;
					   S12+=w*x*y2;
					   S03+=w*y3;
					   S40+=w*x4;
					   S31+=w*x3*y;
					   S22+=w*x2*y2;
					   S13+=w*x*y3;
					   S04+=w*y4;
				   }
				   for (j=0;j<zDim;j++) {
					   z=data[i][1][j];
					   wz=w*z;
					   SZ00[j]+=wz;
					   SZ10[j]+=wz*x;
					   SZ01[j]+=wz*y;
					   if (!forceLinear) {
						   SZ20[j]+=wz*x2;
						   SZ11[j]+=wz*x*y;
						   SZ02[j]+=wz*y2;
					   }
				   }
				   
			   }
		   }
		   //need to decide if there is enough data for linear and quadratic
		   double [][] mAarrayL= {
				   {S20,S11,S10},
				   {S11,S02,S01},
				   {S10,S01,S00}};
		   Matrix M=new Matrix (mAarrayL);
		   Matrix Z;
 	   	   if (debugLevel>3) System.out.println(">>> n="+n+" det_lin="+M.det()+" norm_lin="+normMatix(mAarrayL));
 	   	   double nmL=normMatix(mAarrayL);
		   if ((nmL==0.0) || (Math.abs(M.det())/nmL<thresholdLin)) return null; // not enough data even for the linear approximation
		   double []zAarrayL=new double [3];
		   double [][] ABCDEF=new double[zDim][];
//		   double [] zAarrayL={SZ10,SZ01,SZ00};
		   for (i=0;i<zDim;i++) {
			   zAarrayL[0]=SZ10[i];
			   zAarrayL[1]=SZ01[i];
			   zAarrayL[2]=SZ00[i];
		       Z=new Matrix (zAarrayL,3);
		       ABCDEF[i]= M.solve(Z).getRowPackedCopy();
		   }
		   if (forceLinear) return ABCDEF;
		   // quote try quadratic approximation            
		   double [][] mAarrayQ= {
				   {S40,S22,S31,S30,S21,S20},
				   {S22,S04,S13,S12,S03,S02},
				   {S31,S13,S22,S21,S12,S11},
				   {S30,S12,S21,S20,S11,S10},
				   {S21,S03,S12,S11,S02,S01},
				   {S20,S02,S11,S10,S01,S00}};
		   M=new Matrix (mAarrayQ);
 	   	   if (debugLevel>3) System.out.println("    n="+n+" det_quad="+M.det()+" norm_quad="+normMatix(mAarrayQ)+" data.length="+data.length);
 	   	   double nmQ=normMatix(mAarrayQ);
		   if ((nmQ==0.0) || (Math.abs(M.det())/normMatix(mAarrayQ)<thresholdQuad)) {
			   System.out.println("Using linear approximation, M.det()="+M.det()+" normMatix(mAarrayQ)="+normMatix(mAarrayQ)); //did not happen
			   return ABCDEF; // not enough data for the quadratic approximation, return linear
		   }
//		   double [] zAarrayQ={SZ20,SZ02,SZ11,SZ10,SZ01,SZ00};
		   double [] zAarrayQ=new double [6];
		   for (i=0;i<zDim;i++) {
			   zAarrayQ[0]=SZ20[i];
			   zAarrayQ[1]=SZ02[i];
			   zAarrayQ[2]=SZ11[i];
			   zAarrayQ[3]=SZ10[i];
			   zAarrayQ[4]=SZ01[i];
			   zAarrayQ[5]=SZ00[i];
			   Z=new Matrix (zAarrayQ,6);
			   ABCDEF[i]= M.solve(Z).getRowPackedCopy();
		   }
		   return ABCDEF;
	   }
//	calcualte "volume" made of the matrix row-vectors, placed orthogonally
// to be compared to determinant	   
	public double normMatix(double [][] a) {
        double d,norm=1.0;
        for (int i=0;i<a.length;i++) {
        	d=0;
        	for (int j=0;j<a[i].length;j++) d+=a[i][j]*a[i][j];
        	norm*=Math.sqrt(d);
        }
		return norm;
	}
	*/
/* ======================================================================== */
	public double[][][][] setPatternGridArray(int size) {
		return setPatternGridArray(size,size);
	}
	public double[][][][] setPatternGridArray(int width, int height) {
		int i,j;
		double[][][][] result= new double [height][width][][];
		for (i=0;i<height;i++) for (j=0;j<width;j++) result[i][j]=null;
		return result;	
	}

	
	public static class PatternDetectParameters {
		public double gaussWidth; // <=0 - use Hamming window
		public double corrGamma;
		public double corrSigma;
		public int diffSpectrCorr;
		public double shrinkClusters;
		public int multiplesToTry;
		public double deviation;
		public int deviationSteps;
		public double highpass;
		public double corrRingWidth;
		public double minCorrContrast;
		public double minGridPeriod;
		public double maxGridPeriod;
		public double debugX;
		public double debugY;
		public double debugRadius;
		

		public PatternDetectParameters(
				double gaussWidth,
				double corrGamma,
				double corrSigma,
				int diffSpectrCorr,
				double shrinkClusters,
				int multiplesToTry,
				double deviation,
				int deviationSteps,
				double highpass,
				double corrRingWidth,
				double minCorrContrast,
				double minGridPeriod,
				double maxGridPeriod,
				double debugX,
				double debugY,
				double debugRadius
				) {
			this.gaussWidth=gaussWidth;
			this.corrGamma = corrGamma;
			this.corrSigma = corrSigma;
			this.diffSpectrCorr = diffSpectrCorr;
			this.shrinkClusters = shrinkClusters;
			this.multiplesToTry = multiplesToTry;
			this.deviation = deviation;
			this.deviationSteps = deviationSteps;
			this.highpass = highpass;
			this.corrRingWidth = corrRingWidth;
			this.minCorrContrast = minCorrContrast;
			this.minGridPeriod=minGridPeriod;
			this.maxGridPeriod=maxGridPeriod;
			this.debugX=debugX;
			this.debugY=debugY;
			this.debugRadius=debugRadius;
		}

		public void setProperties(String prefix,Properties properties){
			properties.setProperty(prefix+"gaussWidth",this.gaussWidth+"");
			properties.setProperty(prefix+"corrGamma",this.corrGamma+"");
			properties.setProperty(prefix+"corrSigma",this.corrSigma+"");
			properties.setProperty(prefix+"diffSpectrCorr",this.diffSpectrCorr+"");
			properties.setProperty(prefix+"shrinkClusters",this.shrinkClusters+"");
			properties.setProperty(prefix+"multiplesToTry",this.multiplesToTry+"");
			properties.setProperty(prefix+"deviation",this.deviation+"");
			properties.setProperty(prefix+"deviationSteps",this.deviationSteps+"");
			properties.setProperty(prefix+"highpass",this.highpass+"");
			properties.setProperty(prefix+"corrRingWidth",this.corrRingWidth+"");
			properties.setProperty(prefix+"minCorrContrast",this.minCorrContrast+"");
			properties.setProperty(prefix+"minGridPeriod",this.minGridPeriod+"");
			properties.setProperty(prefix+"maxGridPeriod",this.maxGridPeriod+"");
			properties.setProperty(prefix+"debugX",this.debugX+"");
			properties.setProperty(prefix+"debugY",this.debugY+"");
			properties.setProperty(prefix+"debugRadius",this.debugRadius+"");
		}
		
		public void getProperties(String prefix,Properties properties){
			this.gaussWidth=Double.parseDouble(properties.getProperty(prefix+"gaussWidth"));
			this.corrGamma=Double.parseDouble(properties.getProperty(prefix+"corrGamma"));
			this.corrSigma=Double.parseDouble(properties.getProperty(prefix+"corrSigma"));
			this.diffSpectrCorr=Integer.parseInt(properties.getProperty(prefix+"diffSpectrCorr"));
			this.shrinkClusters=Double.parseDouble(properties.getProperty(prefix+"shrinkClusters"));
			this.multiplesToTry=Integer.parseInt(properties.getProperty(prefix+"multiplesToTry"));
			this.deviation=Double.parseDouble(properties.getProperty(prefix+"deviation"));
			this.deviationSteps=Integer.parseInt(properties.getProperty(prefix+"deviationSteps"));
			this.highpass=Double.parseDouble(properties.getProperty(prefix+"highpass"));
			this.corrRingWidth=Double.parseDouble(properties.getProperty(prefix+"corrRingWidth"));
			this.minCorrContrast=Double.parseDouble(properties.getProperty(prefix+"minCorrContrast"));
			if (properties.getProperty(prefix+"minGridPeriod")!=null)
				this.minGridPeriod=Double.parseDouble(properties.getProperty(prefix+"minGridPeriod"));
			else this.minGridPeriod=0.0;
			if (properties.getProperty(prefix+"maxGridPeriod")!=null)
				this.minGridPeriod=Double.parseDouble(properties.getProperty(prefix+"maxGridPeriod"));
			else this.maxGridPeriod=0.0;
			if (properties.getProperty(prefix+"debugX")!=null)
				this.debugX=Double.parseDouble(properties.getProperty(prefix+"debugX"));
			if (properties.getProperty(prefix+"debugY")!=null)
				this.debugY=Double.parseDouble(properties.getProperty(prefix+"debugY"));
			if (properties.getProperty(prefix+"debugRadius")!=null)
				this.debugRadius=Double.parseDouble(properties.getProperty(prefix+"debugRadius"));
		}
		
	}

	/* ======================================================================== */
	
	public static class DistortionParameters {
		public int   correlationSize;
		public int   maximalCorrelationSize;
		public double correlationGaussWidth; // 0 - no window, <0 - use Hamming
		public boolean absoluteCorrelationGaussWidth=false; // do not scale correlationGaussWidth when the FFT size is increased  
		public int zeros; // leave this number of zeros on the margins of the window (toatal from both sides). If correlationGaussWidth>0 will 
        // additionally multiply by Hamming
		public int   FFTSize;
		public double fftGaussWidth;
		public double phaseCorrelationFraction=1.0; // 1.0 - phase correlation, 0.0 - just cross-correlation 
		public double correlationHighPassSigma; 
		public double correlationLowPassSigma;
		public double correlationRingWidth; // ring (around r=0.5 dist to opposite corr) width , center circle r=0.5*correlationRingWidth
		public double correlationMaxOffset;     // maximal distance between predicted and actual pattern node
		public double correlationMinContrast;   // minimal contrast for the pattern to pass
		public double correlationMinInitialContrast;   // minimal contrast for the pattern of the center (initial point)
		public double correlationMinAbsoluteContrast;   // minimal contrast for the pattern to pass, does not compensate for low ligt
		public double correlationMinAbsoluteInitialContrast;   // minimal contrast for the pattern of the center (initial point)
		
		public double scaleFirstPassContrast; // Decrease contrast of cells that are too close to the border to be processed in refinement pass
		public double contrastSelectSigma; // Gaussian sigma to select correlation centers (fraction of UV period), 0.1
		public double contrastAverageSigma; // Gaussian sigma to average correlation variations (as contrast reference) 0.5
		
		public int    minimalPatternCluster;       //    minimal pattern cluster size (0 - disable retries)
		public double scaleMinimalInitialContrast; // increase/decrease minimal contrast if initial cluster is >0 but less than minimalPatternCluster
		
		public double searchOverlap;         // when searching for grid, step this amount of the FFTSize
		public int    patternSubdiv;
		public double correlationDx; // not saved
		public double correlationDy; // not saved
		public int gridSize;
		public int loop_debug_level;
		public boolean refineCorrelations;
		public boolean fastCorrelationOnFirstPass;
		public boolean fastCorrelationOnFinalPass;
		public double bPatternSigma; // blur bPattern with this sigma
		public double barraySigma; // blur barray with this sigma, multiplied by subdiv
		public double correlationWeightSigma; // sigma (in pixels) for maximum approximation - UNUSED (other maximum methods)
		public double correlationRadiusScale; // maximal radius to consider, in sigmas (if 0 - use sigma as radius) - UNUSED
		public int    correlationRadius;    // radius (green pixel) of the correlation maximum to use for x/y measurement
		public double correlationThreshold; // fraction of the value of the maximum fro the point to be included in centroid calculation
		public int    correlationSubdiv;    // Total subdivision of the correlation maximum (linear and FFT)
		public int    correlationFFTSubdiv; // Increase density of the correlation using FFT 
        public boolean correlationAverageOnRefine; // average position between neighbor samples
        public boolean refineInPlace;       // Update coordinates of the grid points as they are recalculated (false - then update all at once)
        public double averageOrthoDist;     // distance to up/down/right left neighbors (0.5)
        public double averageOrthoWeight;   // weight of 4 ortho neighbors (combined) - 0.4), weight of center -s 1.0-averageOrthoWeight-averageDiagWeight
        public double averageDiagDist;     // distance to diagonal neighbors (projection on x/y) (0.5)
        public double averageDiagWeight;   // weight of 4 diagonal neighbors (combined) - 0.4)
        public boolean useQuadratic;       // use quadratic extrapolation to predict position/wave vectors of a new pixel (false - use linear)
        public boolean removeLast;         // remove outer (unreliable) row of nodes
        public int    numberExtrapolated;  // add this number of extrapolated nodes
        public double extrapolationSigma;  // use instead of the correlationWeightSigma during final extrapolation
        public double minUVSpan;           // Minimal u/v span in correlation window that triggers increase of the correlation FFT size
        public boolean flatFieldCorrection=true; // compensate grid uneven intensity (vignetting, illumination)
        public double flatFieldExtarpolate=1.0;  // extrapolate flat field intensity map (relative to the average grid period)
        public double flatFieldBlur=1.0;   // blur the intensity map (relative to the average grid period)
        public double flatFieldMin=0.1;    // do not try to compensate if intensity less than this part of maximal
        public double flatFieldShrink=1.0;     // Shrink before extrapolating intensity map (relative to the average grid period) 
        public double flatFieldExpand=3.0;     // Expand during extrapolation (relative to the average grid period)
        public double flatFieldSigmaRadius=1.0;// Extrapolation weight effective radius (relative to the average grid period)
        public double flatFieldExtraRadius=1.5;// Consider pixels in a square with the side twice this (relative to flatFieldSigmaRadius)
	    public double averagingAreaScale=  2.0;  // multiply the average grid period to determine the area for averaging the grig brightness


//        match pointers errors
        public int errTooFewCells=    -10; 
        public int errPatternNotFound=-11; 
        public boolean legacyMode=false;   // legacy mode
        
		public DistortionParameters(
				int correlationSize,
				int maximalCorrelationSize,
				double correlationGaussWidth,
				boolean absoluteCorrelationGaussWidth,
				int zeros,  
				int FFTSize,
				double fftGaussWidth,
				double phaseCorrelationFraction,
				double correlationHighPassSigma,
				double correlationLowPassSigma,
				double correlationRingWidth,
				double correlationMaxOffset,     // maximal distance between predicted and actual pattern node
				double correlationMinContrast,   // minimal contrast for the pattern to pass
				double correlationMinInitialContrast,   // minimal contrast for the pattern of the center (initial point)
				double correlationMinAbsoluteContrast,   // minimal contrast for the pattern to pass, does not compensate for low ligt
				double correlationMinAbsoluteInitialContrast,   // minimal contrast for the pattern of the center (initial point)

				double scaleFirstPassContrast, // Decrease contrast of cells that are too close to the border to be processed in refinement pass
				double contrastSelectSigma, // Gaussian sigma to select correlation centers (fraction of UV period), 0.1
				double contrastAverageSigma, // Gaussian sigma to average correlation variations (as contrast reference) 0.5
				int    minimalPatternCluster,       //    minimal pattern cluster size (0 - disable retries)
				double scaleMinimalInitialContrast, // increase/decrease minimal contrast if initial cluster is >0 but less than minimalPatternCluster
				double searchOverlap,         // when searching for grid, step this amount of the FFTSize
				int patternSubdiv,
				double correlationDx,
				double correlationDy,
				int gridSize,
				int loop_debug_level,
				boolean refineCorrelations,
				boolean fastCorrelationOnFirstPass, // use fast (less precise) correlation on first pass
				boolean fastCorrelationOnFinalPass, // use fast (less precise) correlation on refine pass
				double bPatternSigma,          // blur bPattern with this sigma
				double barraySigma,            // blur barray with this sigma, multiplied by subdiv
				double correlationWeightSigma, // sigma (in pixels) for maximum approximation
				double correlationRadiusScale, // maximal radius to consider, in sigmas (if 0 - use sigma as radius)
				int    correlationRadius,      // radius (green pixel) of the correlation maximum to use for x/y measurement
				double correlationThreshold,   // fraction of the value of the maximum fro the point to be included in centroid calculation
				int    correlationSubdiv,      // Total subdivision of the correlation maximum (linear and FFT)
				int    correlationFFTSubdiv,
		        boolean correlationAverageOnRefine, // average position between neighbor samples
		        boolean refineInPlace,         // Update coordinates of the grid points as they are recalculated (false - then update all at once)
		        double averageOrthoDist,       // distance to up/down/right left neighbors (0.5)
		        double averageOrthoWeight,     // weight of 4 ortho neighbors (combined) - 0.4), weight of center -s 1.0-averageOrthoWeight-averageDiagWeight
		        double averageDiagDist,        // distance to diagonal neighbors (projection on x/y) (0.5)
		        double averageDiagWeight,      // weight of 4 diagonal neighbors (combined) - 0.4)
		        boolean useQuadratic,          // use quadratic extrapolation to predict position/wave vectors of a new pixel (false - use linear)
		        boolean removeLast,            // remove outer (unreliable) row of nodes
		        int    numberExtrapolated,     // add this number of extrapolated nodes
		        double extrapolationSigma,     // use instead of the correlationWeightSigma during final extrapolation
		        double minUVSpan,              // Minimal u/v span in correlation window that triggers increase of the correlation FFT size
		        boolean flatFieldCorrection,   // compensate grid uneven intensity (vignetting, illumination)
		        double flatFieldExtarpolate,   // extrapolate flat field intensity map (relative to the average grid period)
		        double flatFieldBlur,          // blur the intensity map (relative to the average grid period)
		        double flatFieldMin,           // do not try to compensate if intensity less than this part of maximal
		        double flatFieldShrink,        // Shrink before extrapolating intensity map (relative to the average grid period) 
		        double flatFieldExpand,        // Expand during extrapolation (relative to the average grid period)
		        double flatFieldSigmaRadius,   // Extrapolation weight effective radius (relative to the average grid period)
		        double flatFieldExtraRadius,   // Consider pixels in a square with the side twice this (relative to flatFieldSigmaRadius)
			    double averagingAreaScale,     // multiply the average grid period to determine the area for averaging the grig brightness
		        boolean legacyMode
				){

			this.correlationSize = correlationSize;
			this.maximalCorrelationSize=maximalCorrelationSize;
			this.correlationGaussWidth = correlationGaussWidth;
			this.absoluteCorrelationGaussWidth=absoluteCorrelationGaussWidth;
			this.zeros=zeros;
			this.FFTSize = FFTSize;
			this.fftGaussWidth = fftGaussWidth;
			this.phaseCorrelationFraction=phaseCorrelationFraction;
			this.correlationHighPassSigma=correlationHighPassSigma;
			this.correlationLowPassSigma=correlationLowPassSigma;
			this.correlationRingWidth=correlationRingWidth;
			this.correlationMaxOffset=correlationMaxOffset;
			this.correlationMinContrast=correlationMinContrast;
			this.correlationMinInitialContrast=correlationMinInitialContrast;
			this.correlationMinAbsoluteContrast=correlationMinAbsoluteContrast;   // minimal contrast for the pattern to pass, does not compensate for low ligt
			this.correlationMinAbsoluteInitialContrast=correlationMinAbsoluteInitialContrast;   // minimal contrast for the pattern of the center (initial point)
			this.scaleFirstPassContrast=scaleFirstPassContrast; // Decrease contrast of cells that are too close to the border to be processed in refinement pass
			this.contrastSelectSigma=contrastSelectSigma; // Gaussian sigma to select correlation centers (fraction of UV period), 0.1
			this.contrastAverageSigma=contrastAverageSigma; // Gaussian sigma to average correlation variations (as contrast reference) 0.5
			this.minimalPatternCluster=minimalPatternCluster;        //    minimal pattern cluster size (0 - disable retries)
			this.scaleMinimalInitialContrast=scaleMinimalInitialContrast; // increase/decrease minimal contrast if initial cluster is >0 but less than minimalPatternCluster
			this.searchOverlap=searchOverlap;         // when searching for grid, step this amount of the FFTSize
			this.patternSubdiv=patternSubdiv;
			this.correlationDx=correlationDx;
			this.correlationDy=correlationDy;
			this.gridSize=gridSize;
			this.loop_debug_level=loop_debug_level;
			this.refineCorrelations=refineCorrelations;
			this.fastCorrelationOnFirstPass=fastCorrelationOnFirstPass;
			this.fastCorrelationOnFinalPass=fastCorrelationOnFinalPass;
			this.bPatternSigma=bPatternSigma; // overwrites SimulationParameters.bPatternSigma
			this.barraySigma=barraySigma;
			this.correlationWeightSigma=correlationWeightSigma;
			this.correlationRadiusScale=correlationRadiusScale;
			this.correlationRadius=correlationRadius;
			this.correlationThreshold=correlationThreshold;
			this.correlationSubdiv=correlationSubdiv;
			this.correlationFFTSubdiv=correlationFFTSubdiv;
			this.correlationAverageOnRefine=correlationAverageOnRefine;
			this.refineInPlace=refineInPlace;
			this.averageOrthoDist=averageOrthoDist;
			this.averageOrthoWeight=averageOrthoWeight;
			this.averageDiagDist=averageDiagDist;
			this.averageDiagWeight=averageDiagWeight;
			this.useQuadratic=useQuadratic;
			this.removeLast=removeLast;
			this.numberExtrapolated=numberExtrapolated;
			this.extrapolationSigma=extrapolationSigma;
			this.minUVSpan=minUVSpan;
			this.flatFieldCorrection=flatFieldCorrection;   // compensate grid uneven intensity (vignetting, illumination)
			this.flatFieldExtarpolate=flatFieldExtarpolate;   // extrapolate flat field intensity map (relative to the average grid period)
			this.flatFieldBlur=flatFieldBlur;          // blur the intensity map (relative to the average grid period)
			this.flatFieldMin=flatFieldMin;
			this.flatFieldShrink=flatFieldShrink; 
			this.flatFieldExpand=flatFieldExpand;
			this.flatFieldSigmaRadius=flatFieldSigmaRadius;
			this.flatFieldExtraRadius=flatFieldExtraRadius;
		    this.averagingAreaScale=averagingAreaScale;
			this.legacyMode=legacyMode;

		}
		public DistortionParameters clone() {
			return new DistortionParameters(
			this.correlationSize,
			this.maximalCorrelationSize,
			this.correlationGaussWidth,
			this.absoluteCorrelationGaussWidth,
			this.zeros,
			this.FFTSize,
			this.fftGaussWidth,
			this.phaseCorrelationFraction,
			this.correlationHighPassSigma,
			this.correlationLowPassSigma,
			this.correlationRingWidth,
			this.correlationMaxOffset,     // maximal distance between predicted and actual pattern node
			this.correlationMinContrast,   // minimal contrast for the pattern to pass
			this.correlationMinInitialContrast,
			this.correlationMinAbsoluteContrast,   // minimal contrast for the pattern to pass, does not compensate for low ligt
			this.correlationMinAbsoluteInitialContrast,   // minimal contrast for the pattern of the center (initial point)
			this.scaleFirstPassContrast, // Decrease contrast of cells that are too close to the border to be processed in refinement pass
			this.contrastSelectSigma, // Gaussian sigma to select correlation centers (fraction of UV period), 0.1
			this.contrastAverageSigma, // Gaussian sigma to average correlation variations (as contrast reference) 0.5
			this.minimalPatternCluster,        //    minimal pattern cluster size (0 - disable retries)
			this.scaleMinimalInitialContrast,  // increase/decrease minimal contrast if initial cluster is >0 but less than minimalPatternCluster
			this.searchOverlap,         // when searching for grid, step this amount of the FFTSize
			this.patternSubdiv,
			this.correlationDx,
			this.correlationDy,
			this.gridSize,
			this.loop_debug_level,
			this.refineCorrelations,
			this.fastCorrelationOnFirstPass, // use fast (less precise) correlation on first pass
			this.fastCorrelationOnFinalPass, // use fast (less precise) correlation on refine pass
			this.bPatternSigma, // blur bPattern with this sigma
			this.barraySigma,
			this.correlationWeightSigma, // sigma (in pixels) for maximum approximation
			this.correlationRadiusScale, // maximal radius to consider, in sigmas (if 0 - use sigma as radius)
			this.correlationRadius,    // radius (green pixel) of the correlation maximum to use for x/y measurement
			this.correlationThreshold,
			this.correlationSubdiv,    // Total subdivision of the correlation maximum (linear and FFT)
			this.correlationFFTSubdiv,
			this.correlationAverageOnRefine, // average position between neighbor samples
			this.refineInPlace,       // Update coordinates of the grid points as they are recalculated (false - then update all at once)
			this.averageOrthoDist,    // distance to up/down/right left neighbors (0.5)
			this.averageOrthoWeight,  // weight of 4 ortho neighbors (combined) - 0.4), weight of center -s 1.0-averageOrthoWeight-averageDiagWeight
			this.averageDiagDist,     // distance to diagonal neighbors (projection on x/y) (0.5)
			this.averageDiagWeight,   // weight of 4 diagonal neighbors (combined) - 0.4)
			this.useQuadratic,        // use quadratic extrapolation to predict position/wave vectors of a new pixel (false - use linear)
			this.removeLast,          // remove outer (unreliable) row of nodes
			this.numberExtrapolated,  // add this number of extrapolated nodes
			this.extrapolationSigma,   // use instead of the correlationWeightSigma during final extrapolation
	        this.minUVSpan,            // Minimal u/v span in correlation window that triggers increase of the correlation FFT size
			this.flatFieldCorrection,  // compensate grid uneven intensity (vignetting, illumination)
			this.flatFieldExtarpolate, // extrapolate flat field intensity map (relative to the average grid period)
			this.flatFieldBlur,        // blur the intensity map (relative to the average grid period)
			this.flatFieldMin,
			this.flatFieldShrink, 
			this.flatFieldExpand,
			this.flatFieldSigmaRadius,
			this.flatFieldExtraRadius,
			this.averagingAreaScale,
	        this.legacyMode
			);
		}

		public void setProperties(String prefix,Properties properties){
			properties.setProperty(prefix+"correlationSize",this.correlationSize+"");
			properties.setProperty(prefix+"maximalCorrelationSize",this.maximalCorrelationSize+"");
			properties.setProperty(prefix+"correlationGaussWidth",this.correlationGaussWidth+"");
			properties.setProperty(prefix+"absoluteCorrelationGaussWidth",this.absoluteCorrelationGaussWidth+"");
			properties.setProperty(prefix+"zeros",this.zeros+"");
			properties.setProperty(prefix+"FFTSize",this.FFTSize+"");
			properties.setProperty(prefix+"fftGaussWidth",this.fftGaussWidth+"");
			properties.setProperty(prefix+"phaseCorrelationFraction",this.phaseCorrelationFraction+"");
			properties.setProperty(prefix+"correlationHighPassSigma",this.correlationHighPassSigma+"");
			properties.setProperty(prefix+"correlationLowPassSigma",this.correlationLowPassSigma+"");
			properties.setProperty(prefix+"correlationRingWidth",this.correlationRingWidth+"");
			properties.setProperty(prefix+"correlationMaxOffset",this.correlationMaxOffset+"");
			properties.setProperty(prefix+"correlationMinContrast",this.correlationMinContrast+"");
			properties.setProperty(prefix+"correlationMinInitialContrast",this.correlationMinInitialContrast+"");
			properties.setProperty(prefix+"correlationMinAbsoluteContrast",this.correlationMinAbsoluteContrast+"");
			properties.setProperty(prefix+"correlationMinAbsoluteInitialContrast",this.correlationMinAbsoluteInitialContrast+"");
			properties.setProperty(prefix+"scaleFirstPassContrast",this.scaleFirstPassContrast+"");
			properties.setProperty(prefix+"contrastSelectSigma",this.contrastSelectSigma+"");
			properties.setProperty(prefix+"contrastAverageSigma",this.contrastAverageSigma+"");
			properties.setProperty(prefix+"minimalPatternCluster",this.minimalPatternCluster+"");
			properties.setProperty(prefix+"scaleMinimalInitialContrast",this.scaleMinimalInitialContrast+"");
			properties.setProperty(prefix+"searchOverlap",this.searchOverlap+"");
			properties.setProperty(prefix+"patternSubdiv",this.patternSubdiv+"");
			properties.setProperty(prefix+"correlationDx",this.correlationDx+"");
			properties.setProperty(prefix+"correlationDy",this.correlationDy+"");
			properties.setProperty(prefix+"gridSize",this.gridSize+"");
			properties.setProperty(prefix+"loop_debug_level",this.loop_debug_level+"");
			properties.setProperty(prefix+"refineCorrelations",this.refineCorrelations+"");
			properties.setProperty(prefix+"fastCorrelationOnFirstPass",this.fastCorrelationOnFirstPass+"");
			properties.setProperty(prefix+"fastCorrelationOnFinalPass",this.fastCorrelationOnFinalPass+"");
			properties.setProperty(prefix+"bPatternSigma",this.bPatternSigma+"");
			properties.setProperty(prefix+"barraySigma",this.barraySigma+"");
			properties.setProperty(prefix+"correlationWeightSigma",this.correlationWeightSigma+"");
			properties.setProperty(prefix+"correlationRadiusScale",this.correlationRadiusScale+"");
			properties.setProperty(prefix+"correlationRadius",this.correlationRadius+"");
			properties.setProperty(prefix+"correlationThreshold",this.correlationThreshold+"");
			properties.setProperty(prefix+"correlationSubdiv",this.correlationSubdiv+"");
			properties.setProperty(prefix+"correlationFFTSubdiv",this.correlationFFTSubdiv+"");
			properties.setProperty(prefix+"correlationAverageOnRefine",this.correlationAverageOnRefine+"");
			properties.setProperty(prefix+"refineInPlace",this.refineInPlace+"");
			properties.setProperty(prefix+"averageOrthoDist",this.averageOrthoDist+"");
			properties.setProperty(prefix+"averageOrthoWeight",this.averageOrthoWeight+"");
			properties.setProperty(prefix+"averageDiagDist",this.averageDiagDist+"");
			properties.setProperty(prefix+"averageDiagWeight",this.averageDiagWeight+"");
			properties.setProperty(prefix+"useQuadratic",this.useQuadratic+"");
			properties.setProperty(prefix+"removeLast",this.removeLast+"");
			properties.setProperty(prefix+"numberExtrapolated",this.numberExtrapolated+"");
			properties.setProperty(prefix+"extrapolationSigma",this.extrapolationSigma+"");
			properties.setProperty(prefix+"minUVSpan",this.minUVSpan+"");
			
			properties.setProperty(prefix+"flatFieldCorrection",this.flatFieldCorrection+"");
			properties.setProperty(prefix+"flatFieldExtarpolate",this.flatFieldExtarpolate+"");
			properties.setProperty(prefix+"flatFieldBlur",this.flatFieldBlur+"");
			properties.setProperty(prefix+"flatFieldMin",this.flatFieldMin+"");

			properties.setProperty(prefix+"flatFieldShrink",this.flatFieldShrink+"");
			properties.setProperty(prefix+"flatFieldExpand",this.flatFieldExpand+"");
			properties.setProperty(prefix+"flatFieldSigmaRadius",this.flatFieldSigmaRadius+"");
			properties.setProperty(prefix+"flatFieldExtraRadius",this.flatFieldExtraRadius+"");
			properties.setProperty(prefix+"averagingAreaScale",this.averagingAreaScale+"");
			properties.setProperty(prefix+"legacyMode",this.minUVSpan+"");
		}
		public void getProperties(String prefix,Properties properties){
			if (properties.getProperty(prefix+"correlationSize")!=null)
			    this.correlationSize=Integer.parseInt(properties.getProperty(prefix+"correlationSize"));
			if (properties.getProperty(prefix+"maximalCorrelationSize")!=null)
			    this.maximalCorrelationSize=Integer.parseInt(properties.getProperty(prefix+"maximalCorrelationSize"));
			if (properties.getProperty(prefix+"correlationGaussWidth")!=null)
			    this.correlationGaussWidth=Double.parseDouble(properties.getProperty(prefix+"correlationGaussWidth"));
			if (properties.getProperty(prefix+"FFTSize")!=null)
			    this.FFTSize=Integer.parseInt(properties.getProperty(prefix+"FFTSize"));
			if (properties.getProperty(prefix+"absoluteCorrelationGaussWidth")!=null)
			    this.absoluteCorrelationGaussWidth=Boolean.parseBoolean(properties.getProperty(prefix+"absoluteCorrelationGaussWidth"));
			if (properties.getProperty(prefix+"zeros")!=null)
			    this.zeros=Integer.parseInt(properties.getProperty(prefix+"zeros"));
			if (properties.getProperty(prefix+"fftGaussWidth")!=null)
			    this.fftGaussWidth=Double.parseDouble(properties.getProperty(prefix+"fftGaussWidth"));
			if (properties.getProperty(prefix+"phaseCorrelationFraction")!=null)
			    this.phaseCorrelationFraction=Double.parseDouble(properties.getProperty(prefix+"phaseCorrelationFraction"));
			if (properties.getProperty(prefix+"correlationHighPassSigma")!=null)
			    this.correlationHighPassSigma=Double.parseDouble(properties.getProperty(prefix+"correlationHighPassSigma"));
			if (properties.getProperty(prefix+"correlationLowPassSigma")!=null)
			    this.correlationLowPassSigma=Double.parseDouble(properties.getProperty(prefix+"correlationLowPassSigma"));
			if (properties.getProperty(prefix+"correlationRingWidth")!=null)
			    this.correlationRingWidth=Double.parseDouble(properties.getProperty(prefix+"correlationRingWidth"));
			if (properties.getProperty(prefix+"correlationMaxOffset")!=null)
			    this.correlationMaxOffset=Double.parseDouble(properties.getProperty(prefix+"correlationMaxOffset"));
			if (properties.getProperty(prefix+"correlationMinContrast")!=null)
			    this.correlationMinContrast=Double.parseDouble(properties.getProperty(prefix+"correlationMinContrast"));
			if (properties.getProperty(prefix+"correlationMinInitialContrast")!=null)
			    this.correlationMinInitialContrast=Double.parseDouble(properties.getProperty(prefix+"correlationMinInitialContrast"));
			
			if (properties.getProperty(prefix+"correlationMinAbsoluteContrast")!=null)
			    this.correlationMinAbsoluteContrast=Double.parseDouble(properties.getProperty(prefix+"correlationMinAbsoluteContrast"));
			if (properties.getProperty(prefix+"correlationMinAbsoluteInitialContrast")!=null)
			    this.correlationMinAbsoluteInitialContrast=Double.parseDouble(properties.getProperty(prefix+"correlationMinAbsoluteInitialContrast"));
			
			if (properties.getProperty(prefix+"scaleFirstPassContrast")!=null)
			    this.scaleFirstPassContrast=Double.parseDouble(properties.getProperty(prefix+"scaleFirstPassContrast"));
			if (properties.getProperty(prefix+"contrastSelectSigma")!=null)
			    this.contrastSelectSigma=Double.parseDouble(properties.getProperty(prefix+"contrastSelectSigma"));
			if (properties.getProperty(prefix+"contrastAverageSigma")!=null)
			    this.contrastAverageSigma=Double.parseDouble(properties.getProperty(prefix+"contrastAverageSigma"));
			
			if (properties.getProperty(prefix+"minimalPatternCluster")!=null)
			    this.minimalPatternCluster=Integer.parseInt(properties.getProperty(prefix+"minimalPatternCluster"));
			if (properties.getProperty(prefix+"scaleMinimalInitialContrast")!=null)
			    this.scaleMinimalInitialContrast=Double.parseDouble(properties.getProperty(prefix+"scaleMinimalInitialContrast"));
			if (properties.getProperty(prefix+"searchOverlap")!=null)
			    this.searchOverlap=Double.parseDouble(properties.getProperty(prefix+"searchOverlap"));
			if (properties.getProperty(prefix+"patternSubdiv")!=null)
			    this.patternSubdiv=Integer.parseInt(properties.getProperty(prefix+"patternSubdiv"));
			if (properties.getProperty(prefix+"correlationDx")!=null)
			    this.correlationDx=Double.parseDouble(properties.getProperty(prefix+"correlationDx"));
			if (properties.getProperty(prefix+"correlationDy")!=null)
			    this.correlationDy=Double.parseDouble(properties.getProperty(prefix+"correlationDy"));
			if (properties.getProperty(prefix+"gridSize")!=null)
			    this.gridSize=Integer.parseInt(properties.getProperty(prefix+"gridSize"));
			if (properties.getProperty(prefix+"loop_debug_level")!=null)
			    this.loop_debug_level=Integer.parseInt(properties.getProperty(prefix+"loop_debug_level"));
			if (properties.getProperty(prefix+"refineCorrelations")!=null)
			    this.refineCorrelations=Boolean.parseBoolean(properties.getProperty(prefix+"refineCorrelations"));
			if (properties.getProperty(prefix+"fastCorrelationOnFirstPass")!=null)
			    this.fastCorrelationOnFirstPass=Boolean.parseBoolean(properties.getProperty(prefix+"fastCorrelationOnFirstPass"));
			if (properties.getProperty(prefix+"fastCorrelationOnFinalPass")!=null)
			    this.fastCorrelationOnFinalPass=Boolean.parseBoolean(properties.getProperty(prefix+"fastCorrelationOnFinalPass"));
			if (properties.getProperty(prefix+"bPatternSigma")!=null)
			    this.bPatternSigma=Double.parseDouble(properties.getProperty(prefix+"bPatternSigma"));
			if (properties.getProperty(prefix+"barraySigma")!=null)
			    this.barraySigma=Double.parseDouble(properties.getProperty(prefix+"barraySigma"));
			if (properties.getProperty(prefix+"correlationWeightSigma")!=null)
			    this.correlationWeightSigma=Double.parseDouble(properties.getProperty(prefix+"correlationWeightSigma"));
			if (properties.getProperty(prefix+"correlationRadiusScale")!=null)
			    this.correlationRadiusScale=Double.parseDouble(properties.getProperty(prefix+"correlationRadiusScale"));
			if (properties.getProperty(prefix+"correlationRadius")!=null)
			    this.correlationRadius=Integer.parseInt(properties.getProperty(prefix+"correlationRadius"));
			if (properties.getProperty(prefix+"correlationThreshold")!=null)
			    this.correlationThreshold=Double.parseDouble(properties.getProperty(prefix+"correlationThreshold"));
			if (properties.getProperty(prefix+"correlationSubdiv")!=null)
			    this.correlationSubdiv=Integer.parseInt(properties.getProperty(prefix+"correlationSubdiv"));
			if (properties.getProperty(prefix+"correlationFFTSubdiv")!=null)
			    this.correlationFFTSubdiv=Integer.parseInt(properties.getProperty(prefix+"correlationFFTSubdiv"));
			if (properties.getProperty(prefix+"correlationAverageOnRefine")!=null)
			    this.correlationAverageOnRefine=Boolean.parseBoolean(properties.getProperty(prefix+"correlationAverageOnRefine"));
			if (properties.getProperty(prefix+"refineInPlace")!=null)
			    this.refineInPlace=Boolean.parseBoolean(properties.getProperty(prefix+"refineInPlace"));
			if (properties.getProperty(prefix+"averageOrthoDist")!=null)
			    this.averageOrthoDist=Double.parseDouble(properties.getProperty(prefix+"averageOrthoDist"));
			if (properties.getProperty(prefix+"averageOrthoWeight")!=null)
			    this.averageOrthoWeight=Double.parseDouble(properties.getProperty(prefix+"averageOrthoWeight"));
			if (properties.getProperty(prefix+"averageDiagDist")!=null)
			    this.averageDiagDist=Double.parseDouble(properties.getProperty(prefix+"averageDiagDist"));
			if (properties.getProperty(prefix+"correlationRadiusScale")!=null)
			    this.averageDiagWeight=Double.parseDouble(properties.getProperty(prefix+"averageDiagWeight"));
			if (properties.getProperty(prefix+"useQuadratic")!=null)
			    this.useQuadratic=Boolean.parseBoolean(properties.getProperty(prefix+"useQuadratic"));
			if (properties.getProperty(prefix+"removeLast")!=null)
			    this.removeLast=Boolean.parseBoolean(properties.getProperty(prefix+"removeLast"));
			if (properties.getProperty(prefix+"numberExtrapolated")!=null)
				this.numberExtrapolated=Integer.parseInt(properties.getProperty(prefix+"numberExtrapolated"));
			if (properties.getProperty(prefix+"extrapolationSigma")!=null)
			    this.extrapolationSigma=Double.parseDouble(properties.getProperty(prefix+"extrapolationSigma"));
			if (properties.getProperty(prefix+"minUVSpan")!=null)
			    this.minUVSpan=Double.parseDouble(properties.getProperty(prefix+"minUVSpan"));
			if (properties.getProperty(prefix+"flatFieldCorrection")!=null)
			    this.flatFieldCorrection=Boolean.parseBoolean(properties.getProperty(prefix+"flatFieldCorrection"));
			if (properties.getProperty(prefix+"flatFieldExtarpolate")!=null)
			    this.flatFieldExtarpolate=Double.parseDouble(properties.getProperty(prefix+"flatFieldExtarpolate"));
			if (properties.getProperty(prefix+"flatFieldBlur")!=null)
			    this.flatFieldBlur=Double.parseDouble(properties.getProperty(prefix+"flatFieldBlur"));
			if (properties.getProperty(prefix+"flatFieldMin")!=null)
			    this.flatFieldMin=Double.parseDouble(properties.getProperty(prefix+"flatFieldMin"));
			if (properties.getProperty(prefix+"flatFieldShrink")!=null)
			    this.flatFieldShrink=Double.parseDouble(properties.getProperty(prefix+"flatFieldShrink"));
			if (properties.getProperty(prefix+"flatFieldExpand")!=null)
			    this.flatFieldExpand=Double.parseDouble(properties.getProperty(prefix+"flatFieldExpand"));
			if (properties.getProperty(prefix+"flatFieldSigmaRadius")!=null)
			    this.flatFieldSigmaRadius=Double.parseDouble(properties.getProperty(prefix+"flatFieldSigmaRadius"));
			if (properties.getProperty(prefix+"flatFieldExtraRadius")!=null)
			    this.flatFieldExtraRadius=Double.parseDouble(properties.getProperty(prefix+"flatFieldExtraRadius"));
			if (properties.getProperty(prefix+"averagingAreaScale")!=null)
			    this.averagingAreaScale=Double.parseDouble(properties.getProperty(prefix+"averagingAreaScale"));
			if (properties.getProperty(prefix+"legacyMode")!=null)
			    this.legacyMode=Boolean.parseBoolean(properties.getProperty(prefix+"legacyMode"));
			}
	}
///===================================
	/* Use ROI */
	/* Supply rectangle */
	// Now accepts rectangles not completely contained in the image, pixels will be copied from the image edge
//	private double[][] splitBayer (ImagePlus imp, Rectangle r, boolean equalize_greens) {
	public double[][] splitBayer (ImagePlus imp, Rectangle r, boolean equalize_greens) {
		return splitBayer (imp, 1, r, equalize_greens);
	}
//	private double[][] splitBayer (ImagePlus imp,  int sliceNumber, Rectangle r, boolean equalize_greens) {
	public double[][] splitBayer (ImagePlus imp,  int sliceNumber, Rectangle r, boolean equalize_greens) {
		if (imp==null) return null;
		ImageProcessor ip=null;
		float [] pixels;
		if (imp.getStackSize()>1){
		  ip=imp.getStack().getProcessor(sliceNumber);
		} else {
		  ip=imp.getProcessor();
		}  
		pixels=(float[])ip.getPixels();   // null pointer 
		int full_width= imp.getWidth();  // full image width
		int full_height=imp.getHeight(); // full image height
		if (r==null) r=new Rectangle(0,0,full_width,full_height);
		if (debugLevel>10) IJ.showMessage("splitBayer","r.width="+r.width+
				"\nr.height="+r.height+
				"\nr.x="+r.x+
				"\nr.y="+r.y+
				"\nlength="+pixels.length);
		if ((debugLevel>2) && ((r.x<0) || (r.y<0) || ((r.x+r.width)>=full_width) || ((r.y+r.height)>=full_height))) System.out.println("r.width="+r.width+
				" r.height="+r.height+
				" r.x="+r.x+
				" r.y="+r.y);
		int x,y,base,base_b,bv,i,j;
		int half_height=r.height>>1;
        	int half_width=r.width>>1;
		   // make them all 0 if not a single pixel falls into the image        
		   int numColors=(half_height==half_width)?5:4;
		   int pixX,pixY;
		   double [][] bayer_pixels=new double[numColors][half_height * half_width];
		   if ((r.x>=full_width) || (r.y>=full_height) || ((r.x+r.width)<0)  || ((r.y+r.height)<0)) {
			   for (i=0;i<bayer_pixels.length;i++) for (j=0;j<bayer_pixels[i].length;j++) bayer_pixels[i][j]=0.0;
			   return bayer_pixels;
		   }
		   //      base=r.width*((y<<1)+bv);
		   for (y=0; y<half_height; y++) for (bv=0;bv<2;bv++){
			   pixY=(y*2)+bv+r.y;
			   base_b=half_width*y;
			   //					if ((pixY>=0)

			   if (pixY<0) {
				   pixY=bv;
			   } else if (pixY>=full_height){
				   pixY=full_height-2+bv;
			   }
			   base=full_width*pixY+((r.x>0)?r.x:0);
			   //						base=full_width*((y*2)+bv+r.y)+r.x;
			   pixX=r.x;
			   if (bv==0) for (x=0; x<half_width; x++) {
				   if ((pixX<0) || (pixX>=(full_width-2))) {
					   bayer_pixels[0][base_b]= pixels[base];
					   bayer_pixels[1][base_b]= pixels[base+1];
				   } else {
					   bayer_pixels[0][base_b]= pixels[base++];
					   bayer_pixels[1][base_b]= pixels[base++];
				   }
				   base_b++;
				   pixX+=2;
			   } else  for (x=0; x<half_width; x++) {
				   if ((pixX<0) || (pixX>=(full_width-2))) {
					   bayer_pixels[2][base_b]= pixels[base];
					   bayer_pixels[3][base_b]= pixels[base+1];
				   } else {
					   bayer_pixels[2][base_b]= pixels[base++];
					   bayer_pixels[3][base_b]= pixels[base++];
				   }
				   base_b++;
				   pixX+=2;
			   }
		   }
		   if (equalize_greens) {
			   double g0=0.0,g3=0.0,g02=0.0,g32=0.0,a0,a3,b0,b3;
			   int n=bayer_pixels[0].length;
			   for (i=0;i<bayer_pixels[0].length;i++) {
				   g0 +=bayer_pixels[0][i];
				   g02+=bayer_pixels[0][i]*bayer_pixels[0][i];
				   g3 +=bayer_pixels[3][i];
				   g32+=bayer_pixels[3][i]*bayer_pixels[3][i];
			   }
			   g0/=n; // mean value
			   g3/=n; // meran value
			   g02=g02/n-g0*g0;
			   g32=g32/n-g3*g3;
			   b0=Math.sqrt(Math.sqrt(g32/g02));
			   b3 = 1.0/b0;
			   a0= (g0+g3)/2 -b0*g0;
			   a3= (g0+g3)/2 -b3*g3;
			   if (debugLevel>2) {
				   System.out.println("g0= "+g0+ ", g3= "+g3);
				   System.out.println("g02="+g02+", g32="+g32);
				   System.out.println("a0="+a0+", b0="+b0);
				   System.out.println("a3="+a3+", b3="+b3);
			   }
			   for (i=0;i<bayer_pixels[0].length;i++) {
				   bayer_pixels[0][i]=a0+bayer_pixels[0][i]*b0;
				   bayer_pixels[3][i]=a3+bayer_pixels[3][i]*b3;
			   }

		   }

		   if (numColors>4) bayer_pixels[4]=combineDiagonalGreens (bayer_pixels[0], bayer_pixels[3],  half_width, half_height);
		   return bayer_pixels;
	}
	public double[][] splitBayerOne (ImagePlus imp, Rectangle r, boolean equalize_greens) {
		ImageProcessor ip=imp.getProcessor();
		float [] pixels;
		pixels=(float[])ip.getPixels();    
		int full_width= imp.getWidth();  // full image width
		int full_height=imp.getHeight(); // full image height
		if (debugLevel>10) IJ.showMessage("splitBayer","r.width="+r.width+
				"\nr.height="+r.height+
				"\nr.x="+r.x+
				"\nr.y="+r.y+
				"\nlength="+pixels.length);
		if ((debugLevel>2) && ((r.x<0) || (r.y<0) || ((r.x+r.width)>=full_width) || ((r.y+r.height)>=full_height))) System.out.println("r.width="+r.width+
				" r.height="+r.height+
				" r.x="+r.x+
				" r.y="+r.y);
		int x,y,base,base_b,bv,i,j;
		int half_height=r.height>>1;
        	int half_width=r.width>>1;
		   // make them all 0 if not a single pixel falls into the image        
		   int numColors=(half_height==half_width)?5:4;
		   int pixX,pixY;
		   double [][] bayer_pixels=new double[numColors][half_height * half_width];
		   if ((r.x>=full_width) || (r.y>=full_height) || ((r.x+r.width)<0)  || ((r.y+r.height)<0)) {
			   for (i=0;i<bayer_pixels.length;i++) for (j=0;j<bayer_pixels[i].length;j++) bayer_pixels[i][j]=0.0;
			   return bayer_pixels;
		   }
		   //      base=r.width*((y<<1)+bv);
		   for (y=0; y<half_height; y++) for (bv=0;bv<2;bv++){
			   pixY=(y*2)+bv+r.y;
			   base_b=half_width*y;
			   //					if ((pixY>=0)

			   if (pixY<0) {
				   pixY=bv;
			   } else if (pixY>=full_height){
				   pixY=full_height-2+bv;
			   }
			   base=full_width*pixY+((r.x>0)?r.x:0);
			   //						base=full_width*((y*2)+bv+r.y)+r.x;
			   pixX=r.x;
			   if (bv==0) for (x=0; x<half_width; x++) {
				   if ((pixX<0) || (pixX>=(full_width-2))) {
					   bayer_pixels[0][base_b]= pixels[base];
					   bayer_pixels[1][base_b]= pixels[base+1];
				   } else {
					   bayer_pixels[0][base_b]= pixels[base++];
					   bayer_pixels[1][base_b]= pixels[base++];
				   }
				   base_b++;
				   pixX+=2;
			   } else  for (x=0; x<half_width; x++) {
				   if ((pixX<0) || (pixX>=(full_width-2))) {
					   bayer_pixels[2][base_b]= pixels[base];
					   bayer_pixels[3][base_b]= pixels[base+1];
				   } else {
					   bayer_pixels[2][base_b]= pixels[base++];
					   bayer_pixels[3][base_b]= pixels[base++];
				   }
				   base_b++;
				   pixX+=2;
			   }
		   }
		   if (equalize_greens) {
			   double g0=0.0,g3=0.0,g02=0.0,g32=0.0,a0,a3,b0,b3;
			   int n=bayer_pixels[0].length;
			   for (i=0;i<bayer_pixels[0].length;i++) {
				   g0 +=bayer_pixels[0][i];
				   g02+=bayer_pixels[0][i]*bayer_pixels[0][i];
				   g3 +=bayer_pixels[3][i];
				   g32+=bayer_pixels[3][i]*bayer_pixels[3][i];
			   }
			   g0/=n; // mean value
			   g3/=n; // meran value
			   g02=g02/n-g0*g0;
			   g32=g32/n-g3*g3;
			   b0=Math.sqrt(Math.sqrt(g32/g02));
			   b3 = 1.0/b0;
			   a0= (g0+g3)/2 -b0*g0;
			   a3= (g0+g3)/2 -b3*g3;
			   if (debugLevel>2) {
				   System.out.println("g0= "+g0+ ", g3= "+g3);
				   System.out.println("g02="+g02+", g32="+g32);
				   System.out.println("a0="+a0+", b0="+b0);
				   System.out.println("a3="+a3+", b3="+b3);
			   }
			   for (i=0;i<bayer_pixels[0].length;i++) {
				   bayer_pixels[0][i]=a0+bayer_pixels[0][i]*b0;
				   bayer_pixels[3][i]=a3+bayer_pixels[3][i]*b3;
			   }

		   }

		   if (numColors>4) bayer_pixels[4]=combineDiagonalGreens (bayer_pixels[0], bayer_pixels[3],  half_width, half_height);
		   return bayer_pixels;
	}

	public double[][] splitBayerZero (ImagePlus imp, Rectangle r, boolean equalize_greens) {
		ImageProcessor ip=imp.getProcessor();
		float [] pixels;
		pixels=(float[])ip.getPixels();    
		if (debugLevel>10) IJ.showMessage("splitBayer","r.width="+r.width+
				"\nr.height="+r.height+
				"\nr.x="+r.x+
				"\nr.y="+r.y+
				"\nlength="+pixels.length);
		int x,y,base,base_b,bv,i;
		int half_height=r.height>>1;
			   int half_width=r.width>>1;
			   int full_width= imp.getWidth();  // full image width
			   int full_height=imp.getHeight(); // full image height
			   int numColors=(half_height==half_width)?5:4;
			   int pixX,pixY;
			   double [][] bayer_pixels=new double[numColors][half_height * half_width];
			   //      base=r.width*((y<<1)+bv);
			   for (y=0; y<half_height; y++) for (bv=0;bv<2;bv++){
				   pixY=(y*2)+bv+r.y;
				   base_b=half_width*y;
				   if ((pixY<0) || (pixY>=full_height)) {
					   if (bv==0) for (x=0; x<half_width; x++) {
						   bayer_pixels[0][base_b]= 0.0;
						   bayer_pixels[1][base_b]= 0.0;
						   base_b++;
					   } else  for (x=0; x<half_width; x++) {
						   bayer_pixels[2][base_b]= 0.0;
						   bayer_pixels[3][base_b]= 0.0;
						   base_b++;
					   }
				   } else {
					   base=full_width*((y*2)+bv+r.y)+r.x;
					   pixX=r.x;
					   if (bv==0) for (x=0; x<half_width; x++) {
						   if ((pixX<0) || (pixX>=(full_width-1))) {
							   bayer_pixels[0][base_b]= 0.0;
							   bayer_pixels[1][base_b]= 0.0;
							   base+=2;
						   } else {
							   bayer_pixels[0][base_b]= pixels[base++];
							   bayer_pixels[1][base_b]= pixels[base++];
						   }
						   base_b++;
						   pixX+=2;
					   } else  for (x=0; x<half_width; x++) {
						   if ((pixX<0) || (pixX>=(full_width-1))) {
							   bayer_pixels[2][base_b]= 0.0;
							   bayer_pixels[3][base_b]= 0.0;
							   base+=2;
						   } else {
							   bayer_pixels[2][base_b]= pixels[base++];
							   bayer_pixels[3][base_b]= pixels[base++];
						   }
						   base_b++;
						   pixX+=2;
					   }
				   }
			   }
			   if (equalize_greens) {
				   double g0=0.0,g3=0.0,g02=0.0,g32=0.0,a0,a3,b0,b3;
				   int n=bayer_pixels[0].length;
				   for (i=0;i<bayer_pixels[0].length;i++) {
					   g0 +=bayer_pixels[0][i];
					   g02+=bayer_pixels[0][i]*bayer_pixels[0][i];
					   g3 +=bayer_pixels[3][i];
					   g32+=bayer_pixels[3][i]*bayer_pixels[3][i];
				   }
				   g0/=n; // mean value
				   g3/=n; // meran value
				   g02=g02/n-g0*g0;
				   g32=g32/n-g3*g3;
				   b0=Math.sqrt(Math.sqrt(g32/g02));
				   b3 = 1.0/b0;
				   a0= (g0+g3)/2 -b0*g0;
				   a3= (g0+g3)/2 -b3*g3;
				   if (debugLevel>2) {
					   System.out.println("g0= "+g0+ ", g3= "+g3);
					   System.out.println("g02="+g02+", g32="+g32);
					   System.out.println("a0="+a0+", b0="+b0);
					   System.out.println("a3="+a3+", b3="+b3);
				   }
				   for (i=0;i<bayer_pixels[0].length;i++) {
					   bayer_pixels[0][i]=a0+bayer_pixels[0][i]*b0;
					   bayer_pixels[3][i]=a3+bayer_pixels[3][i]*b3;
				   }

			   }

			   if (numColors>4) bayer_pixels[4]=combineDiagonalGreens (bayer_pixels[0], bayer_pixels[3],  half_width, half_height);
			   return bayer_pixels;
	}

		/* Create a Thread[] array as large as the number of processors available.
		 * From Stephan Preibisch's Multithreading.java class. See:
		 * http://repo.or.cz/w/trakem2.git?a=blob;f=mpi/fruitfly/general/MultiThreading.java;hb=HEAD
		 */
		private Thread[] newThreadArray(int maxCPUs) {
			int n_cpus = Runtime.getRuntime().availableProcessors();
			if (n_cpus>maxCPUs)n_cpus=maxCPUs;
			return new Thread[n_cpus];
		}
	/* Start all given threads and wait on each of them until all are done.
		 * From Stephan Preibisch's Multithreading.java class. See:
		 * http://repo.or.cz/w/trakem2.git?a=blob;f=mpi/fruitfly/general/MultiThreading.java;hb=HEAD
		 */
		private static void startAndJoin(Thread[] threads)
		{
			for (int ithread = 0; ithread < threads.length; ++ithread)
			{
				threads[ithread].setPriority(Thread.NORM_PRIORITY);
				threads[ithread].start();
			}

			try
			{   
				for (int ithread = 0; ithread < threads.length; ++ithread)
					threads[ithread].join();
			} catch (InterruptedException ie)
			{
				throw new RuntimeException(ie);
			}
		}
// Parameters for identifying red laser pointers on the image of the pattern grid
		
	    public static class LaserPointer{
	    	public double headLasersTilt=  1.06; // degrees, right laser lower than left laser
	    	public double minimalIntensity=0.05; // of scaled saturation when laser is on
	    	public double maximalIntensity=1.5;  // of scaled saturation when laser is off
	    	public int    overexposedRadius = 30;   // no pointers closer than this to overexposed areas
	    	public double lowpassSigma=1.0; // 0.8;    // low pass sigma, in pixels
	    	public double highpassSigma=20;    // high pass sigma, in pixels
	    	public double headLowpassSigma=0.8;    // low pass sigma, in pixels for optical head lasers
	    	public double quadraticScaleSigma=1.0; // find local maximum by quadratic intrepolating pixels around maximal value (relative to lkow pass sigma) 
	    	public int    algorithmNumber=4;
	    	public int    closestOffender=3;
	    	public int    fartherstOffender=200;
	    	public double fatZero =0.05;
	    	public double greenFloor=0.6;      // when dividing by green, add this fraction of maximal value (decrease green accordingly)
	    	public boolean useOther=true; // when true - use red and other color, when false - only red
	    	public boolean otherGreen=true; // other color is green (false - blue)
	    	public double threshold=0.1;
	    	// default grid orientation, used if not enough pointers visible (modified when more visible)
	    	public boolean swapUV=false; // first
	    	public boolean flipU=false;
	    	public boolean flipV=false;
	    	public boolean whiteOnly=true; // verify laser is on the white pattern cell
	    	public double  maxOffsetFromCenter=0.6; // maximal offset of the laser spot from the center, relative to cell radius
	    	public double [][] laserUVMap; // first index - number of pointer points
	    	// new variables TODO: add handling (all linear dimensions in sensor pixels)
	    	public double laserSignalToNoise=1.5; // Minimal signal-to-noise ratio for laser pointers
	    	public double localMaxRadius=10; // sensor pix. currently uses just square (2*localMaxRadius+1)**2 
	    	public boolean usePatternFilter=true; // Filter laser positions by likely pattern white cells
	    	public int decimatePatternFilter=2; // reduce resolution for pattern filter
	    	public double localContrastSigma=40; // use to calculate local level and contrast
	    	public double localToGlobalContrast=0.8; // 0 - same contrast normalization for the whole image, 1.0 - pure local
	    	public double patternLowPassSigma=4.0; // filter normalized patetrn before thresholding 
	    	public double patternThreshold=0.2; // fraction of dispersion (same positive for white cells, negative for black ones) 
	    	public double maximalCellSize=30.0; // White cells should have black pixels in all 4 quadrants not farther than this
	    	public int numPasses=3; // number of black/white alternations of the surrounding cells to use in quadrant filtering
	    	public boolean bordersOK=false; // frame border as good cell for quadrant filter
	    	public double blurredMaskThreshold=0.1; // select only areas with multiple pattern white cells
	    	public double maskGrow=2.0; // grow final mask (pixels)              
	    	public int    debugLevel=1;
	    	private long startTime=System.nanoTime();
	    	private long lastTime=startTime;
	    	private long thisTime=startTime;
	    	private void printTiming(String title){
    			this.thisTime=System.nanoTime();
    			System.out.println(title+ " calculated at "+IJ.d2s(0.000000001*(this.thisTime-this.startTime),3)+
    					" (+"+IJ.d2s(0.000000001*(this.thisTime-this.lastTime),3)+") sec");
    			this.lastTime=this.thisTime;
	    	}
	    	private void printTimingInit(){
	    		this.startTime=System.nanoTime();
	    		this.lastTime=this.startTime;
	    		this.thisTime=this.startTime;
	    	}
	    	public LaserPointer(
	    			double headLasersTilt, // degrees, right laser lower than left laser
	    			double minimalIntensity,
	    			double maximalIntensity,
	    			int    overexposedRadius,
	    	    	double lowpassSigma,    // low pass sigma, in pixels
	    	    	double highpassSigma,
	    	    	double headLowpassSigma,    // low pass sigma, in pixels for optical head lasers
	    	    	double quadraticScaleSigma, // find local maximum by quadratic intrepolating pixels around maximal value (relative to lkow pass sigma) 
	    	    	int    algorithmNumber,
	    	    	int    closestOffender,
	    	    	int    fartherstOffender,
	    	    	double fatZero,
	    	    	double greenFloor,      // when dividing by green, add this fraction of maximal value (decrease green accordingly)
	    	    	boolean useOther, // when true - use red and other color, when false - only red
	    	    	boolean otherGreen, // other color is green (false - blue)
	    	    	double threshold,
	    	    	boolean swapUV, // first
	    	    	boolean flipU,
	    	    	boolean flipV,
	    	    	boolean whiteOnly, // verify laser is on the white pattern cell
	    	    	double  maxOffsetFromCenter, // maximal offset of the laser spot from the center (<0.5)
	    	    	double [][] laserUVMap, // first index - number of pointer points
	    	    	double laserSignalToNoise, // Minimal signal-to-noise ratio for laser pointers
	    	    	double localMaxRadius, // sensor pix. currently uses just square (2*localMaxRadius+1)**2 
	    	    	boolean usePatternFilter, // Filter laser positions by likely pattern white cells
	    	    	int decimatePatternFilter,
	    	    	double localContrastSigma,
	    	    	double localToGlobalContrast,
	    	    	double patternLowPassSigma, 
	    	    	double patternThreshold, 
	    	    	double maximalCellSize,
	    	    	int numPasses, // number of black/white alternations of the surrounding cells to use in quadrant filtering
	    	    	boolean bordersOK, // frame boreder as good cell for quadrant filter
	    	    	double blurredMaskThreshold, // select only areas with multiple pattern white cells
	    	    	double maskGrow, // grow final mask (pixels)              
	    	    	int    debugLevel
	    			) {
	    		this.headLasersTilt=headLasersTilt; // degrees, right laser lower than left laser
    			this.minimalIntensity=minimalIntensity;
    			this.maximalIntensity=maximalIntensity;
    			this.overexposedRadius=overexposedRadius;
    	    	this.lowpassSigma=lowpassSigma;
    	    	this.highpassSigma=highpassSigma;
    	    	this.headLowpassSigma=headLowpassSigma;    // low pass sigma, in pixels for optical head lasers
    	    	this.quadraticScaleSigma= quadraticScaleSigma; // find local maximum by quadratic intrepolating pixels around maximal value (relative to lkow pass sigma) 

    	    	this.algorithmNumber=algorithmNumber;
    	    	this.closestOffender=closestOffender;
    	    	this.fartherstOffender=fartherstOffender;
    	    	this.fatZero=fatZero;

    	    	this.greenFloor=  greenFloor;
    	    	this.useOther=useOther;
    	    	this.otherGreen=otherGreen;
    	    	this.threshold=   threshold;
    	    	this.swapUV=swapUV; // first
    	    	this.flipU=flipU;
    	    	this.flipV=flipV;
    	    	this.whiteOnly=whiteOnly;
    	    	this.maxOffsetFromCenter=maxOffsetFromCenter;
    	    	this.laserUVMap=new double[laserUVMap.length][2];
    	    	for (int i=0;i<laserUVMap.length;i++) {
    	    		this.laserUVMap[i][0]=laserUVMap[i][0];
    	    		this.laserUVMap[i][1]=laserUVMap[i][1];
    	    	}
    	    	this.laserSignalToNoise=laserSignalToNoise; // Minimal signal-to-noise ratio for laser pointers
    	    	this.localMaxRadius=localMaxRadius; // sensor pix. currently uses just square (2*localMaxRadius+1)**2
    	    	this.usePatternFilter=usePatternFilter; // Filter laser positions by likely pattern white cells
    	    	this.decimatePatternFilter=decimatePatternFilter;
    	    	this.localContrastSigma=localContrastSigma;
    	    	this.localToGlobalContrast=localToGlobalContrast;
    	    	this.patternLowPassSigma=patternLowPassSigma; 
    	    	this.patternThreshold=patternThreshold; 
    	    	this.maximalCellSize=maximalCellSize; 
    	    	this.numPasses=numPasses; // number of black/white alternations of the surrounding cells to use in quadrant filtering
    	    	this.bordersOK=bordersOK; // frame boreder as good cell for quadrant filter
    	    	this.blurredMaskThreshold=blurredMaskThreshold; // select only areas with multiple pattern white cells
    	    	this.maskGrow=maskGrow; // grow final mask (pixels)              
    	    	this.debugLevel=debugLevel;
	    	}
	    	public int getNumberOfPointers(){
	    		return this.laserUVMap.length;
	    	}
	    	public LaserPointer clone(){
	    		return new LaserPointer(
	    	    		this.headLasersTilt,// degrees, right laser lower than left laser
	        			this.minimalIntensity,
	        			this.maximalIntensity,
	        			this.overexposedRadius,
		    	    	this.lowpassSigma,    // low pass sigma, in pixels
		    	    	this.highpassSigma,
		    	    	this.quadraticScaleSigma, // find local maximum by quadratic intrepolating pixels around maximal value (relative to lkow pass sigma) 
		    	    	this.headLowpassSigma,
		    	    	this.algorithmNumber,
		    	    	this.closestOffender,
		    	    	this.fartherstOffender,
		    	    	this.fatZero,
		    	    	this.greenFloor,      // when dividing by green, add this fraction of maximal value (decrease green accordingly)
		    	    	this.useOther,
		    	    	this.otherGreen,		    	    	
		    	    	this.threshold,
		    	    	this.swapUV,
		    	    	this.flipU,
		    	    	this.flipV,
		    	    	this.whiteOnly,
		    	    	this.maxOffsetFromCenter,
		    	    	this.laserUVMap, // first index - number of pointer points
		    	    	this.laserSignalToNoise, // Minimal signal-to-noise ratio for laser pointers
		    	    	this.localMaxRadius, // sensor pix. currently uses just square (2*localMaxRadius+1)**2 
		    	    	this.usePatternFilter, // Filter laser positions by likely pattern white cells
		    	    	this.decimatePatternFilter,
		    	    	this.localContrastSigma,
		    	    	this.localToGlobalContrast,
		    	    	this.patternLowPassSigma, 
		    	    	this.patternThreshold, 
		    	    	this.maximalCellSize,
		    	    	this.numPasses, // number of black/white alternations of the surrounding cells to use in quadrant filtering
		    	    	this.bordersOK,
		    	    	this.blurredMaskThreshold,// select only areas with multiple pattern white cells
		    	    	this.maskGrow,  // grow final mask (pixels)              
		    	    	this.debugLevel
	    				);
	    	}
	    	public void setProperties(String prefix,Properties properties){
	    		properties.setProperty(prefix+"headLasersTilt",this.headLasersTilt+"");
	    		properties.setProperty(prefix+"minimalIntensity",this.minimalIntensity+"");
	    		properties.setProperty(prefix+"maximalIntensity",this.maximalIntensity+"");
	    		properties.setProperty(prefix+"overexposedRadius",this.overexposedRadius+"");
	    		properties.setProperty(prefix+"lowpassSigma",this.lowpassSigma+"");
	    		properties.setProperty(prefix+"highpassSigma",this.highpassSigma+"");
	    		properties.setProperty(prefix+"headLowpassSigma",this.headLowpassSigma+"");
	    		properties.setProperty(prefix+"quadraticScaleSigma",this.quadraticScaleSigma+"");
	    		properties.setProperty(prefix+"algorithmNumber",this.algorithmNumber+"");
	    		properties.setProperty(prefix+"closestOffender",this.closestOffender+"");
	    		properties.setProperty(prefix+"fartherstOffender",this.fartherstOffender+"");
	    		properties.setProperty(prefix+"fatZero",this.fatZero+"");
	    		properties.setProperty(prefix+"greenFloor",this.greenFloor+"");
	    		properties.setProperty(prefix+"useOther",this.useOther+"");
	    		properties.setProperty(prefix+"otherGreen",this.otherGreen+"");
	    		properties.setProperty(prefix+"threshold",this.threshold+"");
	    		properties.setProperty(prefix+"swapUV",this.swapUV+"");
	    		properties.setProperty(prefix+"flipU",this.flipU+"");
	    		properties.setProperty(prefix+"flipV",this.flipV+"");
	    		properties.setProperty(prefix+"whiteOnly",this.whiteOnly+"");
	    		properties.setProperty(prefix+"maxOffsetFromCenter",this.maxOffsetFromCenter+"");
	    		properties.setProperty(prefix+"numberOfLaserPoints",this.laserUVMap.length+"");
	    		for (int i=0;i<this.laserUVMap.length;i++) {
		    		properties.setProperty(prefix+"laserUVMap_"+i+"u",this.laserUVMap[i][0]+"");
		    		properties.setProperty(prefix+"laserUVMap_"+i+"v",this.laserUVMap[i][1]+"");
	    		}
	    		properties.setProperty(prefix+"laserSignalToNoise",this.laserSignalToNoise+"");
	    		properties.setProperty(prefix+"localMaxRadius",this.localMaxRadius+"");
	    		properties.setProperty(prefix+"usePatternFilter",this.usePatternFilter+"");
	    		properties.setProperty(prefix+"decimatePatternFilter",this.decimatePatternFilter+"");
	    		properties.setProperty(prefix+"localContrastSigma",this.localContrastSigma+"");
	    		properties.setProperty(prefix+"localToGlobalContrast",this.localToGlobalContrast+"");
	    		properties.setProperty(prefix+"patternLowPassSigma",this.patternLowPassSigma+"");
	    		properties.setProperty(prefix+"patternThreshold",this.patternThreshold+"");
	    		properties.setProperty(prefix+"maximalCellSize",this.maximalCellSize+"");
	    		properties.setProperty(prefix+"numPasses",this.numPasses+"");
	    		properties.setProperty(prefix+"bordersOK",this.bordersOK+"");
	    		properties.setProperty(prefix+"blurredMaskThreshold",this.blurredMaskThreshold+"");
	    		properties.setProperty(prefix+"maskGrow",this.maskGrow+"");
	    		properties.setProperty(prefix+"debugLevel",this.debugLevel+"");
	    	}
	    	public void getProperties(String prefix,Properties properties){
	    		int numberOfLaserPoints=0;
	    		if (properties.getProperty(prefix+"headLasersTilt")!=null)
	    			this.headLasersTilt=Double.parseDouble(properties.getProperty(prefix+"headLasersTilt"));
	    		if (properties.getProperty(prefix+"minimalIntensity")!=null)
	    			this.minimalIntensity=Double.parseDouble(properties.getProperty(prefix+"minimalIntensity"));
	    		if (properties.getProperty(prefix+"maximalIntensity")!=null)
	    			this.maximalIntensity=Double.parseDouble(properties.getProperty(prefix+"maximalIntensity"));
	    		if (properties.getProperty(prefix+"overexposedRadius")!=null)
	    			this.overexposedRadius=Integer.parseInt(properties.getProperty(prefix+"overexposedRadius"));
	    		if (properties.getProperty(prefix+"lowpassSigma")!=null)
	    			this.lowpassSigma=Double.parseDouble(properties.getProperty(prefix+"lowpassSigma"));
	    		if (properties.getProperty(prefix+"highpassSigma")!=null)
	    			this.highpassSigma=Double.parseDouble(properties.getProperty(prefix+"highpassSigma"));
	    		if (properties.getProperty(prefix+"headLowpassSigma")!=null)
	    			this.headLowpassSigma=Double.parseDouble(properties.getProperty(prefix+"headLowpassSigma"));
	    		if (properties.getProperty(prefix+"quadraticScaleSigma")!=null)
	    			this.quadraticScaleSigma=Double.parseDouble(properties.getProperty(prefix+"quadraticScaleSigma"));
	    		if (properties.getProperty(prefix+"algorithmNumber")!=null)
	    			this.algorithmNumber=Integer.parseInt(properties.getProperty(prefix+"algorithmNumber"));
	    		if (properties.getProperty(prefix+"closestOffender")!=null)
	    			this.closestOffender=Integer.parseInt(properties.getProperty(prefix+"closestOffender"));
	    		if (properties.getProperty(prefix+"fartherstOffender")!=null)
	    			this.fartherstOffender=Integer.parseInt(properties.getProperty(prefix+"fartherstOffender"));
	    		if (properties.getProperty(prefix+"fatZero")!=null)
	    			this.fatZero=Double.parseDouble(properties.getProperty(prefix+"fatZero"));
	    		if (properties.getProperty(prefix+"greenFloor")!=null)
	    			this.greenFloor=Double.parseDouble(properties.getProperty(prefix+"greenFloor"));
	    		if (properties.getProperty(prefix+"useOther")!=null)
	    			this.useOther=Boolean.parseBoolean(properties.getProperty(prefix+"useOther"));
	    		if (properties.getProperty(prefix+"otherGreen")!=null)
	    			this.otherGreen=Boolean.parseBoolean(properties.getProperty(prefix+"otherGreen"));
	    		if (properties.getProperty(prefix+"threshold")!=null)
	    			this.threshold=Double.parseDouble(properties.getProperty(prefix+"threshold"));
	    		if (properties.getProperty(prefix+"swapUV")!=null)
	    			this.swapUV=Boolean.parseBoolean(properties.getProperty(prefix+"swapUV"));
	    		if (properties.getProperty(prefix+"flipU")!=null)
	    			this.flipU=Boolean.parseBoolean(properties.getProperty(prefix+"flipU"));
	    		if (properties.getProperty(prefix+"flipV")!=null)
	    			this.flipV=Boolean.parseBoolean(properties.getProperty(prefix+"flipV"));
	    		if (properties.getProperty(prefix+"whiteOnly")!=null)
	    			this.whiteOnly=Boolean.parseBoolean(properties.getProperty(prefix+"whiteOnly"));
	    		if (properties.getProperty(prefix+"maxOffsetFromCenter")!=null)
	    			this.maxOffsetFromCenter=Double.parseDouble(properties.getProperty(prefix+"maxOffsetFromCenter"));
	    		if (properties.getProperty(prefix+"numberOfLaserPoints")!=null) {
	    			numberOfLaserPoints=Integer.parseInt(properties.getProperty(prefix+"numberOfLaserPoints"));
	    			this.laserUVMap=new double[numberOfLaserPoints][2];
		    		for (int i=0;i<this.laserUVMap.length;i++) {
		    			this.laserUVMap[i][0]=Double.parseDouble(properties.getProperty(prefix+"laserUVMap_"+i+"u"));
		    			this.laserUVMap[i][1]=Double.parseDouble(properties.getProperty(prefix+"laserUVMap_"+i+"v"));
		    		}
	    		}
	    		if (properties.getProperty(prefix+"laserSignalToNoise")!=null)
	    			this.laserSignalToNoise=Double.parseDouble(properties.getProperty(prefix+"laserSignalToNoise"));
	    		if (properties.getProperty(prefix+"localMaxRadius")!=null)
	    			this.localMaxRadius=Double.parseDouble(properties.getProperty(prefix+"localMaxRadius"));
	    		if (properties.getProperty(prefix+"usePatternFilter")!=null)
	    			this.usePatternFilter=Boolean.parseBoolean(properties.getProperty(prefix+"usePatternFilter"));
	    		if (properties.getProperty(prefix+"decimatePatternFilter")!=null)
	    			this.decimatePatternFilter=Integer.parseInt(properties.getProperty(prefix+"decimatePatternFilter"));
	    		if (properties.getProperty(prefix+"localContrastSigma")!=null)
	    			this.localContrastSigma=Double.parseDouble(properties.getProperty(prefix+"localContrastSigma"));
	    		if (properties.getProperty(prefix+"localToGlobalContrast")!=null)
	    			this.localToGlobalContrast=Double.parseDouble(properties.getProperty(prefix+"localToGlobalContrast"));
	    		if (properties.getProperty(prefix+"patternLowPassSigma")!=null)
	    			this.patternLowPassSigma=Double.parseDouble(properties.getProperty(prefix+"patternLowPassSigma"));
	    		if (properties.getProperty(prefix+"patternThreshold")!=null)
	    			this.patternThreshold=Double.parseDouble(properties.getProperty(prefix+"patternThreshold"));
	    		if (properties.getProperty(prefix+"maximalCellSize")!=null)
	    			this.maximalCellSize=Double.parseDouble(properties.getProperty(prefix+"maximalCellSize"));
	    		if (properties.getProperty(prefix+"numPasses")!=null)
	    			this.numPasses=Integer.parseInt(properties.getProperty(prefix+"numPasses"));
	    		if (properties.getProperty(prefix+"bordersOK")!=null)
	    			this.bordersOK=Boolean.parseBoolean(properties.getProperty(prefix+"bordersOK"));
	    		if (properties.getProperty(prefix+"blurredMaskThreshold")!=null)
	    			this.blurredMaskThreshold=Double.parseDouble(properties.getProperty(prefix+"blurredMaskThreshold"));
	    		if (properties.getProperty(prefix+"maskGrow")!=null)
	    			this.maskGrow=Double.parseDouble(properties.getProperty(prefix+"maskGrow"));
	    		if (properties.getProperty(prefix+"debugLevel")!=null)
	    			this.debugLevel=Integer.parseInt(properties.getProperty(prefix+"debugLevel"));

	    		
	    	}
	    	public boolean showFilterDialog(String title){ 
	    		GenericDialog gd = new GenericDialog(title);
		    		gd.addNumericField("Decimate image for Pattern filter", this.decimatePatternFilter, 0,1,"x");
		    		gd.addNumericField("Sigma to calculate local level and contrast", this.localContrastSigma, 2,5,"sensor pixels");
		    		gd.addNumericField("Local/global contrast normalization", 100*this.localToGlobalContrast, 1,5,"%");
		    		gd.addNumericField("Filter sigma to apply to the pattern before thresholding",       this.patternLowPassSigma, 1,5,"sensor pix");
		    		gd.addNumericField("Pattern cell threshold as a fraction of dispersion",             100*this.patternThreshold, 1,5,"%");
		    		gd.addNumericField("Maximal pattern cell size (for discrimination)",  this.maximalCellSize, 1,5,"pix");
		    		
		    		gd.addNumericField("Number of black/white alternations of the surrounding cells to use in quadrant filtering", this.numPasses, 0);
		    		gd.addCheckbox   ("Borders as good cells for quadrant filter of possible pattern",  this.bordersOK);
		    		gd.addNumericField("Blurred white pattern cells mask (to remove separate white cells)", 100*this.blurredMaskThreshold, 1,5,"%");
		    		gd.addNumericField("Grow final detected pattern white cells mask", this.maskGrow, 2,5,"sensor pixels");
		    		gd.addNumericField("Debug level",   this.debugLevel, 0);
	    		gd.showDialog();
	    		if (gd.wasCanceled()) return false;
	    		
	    		this.decimatePatternFilter= (int) gd.getNextNumber();
	    		this.localContrastSigma=          gd.getNextNumber();
	    		this.localToGlobalContrast=  0.01*gd.getNextNumber();
	    		this.patternLowPassSigma=         gd.getNextNumber();
	    		this.patternThreshold=       0.01*gd.getNextNumber();
	    		this.maximalCellSize=             gd.getNextNumber();
	    		this.numPasses=             (int) gd.getNextNumber();
    	    	this.bordersOK=                   gd.getNextBoolean();
	    		this.blurredMaskThreshold=   0.01*gd.getNextNumber();
	    		this.maskGrow=                    gd.getNextNumber();
	    		this.debugLevel=            (int) gd.getNextNumber();
	    		return true;
	    	}
	    	
	    	
	    	
	    	public boolean showDialog(String title){
	    		return showDialog(title, -1);
	    	}
	    	public boolean showDialog(String title,
	    			int numberOfPoints) { // >0 - ask only UV, <=0 all, use same number of points 
	    		GenericDialog gd = new GenericDialog(title);
	    		if (numberOfPoints<=0) {
	    			
		    		gd.addNumericField("Angle between 2 laser spots and horizontal (right lower than left - positive)", this.headLasersTilt, 3,7,"degrees");
		    		gd.addNumericField("Minimal intensity at the expected pointer as a fraction of saturation", 100*this.minimalIntensity, 1,5,"%");
		    		gd.addNumericField("Maximal expected intensity at pointer (when it is off as a fraction of scaled saturation)", 100*this.maximalIntensity, 1,5,"%");
		    		gd.addNumericField("Do not look for the pointer closer than this distance from overexposed areas", this.overexposedRadius, 0,5,"pix");
		    				    		
		    		gd.addNumericField("Target laser spot detection low pass filter (4 spots)",         this.lowpassSigma, 1,5,"pix");
		    		gd.addNumericField("Target spot detection high pass filter  (4 spots)",             this.highpassSigma, 1,5,"pix");
		    		
		    		gd.addNumericField("Optical head laser spot detection low pass filter  (2 spots)",  this.headLowpassSigma, 1,5,"pix");
		    		gd.addNumericField("Scale low pass sigma when quadratic interpolate for maximum (0 - no interpolation)",   this.quadraticScaleSigma, 1,5,"x");
		    		
		    		gd.addNumericField("Algorithm number to detect pointers ",                          this.algorithmNumber, 0,1,"");
		    		gd.addNumericField("Do not check for above-threshold closer to the current point",  this.closestOffender, 0,5,"pix");
		    		gd.addNumericField("Prohibit above-threshold points closer to each other than",     this.fartherstOffender, 0,5,"pix");
		    		gd.addNumericField("Fat zero for combining differences",                            this.fatZero, 3,5,"");
		    		
		    		gd.addNumericField("Normalization to green, floor (100% - no normalization)", 100.0*this.greenFloor,  1,5,"%");
		    		
		    		gd.addCheckbox("Compare red to other color (green or blue)", this.useOther);
		    		gd.addCheckbox("If compare, compare to green (unchecked - blue)", this.otherGreen);
		    		
		    		gd.addNumericField("Red/Green difference to R/G average to be a laser spot",  100.0*this.threshold,  1,7,"%");
			    	gd.addMessage("Default grid orientation, used if not enough pointers are visible (auto-modified when more appear)");
			    	gd.addCheckbox("Swap U avd V",this.swapUV); // first
			    	gd.addCheckbox("Flip U direction",this.flipU);
			    	gd.addCheckbox("Flip V direction",this.flipV);
			    	
			    	gd.addCheckbox("Allow laser pointers on the white cells only",this.whiteOnly);
		    		gd.addNumericField("Maximal relative distance of the laser spot from the pattern cell center ",  100.0*this.maxOffsetFromCenter,  1,7,"%");
		    		gd.addNumericField("Minimal pointer S/N ratio", this.laserSignalToNoise, 2,5,"x");
		    		gd.addNumericField("Radius for finding local maximums", this.localMaxRadius, 2,5,"sensor pixels");
		    		gd.addMessage("==== Pattern Filter Parameters ====");
		    		gd.addCheckbox    ("Use pattern filter for laser pointer detection (and apply next settings)",  this.usePatternFilter);
		    		gd.addNumericField("Decimate image for Pattern filter", this.decimatePatternFilter, 0,1,"x");
		    		gd.addNumericField("Sigma to calculate local level and contrast", this.localContrastSigma, 2,5,"sensor pixels");
		    		gd.addNumericField("Local/global contrast normalization", 100*this.localToGlobalContrast, 1,5,"%");
		    		gd.addNumericField("Filter sigma to apply to the pattern before thresholding",       this.patternLowPassSigma, 1,5,"sensor pix");
		    		gd.addNumericField("Pattern cell threshold as a fraction of dispersion",             100*this.patternThreshold, 1,5,"%");
		    		gd.addNumericField("Maximal pattern cell size (for discrimination)",  this.maximalCellSize, 1,5,"pix");
		    		gd.addNumericField("Number of black/white alternations of the surrounding cells to use in quadrant filtering", this.numPasses, 0);
		    		gd.addCheckbox    ("Borders as good cells for quadrant filter of possible pattern",  this.bordersOK);
		    		gd.addNumericField("Blurred white pattern cells mask (to remove separate white cells)", 100*this.blurredMaskThreshold, 1,5,"%");
		    		gd.addNumericField("Grow final detected pattern white cells mask", this.maskGrow, 2,5,"sensor pixels");
		    		gd.addNumericField("Pattern filter debug level",   this.debugLevel, 0);
			    	
	    		} else {
	    			double [][] newLaserUVMap = new double [numberOfPoints][2];
	    			for (int i=0; i<numberOfPoints;i++) {
	    				newLaserUVMap[i][0]=(i>(laserUVMap.length-1))?0.0:this.laserUVMap[i][0]; 
	    				newLaserUVMap[i][1]=(i>(laserUVMap.length-1))?0.0:this.laserUVMap[i][1];
	    			}
	    			this.laserUVMap=newLaserUVMap;
	    		}
	    		for (int i=0;i<this.laserUVMap.length;i++) {
	    			gd.addMessage("Laser point "+i+" grid coordinates:");
	    			gd.addNumericField("Grid U "+i,                   this.laserUVMap[i][0], 2,7,"grid periods");
	    			gd.addNumericField("Grid V "+i,                   this.laserUVMap[i][1], 2,7,"grid periods");
	    		}
	    		if (numberOfPoints<=0) {
		    		gd.addNumericField("Number of laser points (will ask  for U V if modified)",  this.laserUVMap.length,  0,2,"");
	    		}
	    		
	    		WindowTools.addScrollBars(gd);
	    		gd.showDialog();
	    		if (gd.wasCanceled()) return false;
	    		if (numberOfPoints<=0) {
		    		this.headLasersTilt=gd.getNextNumber();
	    			this.minimalIntensity=   0.01*gd.getNextNumber();
	    			this.maximalIntensity=   0.01*gd.getNextNumber();
	    			this.overexposedRadius= (int) gd.getNextNumber();
	    			this.lowpassSigma=            gd.getNextNumber();
	    			this.highpassSigma=           gd.getNextNumber();
	    	    	this.headLowpassSigma=        gd.getNextNumber();
	    	    	this.quadraticScaleSigma=     gd.getNextNumber(); 
		    		this.algorithmNumber=   (int) gd.getNextNumber();
	    	    	this.closestOffender=   (int) gd.getNextNumber();
	    	    	this.fartherstOffender=  (int) gd.getNextNumber();
	    	    	this.fatZero=                 gd.getNextNumber();
	    			this.greenFloor=         0.01*gd.getNextNumber();
		    		this.useOther=                gd.getNextBoolean();
		    		this.otherGreen=              gd.getNextBoolean();
	    			this.threshold=          0.01*gd.getNextNumber();
			    	this.swapUV=                  gd.getNextBoolean();
			    	this.flipU=                   gd.getNextBoolean();
			    	this.flipV=                   gd.getNextBoolean();
			    	this.whiteOnly=               gd.getNextBoolean();
	    			this.maxOffsetFromCenter=0.01*gd.getNextNumber();
	    	    	this.laserSignalToNoise=      gd.getNextNumber();
	    	    	this.localMaxRadius=          gd.getNextNumber(); 
	    			this.usePatternFilter=            gd.getNextBoolean();
	    			this.decimatePatternFilter= (int) gd.getNextNumber();
		    		this.localContrastSigma=          gd.getNextNumber();
		    		this.localToGlobalContrast=  0.01*gd.getNextNumber();
		    		this.patternLowPassSigma=         gd.getNextNumber();
		    		this.patternThreshold=       0.01*gd.getNextNumber();
		    		this.maximalCellSize=             gd.getNextNumber();
		    		this.numPasses=             (int) gd.getNextNumber();
			    	this.bordersOK=                   gd.getNextBoolean();
		    		this.blurredMaskThreshold=   0.01*gd.getNextNumber();
		    		this.maskGrow=                    gd.getNextNumber();
		    		this.debugLevel=            (int) gd.getNextNumber();
	    		}
	    		for (int i=0;i<this.laserUVMap.length;i++) {
	    			this.laserUVMap[i][0]=     gd.getNextNumber();
	    			this.laserUVMap[i][1]=     gd.getNextNumber();
	    		}
	    		if (numberOfPoints<=0) {
	    			numberOfPoints= (int) gd.getNextNumber();
	    			if ((numberOfPoints > 0) && (numberOfPoints!=this.laserUVMap.length)) showDialog(title,numberOfPoints);
	    		}
	    		return true;
	    	}
	    	/*
					ponterXY=laserPointers.laserPointer.getPointerXY( // returns x,y pair or null if pointer not detected
							greens,        // combined Bayer greens for each image, starting with no-laser
							this.laserPointers.laserWasOn(nPointer), // array specifying which image should have pointer on
							imp_pointed.getWidth(),// image width in pixels
			    			imp_pointed.getTitle()+"-"+nPointer,             // String title,
							this.debugLevel             // debug level (normal == 1)
					);

	    	 */
	    	public boolean [] localMaximum(
	    			double [] pixels,
	    			int width,
	    			int radius,
	    			int debugLevel){
	    		int height=pixels.length/width;
	    		showDoubleFloatArrays sdfra_instance= null;
	    		if (debugLevel>1) {
	    			sdfra_instance= new showDoubleFloatArrays(); // just for debugging?
	    		}

	    		boolean [] bmax=new boolean[pixels.length];
	    		// horizontal pass
	    		double []hmax = new double [pixels.length];
	    		for (int i=0;i<height;i++){
	    			int j0,j1;
	    			double max=0.0;
	    			for (int j=0;j<width;j++){
	    				j0=j-radius;
	    				if (j0<0) j0=0;
	    				j1=j+radius;
	    				if (j1>=width) j1=width-1;
	    				if (j>0) {
		    				max=Math.max(max,pixels[width*i+j1]);
	    					if ((j0>0) && (pixels[width*i+j0-1]<max)) {
	    	    				hmax[i*width+j]=max;
	    						continue; // first (to be removed) pixel was not max
	    					}
	    				}
	    				max=pixels[width*i+j0];
	    				for (int k=width*i+j0+1;k<=width*i+j1;k++) if (pixels[k]>max) max= pixels[k];
	    				hmax[i*width+j]=max;
	    			}
	    		}
   	    		if (debugLevel>2) sdfra_instance.showArrays(hmax,   width, height, "hmax-"+radius);

	    		//vertical pass
	    		for (int j=0;j<width;j++){
	    			int i0,i1;
	    			double max=0.0;
	    			for (int i=0;i<height;i++){
	    				int index=i*width+j;
	    				i0=i-radius;
	    				if (i0<0) i0=0;
	    				i1=i+radius;
	    				if (i1>=height) i1=height-1;
	    				if (i>0) {
		    				max=Math.max(max,hmax[width*i1+j]);
	    					if ((i0>0) && (hmax[width*(i0-1)+j]<max)) {
	    						bmax[index]= (pixels[index]==max);
	    						continue; // first (to be removed) pixel was not max
	    					}
	    				}
	    				max=pixels[width*i0+j];
	    				for (int k=width*i0+j+width;k<=width*i1+j;k+=width) if (hmax[k]>max) max= hmax[k];
						bmax[index]= (pixels[index]==max);
	    			}
	    		}
	    		return bmax;
	    	}
	    	
			public boolean [] getPatternMask(
					double [] pixels,
					int width
					){
	    		showDoubleFloatArrays sdfra_instance= null;
	    		if (this.debugLevel>1) sdfra_instance= new showDoubleFloatArrays(); // just for debugging?
	    		DoubleGaussianBlur gb=new DoubleGaussianBlur();
				double initialScale=0.5;
				int height=pixels.length/width;
				double scale=initialScale/this.decimatePatternFilter;
				double [] dpixels;
				int dheight,dwidth;
				int extraWidth=0,extraHeight=0;
				double k=1.0/(this.decimatePatternFilter*this.decimatePatternFilter);
				double k1=0.0,k2=0.0,k3=0.0;
				dwidth=width/this.decimatePatternFilter;
				if (dwidth*this.decimatePatternFilter<width){
					extraWidth=width-(dwidth*this.decimatePatternFilter);
					dwidth++;
					k1=1.0/(this.decimatePatternFilter*extraWidth);
				}
				dheight=height/this.decimatePatternFilter;
				if (dheight*this.decimatePatternFilter<height) {
					extraHeight=height- (dheight*this.decimatePatternFilter);
					dheight++;
					k2=1.0/(this.decimatePatternFilter*extraHeight);
				}
				if ((dheight>0) && (dwidth>0)) k3=1.0/(extraWidth*extraHeight);
				double d;
				// decimate original image (to speedup calculations)
				if (this.decimatePatternFilter>1){
					int i,j;
					dpixels=new double [dwidth*dheight];
					for (i=0;i<height/this.decimatePatternFilter;i++){
						for (j=0;j<width/this.decimatePatternFilter;j++){
							d=0;
							int index=this.decimatePatternFilter*(i*width+j);
							for (int m=0;m<this.decimatePatternFilter;m++){
								for (int n=0;n<this.decimatePatternFilter;n++){
									d+=pixels[index+n];
								}
								index+=width;
							}
/*							
							if (i*dwidth+j>=dpixels.length){
								System.out.println(
										"dpixels.length="+dpixels.length+"\n"+
										"width="+width+"\n"+
										"height="+height+"\n"+
										"dwidth="+dwidth+"\n"+
										"dheight="+dheight+"\n"+
										"i="+i+"\n"+
										"j="+j+"\n");
							}
*/							
							dpixels[i*dwidth+j]=d*k;
						}
						if (extraWidth>0){ // j==dWidth-1
							d=0;
							int index=this.decimatePatternFilter*(i*width+j);
							for (int m=0;m<extraWidth;m++){
								for (int n=0;n<this.decimatePatternFilter;n++){
									d+=pixels[index+n];
								}
								index+=width;
							}
							dpixels[i*dwidth+j]=d*k1;
						}
					}
					if (extraHeight>0){ // i==dHeight-1
						for (j=0;j<width/this.decimatePatternFilter;j++){
							d=0;
							int index=this.decimatePatternFilter*(i*width+j);
							for (int m=0;m<extraHeight;m++){
								for (int n=0;n<this.decimatePatternFilter;n++){
									d+=pixels[index+n];
								}
								index+=width;
							}
							dpixels[i*dwidth+j]=d*k2;
						}
						if (extraWidth>0){ // j==dWidth-1
							d=0;
							int index=this.decimatePatternFilter*(i*width+j);
							for (int m=0;m<extraWidth;m++){
								for (int n=0;n<this.decimatePatternFilter;n++){
									d+=pixels[index+n];
								}
								index+=width;
							}
							dpixels[i*dwidth+j]=d*k3;
						}
					}
				} else dpixels=pixels.clone();
				if (this.debugLevel>2) sdfra_instance.showArrays(dpixels, dwidth, dheight,  "decimated");
				double [] dpixels_lp=dpixels.clone();
				gb.blurDouble(dpixels_lp, dwidth, dheight, scale*this.localContrastSigma, scale*this.localContrastSigma, 0.01);
				if (this.debugLevel>2) sdfra_instance.showArrays(dpixels_lp, dwidth, dheight,  "dpixels_lp");
				double sum=0.0;
				for (int i=0;i<dpixels.length;i++){
					dpixels[i]-=dpixels_lp[i];
					dpixels_lp[i]=dpixels[i]*dpixels[i];
					sum+=dpixels_lp[i];
				}
				double corr=(1.0-this.localToGlobalContrast)*Math.sqrt(sum/(dwidth*dheight));
				if (this.debugLevel>2) sdfra_instance.showArrays(dpixels_lp, dwidth, dheight,  "dpixels_var");
				gb.blurDouble(dpixels_lp, dwidth, dheight, scale*this.localContrastSigma, scale*this.localContrastSigma, 0.01);
				if (this.debugLevel>2) sdfra_instance.showArrays(dpixels_lp, dwidth, dheight,  "dpixels_var_blur");
				for (int i=0;i<dpixels.length;i++){
					dpixels_lp[i]=this.localToGlobalContrast*Math.sqrt(dpixels_lp[i])+corr;
					dpixels[i]/=dpixels_lp[i];
				}
				if (this.debugLevel>2) sdfra_instance.showArrays(dpixels_lp, dwidth, dheight,  "dpixels_denom");
				if (this.patternLowPassSigma>0) {
					if (this.debugLevel>2) sdfra_instance.showArrays(dpixels, dwidth, dheight,    "dpixels_normalized");
					gb.blurDouble(dpixels, dwidth, dheight, scale*this.patternLowPassSigma, scale*this.patternLowPassSigma, 0.01);
				}
				if (this.debugLevel>1) sdfra_instance.showArrays(dpixels, dwidth, dheight,    "dpixels_norm_blur");
				int [] ipixels=new int [dpixels.length];
				for (int i=0;i<dpixels.length;i++){
					if (dpixels[i]>this.patternThreshold) ipixels[i]=1;
					else if (dpixels[i]<-this.patternThreshold) ipixels[i]=-1;
					else ipixels[i]=0;
				}
				if (this.debugLevel>1)	sdfra_instance.showArrays(ipixels, dwidth, dheight,    "ipixels_threshold");
				
				int maxDist = (int) Math.round(scale*this.maximalCellSize);
				if (maxDist<1) maxDist=1;
				// TODO: save intermediate ipixels and use it with blurred version from more passes?
				for (int pass=this.numPasses-1;pass>=0;pass--) {
					filterQuadrant(
							ipixels,
							dwidth,
							(pass & 1)==0,  // false, // each black cell should have white in all 4 quadrants
							maxDist,
							this.bordersOK
							);
					if (this.debugLevel>2)	sdfra_instance.showArrays(ipixels, dwidth, dheight,    "pass-"+pass);
				}
				//blur-threshold-grow
				// convert to Double (white cells - 1.0, black and none - 0.0)
				for (int i=0;i<dpixels.length;i++) dpixels[i]= (ipixels[i]>0)?1.0:0.0;
				// blur result with sigma > cell period to find continuous pattern cell areas
				gb.blurDouble(dpixels, dwidth, dheight, scale*this.localContrastSigma, scale*this.localContrastSigma, 0.01);
				if (this.debugLevel>2)	sdfra_instance.showArrays(dpixels, dwidth, dheight,    "white_blurred");
				// Threshold to boolean mask
				boolean [] dmask=new boolean[dpixels.length];
				for (int i=0;i<dpixels.length;i++) dmask[i]= (dpixels[i]>=this.blurredMaskThreshold);
				if (this.debugLevel>1)	sdfra_instance.showArrays(dmask, dwidth, dheight,    "white_threshold");
				int iBlurMaskGrow= (int) Math.round(scale*this.localContrastSigma); // does in need separate coefficient?
				growMask( dmask, //boolean [] pixels,
						dwidth, // int width,
						iBlurMaskGrow); //int grow);
				if (this.debugLevel>1)	sdfra_instance.showArrays(dmask, dwidth, dheight,    "white_threshold_grown"+iBlurMaskGrow);
				// Mask out white cells outside of the compact areas just found
				for (int i=0;i<dpixels.length;i++) if (!dmask[i])  ipixels[i]=0;
				if (this.debugLevel>1)	sdfra_instance.showArrays(ipixels, dwidth, dheight,    "ipixels_masked");
				boolean [] mask=new boolean[pixels.length];
				if (this.decimatePatternFilter>1){
					for (int i=0;i<pixels.length;i++){
						int y= (i/width)/this.decimatePatternFilter;
						int x= (i%width)/this.decimatePatternFilter;
						mask[i]= (ipixels[x+dwidth*y]==1); // "good" white cells
					}
					
				} else {
					for (int i=0;i<pixels.length;i++) mask[i]= (ipixels[i]==1); // "good" white cells  
				}
				int finalMaskGrow=(int) Math.round(this.maskGrow*initialScale);
				if (this.maskGrow>0.0) {
					growMask(mask, //boolean [] pixels,
							 width, // int width,
							 finalMaskGrow);// //int grow);
				}
				return mask;
			}
			void growMask(
					boolean [] pixels,
					int width,
					int grow){
				int height=pixels.length/width;
				int [] distLeft= new int [pixels.length]; 
				int [] distRight=new int [pixels.length]; // also used for down 
				int [] distUp=  new int [pixels.length];
				int v1,v2;
				int index1,index2;
				int initialValue=width+height;
				for (int i=0;i<height;i++){
					v1=initialValue;
					v2=initialValue;
					index1=i*width;
					index2=index1+width-1;
					for (int j=0;j<width;j++){
						if (pixels[index1]) v1=0; else v1++;
						if (pixels[index2]) v2=0; else v2++;
						distLeft[index1++]=v1;
						distRight[index2--]=v2;
					}
				}
				// combine two (min distance)
				for (int i=0;i<pixels.length;i++) if (distRight[i]<distLeft[i])distLeft[i]=distRight[i];
				// Down
				for (int j=0;j<width;j++)distRight[j]= distLeft[j]; //  very top line (now used for down)
				index1=0;
				for (int i=width;i<pixels.length;i++){
					v1=distRight[index1++]+1;
					distRight[i]= (v1>distLeft[i])?distLeft[i]:v1;
				}
				// Up
				for (int j=pixels.length-width;j<pixels.length;j++) distUp[j]= distLeft[j]; // very bottom line
				index1=pixels.length-1;
				for (int i=pixels.length-1-width;i>=0;i--){
					v1=distUp[index1--]+1;
					distUp[i]= (v1>distLeft[i])?distLeft[i]:v1;
				}
				// combine two (min distance)
				for (int i=0;i<pixels.length;i++) if (distUp[i]<distRight[i])distRight[i]=distUp[i];
				// Now distRight contains the shortest distance from the nearest enabled pixels
				// update the original pixels to include tyhe new ones 
				for (int i=0;i<pixels.length;i++) pixels[i] |= (distRight[i]<=grow); 
			}

			void filterQuadrant(
					int [] pixels,
					int width,
					boolean fromBlack,
					int maxDist,
					boolean bordersOK
					){
				int height=pixels.length/width;
	    		showDoubleFloatArrays sdfra_instance= null;
	    		if (this.debugLevel>1) sdfra_instance= new showDoubleFloatArrays(); // just for debugging?
				
				int [] distLeft= new int [pixels.length]; 
				int [] distRight=new int [pixels.length]; // also used for down 
				int [] distUp=  new int [pixels.length];
				int sign=fromBlack?-1:1;
				int initialValue=bordersOK?0:(width+height);
				int v1,v2;
				int index1,index2;
				for (int i=0;i<height;i++){
					v1=initialValue;
					v2=initialValue;
					index1=i*width;
					index2=index1+width-1;
					for (int j=0;j<width;j++){
						if (pixels[index1]==sign) v1=0; else v1++;
						if (pixels[index2]==sign) v2=0; else v2++;
						distLeft[index1++]=v1;
						distRight[index2--]=v2;
					}
				}
				if (this.debugLevel>3){
					sdfra_instance.showArrays(distLeft, width, height,    "distLeft-"+fromBlack);
					sdfra_instance.showArrays(distRight, width, height,   "distRight-"+fromBlack);

				}
				// combine two (max distance)
				for (int i=0;i<pixels.length;i++) if (distRight[i]>distLeft[i])distLeft[i]=distRight[i];
				if (this.debugLevel>3) sdfra_instance.showArrays(distLeft, width, height,    "comboLeftRight-"+fromBlack);
				
				
				// Down
				for (int j=0;j<width;j++)distRight[j]= bordersOK?0:distLeft[j]; // now used for down
				index1=0;
				for (int i=width;i<pixels.length;i++){
//					v1=distLeft[index1++]+1;
					v1=distRight[index1++]+1;
					distRight[i]= (v1>distLeft[i])?distLeft[i]:v1;
				}
				if (this.debugLevel>3) sdfra_instance.showArrays(distRight, width, height,    "distDown-"+fromBlack);
				// Up
				for (int j=pixels.length-width;j<pixels.length;j++) distUp[j]= bordersOK?0:distLeft[j];
				index1=pixels.length-1;
				for (int i=pixels.length-1-width;i>=0;i--){
//					v1=distLeft[index1--]+1;
					v1=distUp[index1--]+1;
					distUp[i]= (v1>distLeft[i])?distLeft[i]:v1;
				}
				if (this.debugLevel>3) sdfra_instance.showArrays(distUp, width, height,    "distUp-"+fromBlack);
				// combine two  (max distance)
				for (int i=0;i<pixels.length;i++) if (distUp[i]>distRight[i])distRight[i]=distUp[i];
				if (this.debugLevel>3) sdfra_instance.showArrays(distRight, width, height,    "combo-"+fromBlack);
				// Now distRight contains the longest of 4 quadrants distance from "good" pixels - remove bad opposite side ones
				for (int i=0;i<pixels.length;i++) if ((distRight[i]>maxDist) && (pixels[i]!=sign)) pixels[i]=0; // if opposite signe (or 0) make 0 
			}
			
			
	    	public double [] getPointerXY( // returns x,y pair or null if pointer not detected
	    			double [][] backgroundBayer, // Bayer array of the background (lasers off) image 0,3 -G, 1-R,2-B
	    			double [][] pointedBayer,    // Bayer array of the (laser on) image
	    			int width,                   // image width in pixels
	    			boolean modBackground,       // modify background array (on the first pass)
	    			String title,
	    			int debugLevel   // debug level (normal == 1)
	    			){
	    		showDoubleFloatArrays sdfra_instance= null;
	    		if (debugLevel>1) sdfra_instance= new showDoubleFloatArrays(); // just for debugging?
// As high precision is not needed we can map Bayer pixels to the same grid of half resolution of the image
// 0,3 - green 1 - red (laser)
	    		int bayerG1=0;
	    		int bayerG2=3;
	    		int bayerR=1;
	    		double avrgGreenB=0.0;
//	    		double avrgGreenP=0.0;
	    		int len=backgroundBayer[bayerG1].length;
	    		int halfWidth=width/2;
	    		int halfHeight=len/halfWidth;
	    		if (debugLevel>2){
	    			String subtitles[] ={"green1","red","blue","green2","combo","5"};
	    			sdfra_instance.showArrays(pointedBayer.clone(), halfWidth, halfHeight, true, title+"-bayer", subtitles);
	    		}

	    		for (int i=0; i<len;i++){
	    			if (modBackground) backgroundBayer[bayerG1][i]=0.5*(backgroundBayer[bayerG1][i]+backgroundBayer[bayerG2][i]);
	    			avrgGreenB+=backgroundBayer[bayerG1][i];
	    			pointedBayer[bayerG1][i]=0.5*(pointedBayer[bayerG1][i]+pointedBayer[bayerG2][i]);
//	    			avrgGreenP+=pointedBayer[bayerG1][i];
	    		}
	    		avrgGreenB/=len;
//	    		avrgGreenP/=len;
	    		for (int i=0; i<len;i++){
	    			if (modBackground) {
	    				backgroundBayer[bayerR][i]/=(backgroundBayer[bayerG1][i]*(1.0-this.greenFloor)+avrgGreenB*this.greenFloor);
	    			}
	    			pointedBayer[bayerR][i] = pointedBayer[bayerR][i]/
	    			    (backgroundBayer[bayerG1][i]*(1.0-this.greenFloor)+avrgGreenB*this.greenFloor)-backgroundBayer[bayerR][i];
	    		
	    		}
	    		if (debugLevel>3) sdfra_instance.showArrays(pointedBayer[bayerR].clone(), halfWidth, halfHeight, title);
// low pass filter, 2-d
	    		DoubleGaussianBlur gb=new DoubleGaussianBlur();
	    		gb.blurDouble(pointedBayer[bayerR], halfWidth, halfHeight, this.lowpassSigma, this.lowpassSigma, 0.01);
	    		if (debugLevel>2) sdfra_instance.showArrays(pointedBayer[bayerR].clone(), halfWidth, halfHeight, title+"-smooth");
// Finding just maximum, to centroid here
	    		int indx=0;
	    		double max=pointedBayer[bayerR][indx];
	    		for (int i=0; i<len;i++){
	    			if (pointedBayer[bayerR][i]>max) {
	    				max=pointedBayer[bayerR][i];
	    				indx=i;
	    			}
	    		}
	    		double [] result={2*(indx%halfWidth)+1.0, 2*(indx/halfWidth)-1.0};
				if (debugLevel>1) System.out.println("Max="+max+"(>"+this.threshold+"?), x="+result[0]+", y="+result[1]);
	    		if (max>=this.threshold) return result;
	    		return null;
	    	}
	    	
	    	public double [][] getPointerXY( // returns x,y pair or null if pointer not detected
	    			boolean headLaserMode,
	    			double saturationRed, // maximal red intensity scaled to reduced exposure
	    			double scaleExposureForLasers,
//	    			boolean skipFirst, // do not use no-pointer image (may have different exposure time)
	    			double [][] pre_greens, // combined Bayer greens for each image, starting with no-laser (maybe Blue or none)
	    			double [][] reds,   // red Bayer component for each image, starting with no-laser
	    			boolean [][] whichOn, // array specifying which image should have pointer on
	    			int width,                   // image width in pixels
	    			String title,
	    			int debugLevel   // debug level (normal == 1)
	    			){
	    		int debugTiming=1;
	    		if (debugLevel>debugTiming) printTimingInit();
    			boolean skipFirst=scaleExposureForLasers>0.0; // do not use no-pointer image (may have different exposure time)
///								if (scaleExposureForLasers>0) saturationRed*=scaleExposureForLasers; // scaled to reduced exposure time
    			double noLaserMaxRed=(scaleExposureForLasers>0)?(saturationRed/scaleExposureForLasers):saturationRed;
    			if (this.maximalIntensity<1.0) noLaserMaxRed*=this.maximalIntensity;
    			

	    		double [][] greens=headLaserMode?null:pre_greens;
	    		int startIndex=skipFirst?1:0;
	    		showDoubleFloatArrays sdfra_instance= null;
	    		String [] subtitles_all= null;
	    		String [] subtitles= null;
	    		if (debugLevel>1) {
	    			sdfra_instance= new showDoubleFloatArrays(); // just for debugging?
	    			subtitles_all= new String [reds.length];
	    			for (int i=0;i<reds.length;i++){
	    				subtitles_all[i]="";
	    				for (int j=0;j<whichOn.length;j++) subtitles_all[i]+=whichOn[j][i]?"+":"-";
	    			}
	    			subtitles= new String [whichOn.length];
	    			for (int i=0;i<whichOn.length;i++){
	    				subtitles[i]="p-"+i;
	    			}

	    		}
// As high precision is not needed we can map Bayer pixels to the same grid of half resolution of the image
// 0,3 - green 1 - red (laser)
	    		double avrgGreen;
	    		double avrgRed;
	    		int len=reds[0].length;
	    		int halfWidth=width/2;
	    		int halfHeight=len/halfWidth;
	    		
	    		double minimalIntensity=saturationRed*this.minimalIntensity;
	    		double maximalIntensity=saturationRed*this.maximalIntensity;
	    		int [] thresholds = new int [reds[0].length];
	    		boolean [] overexposed = new boolean [len]; /* somewhat duplicates thersholds */
	    		for (int i=0;i<len;i++) overexposed[i]=reds[0][i] > noLaserMaxRed;
	    		for (int i=0;i<thresholds.length;i++) thresholds[i]=0;

	    		if (debugLevel>2){
	    			if (greens!=null) sdfra_instance.showArrays(greens, halfWidth, halfHeight, true, "green-"+title, subtitles_all);
	    			sdfra_instance.showArrays(reds,   halfWidth, halfHeight, true, "red-"+title, subtitles_all);
	    		}
	    		if (debugLevel>debugTiming) printTiming("red-"+title);
	    		boolean[]  patternMask=null;
	    		if (this.usePatternFilter){
	    			double [] ppixels=(greens!=null)?greens[0]:reds[0];
	    			patternMask=getPatternMask(	ppixels, halfWidth );
		    		if (debugLevel>2)	sdfra_instance.showArrays(patternMask, halfWidth, halfHeight,  "patternMask-"+title);
		    		if (debugLevel>debugTiming) printTiming("patternMask-"+title);
	    		}
	    		for (int i=startIndex;i<reds.length;i++) {
	    			avrgGreen=0.0;
	    			avrgRed=0.0;
	    			for (int j=0; j<len;j++) avrgRed+=  reds[i][j];
	    			if (greens!=null) for (int j=0; j<len;j++) avrgGreen+=greens[i][j];
	    			avrgGreen/=len;
	    			avrgRed/=len;
	    			if (greens!=null){
	    				for (int j=0; j<len;j++){
	    					if ((reds[i][j]<minimalIntensity)) thresholds[j] |= 1; // under
	    					if ((reds[i][j]>maximalIntensity)) thresholds[j] |= 2; // over
	    					reds[i][j]=reds[i][j]/avrgRed- greens[i][j]/avrgGreen*(1-this.greenFloor); // always
	    				}
	    			} else {
	    				for (int j=0; j<len;j++){
	    					if ((reds[i][j]<minimalIntensity)) thresholds[j] |= 1; // under
	    					if ((reds[i][j]>maximalIntensity)) thresholds[j] |= 2; // over
	    					reds[i][j]/=avrgRed;
	    				}
	    			}
	    		}
	    		DoubleGaussianBlur gb=new DoubleGaussianBlur();
	    		greens=null; // don't need it anymore
	    		if (debugLevel>2){
	    			System.out.println("saturationRed="+saturationRed+" minimalIntensity="+minimalIntensity+" maximalIntensity="+maximalIntensity);
	    			sdfra_instance.showArrays(reds,   halfWidth, halfHeight, true, "normalized-"+title, subtitles_all);
	    		}
	    		if (debugLevel>debugTiming) printTiming("normalized-"+title);
	    		double [] dThresholds=new double [len];
	    		for (int i=0;i<len;i++){
	    			dThresholds[i]=((thresholds[i] & 1)!=0)?(-1): ( ((thresholds[i] & 2)!=0)?1.0:0.0 );
	    		}
	    		if (debugLevel>2)	sdfra_instance.showArrays(dThresholds,   halfWidth, halfHeight, "intensity_limits-"+title);
	    		if (debugLevel>debugTiming) printTiming("intensity_limits-"+title);
	    		// high-pass images
	    		if (!headLaserMode && (this.highpassSigma>0)){
	    			for (int numImg=startIndex;numImg<reds.length;numImg++){
	    				double [] redBlured=reds[numImg].clone();
		    			gb.blurDouble(redBlured, halfWidth, halfHeight, this.highpassSigma, this.highpassSigma, 0.01);
		    			for (int i=0;i<redBlured.length;i++)reds[numImg][i]-=redBlured[i];
	    			}
	    		}
	    		if (debugLevel>2)sdfra_instance.showArrays(reds,   halfWidth, halfHeight, true, "highpass-"+title, subtitles_all);
	    		if (debugLevel>debugTiming) printTiming("highpass-"+title);
	    		// low pass filter, 2-d
	    		double threshold=headLaserMode?this.threshold:this.threshold; // so far - the same?
	    		double lpSigma=headLaserMode?this.headLowpassSigma:this.lowpassSigma;
	    		
	    		double [][] diffOnOff=new double[whichOn.length][len];
	    		double [][] noise=null; // used in algorithm 4
//	    		int debugX=952,debugY=665,debugAround=3,debugPointer=1;
	    		
//	    		if (headLaserMode || (this.algorithmNumber==0) || (this.algorithmNumber==2)){
	    		if (headLaserMode || (this.algorithmNumber==0)){
	    			for (int numPointer=0; numPointer <whichOn.length;numPointer++) for (int i=0;i<len;i++){
	    				double on=-1.0,off=-1.0;
	    				for (int nImg=startIndex;nImg<whichOn[numPointer].length; nImg++){
	    					if (whichOn[numPointer][nImg]){
	    						if ((on<0)  || (on>reds[nImg][i])) on=reds[nImg][i]; // smallest among "on"
	    					} else {
	    						if ((off<0) || (off<reds[nImg][i])) off=reds[nImg][i]; // largest among "off"
	    					}
	    				}
	    				diffOnOff[numPointer][i]=on-off; // difference between lowest "on" and largest "off"
	    			}
	    		} else if (this.algorithmNumber==1){
	    			for (int numPointer=0; numPointer <whichOn.length;numPointer++) {
//	    				int numSamples=0;
	    				int numPositiveSamples=0;
	    				for (int nImg=startIndex;nImg<whichOn[numPointer].length; nImg++){
//	    					numSamples++;
	    					if (whichOn[numPointer][nImg]) numPositiveSamples++;
	    				}
	    				for (int i=0;i<len;i++) {
	    					diffOnOff[numPointer][i]=1.0;
	    					for (int nImg=startIndex;nImg<whichOn[numPointer].length; nImg++) {
	    						if (whichOn[numPointer][nImg]) diffOnOff[numPointer][i] +=reds[nImg][i];
	    						else  diffOnOff[numPointer][i] -=reds[nImg][i];
	    					}
	    					diffOnOff[numPointer][i]/=numPositiveSamples;
	    				}
	    			}
	    			
	    		} else 	if (this.algorithmNumber==2){
	    			for (int numPointer=0; numPointer <whichOn.length;numPointer++) {
	    				int numSamples=0;
//	    				int numPositiveSamples=0;
	    				for (int nImg=startIndex;nImg<whichOn[numPointer].length; nImg++){
	    					numSamples++;
//	    					if (whichOn[numPointer][nImg]) numPositiveSamples++;
	    				}
	    				double pwr=1.0/numSamples;
	    				for (int i=0;i<len;i++) {
		    				double on=-1.0,off=-1.0;
		    				for (int nImg=startIndex;nImg<whichOn[numPointer].length; nImg++){
		    					if (whichOn[numPointer][nImg]){
		    						if ((on<0)  || (on>reds[nImg][i])) on=reds[nImg][i]; // smallest among "on"
		    					} else {
		    						if ((off<0) || (off<reds[nImg][i])) off=reds[nImg][i]; // largest among "off"
		    					}
		    				}
		    				if (on<off){
	    						diffOnOff[numPointer][i]=0.0;
	    						continue;
		    				}
	    					double average=0.5*(on+off); // middle between highest "off" and lowest "on" 
	    					diffOnOff[numPointer][i]=1.0;
	    					for (int nImg=startIndex;nImg<whichOn[numPointer].length; nImg++) {
	    						double diff=reds[nImg][i]-average+this.fatZero;
	    						 if (!whichOn[numPointer][nImg]) diff=-diff;
	    						if (diff>0.0) diffOnOff[numPointer][i]*=diff;
	    						else {
	    							diffOnOff[numPointer][i]=0;
	    							break;
	    						}
	    					}
	    					if (diffOnOff[numPointer][i]>0.0) {
	    						diffOnOff[numPointer][i]=Math.pow(diffOnOff[numPointer][i],pwr);
	    					}
	    					diffOnOff[numPointer][i]-=this.fatZero;
	    				}
	    			}
	    		} else 	if (this.algorithmNumber==3){
	    			for (int numPointer=0; numPointer <whichOn.length;numPointer++){
	    				for (int i=0;i<len;i++){
	    					double d=0.0;
	    					for (int nImg=1;nImg<whichOn[numPointer].length; nImg++){ // skip first image with all 0ff
	    						if (whichOn[numPointer][nImg]) d+=reds[nImg][i];
	    						else                          d-=reds[nImg][i];
	    					}
	    					diffOnOff[numPointer][i]=d/(whichOn[numPointer].length-1);
	    				}
	    			}
	    		} else 	if (this.algorithmNumber==4){ // sometimes long - 11.391 sec, other - 0.558 s
	    			double avOn,avOff;
	    			noise=new double [whichOn.length][];
		    		if (debugLevel>debugTiming) printTiming("alg_"+this.algorithmNumber+"-start-"+title);
	    			for (int numPointer=0; numPointer <whichOn.length;numPointer++){
			    		if (debugLevel>debugTiming) printTiming("numPointer-"+numPointer+"-"+whichOn[numPointer].length+"-"+len+"-"+title);
	    				noise[numPointer]=new double[len];
	    				for (int i=0;i<len;i++){
//	    					double d=0.0;
	    					avOn= 0.0;
	    					avOff=0.0;
	    					for (int nImg=1;nImg<whichOn[numPointer].length; nImg++){
	    						if (whichOn[numPointer][nImg]) {
//	    							d+=reds[nImg][i];
	    							avOn+=reds[nImg][i];
	    						} else {
//	    							d-=reds[nImg][i];
	    							avOff+=reds[nImg][i];
	    						}
	    					}
	    					double d=avOn-avOff;
	    					diffOnOff[numPointer][i]=d/(whichOn[numPointer].length-1);
	    					avOn/=((whichOn[numPointer].length-1)/2);
	    					avOff/=((whichOn[numPointer].length-1)/2);
	    					double s2=0.0;
	    					for (int nImg=1;nImg<whichOn[numPointer].length; nImg++){
	    						d=reds[nImg][i]-((whichOn[numPointer][nImg])?avOn:avOff);
	    						s2+=d*d;
	    					}
//	    					s2n[numPointer][i]=diffOnOff[numPointer][i]/(Math.sqrt(s2/(whichOn[numPointer].length-1)));
	    					noise[numPointer][i]=(Math.sqrt(s2/(whichOn[numPointer].length-1)));
	    				}
	    			}
	    		}
	    		
	    		// mask out too dim/too bright - TODO: still need to grow overexposed mask
	    		/* Mask out pixels closer than overexposedRadius from overesposed areas*/
	    		//overexposedRadius
	    		if (debugLevel>debugTiming) printTiming("alg_"+this.algorithmNumber+"-done-"+title);
	    		if (!headLaserMode) {
    				int discardBorder=5;
	    			if (this.overexposedRadius>0){
	    				boolean [] overexposedTmp;
	    				for (int n=0;n<this.overexposedRadius;n++){
	    					overexposedTmp=overexposed;
	    					overexposed=new boolean[len];
	    					int i=0;
//	    					System.out.println("halfWidth="+halfWidth+"  halfHeight="+halfHeight+"  len="+len);
	    					for (int iy=0;iy<halfHeight;iy++) for (int ix=0;ix<halfWidth;ix++){
	    						/*	    						
	    						if (i>=len){
	    	    					System.out.println("  iy="+iy+"  ix="+ix+"  i="+i);
	    							break;
	    						}
	    						overexposed[iy*halfWidth+ix]  = ((iy>0) && overexposedTmp[i-halfWidth]);
	    						overexposed[iy*halfWidth+ix] |= ((ix>0) && overexposedTmp[i-1]);
	    						overexposed[iy*halfWidth+ix] |= ((iy<(halfHeight-1)) && overexposedTmp[i+halfWidth]);
	    						overexposed[iy*halfWidth+ix] |= ((ix<(halfWidth-1)) && overexposedTmp[i+1]);

	    						
	    						overexposed[i++]=
	    								((iy>0) && overexposedTmp[i-halfWidth]) ||
	    								((ix>0) && overexposedTmp[i-1]) ||
	    								((iy<(halfHeight-1)) && overexposedTmp[i+halfWidth]) ||
	    								((ix<(halfWidth-1)) && overexposedTmp[i+1]);
*/	    								
	    						overexposed[i]  = overexposedTmp[i];
	    						overexposed[i] |= ((iy>0) && overexposedTmp[i-halfWidth]);
	    						overexposed[i] |= ((ix>0) && overexposedTmp[i-1]);
	    						overexposed[i] |= ((iy<(halfHeight-1)) && overexposedTmp[i+halfWidth]);
	    						overexposed[i] |= ((ix<(halfWidth-1)) && overexposedTmp[i+1]);
	    						i++;
	    					}
	    				}
	    			}
		    		if (debugLevel>debugTiming) printTiming("grown_by_"+this.overexposedRadius+"-"+title);
					int debugNumBorder=0;
					for (int iy=0;iy<halfHeight;iy++) for (int ix=0;ix<halfWidth;ix++){
						if ((iy<discardBorder) || (ix<discardBorder) || (ix>=(halfWidth-discardBorder)) || (iy>=(halfHeight-discardBorder))){
							overexposed[iy*halfWidth+ix]=true;
							debugNumBorder++;
						}
					}
					int debugNumOver=0;
					for (int i=0;i<len;i++){
						if (overexposed[i]) debugNumOver++;
					}
					if (debugLevel>debugTiming) printTiming("Number of overexposed/border pixels="+debugNumOver+" border pixels="+debugNumBorder+" for "+title);

	    			for (int numPointer=0; numPointer <whichOn.length;numPointer++) {
    					for (int i=0;i<len;i++) if (overexposed[i]) diffOnOff[numPointer][i]=0.0; // too close to overexposed in no-lasers image
	    			}
	    			for (int numPointer=0; numPointer <whichOn.length;numPointer++) for (int nImg=startIndex;nImg<whichOn[numPointer].length; nImg++){
	    				if (whichOn[numPointer][nImg]){
//	    					for (int i=0;i<len;i++) if ((thresholds[i]& (1<<nImg))!=0) diffOnOff[numPointer][i]=0.0; // too dim for on
	    					for (int i=0;i<len;i++) if ((thresholds[i]& 1)!=0) diffOnOff[numPointer][i]=0.0; // too dim for on
	    				}
//	    				 else {
//	    					for (int i=0;i<len;i++) if ((thresholds[i]& (1<<(nImg+numImages)))!=0) diffOnOff[numPointer][i]=0.0; // too bright for off
//	    					for (int i=0;i<len;i++) if ((thresholds[i]& 2)!=0) diffOnOff[numPointer][i]=0.0; // too bright for off
//	    				}
	    			}
	    		}
	    		if (debugLevel>2){
	    			sdfra_instance.showArrays(diffOnOff,   halfWidth, halfHeight, true, "diffOnOff-"+title, subtitles);
	    			if (noise!=null) sdfra_instance.showArrays(noise,   halfWidth, halfHeight, true, "noise-"+title, subtitles);
	    		}
	    		if (debugLevel>debugTiming) printTiming("diffOnOff-"+title);
	    		if (lpSigma>0.0) {
	    			for (int numPointer=0; numPointer <whichOn.length;numPointer++){
	    				gb.blurDouble(diffOnOff[numPointer], halfWidth, halfHeight, lpSigma, lpSigma, 0.01);
	    				// TODO: use different (2x?) sigma for noise? Otherwise
	    				if (noise!=null) gb.blurDouble(noise[numPointer], halfWidth, halfHeight, lpSigma, lpSigma, 0.01);
	    			}
	    		}
	    		if (debugLevel>2){
	    			sdfra_instance.showArrays(diffOnOff,   halfWidth, halfHeight, true, "diffSmooth-"+(threshold)+"-"+title, subtitles);
	    			if (noise!=null) sdfra_instance.showArrays(noise,   halfWidth, halfHeight, true, "noise_Smooth-"+(threshold)+"-"+title, subtitles);
	    		}
	    		if (debugLevel>debugTiming) printTiming("diffSmooth-"+title);
    			if (noise!=null){
	    			for (int numPointer=0; numPointer <whichOn.length;numPointer++) for (int i=0;i<len;i++){
	    				noise[numPointer][i]=diffOnOff[numPointer][i]/noise[numPointer][i]; // now - s/n
	    			}
		    		if (debugLevel>2)	sdfra_instance.showArrays(noise,   halfWidth, halfHeight, true, "s2n_Smooth-"+(threshold)+"-"+title, subtitles);
		    		if (debugLevel>debugTiming) printTiming("s2n_Smooth-"+title);
    			}

    			
// zero out non-local max pixels
	    		double [][] localMaxPixels=new double [diffOnOff.length][diffOnOff[0].length];
    			for (int numPointer=0; numPointer <whichOn.length;numPointer++){
    				localMaxPixels[numPointer]=diffOnOff[numPointer].clone();
    				boolean [] isLocalMax=localMaximum(
    						localMaxPixels[numPointer], //double [] pixels,
    						halfWidth, // int width,
    						(int) this.localMaxRadius/2, //); //int radius)
    						debugLevel-1);
    	    		if (debugLevel>3) sdfra_instance.showArrays(isLocalMax,   halfWidth, halfHeight, "isLocalMax-"+(numPointer)+"-"+title);

    				for (int i=0;i<localMaxPixels[numPointer].length;i++) if (!isLocalMax[i]) localMaxPixels[numPointer][i]=0.0;
    			}
    			
    			// remove close to overexposed and border pixels
    			if (!headLaserMode) {
	    			for (int numPointer=0; numPointer <whichOn.length;numPointer++) {
    					for (int i=0;i<len;i++) if (overexposed[i]) localMaxPixels[numPointer][i]=0.0; // too close to overexposed in no-lasers image
	    			}
	    		}    			
    			
	    		if (debugLevel>2) sdfra_instance.showArrays(localMaxPixels,   halfWidth, halfHeight, true, "localmax-"+(threshold)+"-"+title, subtitles);
	    		if (debugLevel>debugTiming) printTiming("localmax-"+title);
	    		// filter by S/N ratio
    			if (noise!=null){
        			for (int numPointer=0; numPointer <whichOn.length;numPointer++){
        				for (int i=0;i<localMaxPixels[numPointer].length;i++) if (noise[numPointer][i]<this.laserSignalToNoise) localMaxPixels[numPointer][i]=0.0;
        			}	    		
    	    		if (debugLevel>2)sdfra_instance.showArrays(localMaxPixels,   halfWidth, halfHeight, true, "localmax_s2n-"+(this.laserSignalToNoise)+"-"+title, subtitles);
    	    		if (debugLevel>debugTiming) printTiming("localmax_s2n-"+title);
    			}
    			
    			
    			double [] firstMax=  new double [whichOn.length];
    			int [] firstMaxIndex=new int [whichOn.length];
    			int [] numberOfMax=new int [whichOn.length];
    			// final filter by a global threshold
    			for (int numPointer=0; numPointer <whichOn.length;numPointer++){
    				firstMax[numPointer]=0.0;
    				numberOfMax[numPointer]=0;
    				firstMaxIndex[numPointer]=0;
    				for (int i=0;i<localMaxPixels[numPointer].length;i++) {
    					if ((localMaxPixels[numPointer][i]> 0.0) && (localMaxPixels[numPointer][i]>=threshold)){ // is threshold>0.0 always
    						if (localMaxPixels[numPointer][i]>firstMax[numPointer]) {
    							firstMax[numPointer]=localMaxPixels[numPointer][i];
    							firstMaxIndex[numPointer]=i;
    						}
    						numberOfMax[numPointer]++;
    					} else{
    						localMaxPixels[numPointer][i]=0.0; 
    					}
    				}
    			}	    		
	    		// zero out all out of pattern maximums
	    		if (patternMask!=null){
	    			for (int numPointer=0; numPointer <whichOn.length;numPointer++){
	    				for (int i=0;i<localMaxPixels[numPointer].length;i++) if (!patternMask[i]) localMaxPixels[numPointer][i]=0.0;
	    			}	    		
		    		if (debugLevel>2)	sdfra_instance.showArrays(localMaxPixels,   halfWidth, halfHeight, true, "in-patt-max-"+(threshold)+"-"+title, subtitles);
    	    		if (debugLevel>debugTiming) printTiming("in-patt-max-"+title);
	    		}
	    		if (debugLevel>2)sdfra_instance.showArrays(localMaxPixels,   halfWidth, halfHeight, true, "localmax_threshold-"+(this.laserSignalToNoise)+"-"+title, subtitles);
	    		if (debugLevel>debugTiming) printTiming("localmax_threshold-"+(this.laserSignalToNoise)+"-"+title);
	    		if (debugLevel>1) {
	    			String dbgStr="";
	    			for (int numPointer=0; numPointer <numberOfMax.length;numPointer++) if (numberOfMax[numPointer]>0){
	    				dbgStr+=" "+numPointer+":"+IJ.d2s(firstMax[numPointer],4)+" ["+(firstMaxIndex[numPointer]%halfWidth)+":"+
	    				(firstMaxIndex[numPointer]/halfWidth)+"]("+numberOfMax[numPointer]+")";
	    			}
	    			if (dbgStr.length()>0) System.out.println("Maximums found: "+dbgStr);
	    		}
	    		// remove smaller maximum if it is too close to larger one, and if there are some left - find the next replacement
	    		
	    		while (this.fartherstOffender>0){
	    			int n1=-1,n2=-1;
	    			boolean tooClose=false;
	    			double co2=0.25*this.fartherstOffender*this.fartherstOffender;
	    			
	    			for (n1=0;n1<numberOfMax.length;n1++) if (numberOfMax[n1]>0){
	    				for (n2=n1+1;n2<numberOfMax.length;n2++) if (numberOfMax[n2]>0){
	    					double dx=(firstMaxIndex[n1]%halfWidth)-(firstMaxIndex[n2]%halfWidth);
	    					double dy=(firstMaxIndex[n1]/halfWidth)-(firstMaxIndex[n2]/halfWidth);
	    					if (dx*dx+dy*dy < co2) {
	    						tooClose=true;
	    						if (debugLevel>0) { // rare events, output details to verify program
	    							System.out.println ("Two detected pointers in image "+title+": "+n1+"("+numberOfMax[n1]+") and "+n2+"("+numberOfMax[n2]+") are too close,");
	    							System.out.println ("Distance="+IJ.d2s(2*Math.sqrt(dx*dx+dy*dy),3)+" sensor pixels is smaller than configured limit of "+this.fartherstOffender);
	    						}
	    						break;
	    					}

	    				}
	    				if (tooClose) break;
	    			}
	    			if (!tooClose) break;
	    			int numPointer= (firstMax[n1]>firstMax[n2])?n2:n1;
					if (debugLevel>0) { // rare events, output details to verify program
						int numFirstPointer=(firstMax[n1]>firstMax[n2])?n1:n2;
						System.out.println ("More intense pointer is #"+numFirstPointer+" ("+firstMax[numFirstPointer]+"), x="
								+(2*(firstMaxIndex[numFirstPointer]%halfWidth))+" y="+(2*(firstMaxIndex[numFirstPointer]/halfWidth)));
						System.out.println ("Will remove other pointer #"+numPointer+" ("+firstMax[numPointer]+"), x="
								+(2*(firstMaxIndex[numPointer]%halfWidth))+" y="+(2*(firstMaxIndex[numPointer]/halfWidth)));
					}
	    			
	    			localMaxPixels[numPointer][firstMaxIndex[numPointer]]=0.0;
	    			numberOfMax[numPointer]--;
	    			
	    			
	    			if (numberOfMax[numPointer]>0){ // look for the next maximum
	    				firstMax[numPointer]=localMaxPixels[numPointer][0];
	    				firstMaxIndex[numPointer]=0;
	    				for (int i=0;i<localMaxPixels[numPointer].length;i++) if ((localMaxPixels[numPointer][i]>0) && (localMaxPixels[numPointer][i]>firstMax[numPointer])){
	    					firstMax[numPointer]=localMaxPixels[numPointer][i];
	    					firstMaxIndex[numPointer]=i;
	    				}
						if (debugLevel>0) { // rare events, output details to verify program
							System.out.println ("Found replacement pointer for #"+numPointer+" ("+firstMax[numPointer]+"), x??="
									+(2*(firstMaxIndex[numPointer]%halfWidth))+" y??="+(2*(firstMaxIndex[numPointer]/halfWidth)));
							System.out.println (numberOfMax[numPointer]+ " pointer candidate"+((numberOfMax[numPointer]>1)?"s":"")+" remain"+((numberOfMax[numPointer]>1)?"s":"")+".");
						}
	    			}
	    		}
	    		
/*	    		
	    		// remove points close (but not too close) to bright/overexposed OBSOLETE?
	    		if (!headLaserMode && (this.fartherstOffender>0)) {
	    			for (int numPointer=0; numPointer <whichOn.length;numPointer++){ //
//	    				for (int i=0;i<diffOnOff[numPointer].length;i++) if (diffOnOff[numPointer][i]>=threshold){
	    				for (int i=0;i<diffOnOff[numPointer].length;i++) if (localMaxPixels[numPointer][i]>=threshold){
	    					int xc=i%halfWidth;
	    					int yc=i/halfWidth;
	    					int xMin=xc-this.fartherstOffender;
	    					int xMax=xc+this.fartherstOffender;
	    					int yMin=yc-this.fartherstOffender;
	    					int yMax=yc+this.fartherstOffender;
	    					int xMinC=xc-this.closestOffender;
	    					int xMaxC=xc+this.closestOffender;
	    					int yMinC=yc-this.closestOffender;
	    					int yMaxC=yc+this.closestOffender;
	    					if (xMin<0) xMin=0;
	    					if (xMax>=halfWidth) xMax=halfWidth-1;
	    					if (yMin<0) yMin=0;
	    					if (yMax>=halfHeight) yMax=halfHeight-1;
	    					for (int y=yMin;y<=yMax;y++)
	    						for (int x=xMin;x<=xMax;x++)
	    							if (!((x>xMinC) && (x<xMaxC) && (y>yMinC) && (y<yMaxC)) && ((thresholds[y*halfWidth+x]& 2)!=0)) {
	    								diffOnOff[numPointer][i]=0.0;
	    								break;
	    							}
	    				}
	    			}
		    		if (debugLevel>2)	sdfra_instance.showArrays(diffOnOff,   halfWidth, halfHeight, true, "filteredSmooth-"+title, subtitles);
	    		}
*/	    		
	    		// Finding just maximums, no centroid here
	    		double [][] pointers = new double[whichOn.length][];
	    		for (int numPointer=0; numPointer <whichOn.length;numPointer++){
		    		if (numberOfMax[numPointer]>0) {
		    			
		    			pointers[numPointer]=new double[2];
		    			int [] localMaxXY={firstMaxIndex[numPointer]%halfWidth,firstMaxIndex[numPointer]/halfWidth};
		    			double [] dMaxXY= {localMaxXY[0], localMaxXY[1]};
	    				if (debugLevel>2) System.out.println("Pointer "+numPointer+" max X="+dMaxXY[0]+" max Y="+dMaxXY[1]);
		    			if (this.quadraticScaleSigma>0.0){
		    				double quadSigma=this.quadraticScaleSigma*((lpSigma>0.0)?lpSigma:1.0);
		    				double k=0.5/(quadSigma*quadSigma);
		    				int range=(int) Math.round(quadSigma*3.0);
		    				if (range<1) range=1;
		    				int minX=localMaxXY[0]-range;
		    				int maxX=localMaxXY[0]+range;
		    				int minY=localMaxXY[1]-range;
		    				int maxY=localMaxXY[1]+range;
		    				if (minX<0)minX=0;
		    				if (minY<0)minY=0;
		    				if (maxX>= halfWidth) maxX= halfWidth-1;
		    				if (maxY>=halfHeight) maxY=halfHeight-1;
		    				double [][][] data =new double [(maxX-minX+1)*(maxY-minY+1)][3][];
		    				int index=0;
		    				for (int y=minY;y<=maxY;y++) for (int x=minX;x<=maxX;x++){
		    					data[index][0] = new double [2];
		    					data[index][0][0] = x-localMaxXY[0];
		    					data[index][0][1] = y-localMaxXY[1];
		    					data[index][1]=     new double[1];
		    					data[index][1][0]=  diffOnOff[numPointer][x+halfWidth*y];
		    					data[index][2]=     new double[1];
		    					data[index][2][0]=  Math.exp(-k*(data[index][0][0]*data[index][0][0] + data[index][0][1]*data[index][0][1]));
		    					index++;
		    				}
		    				double [] corrXY=(new PolynomialApproximation()).quadraticMax2d (data);
		    				if (corrXY!=null) {
		    					dMaxXY[0]+=corrXY[0];
		    					dMaxXY[1]+=corrXY[1];
		    				}
		    				if (debugLevel>2) { // something wrong, (E-17)
		    					if (corrXY!=null) System.out.println("Pointer "+numPointer+" corr X="+corrXY[0]+" corr Y="+corrXY[1]);
		    					else System.out.println("Pointer "+numPointer+": failed to find correction by quadratic approximation of the vicinity");
		    				}
		    			}
		    			// bayer shift
		    			pointers[numPointer][0]=2*dMaxXY[0]+1.0;
		    			pointers[numPointer][1]=2*dMaxXY[1]+0.0; //-1.0;
		    		} else pointers[numPointer]=null;
	    			if (debugLevel>1) {
	    				if (pointers[numPointer]!=null) 	System.out.println("Pointer "+numPointer+": Max="+firstMax[numPointer]+"(>"+this.threshold+"?), x="+
		    					pointers[numPointer][0]+", y="+pointers[numPointer][1]);
	    				else if (debugLevel>2) System.out.println("Pointer "+numPointer+" is not detected");
	    			}
	    		}
	    		if (debugLevel>debugTiming) printTiming("getPointerXY()-done for "+title);
	    		return pointers;
	    	}
	    }
    
}