package com.elphel.imagej.calibration;

/**
 **
 ** 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.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
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 com.elphel.imagej.common.DoubleFHT;
import com.elphel.imagej.common.DoubleGaussianBlur;
import com.elphel.imagej.common.PolynomialApproximation;
import com.elphel.imagej.common.ShowDoubleFloatArrays;
import com.elphel.imagej.common.WindowTools;
import com.elphel.imagej.jp4.JP46_Reader_camera;
import com.elphel.imagej.lwir.LwirReaderParameters;

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.PointRoi;
import ij.gui.Roi;
import ij.process.FHT; // get rid, change to double
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;

public class MatchSimulatedPattern {
	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 static 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 static int[][] dfltShifts = { { 0, 0 }, { 0, 1 }, { 0, 0 }, { 1, 0 }, { 0, 1 }, { 0, 0 }, { 1, 0 },
			{ 0, 0 } };
	final private static 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;
//	}

	public MatchSimulatedPattern(int fft_size) {
		this.FFT_SIZE = fft_size;
	}

	// not real clone, just for threads - if there will be FFT - keep individual
	@Override
	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 static int[][] getRemapMatrix(int[] UVShiftRot) {
		// 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 static 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 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);
		// ShowDoubleFloatArrays.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);

			}
		// ShowDoubleFloatArrays.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)
			ShowDoubleFloatArrays.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))
					ShowDoubleFloatArrays.showArrays(mask, fftSize, fftSize, "mask_color");
				gb.blurDouble(mask, fftSize, fftSize, sigmaScale * averageLength, sigmaScale * averageLength, 0.01);
				if ((c == 0) && (debugLevel > 1))
					ShowDoubleFloatArrays.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);
		// ShowDoubleFloatArrays.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);

			}
		// ShowDoubleFloatArrays.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)
			ShowDoubleFloatArrays.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))
					ShowDoubleFloatArrays.showArrays(mask, fftSize, fftSize, "mask_color");
				gb.blurDouble(mask, fftSize, fftSize, sigmaScale * averageLength, sigmaScale * averageLength, 0.01);
				if ((c == 0) && (debugLevel > 1)) {
					ShowDoubleFloatArrays.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];
					ShowDoubleFloatArrays.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_mono_pixels, // pixel array to process (no windowing!), two
																			// greens will be used
			PatternDetectParameters patternDetectParameters, double min_half_period, double max_half_period,
			boolean greens, // this is a pattern for combined greens (diagonal), adjust results accordingly
			String title) { // title prefix to use for debug images
		if (bayer_mono_pixels == null)
			return null;
		if (bayer_mono_pixels.length < 1)
			return null;
		if (bayer_mono_pixels[0] == null)
			return null;
		if (greens) {
			if (bayer_mono_pixels.length < 4)
				return null;
			if (bayer_mono_pixels[3] == null)
				return null;
		} else {
			if (bayer_mono_pixels.length > 1)
				return null;
		}
		boolean is_mono = bayer_mono_pixels.length == 1;
		int tile_size2 = bayer_mono_pixels[0].length;
		int tile_size = (int) Math.sqrt(tile_size2);
		int hsize = tile_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
				tile_size / 2, // top right
				tile_size * tile_size / 2, // bottom left
				(tile_size + 1) * tile_size / 2, // bottom right
				(tile_size + 1) * tile_size / 4, // center
				tile_size / 4, // top
				tile_size * tile_size / 4, // left
				(tile_size + 2) * tile_size / 4, // right
				(2 * tile_size + 1) * tile_size / 4 }; // bottom

		int i, j, iq;
		int index, qindex;
		if (this.debugLevel > 2)
			ShowDoubleFloatArrays.showArrays(bayer_mono_pixels, tile_size, tile_size, title + "-bayer");
		for (iq = 0; iq < 9; iq++) {
			index = quarterIndex[iq];
			qindex = 0;
			if (is_mono) {
				quarter_pixels[iq] = new double[hsize * hsize];
				for (i = 0; i < hsize; i++) {
					for (j = 0; j < hsize; j++) { // quarter_pixels[iq][qindex++]=input_pixels[index++];
						quarter_pixels[iq][qindex++] = bayer_mono_pixels[0][index++];
					}
					index += hsize; // jump to the next line
				}
			} else {
				for (i = 0; i < hsize; i++) {
					for (j = 0; j < hsize; j++) { // quarter_pixels[iq][qindex++]=input_pixels[index++];
						green0[qindex] = bayer_mono_pixels[0][index];
						green3[qindex++] = bayer_mono_pixels[3][index++];
					}
					// quarter_pixels[iq]=combineDiagonalGreens (
					// green0,
					// green3,
					// hsize,
					// hsize);
					index += hsize; // jump to the next line
				}
				// Moved outside of the i-loop, that was wrong!
				quarter_pixels[iq] = combineDiagonalGreens(green0, green3, hsize, hsize);

			}

			quarter_pixels[iq] = normalizeAndWindow(quarter_pixels[iq], quarterHamming);
			if (this.debugLevel > 2)
				ShowDoubleFloatArrays.showArrays(quarter_pixels[iq], hsize, hsize, title + "-new" + iq);
			// findPattern - see MSP 3290:
			quarter_patterns[iq] = findPattern(null, // DoubleFHT doubleFHT,
					quarter_pixels[iq], hsize, patternDetectParameters, min_half_period, max_half_period, 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 normalize correlation result
			double[][] wVectors, // wave vectors (same units as the pixels array)
			double contrastSelectSigmaCenter, // Gaussian sigma to select correlation centers (in PIXELS), 2.0
			double contrastSelectSigmaOther, // Gaussian sigma to select correlation off-centers centers (fraction of UV
												// period), 0.1

			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)
				contrastSelectSigmaCenter, // Gaussian sigma to select correlation centers (in PIXELS), 2.0
				contrastSelectSigmaOther, // Gaussian sigma to select correlation off-centers centers (fraction of UV
											// period), 0.1
				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" };
			ShowDoubleFloatArrays.showArrays(dbgPixels, size, size, true, title + "_CORR_MASK", titles);
		}
		return contrast;
	}

	public double[] correlationContrast(double[] pixels, // square pixel array
			double[][] wVectors, // wave vectors (same units as the pixels array)
			double sigma_center, // GGaussian sigma to select correlation centers (in PIXELS), 1.5
			double sigma_other, // Gaussian sigma to select correlation off-centers centers (fraction of UV
								// period), 0.1
			double x0, // center coordinates
			double y0, String title, // title base for optional plots names
			int debugLevel) {
		double avg_rad = 2.0 / Math.sqrt(wVectors[0][0] * wVectors[0][0] + wVectors[0][1] * wVectors[0][1]
				+ wVectors[1][0] * wVectors[1][0] + wVectors[1][1] * wVectors[1][1]);
		double max_center_sigma = 0.05 * avg_rad; // approximate - make configurable or just relative rather than
													// absolute?
		if (sigma_center > max_center_sigma) {
			sigma_center = max_center_sigma;
		}
		double[] badContrasts = { -1.0, -1.0 };
		double sigma32_center = 9 * sigma_center * sigma_center; // in pixels
		double k_center = -0.5 / (sigma_center * sigma_center); // in pixels
		double sigma32_other = 9 * sigma_other * sigma_other; // in periods
		double k_other = -0.5 / (sigma_other * sigma_other); // in periods
		double[][] sampleCentersXY = { { 0.0, 0.0 }, { 0.25, 0.25 }, { 0.25, -0.25 }, { -0.25, 0.25 },
				{ -0.25, -0.25 } };
		// System.out.println("avg_rad="+avg_rad);
		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;
		// TODO: limit sigma_center to fit between others;
		double[] dbgMask = new double[size * size];
		Arrays.fill(dbgMask,0.0);
		double[] s = { 0.0, 0.0 };
		double[] w = { 0.0, 0.0 };
		double   max_center = 0.0;
		// double [] dbg_weights = new double [size*size];
		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++) {
					if (sampleTypes[np] == 0) { // center spot, size in pixels
						r2 = xy[0] * xy[0] + xy[1] * xy[1];
						if (r2 < sigma32_center) {
							double m = Math.exp(k_center * r2);
							dbgMask[index] += m;
							w[sampleTypes[np]] += m;
							double d = m * pixels[index];
							max_center = Math.max(d, max_center);
//							if (sampleTypes[np] > 0)
//								d *= pixels[index]; // squared
							s[sampleTypes[np]] += d;
						}
					} else { // between correlation spots, size relative to the periods
						double dx = uv[0] - sampleCentersXY[np][0];
						double dy = uv[1] - sampleCentersXY[np][1];
						r2 = dx * dx + dy * dy;
						if (r2 < sigma32_other) {
							double m = Math.exp(k_other * 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[][] dbg_corr_mask = { pixels, dbgMask };
//		double aCenter = s[0] / w[0];
		double aCenter = max_center;
		double aQuiet = Math.sqrt(s[1] / w[1]);
		double rContrast = aCenter / aQuiet;
//		double aContrast = aCenter / size / size;
		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" };
			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" };
			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[][] findPatternUsedNow(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
		int local_debug_level = this.debugLevel; // to change it in debugger
		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 (local_debug_level > 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 (local_debug_level > 5)
			ShowDoubleFloatArrays.showArrays((float[]) fht.getPixels(), size, size, title + "_FHT");

		// 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 (local_debug_level > 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 (local_debug_level > 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 (local_debug_level > 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];
// show pixels here?
		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 (local_debug_level > 2) {
				System.out.println("findPattern() 1: Failed to find a pattern");
				if (local_debug_level > 2) {
					ShowDoubleFloatArrays.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 (local_debug_level > 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 (local_debug_level > 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 (local_debug_level > 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));
		}
		// returns frequencies (cycles/per oversampled pixel) that assume 4:1 oversampling (
		double[][] max2 = findFirst2MaxOnCorrelation(pixels, startPoints, patternDetectParameters);

		/** TODO: get out on failure */
		if (max2 == null) {
			if (local_debug_level > 2) {
				System.out.println("findPattern() 2: Failed to find a pattern");
				if (local_debug_level > 2) {
					ShowDoubleFloatArrays.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 (local_debug_level > 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 (local_debug_level > 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 (local_debug_level > 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 (local_debug_level > 5) {
				System.out.println("kx=" + IJ.d2s(interpolateKxy[maxIndex][0], 4) + " ky="
						+ IJ.d2s(interpolateKxy[maxIndex][1], 4));
			}
			if (local_debug_level > 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 (local_debug_level > 2)
			System.out.println();
		return result;
	}

	// Above is currently used for years, below is for experiments

	public double[][] findPattern(DoubleFHT doubleFHT, double[] input_pixels, // pixel array to process
			int size, // FFT size
			PatternDetectParameters patternDetectParameters, double min_half_period, double max_half_period,
			boolean greens, // this is a pattern for combined greens (diagonal), adjust results accordingly
			String title) { // title prefix to use for debug images

		if (!patternDetectParameters.use_large_cells) {
			return findPatternUsedNow(input_pixels, // pixel array to process
					size, // FFT size
					patternDetectParameters, greens, // this is a pattern for combined greens (diagonal), adjust results
														// accordingly
					title); // title prefix to use for debug images
		}
		int debug_threshold = 4;

//		this.debugLevel = 11;
		if (this.debugLevel > 5) {
			System.out.println("findPattern(): this.debugLevel = " + this.debugLevel);
		}
		if (doubleFHT == null)
			doubleFHT = new DoubleFHT(); // pass from the caller to re-use
		double[] cpixels = input_pixels.clone();
		if (min_half_period < 2) {
			min_half_period = 2;
		}
		double[] dfht = new double[input_pixels.length]; // will save FHT from phase auto correlation
		double[][] rslt = new double[2][3];
		cpixels = doubleFHT.phaseCorrelate(cpixels, patternDetectParameters.phaseCoeff, 0,
				patternDetectParameters.lowpass_sigma, dfht);
		if (this.debugLevel > (debug_threshold + 1)) {
			double[][] src_corr = { input_pixels, cpixels };
			String[] titles = { "source", "corr" };
			ShowDoubleFloatArrays.showArrays(src_corr, size, size, true,
					"fft_corr-" + patternDetectParameters.phaseCoeff, titles);
		}

		int[][] half_periods = findFirst2MinOnCorrelation(cpixels, min_half_period, max_half_period);
		if (half_periods != null) { // check vectors are not close to colinear
			double sin = (half_periods[0][0] * half_periods[1][1] - half_periods[0][1] * half_periods[1][0])
					/ Math.sqrt((half_periods[0][0] * half_periods[0][0] + half_periods[0][1] * half_periods[0][1])
							* (half_periods[1][0] * half_periods[1][0] + half_periods[1][1] * half_periods[1][1]));
			if (Math.abs(sin) < patternDetectParameters.min_sin) {
				if (this.debugLevel > debug_threshold)
					System.out.println("Half-period vectors on correlation are too close to colinear - sin=" + sin);
				return null;
			}

		}
		if (this.debugLevel > 7) {
			if (half_periods == null) {
				if (this.debugLevel > (debug_threshold + 0))
					System.out.println("Could not find half-periods on correlation");
				return null;
			} else {
				if (this.debugLevel > (debug_threshold + 1))
					System.out.println("first half period (x/y) :" + half_periods[0][0] + "/" + half_periods[0][1]
							+ ", second half period :" + half_periods[1][0] + "/" + half_periods[1][1]);
			}
		}

		double[][] dhp = getPeriodsFromCorrelation(cpixels, half_periods, // a pair of half-periods
				patternDetectParameters.min_frac, patternDetectParameters.no_crazy);
		if (dhp != null) {
			if (this.debugLevel > (debug_threshold + 1))
				System.out.println(String.format("findPattern(): dhp= [[%.5f,%.5f],[%.5f,%.5f]]", dhp[0][0], dhp[0][1],
						dhp[1][0], dhp[1][1]));
		} else {
			if (this.debugLevel > (debug_threshold + 0))
				System.out.println("findPattern():  getPeriodsFromCorrelation() FAILED");
		}

		/* 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(dhp, rotMatrix);
			for (int i = 0; i < 2; i++)
				for (int j = 0; j < 2; j++)
					rslt[i][j] = max2orig[i][j]; // result is [2][3], max2orig is [2][2]
		} else {
			for (int i = 0; i < 2; i++)
				for (int j = 0; j < 2; j++)
					rslt[i][j] = 2.0 * dhp[i][j]; // result is [2][3], max2 is [2][2]
		}
		if (this.debugLevel > (debug_threshold + 0)) {
			System.out.println(
					"Corrected to original pixels[0]  x=" + IJ.d2s(rslt[0][0], 4) + " y=" + IJ.d2s(rslt[0][1], 4));
			System.out.println(
					"Corrected to original pixels[1]  x=" + IJ.d2s(rslt[1][0], 4) + " y=" + IJ.d2s(rslt[1][1], 4));
		}

		// Calculate complex FFT - needed to determine pattern phases (autocorrelation
		// does not provide this
		// dfht array has FHT(input_pixels) save from calculation of the phase
		// autocorrelation

		double[][][] fft_cmplx = doubleFHT.FHT2FFTHalf(dfht, size);
		if (this.debugLevel > (debug_threshold + 2)) {
			ShowDoubleFloatArrays.showComplex(fft_cmplx, "fft_cmplx");
		}

		/*
		 * Calculate locations of the maximums on FFT (corresponding to the diagonals of
		 * the checkerboard pattern)
		 */
		double[][] max_on_FFT = { { 2 * size * (dhp[0][0] - dhp[1][0]), 2 * size * (dhp[0][1] - dhp[1][1]) },
				{ 2 * size * (dhp[0][0] + dhp[1][0]), 2 * size * (dhp[0][1] + dhp[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[] max_phases = getPatternPhasesFromFFT(fft_cmplx, size, max_on_FFT);
		double[] checker_phases = findCheckerPhases(dhp,
				max_phases); /* may be different for greens==true . No, the same */
		for (int i = 0; i < 2; i++)
			rslt[i][2] = checker_phases[i];
		if (this.debugLevel > (debug_threshold + 0))
			System.out.println();
		return rslt;
	}

	/* ======================================================================== */
	// calculate pattern 3D phases by interpolating im/re for the found maximums
	private double[] getPatternPhasesFromFFT(double[][][] fft_complex, int size, double[][] maxOnFFT) {
		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 (int j = 0; j < 2; j++) {
				ix = (interpolateXY[maxIndex][0] + j) % size;
				for (int i = 0; i < 2; i++) {
					iy = interpolateXY[maxIndex][1] + i; // next: OOB 2147483647
					if (iy >= fft_complex.length) {
						iy = fft_complex.length - 1; // was Index 17 out of bounds for length 17
					}
					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));
			}
		}
		return maxPhases;
	}

	/* ======================================================================== */
	// zero is in the center
	private int[][] findFirst2MinOnCorrelation(double[] pixels, double min_half_period, // from white to black
			double max_half_period) { // from white to black
		double min_fract_period = 0.3; // second minimum should be not closer to the first than this fraction of the
										// first
		// distance from zero
		int size = (int) Math.sqrt(pixels.length);
		int size2 = size >> 1;
		if (max_half_period <= 0)
			max_half_period = size;
		if (min_half_period < 1)
			min_half_period = 1;
		double mn2 = min_half_period * min_half_period;
		double mx2 = max_half_period * max_half_period;
		int[][] mins = { { 0, 0 }, { 0, 0 } };
		double mn = 0.0; // minimums are always negative
		// find the first minimum
		for (int row = 0; row <= size2; row++) {
			double dy2 = (row - size2) * (row - size2);
			for (int col = 0; col < size; col++) {
				double d2 = (col - size2) * (col - size2) + dy2;
				if ((d2 >= mn2) && (d2 < mx2) && (pixels[row * size + col] < mn)) {
					mn = pixels[row * size + col];
					mins[0][0] = col - size2;
					mins[0][1] = row - size2; // always <=0
				}
			}
		}
		if (mn == 0.0) {
			return null; // could not find a first minimum
		}
		// find the second minimum (not too close to the first
		int x0 = mins[0][0] + size2;
		int y0 = mins[0][1] + size2;
		int x1 = size - x0;
		int y1 = size - y0;
		mn = 0.0;
		double mn2a = Math.max(mn2, min_fract_period * (mins[0][0] * mins[0][0] + mins[0][1] * mins[0][1]));

		for (int row = 0; row <= size2; row++) {
			double dy2 = (row - size2) * (row - size2);
			for (int col = 0; col < size; col++) {
				double d2 = (col - size2) * (col - size2) + dy2;
				if ((d2 >= mn2) && (d2 < mx2) && (pixels[row * size + col] < mn)) {
					// verify it is far enough from the first minimum and it's mirror
					d2 = (row - y0) * (row - y0) + (col - x0) * (col - x0);
					if (d2 >= 2 * mn2a) { // diagonal
						// verify it is far enough from the mirrored first
						d2 = (row - y1) * (row - y1) + (col - x1) * (col - x1);
						if (d2 >= 2 * mn2a) { // diagonal
							mn = pixels[row * size + col];
							mins[1][0] = col - size2;
							mins[1][1] = row - size2; // always <=0
						}
					}
				}
			}
		}
		if (mn == 0.0) {
			return null; // could not find a second minimum
		}
		return mins;
	}

	private int rad_search(int[][] half_periods) {
		int ahp;
		int amx;
		int dist = -1;
		for (int n = 0; n < half_periods.length; n++) {
			amx = 0;
			for (int i = 0; i < half_periods[0].length; i++) {
				ahp = half_periods[n][i];
				if (ahp < 0)
					ahp = -ahp;
				if (ahp > amx)
					amx = ahp;
			}
			if ((dist < 0) || (dist > amx))
				dist = amx;
		}
		if (dist < 3)
			return 1;
		return (dist - 1) >> 1;
	}

	private boolean isLocalMinMax(double[] pixels, int size, int indx, boolean need_min) {
		int sizesize = size * size;
		if ((indx < 0) || (indx >= sizesize)) {
			if (this.debugLevel > 0) {
				System.out.println("isLocalMinMax(): bad indx = " + indx);
			}
			return false;
		}
		for (int dy = -size; dy <= size; dy += size) {
			for (int dx = -1; dx <= 1; dx++) {
				int indx1 = indx + dy + dx;
				if ((indx1 >= 0) && (indx1 < sizesize)) {
					if (need_min) {
						if (pixels[indx] > pixels[indx1])
							return false;
					} else {
						if (pixels[indx] < pixels[indx1])
							return false;
					}
				}
			}
		}
		return true;
	}

	private double[][] getPeriodsFromCorrelation(double[] pixels, int[][] half_periods, // a pair of half-periods
			double min_frac, // minimal fraction of the center maximum to consider
			boolean no_crazy) { // discard min/max with failed interpolation
		// how far to look around expected maximum/minimum
		double[][] rslt = null;
		int dbg_threshold = 3;
		int search_radius = rad_search(half_periods);
		// below first >= second
		int[][] search_order = { { 1, 0 }, // just half periods, negative
				{ 1, 1 }, // diagonals, positive
				{ 2, 0 }, // full period, positive
				{ 2, 1 }, // 8 (4) directions, negative
				{ 2, 2 } }; // positive
		int size = (int) Math.sqrt(pixels.length);
		int size2 = size / 2;
		int[][] aper = new int[2][2];
		double[][] dhalf_periods = new double[2][2]; // will refine half_periods;
		for (int i = 0; i < 2; i++)
			for (int j = 0; j < 2; j++)
				aper[i][j] = Math.abs(half_periods[i][j]);
		for (int i = 0; i < 2; i++)
			for (int j = 0; j < 2; j++)
				dhalf_periods[i][j] = half_periods[i][j];
		double min_avalue = pixels[size2 * (size + 1)] * min_frac; // minimal absolute value of min/max
		int max_dist = size2 - 2; // arbitrary ??
		int nord = 0; // to preserve after loop
		layers_iteration: for (nord = 0; nord < search_order.length; nord++) {
			int[] xy_ind = search_order[nord];
			// 1 check if too far to fit
			if (((xy_ind[0] * aper[0][0] + xy_ind[1] * aper[1][0]) > max_dist)
					|| ((xy_ind[0] * aper[0][1] + xy_ind[1] * aper[1][1]) > max_dist)
					|| ((xy_ind[1] * aper[0][0] + xy_ind[0] * aper[1][0]) > max_dist)
					|| ((xy_ind[1] * aper[0][1] + xy_ind[0] * aper[1][1]) > max_dist)) {
				break layers_iteration;
			}
			boolean are_mins = ((xy_ind[0] + xy_ind[1]) % 2) != 0;
			int nspots = ((xy_ind[1] == 0) || (xy_ind[0] == xy_ind[1])) ? 2 : 4;
			// double [][] predicted_centers = new double nsamples
			int[][] lin_comb = new int[nspots][2]; // number of half periods (incl. negative) to combine
			if (xy_ind[1] == 0) {
				lin_comb[0][0] = xy_ind[0]; // first spot, first vector
				lin_comb[0][1] = 0; // first spot, second vector
				lin_comb[1][0] = 0; // second spot, first vector
				lin_comb[1][1] = xy_ind[0]; // second spot, second vector
			} else if (xy_ind[0] == xy_ind[1]) {
				lin_comb[0][0] = xy_ind[0]; // first spot, first vector
				lin_comb[0][1] = xy_ind[0]; // first spot, second vector
				lin_comb[1][0] = xy_ind[0]; // second spot, first vector
				lin_comb[1][1] = -xy_ind[0]; // second spot, second vector
			} else {
				lin_comb[0][0] = xy_ind[0]; // first spot, first vector
				lin_comb[0][1] = xy_ind[1]; // first spot, second vector
				lin_comb[1][0] = xy_ind[0]; // second spot, first vector
				lin_comb[1][1] = -xy_ind[1]; // second spot, second vector
				lin_comb[2][0] = xy_ind[1]; // third spot, first vector
				lin_comb[2][1] = xy_ind[0]; // third spot, second vector
				lin_comb[3][0] = -xy_ind[1]; // fourth spot, first vector
				lin_comb[3][1] = xy_ind[0]; // fourth spot, second vector
			}
			double[][] spot_centers = new double[nspots][2];
			// iterate through all min/max spots in a layer, break outer loop on any failure
			// (and use previous layer data)
			for (int nspot = 0; nspot < nspots; nspot++) {
				double[] xyc = { lin_comb[nspot][0] * dhalf_periods[0][0] + lin_comb[nspot][1] * dhalf_periods[1][0],
						lin_comb[nspot][0] * dhalf_periods[0][1] + lin_comb[nspot][1] * dhalf_periods[1][1] };
				int[] ixyc = { (int) Math.round(xyc[0]), (int) Math.round(xyc[1]) };
				if ((ixyc[0] < 0) || (ixyc[1] < 0) || (ixyc[0] >= size) || (ixyc[0] >= size)) {
					break layers_iteration;
				}
				int[] ixym = ixyc.clone();
				for (int y = ixyc[1] - search_radius; y <= ixyc[1] + search_radius; y++)
					if ((y >= -max_dist) && (y < max_dist)) {
						for (int x = ixyc[0] - search_radius; x <= ixyc[0] + search_radius; x++)
							if ((x >= -max_dist) && (x < max_dist)) {
								double d = pixels[(y + size2) * size + (x + size2)]
										- pixels[(ixym[1] + size2) * size + (ixym[0] + size2)];
								if (are_mins)
									d = -d;
								if (d > 0) {
									ixym[0] = x;
									ixym[1] = y;
								}
							}
					}
				// verify it is local max/min
				if (!isLocalMinMax(pixels, size, (ixym[1] + size2) * size + (ixym[0] + size2), are_mins)) {
					if (this.debugLevel > dbg_threshold) {
						System.out.println("getPeriodsFromCorrelation(): is not a local max/min, nord=" + nord);
					}
					break layers_iteration;
				}
				// verify it is strong enough
				if (Math.abs(pixels[(ixym[1] + size2) * size + (ixym[0] + size2)]) < min_avalue) { // BUG: -8030
					if (this.debugLevel > dbg_threshold) {
						System.out.println("getPeriodsFromCorrelation(): min/max is too weak: "
								+ pixels[(ixym[1] + size2) * size + (ixym[0] + size2)] + " < " + min_avalue + " (layer"
								+ nord + ")");
					}
					break layers_iteration;
				}

				// use quadratic interpolation to find min/max
				double[][][] data = new double[9][3][];
				int index = 0;
				for (int dy = -1; dy <= 1; dy++) {
					for (int dx = -1; dx <= 1; dx++) {
						data[index][0] = new double[2];
						data[index][1] = new double[1];
						data[index][2] = new double[1];
						int pix_indx = (ixym[1] + dy + size2) * size + (ixym[0] + dx + size2);
						if ((pix_indx >= 0) && (pix_indx < pixels.length)) {
							data[index][0][0] = dx;
							data[index][0][1] = dy;
							data[index][1][0] = are_mins ? -pixels[pix_indx] : pixels[pix_indx];
							data[index][2][0] = 1.0;
						} else {
							data[index][2][0] = 0.0;
						}
						index++;
					}
				}
				double[] corrXY = (new PolynomialApproximation()).quadraticMax2d(data);
				if (corrXY == null) {
					if (no_crazy) {
						if (this.debugLevel > dbg_threshold) {
							System.out.println(
									"getPeriodsFromCorrelation(): interpolation failed," + " (layer" + nord + ")");
						}
						break layers_iteration;
					}
					corrXY = new double[2];
					corrXY[0] = 0.0;
					corrXY[1] = 0.0;
				}
				// verify that interpolation did not go crazy. If it did - reset to center
				if ((corrXY[0] > 1.5) || (corrXY[1] > 1.5) || (corrXY[0] < -1.5) || (corrXY[1] < -1.5)) {
					if (no_crazy) {
						if (this.debugLevel > dbg_threshold) {
							System.out.println("getPeriodsFromCorrelation(): interpolation failed, " + "corrXY = ["
									+ corrXY[0] + ", " + corrXY[1] + "]  (layer" + nord + ")");
						}
						break layers_iteration;
					}
					corrXY[0] = 0.0;
					corrXY[1] = 0.0;
				}

				spot_centers[nspot][0] = ixym[0] + corrXY[0];
				spot_centers[nspot][1] = ixym[1] + corrXY[1];
			}
			// refine half-period values
			if (xy_ind[1] == 0) {
				dhalf_periods[0][0] = spot_centers[0][0] / xy_ind[0];
				dhalf_periods[0][1] = spot_centers[0][1] / xy_ind[0];
				dhalf_periods[1][0] = spot_centers[1][0] / xy_ind[0];
				dhalf_periods[1][1] = spot_centers[1][1] / xy_ind[0];
			} else if (xy_ind[0] == xy_ind[1]) {
				dhalf_periods[0][0] = (spot_centers[0][0] + spot_centers[1][0]) / 2 / xy_ind[0];
				dhalf_periods[0][1] = (spot_centers[0][1] + spot_centers[1][1]) / 2 / xy_ind[0];
				dhalf_periods[1][0] = (spot_centers[0][0] - spot_centers[1][0]) / 2 / xy_ind[0];
				dhalf_periods[1][1] = (spot_centers[0][1] - spot_centers[1][1]) / 2 / xy_ind[0];
			} else {
				dhalf_periods[0][0] = (spot_centers[0][0] + spot_centers[1][0] + spot_centers[2][0]
						- spot_centers[3][0]) / (xy_ind[0] + xy_ind[1]) / 2;
				dhalf_periods[0][1] = (spot_centers[0][1] + spot_centers[1][1] + spot_centers[2][1]
						- spot_centers[3][1]) / (xy_ind[0] + xy_ind[1]) / 2;
				dhalf_periods[1][0] = (spot_centers[0][0] - spot_centers[1][0] + spot_centers[2][0]
						+ spot_centers[3][0]) / (xy_ind[0] + xy_ind[1]) / 2;
				dhalf_periods[1][1] = (spot_centers[0][1] - spot_centers[1][1] + spot_centers[2][1]
						+ spot_centers[3][1]) / (xy_ind[0] + xy_ind[1]) / 2;
			}
		} // for (int nord = 0; nord < search_order.length; nord++) {
		// double [][] pattern_diagonals = {
		// {dhalf_periods[0][0]+dhalf_periods[1][0],
		// dhalf_periods[0][1]+dhalf_periods[1][1]},
		// {dhalf_periods[0][0]-dhalf_periods[1][0],
		// dhalf_periods[0][1]-dhalf_periods[1][1]}};
		double[][] scaled_v = new double[2][2];
		double k_scale = 4.0;
		for (int i = 0; i < 2; i++)
			for (int j = 0; j < 2; j++) {
				scaled_v[i][j] = k_scale * dhalf_periods[i][j]; // not clear why 4 and not 2
			}
		// rslt = vectToWaveVect(pattern_diagonals);
		// make vectors to have positive y:
		for (int i = 0; i < 2; i++) {
			if (scaled_v[i][1] < 0) {
				scaled_v[i][0] = -scaled_v[i][0];
				scaled_v[i][1] = -scaled_v[i][1];
			}
		}
		rslt = vectToWaveVect(scaled_v);
		// rslt = dhalf_periods;
		// sort so first vector to second vector will be clockwise (positive y is
		// downwards)
		{
			int j = 0, k = 1;
			if ((rslt[j][0] * rslt[k][1] - rslt[k][0] * rslt[j][1]) < 0) {
				double[] tmp = rslt[0];
				rslt[0] = rslt[1];
				rslt[1] = tmp;
			}
		}
		for (int i = 0; i < 2; i++)
			for (int j = 0; j < 2; j++) {
				if (Double.isNaN(rslt[i][j]) || Double.isInfinite(rslt[i][j])) {
					System.out.println("got NaN/Infinity, nord = " + nord);
					return null;
				}
			}
		return rslt;
	}

	// vect[0][] - 1-st vector connecting nodes, vect[1][] - second vector
	// return [0][] - first wave vector, orthogonal to vecgt[0][], return [1][] - to
	// the second vect[1][]
	double[][] vectToWaveVect(double[][] vect) {
		double a = 1.0 / (vect[0][0] * vect[1][1] - vect[0][1] * vect[1][0]);
		// double [] rv0 = {-vect[0][1], vect[0][0]};
		// double [] rv1 = { vect[1][1], -vect[1][0]};
		// double [][] wv= {{a*rv0[0], a*rv0[1]},{a*rv1[0], a*rv1[1]}};
		double[][] wv = { { -a * vect[0][1], a * vect[0][0] }, { a * vect[1][1], -a * vect[1][0] } };
		return wv;
	}

	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]; // .multiplesToTry = 4;

		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; // .diffSpectrCorr=2
			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][0] = (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] = 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; // FIXME 4 - oversampling? Should it be here? Variable?
			maxFinal[i][1] /= size * 4; // FIXME 4 - oversampling? Should it be here? Variable?
		}
		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;
		if (wv0.length > 2)
			v[0][2] = wv0[2];
		if (wv1.length > 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 static 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;
		final int sensor_type = LwirReaderParameters.sensorType(imp);
		Roi roi = imp.getRoi();
		final Rectangle selection;
		if (roi == null) {
			selection = new Rectangle(0, 0, imp.getWidth(), imp.getHeight());
		} else {
			selection = (roi instanceof PointRoi) ? (new Rectangle(0, 0, imp.getWidth(), imp.getHeight()))
					: roi.getBounds();
		}
		MatchSimulatedPattern matchSimulatedPattern = new MatchSimulatedPattern(distortionParameters.getFFTSize(sensor_type));
		matchSimulatedPattern.debugLevel = debugLevel;
		MatchSimulatedPattern matchSimulatedPatternCorr = new MatchSimulatedPattern(
				distortionParameters.getCorrelationSize(sensor_type));
		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)
			ShowDoubleFloatArrays.showArrays(input_bayer, true, "selection-bayer-distortionsTest");
		double[] windowFunction = initWindowFunction(distortionParameters.FFTSize, distortionParameters.fftGaussWidth);
		final double[] windowFunctionCorr = initWindowFunction(distortionParameters.getCorrelationSize(sensor_type),
				distortionParameters.correlationGaussWidth, distortionParameters.zeros);
		double[] greens = normalizeAndWindow(input_bayer[4], windowFunction);

		double[][] pattern = matchSimulatedPattern.findPattern(null, // DoubleFHT doubleFHT,
				greens, distortionParameters.getFFTSize(sensor_type), patternDetectParameters,
				patternDetectParameters.minGridPeriod / 2, patternDetectParameters.maxGridPeriod / 2, 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:
		ShowDoubleFloatArrays.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];
		// }
		// ShowDoubleFloatArrays.showArrays(barray, simulationPattern.barray[0].length,
		// simulationPattern.barray.length,"barray");
		ShowDoubleFloatArrays.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];
		}
		ShowDoubleFloatArrays.showArrays(scanImg, true, "scan_correlation");
		ShowDoubleFloatArrays.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(
			LwirReaderParameters lwirReaderParameters, // null is OK
			DoubleFHT doubleFHT,
			double[] point, // xy to try
			final DistortionParameters distortionParameters, //
			final MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
			final double min_half_period,
			final double max_half_period,
			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) {
//		this.debugLevel = 3;
		if (this.debugLevel == 3) {
			System.out.println("tryPattern(): this.debugLevel = 3");
		}
		int debug_threshold = 2;
		if (imp == null) {
			if (dbgStr != null)
				System.out.println(dbgStr + " imp==null");
			return null;
		}
		boolean is_lwir = ((lwirReaderParameters != null) && LwirReaderParameters.is_LWIR(imp));
		int fft_size = is_lwir ? distortionParameters.FFTSize_lwir : distortionParameters.FFTSize;

		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 instanceof PointRoi) ? (new Rectangle(0, 0, imp.getWidth(), imp.getHeight()))
					: roi.getBounds();
		}
		Rectangle initialPatternCell = new Rectangle(xc - fft_size, yc - fft_size, 2 * fft_size, 2 * fft_size);
		if (is_lwir) {// move to to all? It requires twice FFT size to be able to select, maybe single
						// is still OK?
			initialPatternCell = new Rectangle(xc - fft_size / 2, yc - fft_size / 2, fft_size, fft_size);

		}
		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
		}
		double[][] pattern = null;
		if (is_lwir) { // monochrome
			double[] dpixels = getNoBayer(imp, initialPatternCell);
			double[] dbg_dpixels = dpixels.clone();
			normalizeAndWindow(dpixels, windowFunction);
			if (debugLevel > (debug_threshold + 0)) {
				double[][] dbg_img = { dbg_dpixels, dpixels };
				ShowDoubleFloatArrays.showArrays(dbg_img, true,
						"selection-input" + (initialPatternCell.x + initialPatternCell.width / 2) + ":"
								+ (initialPatternCell.y + initialPatternCell.height / 2));
			}
			pattern = matchSimulatedPattern.findPattern(doubleFHT, dpixels, fft_size, patternDetectParameters,
					min_half_period, max_half_period, false, // this is a pattern for combined greens (diagonal), adjust
																// results accordingly
					"Pattern"); // title - will not be used
		} else { // Bayer - extract green
			// create diagonal green selection around xc,yc
			double[][] input_bayer = splitBayer(imp, initialPatternCell, equalizeGreens);
			if (debugLevel > (debug_threshold + 0)) {
				ShowDoubleFloatArrays.showArrays(input_bayer, true, "selection--bayer");
			}
			double[] greens = normalizeAndWindow(input_bayer[4], windowFunction);
			pattern = matchSimulatedPattern.findPattern(doubleFHT, greens, fft_size, patternDetectParameters,
					min_half_period, max_half_period, 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 > (debug_threshold + 0))
			System.out.println("FX1=" + pattern[0][0] + "  FY1=" + pattern[0][1] + "  phase1=" + pattern[0][2]);
		if (debugLevel > (debug_threshold + 0))
			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 > (debug_threshold + 0))
			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 > (debug_threshold + 0))
				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 > (debug_threshold + 0))
				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 > (debug_threshold + 0))
			System.out.println("xc=" + xc + " yc=" + yc);
		if (debugLevel > (debug_threshold + 0))
			System.out.println("dX=" + dxy[phaseSel][0] + " dY=" + dxy[phaseSel][1]);
		double[] centerXY0 = { xc - dxy[phaseSel][0], yc - dxy[phaseSel][1] };
		if (debugLevel > (debug_threshold - 1))
			System.out.println("+++ Initial center x=" + IJ.d2s(centerXY0[0], 3) + " y=" + IJ.d2s(centerXY0[1], 3));
		/*
		 * if (debugLevel > (debug_threshold + 0)){ double [] test_error_offset = {0.0,
		 * 2.0}; centerXY0[0] += test_error_offset[0]; centerXY0[1] +=
		 * test_error_offset[1];
		 * System.out.println("+++ Initial center with intentional error offset: x="+IJ.
		 * d2s(centerXY0[0],3)+" y="+ IJ.d2s(centerXY0[1],3)); }
		 */
		double[] centerXY = correctedPatternCrossLocation(lwirReaderParameters, // LwirReaderParameters
																				// lwirReaderParameters, // null is OK
				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, // is_lwir, // false, // if true - invert pattern TODO: Put is_lwir here?
				null, // will create new instance of DoubleFHT class
				distortionParameters.fastCorrelationOnFirstPass, locsNeib, debugLevel, dbgStr);
		if (debugLevel > (debug_threshold - 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))));
		// TODO: check statistics of correction - see if there is any dc bias

		/*
		 * if ((debugLevel > (debug_threshold + 0)) && (centerXY != null)) { // trying
		 * second time - watch for conversion double []
		 * centerXY1=correctedPatternCrossLocation( lwirReaderParameters, //
		 * LwirReaderParameters lwirReaderParameters, // null is OK centerXY, // 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, // is_lwir, // false, // if true - invert pattern TODO: Put is_lwir
		 * here? null, // will create new instance of DoubleFHT class
		 * distortionParameters.fastCorrelationOnFirstPass, locsNeib, debugLevel,
		 * dbgStr);
		 * System.out.println("--- Second run: initial center x="+IJ.d2s(centerXY[0],3)
		 * +" y="+ IJ.d2s(centerXY[1],3)+
		 * " -> "+((centerXY1==null)?" NULL ":(IJ.d2s(centerXY1[0],3)+" : "+
		 * IJ.d2s(centerXY1[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;
	}

	// ============= end of public double[][] tryPattern() ==================

	/* ================================================================ */
	// 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 int distortions( // returns number of grid cells
			final LwirReaderParameters lwirReaderParameters, // null is OK
			final boolean[] triedIndices,
			final DistortionParameters distortionParameters, //
			final MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
			final double min_half_period,
			final double max_half_period,
			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) {
		if (imp == null)
			return 0;
		final int debugThreshold = 1;
		
		final int sensor_type = LwirReaderParameters.sensorType(imp);
		final int fft_size =                distortionParameters.getFFTSize(sensor_type);
		final int correlation_size =        distortionParameters.getCorrelationSize(sensor_type);
		final int minimal_pattern_cluster = distortionParameters.getMinimalPatternCluster(sensor_type);
		final int[][] directionsUV = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 } }; // should have opposite direction
		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 neighbors 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 instanceof PointRoi) ? (new Rectangle(0, 0, imp.getWidth(), imp.getHeight()))
						: 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(fft_size);
		matchSimulatedPattern.debugLevel = debugLevel;
		MatchSimulatedPattern matchSimulatedPatternCorr = new MatchSimulatedPattern(correlation_size);
		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);
		double[] windowFunction = initWindowFunction(fft_size, distortionParameters.fftGaussWidth);
		// may need to decrease relative Gaussian 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(correlation_size,
				distortionParameters.correlationGaussWidth, distortionParameters.zeros);
		final double[] windowFunctionCorr2 = initWindowFunction(2 * correlation_size,
				(distortionParameters.absoluteCorrelationGaussWidth ? 0.5 : 1.0)
						* distortionParameters.correlationGaussWidth,
				distortionParameters.zeros);
		final double[] windowFunctionCorr4 = initWindowFunction(4 * correlation_size,
				(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
		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 = fft_size; i < effectiveWidth; i *= 2)
				tryHor++;
			for (int i = fft_size; 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);
			boolean oldMode = false; // true; // false;

			if (oldMode) { // old (single-threaded) mode
				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(lwirReaderParameters, null, point, // xy to try
									thisDistortionParameters, // no control of the displacement
									patternDetectParameters, min_half_period, max_half_period, thisSimulParameters,
									matchSimulatedPattern, matchSimulatedPatternCorr, simulationPattern, equalizeGreens,
									imp, // image to process
									bPattern, windowFunction, windowFunctionCorr, windowFunctionCorr2,
									windowFunctionCorr4, locsNeib, // which neighbors to try (here - just the center)
									null // dbgStr
							);
							debugLevel = was_debug_level;
							if ((node != null) && (node[0] != null)) {
								nodeQueue.add(new GridNode(startScanIndex,node));
								break;
							}
						} else {
							triedIndices[startScanIndex] = true; // only mark failed here, good ones will be marked when used
						}
					}
			} else { // new multithreaded mode
				int startScanIndex = 3;
				for (; (startScanIndex < numTries) && triedIndices[startScanIndex]; startScanIndex++)
					; // skip already tried indices
				if ((global_debug_level > debugThreshold) && (startScanIndex > 3))
					System.out.println("distortions(): startScanIndex=" + startScanIndex + " > 3 ####");

				if (startScanIndex < numTries) {
					nodeQueue = findPatternCandidates(
							lwirReaderParameters, // LwirReaderParameters
							triedIndices,
							startScanIndex, // [0] will be updated
							tryHor,
							tryVert,
							// numTries,
							selection,
							thisDistortionParameters, // no control of the displacement
							patternDetectParameters,
							min_half_period,
							max_half_period,
							thisSimulParameters,
							matchSimulatedPattern,
							matchSimulatedPatternCorr,
							simulationPattern,
							equalizeGreens,
							imp, // image to process
							bPattern,
							windowFunction,
							windowFunctionCorr,
							windowFunctionCorr2,
							windowFunctionCorr4,
							locsNeib, // which neighbors 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 > (debugThreshold + 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 > debugThreshold) {
							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 > (debugThreshold + 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(-1,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
			// Only here mark triedIndices !!
			if (gn.triedIndex >= 0) {
				triedIndices[gn.triedIndex] = true; // mark the used seed
			} else {
				System.out.println("Was updating, gn.triedIndex(not used)="+gn.triedIndex);
			}
			
			debug_left--;
			if (global_debug_level > (debugThreshold + 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 > debugThreshold) {
					// 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 > (debugThreshold + 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 > debugThreshold) { // 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;
			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 > (debugThreshold + 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 > (debugThreshold + 1))
					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() {
						@Override
						public void run() {
							SimulationPattern simulationPattern = new SimulationPattern(bPattern);
							MatchSimulatedPattern matchSimulatedPatternCorr = new MatchSimulatedPattern(correlation_size);
//									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;
									if (debugLevel > debugThreshold) {
										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(lwirReaderParameters, 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) { // was no "-2"
									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 > (debugThreshold + 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 > (debugThreshold + 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 > (debugThreshold + 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 > (debugThreshold + 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 > (debugThreshold + 1)) && (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 > (debugThreshold + 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 > (debugThreshold + 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 > (debugThreshold + 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 > (debugThreshold + 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 > (debugThreshold + 2))
					System.out.println("debugUV[] updated to {" + this.debugUV[0] + "," + this.debugUV[1] + "}");

				if (debugLevel > (debugThreshold + 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 > debugThreshold) {
				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 ((minimal_pattern_cluster <= 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 >= minimal_pattern_cluster) // detected enough cells
			) {
				return numDefinedCells;
			}
			if ((numDefinedCells < minimal_pattern_cluster) && (numDefinedCells > 10)) // detected enough cells
			{
				if (global_debug_level > (debugThreshold - 1)) {
					System.out.println("***** Initial cluster has " + numDefinedCells + " cells that is less than "
							+ "minimal_pattern_cluster = " + minimal_pattern_cluster + " *****");
				}
				// return numDefinedCells;
			}
			

			if ((roi != null) && !(roi instanceof PointRoi)) { // 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
		return 0; // none
	}
	// ================= end of public int distortions() ===================

	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 double min_half_period,
			final double max_half_period, 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
																												// neighbors
																												// 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() {
				@Override
				public void run() {
					int nbh, nbv, nh, nv, nb;
					double[] point = new double[2];
					DoubleFHT doubleFHT = new DoubleFHT();
					// 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(null, // LwirReaderParameters lwirReaderParameters, // null
																	// is OK
										doubleFHT, point, // xy to try
										distortionParameters, // no control of the displacement
										patternDetectParameters, min_half_period, max_half_period, 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 {
		int triedIndex; // index in the list of tried points
		double[][] node;
		public GridNode(
				int triedIndex,
				double[][] node) {
			this.triedIndex = triedIndex;
			this.node = node;
		}

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

	private Queue<GridNode> findPatternCandidates(
			final LwirReaderParameters lwirReaderParameters, // null is OK
			final boolean[] triedIndices, // which indices are already tried
			final int startScanIndex,
			final int tryHor,
			final int tryVert,
			final Rectangle selection,
			final DistortionParameters distortionParameters, //
			final MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
			final double min_half_period,
			final double max_half_period,
			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; // -1; // 1; ** Restore 1
		if ((debugThreshold < 0) || (debugLevel < -10000)) {
			System.out.println("findPatternCandidates(): debugThreshold < 0 - restore when done");
		}
		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 AtomicInteger debugNumThreadAtomic = new AtomicInteger(0);

		final Queue<GridNode> nodeQueue = new ConcurrentLinkedQueue<GridNode>();
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				@Override
				public void run() {
					int nbh, nbv, nh, nv, nb;
					double[] point = new double[2];
					DoubleFHT doubleFHT = new DoubleFHT();
					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? Here - one per thread
							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 > 1) // was 2
									System.out.println("trying xc=" + point[0] + ", yc=" + point[1] + "(nv=" + nv
											+ ", nh=" + nh + ")");
								if (debugLevel > 1) // was 2
									System.out.println(debugNumThread + ":" + n + " >> ");
								double[][] node = tryPattern(
										lwirReaderParameters, // LwirReaderParameters lwirReaderParameters, null is OK
										doubleFHT,
										point, // xy to try
										distortionParameters, // no control of the displacement
										patternDetectParameters,
										min_half_period,
										max_half_period,
										thisSimulParameters,
										matchSimulatedPattern,
										matchSimulatedPatternCorr,
										simulationPattern,
										equalizeGreens,
										imp, // image to process
										bPattern,
										windowFunction,
										windowFunctionCorr,
										windowFunctionCorr2,
										windowFunctionCorr4,
										locsNeib, // which neighbors to try (here - just the  center)
										(debugLevel > debugThreshold)
												? ("" + debugNumThread + ":" + n + ", nv=" + nv + ", nh=" + nh + ", nb="
														+ nb + " " + point[0] + "/" + point[1])
												: null);
								if ((node != null) && (node[0] != null)) {
									nodeQueue.add(new GridNode(n, node)); // save tried index for later, will be marked only when used (fixing a very old bug)
									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);
									continue; // so triedIndices[n] will not be set true 
								}
							} else {
								if (debugLevel > debugThreshold)
									System.out
											.println("-----" + debugNumThread + ":" + n + ", nv=" + nv + ", nh=" + nh);
							}
//							triedIndices[n] = true; // regardless - good or bad - that was wrong, and led to skipping retries of good (but not first) nodes
							triedIndices[n] = true; // will come here only for failed nodes.
						}
				}
			};
		}
		startAndJoin(threads);
		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
	}

	/* ================================================================ */
	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 LwirReaderParameters lwirReaderParameters, // null is OK
			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 int sensor_type = LwirReaderParameters.sensorType(imp);
		final double[][][][] patternGrid = this.PATTERN_GRID;
		final int debugThreshold = 1;
		final Rectangle selection = new Rectangle(0, 0, imp.getWidth(), imp.getHeight());
		final int correlation_size = distortionParameters.getCorrelationSize(sensor_type);
		MatchSimulatedPattern matchSimulatedPatternCorr = new MatchSimulatedPattern(correlation_size);
//				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(correlation_size, // distortionParameters.correlationSize,
				distortionParameters.correlationGaussWidth, distortionParameters.zeros);
		final double[] windowFunctionCorr2 = initWindowFunction(2 * correlation_size, // distortionParameters.correlationSize,
				(distortionParameters.absoluteCorrelationGaussWidth ? 0.5 : 1.0)
						* distortionParameters.correlationGaussWidth,
				distortionParameters.zeros);
		final double[] windowFunctionCorr4 = initWindowFunction(4 * correlation_size, // 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() {
				@Override
				public void run() {
					SimulationPattern simulationPattern = new SimulationPattern(bPattern);
					MatchSimulatedPattern matchSimulatedPatternCorr = new MatchSimulatedPattern(correlation_size); // 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
								correlation_size/2); // 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));
							}
						}

						double[] centerXY = correctedPatternCrossLocation(
								lwirReaderParameters, // LwirReaderParameters lwirReaderParameters, null is OK
								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]);
							// refine should provide higher contrast than was there, it is so for EO, but not for LWIR
							// LWIR refined contrast is approximately the same as that of non-refined, twice less than EO
							// boosting it here
							if (sensor_type == 1) {
								centerXY[2] *= 2.0; // boosting refined LWIR
							}

							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() {
							@Override
							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
			boolean debug_bias = true;
			double lwir_refine_dx = -0.25;
			double lwir_refine_dy = -0.25;
			double sw = 0.0, swx = 0.0, swy = 0.0; // finding x,y bias of refining
			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) {
						if (debug_bias) {
							double [] center_refined = newGrid[iUV[1]][iUV[0]];
							double [] center_orig =    patternGrid[iUV[1]][iUV[0]][0];
							sw  += center_refined[2];
							swx += center_refined[2] * (center_refined[0] - center_orig[0]);
							swy += center_refined[2] * (center_refined[1] - center_orig[1]);
						}
						setPatternGridCell(patternGrid, iUV, newGrid[iUV[1]][iUV[0]], null, // double [] wv1,
								null); // double [] wv2);
					} else if (sensor_type == 1) { // FIXME: correcting LWIR refine bias  
						if (isCellDefined(patternGrid, iUV)) {
							patternGrid[iUV[1]][iUV[0]][0][0] += lwir_refine_dx; 
							patternGrid[iUV[1]][iUV[0]][0][1] += lwir_refine_dy; 
						}
					}
			if (debug_bias && (sw > 0.0)) {
				swx /= sw;
				swy /= sw;
				System.out.println("Refine bias dx = "+swx+"pix,  dy = "+swy+"pix");
			}
			// 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;
		final int sensor_type = LwirReaderParameters.sensorType(imp);
		double[][] gridIntensity = calcGridIntensity(4, // bayerComponent
				distortionParameters.getCorrelationSize(sensor_type), // 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];
			ShowDoubleFloatArrays.showArrays(testGI, gridIntensity[0].length, gridIntensity.length,
					imp.getTitle() + "-GI");
		}
		double[] fffg = calcFlatFieldForGrid(gridIntensity, patternGrid, imp.getWidth(), imp.getHeight());

		double averageGridPeriod = averageGridPeriod(patternGrid);
//		if (debugLevel > (dbgThreshold + 2)) {
//			ShowDoubleFloatArrays.showArrays(fffg, imp.getWidth(), imp.getHeight(),
//					imp.getTitle() + "-fftg");
//		}

		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)) {
			ShowDoubleFloatArrays.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)) {
			ShowDoubleFloatArrays.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)) {
			ShowDoubleFloatArrays.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 int sensor_type = LwirReaderParameters.sensorType(imp);
		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.getCorrelationSize(sensor_type)); // 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() {
				@Override
				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]; // just copy from patternGrid[v][u][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)
			ShowDoubleFloatArrays.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;

			ShowDoubleFloatArrays.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() {
					@Override
					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) { // calculate/print number of defined nodes in a grid
		int[] iUV = new int[2];
		int numCells = 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))
					numCells++;
		return numCells;
	}

	public int numStrongCells(double min_contrast) {
		return numStrongCells(this.PATTERN_GRID, min_contrast);
	}
	
	public int numStrongCells(double[][][][] grid, double min_contrast) { // calculate/print number of defined high-contrast nodes in a grid
		int[] iUV = new int[2];
		int numCells = 0;
		for (iUV[1] = 0; iUV[1] < grid.length; iUV[1]++)
			for (iUV[0] = 0; iUV[0] < grid[0].length; iUV[0]++)
				if (this.getCellContrast(grid, iUV) > min_contrast) {
					numCells++;
				}
		return numCells;
	}
	
	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(
			LwirReaderParameters lwirReaderParameters, // null is OK
			MatchSimulatedPattern.DistortionParameters distortionParameters, //
			MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
			SimulationPattern.SimulParameters simulParameters,
			boolean equalizeGreens,
			ImagePlus imp, // image to process has WOI_TOP and possibly WOI_COMPENSATED
			LaserPointer laserPointer, // LaserPointer object or null
			boolean removeOutOfGridPointers, //
			double[][][] hintGrid, // predicted grid array (or null)
			double hintGridTolerance, // allowed 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;
		}
		final int debugThreshold = 1;
		boolean is_lwir = ((lwirReaderParameters != null) && LwirReaderParameters.is_LWIR(imp));
		double min_half_period = (is_lwir ? patternDetectParameters.minGridPeriodLwir
				: patternDetectParameters.minGridPeriod) / 2;
		double max_half_period = (is_lwir ? patternDetectParameters.maxGridPeriodLwir
				: patternDetectParameters.maxGridPeriod) / 2;
		int minimal_pattern_cluster = is_lwir ? distortionParameters.minimalPatternClusterLwir
				: distortionParameters.minimalPatternCluster;
		double threshold_contrast = distortionParameters.threshold_contrast;
		int threshold_number = distortionParameters.threshold_number;
		

		boolean invert = false; // is_lwir;

		int fft_size = is_lwir ? distortionParameters.FFTSize_lwir : distortionParameters.FFTSize;

		long startTime = System.nanoTime();
		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 {
			if (roi instanceof PointRoi) {
				PointRoi pointRoi = (PointRoi) roi;
				Point[] points = pointRoi.getContainedPoints();
				int[][] ipoints = new int[points.length][2];
				for (int n = 0; n < ipoints.length; n++) {
					ipoints[n][0] = points[n].x;
					ipoints[n][1] = points[n].y;
				}
				// as if they a laser pointers
				double[][] xyuv = new double[points.length][4];
				for (int i = 0; i < ipoints.length; i++) {
					xyuv[i][0] = ipoints[i][0];
					xyuv[i][1] = ipoints[i][1];
					xyuv[i][2] = 0.5;
					xyuv[i][3] = 0.5;
				}
				System.out.println(
						"Setting first marker (of " + ipoints.length + ") as pointer 0.5/0.5 - use use multiple?");
				System.out.println(
						"**** Not yet implemented, use 'Manual hint' command for each of the annotated files ****");
//				setPointersXYUV(imp, xyuv);

				setWOI(0, 0, imp.getWidth(), imp.getHeight());
				selection = new Rectangle(0, 0, imp.getWidth(), imp.getHeight());
				// without setting roi to null (maybe setting image roi too?) will not retry if first attempt gets too few points
				// there is also ignoring PointRoi later, but alone it outputs "Removing failed node (normally should not happen!), u=4, v=2"
//				roi = null;
//				imp.setRoi(roi,false);
			} else {
				setWOI(roi.getBounds());
				selection = roi.getBounds();
			}
		}
		this.debugLevel = global_debug_level;
		int patternCells = 0+0;
		// save initial distortionParameters.correlationMinInitialContrast
		double savedCorrelationMinInitialContrast = distortionParameters.correlationMinInitialContrast;
		int reTries = 50; // bail out after these attempts
		boolean foundGoodCluster = false;

		int tryHor = 0, tryVert = 0;
		// 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 = fft_size; i < effectiveWidth; i *= 2)
			tryHor++;
		for (int i = fft_size; 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
		while (reTries > 0) { // outer loop, including refines
			while (reTries-- > 0) {
				this.PATTERN_GRID = null;
				invalidateCalibration();

				patternCells = distortions( // calculates matchSimulatedPattern.DIST_ARRAY // invalidates calibration,
						// flatFieldForGrid, resets this.PATTERN_GRID
						lwirReaderParameters, // null is OK
						triedIndices,
						distortionParameters, //
						patternDetectParameters,
						min_half_period,
						max_half_period,
						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
				}

				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 (" + patternCells
								+ "), continuing scanning from index " + startScanIndex);
					}
				} else { // all tried
					if (global_debug_level > 0)
						System.out.println("--- Tried all - nothing found --- at "
								+ IJ.d2s(0.000000001 * (System.nanoTime() - startTime), 3));
					break;
				}
			}

			// restore initial distortionParameters.correlationMinInitialContrast
			distortionParameters.correlationMinInitialContrast = savedCorrelationMinInitialContrast;
			if (!foundGoodCluster) {
				if (global_debug_level > (debugThreshold + 1))
					System.out.println(
							"calculateDistortions(): Pattern too small, initial cluster had " + patternCells + " cells");
				if (global_debug_level > (debugThreshold + 2))
					IJ.showMessage("Error", "Pattern too small: " + patternCells);
				return distortionParameters.errPatternNotFound;
			}
			if (!patternOK()) {
				if (global_debug_level > (debugThreshold + 1))
					System.out.println("Pattern not found");
				if (global_debug_level > (debugThreshold + 2))
					IJ.showMessage("Error", "Pattern not found");
				return distortionParameters.errPatternNotFound;
			} else {
				if (global_debug_level > 0) // (debugThreshold + 1))
					System.out.println("Initial pattern cluster has " + patternCells + " cells");
			}
			if (global_debug_level > (debugThreshold + 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, // // makes no sense for LWIR as it normalizes absolute data - actually it does! 
						equalizeGreens,
						global_debug_level,
						updateStatus,
						threadsMax);
			else
				imp_eq = imp;

			if (distortionParameters.refineCorrelations) {
				double maxActualCorr = refineDistortionCorrelation(
						lwirReaderParameters, // LwirReaderParameters lwirReaderParameters, // null is OK
						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
				System.out.println(".... maxActualCorr="+maxActualCorr);
				if (maxActualCorr <= 0.0) {
//					return distortionParameters.errRefineFailed; // 2021
					continue; 
				}
				recalculateWaveVectors(
						updateStatus,
						debug_level);// debug level used inside loops
				if (global_debug_level > (debugThreshold + 1))
					System.out.println("Second pass over at " + IJ.d2s(0.000000001 * (System.nanoTime() - startTime), 3));
			}
			// hack gridSize
			if ((distortionParameters.gridSize & 1) != 0) {
				refineDistortionCorrelation(lwirReaderParameters, // LwirReaderParameters lwirReaderParameters, // null is OK
						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
			}
			// Test number of cells with contrast > (~10.0) and fail if number Of over threshold is below minimal_pattern_cluster?
			patternCells = numDefinedCells();
//			if ((roi != null) && (patternCells < minimal_pattern_cluster) && !(roi instanceof PointRoi)) {
			if (patternCells < minimal_pattern_cluster) { // retry regardless of ROI
				if (global_debug_level > (debugThreshold - 1))
					System.out.println("Detected pattern is too small: " + patternCells + ", minimum is set to "
							+ minimal_pattern_cluster+". Will try again search for a new start point.");
				//return distortionParameters.errTooFewCells; // -10
				continue;
			}
			patternCells = numStrongCells(threshold_contrast);
			if (patternCells < threshold_number) {  // retry regardless of ROI
				if (global_debug_level > (debugThreshold - 1))
					System.out.println("Detected pattern has too few strong cells (with contrast > "+ threshold_contrast+
							"): " + patternCells + ", minimum number is set to "
							+ threshold_number+". Will try again search for a new start point.");
				//return distortionParameters.errTooFewCells; // -10
				continue;
			}
			break; // enough cells?
		} // end of outer loop that includes refines
		
		patternCells = numDefinedCells();
		if ((roi != null) && (patternCells < minimal_pattern_cluster) && !(roi instanceof PointRoi)) {
			if (global_debug_level > (debugThreshold + 0))
				System.out.println("Detected pattern is too small: " + patternCells + ", minimum is set to "
						+ minimal_pattern_cluster);
			return distortionParameters.errTooFewCells; // -10
		}
		patternCells = numStrongCells(threshold_contrast);
		if ((roi != null) && (patternCells < threshold_number) && !(roi instanceof PointRoi)) {
			if (global_debug_level > (debugThreshold + 0))
				System.out.println("Detected pattern has too few strong cells (with contrast > "+ threshold_contrast+
						"): " + patternCells + ", minimum number is set to "
						+ threshold_number+".");
			//return distortionParameters.errTooFewCells; // -10
			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 > (debugThreshold + 0)) {
				System.out.println(msg);
				if (global_debug_level > (debugThreshold + 1))
					IJ.showMessage(msg);
			}

		} else if (numDifferentFFT > 0) {
			String msg = "Single correlation FFT size used: " + (1 << maxLn2);
			if (global_debug_level > (debugThreshold + 0))
				System.out.println(msg);
		}
		zeroNaNContrast(); // replace grid NaN with 0

		int numPointers = (laserPointer != null) ? laserPointer.laserUVMap.length : 0;
		double[][] pointersXY = (numPointers > 0) ? getPointersXYUV(imp, laserPointer) : null;

		if (global_debug_level > (debugThreshold + 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, // allowed mismatch (fraction of period) or 0 - orientation only
				invert, // b/w
				global_debug_level, // DEBUG_LEVEL
				noMessageBoxes);
	}

	
	
	// ====== end of calculateDistortions()
	// ==============================================
	/**
	 * 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) {
			ShowDoubleFloatArrays.showArrays(psf, size, size, title + "-psf");
		}
		if (debugLevel > 2) {
			ShowDoubleFloatArrays.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) {
				ShowDoubleFloatArrays.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);

				}
	}

	/* ======================================================================== */
	@Deprecated
	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);
			JP46_Reader_camera.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;
	}

	// laserPointer used as a backup if images do not contain data, and specify if
	// they are needed at all (null - skip)
	public static double[][] getPointersXYUV(ImagePlus imp, LaserPointer laserPointer) {
		int numPointers = (laserPointer != null) ? laserPointer.laserUVMap.length : 0;
		// 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);
			JP46_Reader_camera.decodeProperiesFromInfo(imp);
		}
		double[][] pointersXY = new double[numPointers][];
		int numPointerDetected = 0;
		// Normally all pointers should either have or not U,V - the smallest one will
		// later be used
		for (int i = 0; i < pointersXY.length; i++) {
			pointersXY[i] = null;
			if ((imp.getProperty("POINTER_X_" + i) != null) && (imp.getProperty("POINTER_Y_" + i) != null)) {
				if ((imp.getProperty("POINTER_U_" + i) != null) && (imp.getProperty("POINTER_V_" + i) != null)) {
					pointersXY[i] = new double[4];
					pointersXY[i][2] = Double.parseDouble((String) imp.getProperty("POINTER_U_" + i));
					pointersXY[i][3] = Double.parseDouble((String) imp.getProperty("POINTER_V_" + i));
				} else if  ((laserPointer != null) &&(laserPointer.laserUVMap[i] != null)) {
					pointersXY[i] = new double[4];
					pointersXY[i][2] = laserPointer.laserUVMap[i][0];
					pointersXY[i][3] = laserPointer.laserUVMap[i][1];
				} else {
					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 static void setPointersXYUV(ImagePlus imp, double[][] pointersXYUV) {

//		JP46_Reader_camera jp4_instance= new JP46_Reader_camera(false);
//		JP46_Reader_camera.decodeProperiesFromInfo(imp);
		String prefix = "POINTER_";
		Properties prop = imp.getProperties();
		// Delete all properties starting with "POINTER_"
		Enumeration<Object> enumKey = prop.keys();
		while (enumKey.hasMoreElements()) {
			Object key = enumKey.nextElement();
			if (((String) key).startsWith(prefix)) {
				prop.remove(key);
			}
		}
		// Delete all properties starting with "POINTER_"
		int num_used = 0;
		for (int i = 0; i < pointersXYUV.length; i++)
			if (pointersXYUV[i] != null) {
				prop.setProperty(prefix + "X_" + i, "" + pointersXYUV[i][0]);
				prop.setProperty(prefix + "Y_" + i, "" + pointersXYUV[i][1]);
				if (pointersXYUV[i].length > 2) {
					prop.setProperty(prefix + "U_" + i, "" + pointersXYUV[i][2]);
					prop.setProperty(prefix + "V_" + i, "" + pointersXYUV[i][3]);
				}
				num_used++;
			}
		prop.setProperty("USED_POINTERS", "" + num_used);
//		jp4_instance.encodeProperiesToInfo(imp);
//		(new FileSaver(imp)).saveAsTiff(path);

	}

	/* ======================================================================== */

	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);
			JP46_Reader_camera.decodeProperiesFromInfo(imp);
		}
		if (imp.getProperty("channel") == null)
			return null;
		return (String) imp.getProperty("channel");
	}

	/* ======================================================================== */
	public void showFlatFieldForGrid() {
		if (this.flatFieldForGrid != null)
			ShowDoubleFloatArrays.showArrays(this.flatFieldForGrid, getImageWidth(), getImageHeight(),
					"Flat_field_for_grid");
	}

	public void showFFCorrectedGrid() {
		if (this.gridFFCorr != null)
			ShowDoubleFloatArrays.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;
			ShowDoubleFloatArrays.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];
			ShowDoubleFloatArrays.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++;
				}
			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;

		} else {
			if (this.debugLevel > 0) {
				System.out.println("coeff == null");
			}
		}
		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, boolean invert) {
		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 = (invert ^ 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 > 0) {
			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]), 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) {
		return worstGridMatchRotSkew(coeff, false);
	}
	
	
	public double worstGridMatchRotSkew(
			double[][] coeff,
			boolean invert
			) {
		int[][] iCoeff = gridMatrixApproximate(coeff, invert);
		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!
		return worstGridMatchTranslate(coeff, false);
	}
	
	public double worstGridMatchTranslate(
			double[][] coeff,
			boolean invert) { // in grids half-periods, not pixels!
		int[][] iCoeff = gridMatrixApproximate(coeff, invert);
		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 = Math.round(pixels[2][i]);
					int v = 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); // FIXME: Does nothing
				this.PATTERN_GRID = setPatternGridArray(maxU - minU + 1, maxV - minV + 1); // FIXME: Does nothing
				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] = (Math.round(pixels[2][i])) - minU;
						uv[1] = (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)
			ShowDoubleFloatArrays.showArrays(UV_float0, getWOI().width, getWOI().height, "UV_float0"); // all -1
		if (global_debug_level > 2)
			ShowDoubleFloatArrays.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 DEPRECATE, move to
	 *                                pointersXYUV
	 * @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_deprecated(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, false);
				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;

	}

// Modified 06/19 to move laser pointers to files
	public int combineGridCalibration(LaserPointer lp, // Only for possible hint on rotations/ flips.LaserPointer object
														// or null
			double[][]   pointersXYUV, boolean removeOutOfGridPointers, //
			double[][][] hintGrid, // predicted grid array (or null)
			double       hintGridTolerance, // allowed mismatch (fraction of period) or 0 - orientation only
			boolean      invert,            // for lwir
			int global_debug_level, // DEBUG_LEVEL
			boolean noMessageBoxes) {
		boolean has_lasers = false;
		if (pointersXYUV != null) {
			for (double[] e : pointersXYUV)
				if ((e != null) && (e.length > 2)) {
					has_lasers = true;
					break;
				}
		}
		int acalibrated = 0;
		double[][] gridMatchCoeff = null;
		double searchAround = 50; // 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, invert);
				if (global_debug_level > 0) {
					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, invert), 5));
					System.out.println("worstGridMatchTranslate()=" + IJ.d2s(worstGridMatchTranslate(gridMatchCoeff, invert), 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 > 0)
							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 > 0)
							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"); // here
				}
			} else {
				if (global_debug_level > 0)
					System.out.println("*** Warning: combineGridCalibration(): gridMatchCoeff() failed");
				return -1;
			}
		}
		if (has_lasers || ((iGridTranslateUV != null) && (gridRotation >= 0))) { // no laser pointers, but hint grid
																					// with specified tolerance
			if ((global_debug_level > 1) && (pointersXYUV == null))
				System.out.println("This image does not contain any laser pointer data");
			double maxOffsetFromCenter = 0.6; // maximal offset of the laser spot from the center, relative to cell
												// radius
			boolean white_only = true;
			if (lp != null) {
				maxOffsetFromCenter = lp.maxOffsetFromCenter;
				white_only = lp.whiteOnly;
			}
			acalibrated = calibrateGrid( // now should work without laser pointers too
					lp, // for flips hint only
					white_only, maxOffsetFromCenter, pointersXYUV, 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;
						}
					}
				}
			ShowDoubleFloatArrays.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 (averaged around the grid node) seventh slice - green
	 * intensity of the grid (averaged around the grid node) eighth slice - blue
	 * intensity of the grid (averaged 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"));
//			System.out.println("Using grid w/o absolute calibration.");
			unCalibrateGrid();
			// 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] = this.targetUV[v][u][0];
					pixels[3][index] = 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);
		if (imp_result == null) {
			System.out.println("getCalibratedPatternAsImage(): Grid is empty !");
			return null;
		}

		// 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 calibrateGrid(LaserPointer lp, // only as hint for rotations/flips (may update), null is OK
			boolean white_only, // laser pointer only on white
			double maxOffsetFromCenter, double[][] xyuv, // null and zero length OK now combines x,y and
															// laserPointer.laserUVMap u,v
			// each non- null xyuv[] should be either 2 or 4 long
			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 (xyuv == null)
			xyuv = new double[0][];
		invalidateCalibration();
		boolean has_lasers = false;
		for (double[] e : xyuv)
			if ((e != null) && (e.length > 2)) {
				has_lasers = true;
				break;
			}
		double[][] uv = uvFromXY(xyuv, removeOutOfGridPointers ? 2.0 : -1);
		// if (uv==null) return -1;
		int numPointesLeft = 0;
		for (int i = 0; i < xyuv.length; i++)
			if ((xyuv[i] != null) && (uv[i] != null))
				numPointesLeft++;
		if (debugLevel > 1) {
			int numRemoved = 0;
			for (int i = 0; i < xyuv.length; i++)
				if ((xyuv[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 (has_lasers && white_only) {
			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) { // xyuv[i] != null, xyuv[j] != null
							pairMatch = false;
							allMatch = false;
							diffUVTable[0] = (int) Math.round(xyuv[j][2] - xyuv[i][2]); // should not get here if uv is
																						// {}
							diffUVTable[1] = (int) Math.round(xyuv[j][3] - xyuv[i][3]);
							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(xyuv[i][2], 2) + ":"
											+ IJ.d2s(xyuv[i][3], 2) + "] -> [" + IJ.d2s(uv[i][0], 2) + ":"
											+ IJ.d2s(uv[i][1], 2) + "] and \n" + j + " [" + IJ.d2s(xyuv[j][2], 2) + ":"
											+ IJ.d2s(xyuv[j][3], 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(xyuv[k][2], 2) + ":" + IJ.d2s(xyuv[k][3], 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 = (lp != null) ? (flipsToRot(lp.swapUV, lp.flipU, lp.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 (lp != null) {
			lp.swapUV = rotToFlips(rotation)[0];
			lp.flipU = rotToFlips(rotation)[1];
			lp.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] * xyuv[i][2] + rotations[rotation][0][1] * xyuv[i][3]));
				uvShift[1] = (int) Math.round(
						uv[i][1] - (rotations[rotation][1][0] * xyuv[i][2] + rotations[rotation][1][1] * xyuv[i][3]));
				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);
		return applyUVShiftRot(UVRot, // int [] UVShiftRot,
				uv, // double [][] uv,
				xyuv, // double [][] xyuv, // [][2], [][3] contain laser pointers u,v
				maxOffsetFromCenter, // double maxOffsetFromCenter,
//				LaserPointer laserPointer,
				noMessageBoxes);
	}

	public int applyUVShiftRot(int[] UVShiftRot, double[][] uv, LaserPointer laserPointer, boolean noMessageBoxes) {
		if (UVShiftRot != null)
			this.UVShiftRot = UVShiftRot.clone();
		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;
	}

	public int applyUVShiftRot(int[] UVShiftRot, double[][] uv, double[][] xyuv, // [][2], [][3] contain laser pointers
																					// u,v
			double maxOffsetFromCenter,
//			LaserPointer laserPointer,
			boolean noMessageBoxes) {
		if (UVShiftRot != null)
			this.UVShiftRot = UVShiftRot.clone();
		boolean has_lasers = false;
		for (double[] e : xyuv)
			if ((e != null) && (e.length > 2)) {
				has_lasers = true;
				break;
			}
		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 (has_lasers) {
			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] - xyuv[i][2];
					distUV[1] = reReMap[1][0] * uv[i][0] + reReMap[1][1] * uv[i][1] + reReMap[1][2] - xyuv[i][3];
					dist = Math.sqrt(distUV[0] * distUV[0] + distUV[1] * distUV[1]);
					if (debugLevel > 0) { // 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) > 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 * 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(
			LwirReaderParameters lwirReaderParameters, // null is OK
			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(
					lwirReaderParameters, // LwirReaderParameters
					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)
			ShowDoubleFloatArrays.showArrays(input_bayer, true, "centered");
		if (debug_level > 2)
			ShowDoubleFloatArrays.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
						false);// boolean mono
				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);
					ShowDoubleFloatArrays.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);

					ShowDoubleFloatArrays.showArrays(simGreensCentered.clone(), "simGreensCentered" + ixc + ":" + iyc);
					ShowDoubleFloatArrays.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);
					ShowDoubleFloatArrays.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.contrastSelectSigmaCenter, // Gaussian sigma to select correlation
																			// centers (pixels, 2.0)
							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
					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(
			LwirReaderParameters lwirReaderParameters, // null is OK
			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) {
		if (imp == null) {
			return null;
		}
		/*
		boolean is_lwir = ((lwirReaderParameters != null) && lwirReaderParameters.is_LWIR(imp));
		int correlation_size = is_lwir ? distortionParameters.correlationSizeLwir
				: distortionParameters.correlationSize;
		int max_correlation_size = is_lwir ? distortionParameters.maximalCorrelationSizeLwir
				: distortionParameters.maximalCorrelationSize;
		*/
		final int sensor_type = LwirReaderParameters.sensorType(imp);
		final int correlation_size =        distortionParameters.getCorrelationSize(sensor_type);
		final int max_correlation_size =    distortionParameters.getMaximalCorrelationSize(sensor_type);
		
		boolean is_mono = false;
		try {
			is_mono = Boolean.parseBoolean((String) imp.getProperty("MONOCHROME"));
		} catch (Exception e) {
		}
		is_mono |= (sensor_type == 1); // is_lwir;

		int debug_threshold = 3;
		// 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);
		// dbgThis=true;
//		dbgThis=true;
		if (dbgThis) {
			System.out.println("correctedPatternCrossLocationAverage4(), beforeXY[0]=" + beforeXY[0] + ", beforeXY[1]="
					+ beforeXY[1]);
			debug_level += 3;
		}
		// Just for testing
		beforeXY[0] += distortionParameters.correlationDx; // offset, X (in pixels)
		beforeXY[1] += distortionParameters.correlationDy; // offset y (in pixels)

		double[][] invConvMatrix = { { 1, 0 }, { 0, 1 } }; // identity
		if (sensor_type != 1) { //(!is_lwir) {
			double[][] convMatrix = { { 1.0, -1.0 }, { 1.0, 1.0 } }; // from greens2 to pixel WV
			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[][] WVgreensMono = matrix2x2_mul(wv, invConvMatrix); // rotated for greens, same dir for mono (lwir)
		if (debug_level > debug_threshold)
			System.out.println("WVgreensMono[0][0]=" + IJ.d2s(WVgreensMono[0][0], 3) + " WVgreensMono[0][1]="
					+ IJ.d2s(WVgreensMono[0][1], 3) + " WVgreensMono[1][0]=" + IJ.d2s(WVgreensMono[1][0], 3)
					+ " WVgreensMono[1][1]=" + IJ.d2s(WVgreensMono[1][1], 3));
		double[] dUV;
//		double [] simGreensCentered;
		double[] simCentered;
		// 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) * correlation_size;
		double v_span = Math.sqrt(wv1x * wv1x + wv1y * wv1y) * correlation_size;
		double min_span = Math.min(u_span, v_span);
		int thisCorrelationSize = correlation_size;
		double[] thisWindow = window;
		double uv_threshold = distortionParameters.minUVSpan * 0.25 * Math.sqrt(2.0);

		if ((min_span < uv_threshold) && (window2 != null) && (thisCorrelationSize < max_correlation_size)) { // trying
																												// to
																												// increase
																												// only
																												// twice
			thisCorrelationSize *= 2;
			min_span *= 2;
			thisWindow = window2;
			if ((min_span < uv_threshold) && (window4 != null) && (thisCorrelationSize < max_correlation_size)) {
				thisCorrelationSize *= 2;
				min_span *= 2;
				thisWindow = window4;
			}
		}

		setCorrelationSizesUsed(thisCorrelationSize);
		if ((debug_level > (debug_threshold - 2)) && (thisCorrelationSize > correlation_size))
			System.out.println("**** u/v span too small, increasing FFT size to " + thisCorrelationSize);
		Rectangle centerCross = correlationSelection(beforeXY, // initial coordinates of the pattern cross point
//				(is_lwir ? (thisCorrelationSize / 2) : (thisCorrelationSize)));
				((sensor_type == 1) ? (thisCorrelationSize / 2) : (thisCorrelationSize)));

		int ixc = centerCross.x + centerCross.width / 2;
		int iyc = centerCross.y + centerCross.height / 2;
		double[] diffBeforeXY = { beforeXY[0] - ixc, beforeXY[1] - iyc };
		double[] greens_mono; // greens or mono
//		if (is_lwir) {
		if (sensor_type == 1) {
			greens_mono = getNoBayer(imp, centerCross);
			if (debug_level > (debug_threshold + 0))
				ShowDoubleFloatArrays.showArrays(greens_mono, "greens_mono");
			if (debug_level > (debug_threshold + 0))
				System.out.println("ixc=" + ixc + " iyc=" + iyc);
			normalizeAndWindow(greens_mono, thisWindow);
			// Twice lower contrast than EO - doubling below - did not work
//			for (int i = 0; i < greens_mono.length; i++) {
//				greens_mono[i] *= 2.0;
//			}
			
		} else {
			double[][] input_bayer = splitBayer(imp, centerCross, equalizeGreens);
			if (debug_level > (debug_threshold + 1))
				ShowDoubleFloatArrays.showArrays(input_bayer, true, "centered");
			if (debug_level > (debug_threshold + 0))
				ShowDoubleFloatArrays.showArrays(input_bayer[4], "greens");
			if (debug_level > (debug_threshold + 0))
				System.out.println("ixc=" + ixc + " iyc=" + iyc);
			greens_mono = normalizeAndWindow(input_bayer[4], thisWindow);
		}
		if (debug_level > (debug_threshold + 0))
			ShowDoubleFloatArrays.showArrays(greens_mono, "greens_mono_Windowed");
		// average is not zero - probably
		if (debug_level > (debug_threshold + 0)) {
			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[][] gridNeib = { { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 } };
		int numOfNeib = distortionParameters.correlationAverageOnRefine ? gridNeib.length : 1;
		if (debug_level > (debug_threshold + 0)) {
			System.out.println(" numOfNeib=" + numOfNeib + " (distortionParameters.correlationAverageOnRefine="
					+ distortionParameters.correlationAverageOnRefine);
		}
		if (locsNeib.length == 1) {
			numOfNeib = 1; // on the first pass, from legacy
			if (debug_level > (debug_threshold + 1)) {
				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++) {
//			if (is_lwir) { // monochrome, use all pixels
			if (sensor_type == 1) { // monochrome, use all pixels
				neibCenter[0] = diffBeforeXY[0] + gridNeib[numNeib][0];
				neibCenter[1] = diffBeforeXY[1] + gridNeib[numNeib][1];
			} else {
				neibCenter[0] = diffBeforeXY[0] + 0.5 * (gridNeib[numNeib][0] + gridNeib[numNeib][1]);
				neibCenter[1] = diffBeforeXY[1] + 0.5 * (gridNeib[numNeib][0] - gridNeib[numNeib][1]);
			}
			double[] barray;
//			if (is_lwir) {
			if (sensor_type == 1) {				
				// negative=!negative;
				dUV = matrix2x2_scale(matrix2x2_mul(wv, neibCenter), -2 * Math.PI);
//				dUV[0] = 0.0; dUV[1] = 0.0;
				boolean dbg_once = false;
				if (dbg_once || (debug_level > (debug_threshold + 20))) {
					double[] barray0 = simulationPattern.simulatePatternFullPatternSafe( // Is it the most
																							// time-consuming part?
																							// should it be done once
																							// and then only extraction
																							// separate?
							wv0x, wv0y, (negative ? (-Math.PI / 2) : Math.PI / 2), // negative?(-Math.PI/2):Math.PI/2,
							wv1x, wv1y, Math.PI / 2, // Math.PI/2,
							corr, // null, // no mesh distortion here
							thisSimulParameters.subdiv, // SIMUL.subdiv, - do not need high quality here
							thisCorrelationSize, false, // false); // center for greens ???
							false);// boolean mono
					double[] barray1 = simulationPattern.simulatePatternFullPatternSafe( // Is it the most
																							// time-consuming part?
																							// should it be done once
																							// and then only extraction
																							// separate?
							wv0x, wv0y, (negative ? (-Math.PI / 2) : Math.PI / 2), // negative?(-Math.PI/2):Math.PI/2,
							wv1x, wv1y, Math.PI / 2, // Math.PI/2,
							corr, // null, // no mesh distortion here
							thisSimulParameters.subdiv, // SIMUL.subdiv, - do not need high quality here
							thisCorrelationSize, true, // false); // center for greens ???
							false);// boolean mono
					double[] barray2 = simulationPattern.simulatePatternFullPatternSafe( // Is it the most
																							// time-consuming part?
																							// should it be done once
																							// and then only extraction
																							// separate?
							wv0x, wv0y, (negative ? (-Math.PI / 2) : Math.PI / 2), // negative?(-Math.PI/2):Math.PI/2,
							wv1x, wv1y, Math.PI / 2, // Math.PI/2,
							corr, // null, // no mesh distortion here
							thisSimulParameters.subdiv, // SIMUL.subdiv, - do not need high quality here
							thisCorrelationSize, true, // false); // center for greens ???
							true);// boolean mono
					double[][] dbg_barray = { barray0, barray1, barray2 };
					System.out.println(">=========Showing barray01" + ixc + ":" + iyc);
					ShowDoubleFloatArrays.showArrays(dbg_barray, true, "barray" + ixc + ":" + iyc);
					double[] sim_pix0 = simulationPattern.extractSimulMono(barray0, thisSimulParameters, 1, // subdivide
																											// output
																											// pixels
							thisCorrelationSize, // number of Bayer cells in width of the square selection (half number
													// of pixels)
							0, 0);
					double[] sim_pix1 = simulationPattern.extractSimulMono(barray1, thisSimulParameters, 1, // subdivide
																											// output
																											// pixels
							thisCorrelationSize, // number of Bayer cells in width of the square selection (half number
													// of pixels)
							0, 0);
					double[] sim_pix2 = simulationPattern.extractSimulMono(barray2, thisSimulParameters, 1, // subdivide
																											// output
																											// pixels
							thisCorrelationSize, // number of Bayer cells in width of the square selection (half number
													// of pixels)
							0, 0);
					double[][] dbg_sim_pix = { sim_pix0, sim_pix1, sim_pix2 };
					System.out.println(">=========Showing barray01" + ixc + ":" + iyc);
					ShowDoubleFloatArrays.showArrays(dbg_sim_pix, true, "sim_pix" + ixc + ":" + iyc);

				}

				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, // false); // center for greens ???
						true);// boolean mono
				if (debug_level > (debug_threshold + 0)) {
					System.out.println(">=========Showing barray" + ixc + ":" + iyc);
					ShowDoubleFloatArrays.showArrays(barray, "barray" + ixc + ":" + iyc);
				}
				// barray for dUV=={0,0} is symmetrical around center pixel,
				// sim_pix - around {center - 0.5, center - 0.5}
				// for center_for_g2 - sim_pix is symmetrical around [center,center],
				// for !mono && !center_for_g2 - [center+0.5, center+0.5]
				// TODO: reduce size of barray for mono twice in each direction
				double[] sim_pix = simulationPattern.extractSimulMono(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 dbgD : sim_pix)
						dbgSumWindow += dbgD;
					System.out.println(dbgStr + ": SUM of sim_pix=" + dbgSumWindow); // First difference good/bad
				}

				simCentered = normalizeAndWindow(sim_pix, thisWindow);

			} else {
				dUV = matrix2x2_scale(matrix2x2_mul(wv, neibCenter), -2 * Math.PI);
				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
						false);// boolean mono
				if (debug_level > (debug_threshold + 0)) {
					System.out.println(">=========Showing barray" + ixc + ":" + iyc);
					ShowDoubleFloatArrays.showArrays(barray, "barray" + ixc + ":" + iyc);
				}
//		double[][] sim_pix;
				double[][] sim_pix = null;
				if (is_mono) {
					sim_pix = new double[1][];
					sim_pix[0] = simulationPattern.extractSimulMono( // TODO: can use twice smaller barray
							barray, thisSimulParameters, 1, // subdivide output pixels - now 4
							thisCorrelationSize, // number of Bayer cells in width of the square selection (half number
													// of pixels)
							0, // selection center, X (in pixels)
							0);
				} else {
					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

				}

				simCentered = normalizeAndWindow(sim_pix[4], thisWindow);
			}

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

			debugGreens[numNeib] = simCentered.clone();

			modelCorrs[numNeib] = fht_instance.phaseCorrelate(greens_mono.clone(), simCentered,
					patternDetectParameters.phaseCoeff, 0, // 0.5, distortionParameters.correlationHighPassSigma,
					patternDetectParameters.lowpass_sigma, // 0.3, (fast?distortionParameters.correlationLowPassSigma:0.0),//
															// moved to decimation via FFT
					null, null);

			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 > (debug_threshold + 0)) {
			System.out.println(">=========Showing simCentered" + ixc + ":" + iyc);
			ShowDoubleFloatArrays.showArrays(debugGreens, true, "simCentered" + ixc + ":" + iyc);
		}
		if (debug_level > (debug_threshold + 0)) {
			System.out.println(">=========Showing modelCorrs, passNumber=" + passNumber);
			ShowDoubleFloatArrays.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;
		int thisFFTSubdiv = distortionParameters.correlationFFTSubdiv;
		double thisLowpass = distortionParameters.correlationLowPassSigma;
		double[] modelCorr;
		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 > (debug_threshold + 0)) {
			System.out.println(">==========Showing modelCorr");
			ShowDoubleFloatArrays.showArrays(modelCorr, thisCorrelationSize, thisCorrelationSize, "modelCorr");
		}

		if (fast)
			centerXY = correlationMaximum( // maybe twice actual size if
					modelCorr, distortionParameters.correlationMaxOffset,
					(debug_level > (debug_threshold + 0)) && (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 > (debug_threshold + 0)));
		if (centerXY == null) {
			if (debug_level > (debug_threshold - 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;
		}

		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++)
					WVgreensMono[i][j] *= 0.5;
		}

		double[] contrasts = correlationContrast(modelCorr,
				greens_mono,
				WVgreensMono, // wave vectors (same units as the pixels array)
				distortionParameters.contrastSelectSigmaCenter, // 2.0 Gaussian sigma to select correlation (pixels, 2.0)
				distortionParameters.contrastSelectSigma, // 0.1 Gaussian sigma to select correlation centers (fraction of UV period), 0.1
				centerXY[0], // x0, // center coordinates
				centerXY[1], // y0,
				"test-contrast"); // title base for optional plots names
		if ((debug_level > (debug_threshold - 1))) {
			System.out.println("contrast = " + contrasts[0]);
		}
		contrast = contrasts[0];
		result[2] = contrast;
//		System.out.println("cobntrasts = "+contrasts[0]+", "+contrasts[1]);
		if (Double.isNaN(contrasts[0]) || ((distortionParameters.correlationMinContrast > 0)
				&& (contrasts[0] < distortionParameters.correlationMinContrast))) {
			if ((debug_level > (debug_threshold - 1)))
				System.out.println(
						"Contrast too low - " + contrasts[0] + "<" + distortionParameters.correlationMinContrast);
			if (debug_level > (debug_threshold - 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 > (debug_threshold - 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 > (debug_threshold - 1))
				System.out.println("Absolute contrast too low - " + contrasts[1] + "<"
						+ distortionParameters.correlationMinAbsoluteContrast);
			if (debug_level > (debug_threshold - 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 > (debug_threshold - 0))
			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));

// FIXME: maybe wrong for mono?
//		if (is_lwir) {
		if (sensor_type == 1) {			
			result[0] = ixc + diffBeforeXY[0] + centerXY[0];
			result[1] = iyc + diffBeforeXY[1] + centerXY[1];
			// Twice lower contrast than EO - doubling below - did not work
//			result[2] *= 2.0; // not just for refine
		} else {
			result[0] = ixc + diffBeforeXY[0] - (-centerXY[0] - centerXY[1]);
			result[1] = iyc + diffBeforeXY[1] - (centerXY[0] - centerXY[1]);
		}
		if (debug_level > (debug_threshold + 0))
			System.out.println(">---correctedPatternCrossLocation: before x=" + IJ.d2s(beforeXY[0], 3) + " y="
					+ IJ.d2s(beforeXY[1], 3));
		if (debug_level > (debug_threshold + 0))
			System.out.println(">+++correctedPatternCrossLocation: after  x=" + IJ.d2s(result[0], 3) + " y="
					+ IJ.d2s(result[1], 3));
		return result;
	}

	// ======= end of private double [] correctedPatternCrossLocationAverage4() ===
	private double[] correctedPatternCrossLocationAverage4TestOldNew(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) {
		int debug_threshold = 3;
		// 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);
		dbgThis = true;
		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 > debug_threshold)
			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;
			}
		}

		setCorrelationSizesUsed(thisCorrelationSize);
		if ((debug_level > (debug_threshold - 2)) && (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 > (debug_threshold + 1))
			ShowDoubleFloatArrays.showArrays(input_bayer, true, "centered");
		if (debug_level > (debug_threshold + 0))
			ShowDoubleFloatArrays.showArrays(input_bayer[4], "greens");
		if (debug_level > (debug_threshold + 0))
			System.out.println("ixc=" + ixc + " iyc=" + iyc);
		double[] greens = normalizeAndWindow(input_bayer[4], thisWindow);
		if (debug_level > (debug_threshold + 0))
			ShowDoubleFloatArrays.showArrays(greens, "greensWindowed");
		// average is not zero - probably

		if (debug_level > (debug_threshold + 0)) {
			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 > (debug_threshold + 0)) {
			System.out.println(" numOfNeib=" + numOfNeib + " (distortionParameters.correlationAverageOnRefine="
					+ distortionParameters.correlationAverageOnRefine);
		}
		if (locsNeib.length == 1) {
			numOfNeib = 1; // on the first pass, from legacy
			if (debug_level > (debug_threshold + 0)) {
				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[][] modelCorrs_new = 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
					false);// boolean mono
			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
			double[] simGreensCenteredClone = simGreensCentered.clone();

			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);
			modelCorrs_new[numNeib] = fht_instance.phaseCorrelate(greens.clone(), simGreensCenteredClone,
					patternDetectParameters.phaseCoeff, 0, // distortionParameters.correlationHighPassSigma,
					patternDetectParameters.lowpass_sigma, // (fast?distortionParameters.correlationLowPassSigma:0.0),//
															// moved to decimation via FFT
					null, null);

			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 > (debug_threshold + 0)) {
			System.out.println(">=========Showing simGreensCentered" + ixc + ":" + iyc);
			ShowDoubleFloatArrays.showArrays(debugGreens, true, "simGreensCentered" + ixc + ":" + iyc);
		}

		if (debug_level > (debug_threshold + 0)) {
			System.out.println(">=========Showing modelCorrs, passNumber=" + passNumber);
			ShowDoubleFloatArrays.showArrays(modelCorrs, true, "modelCorrs:" + numOfNeib);
			ShowDoubleFloatArrays.showArrays(modelCorrs_new, true, "modelCorrs_new:" + numOfNeib);
		}

		// combine 4 correlations into the double resolution, same output size (so half
		// input size) array
		int halfSize = thisCorrelationSize / 2;
		int qSize = thisCorrelationSize / 4;
		int thisFFTSubdiv = distortionParameters.correlationFFTSubdiv;
		double thisLowpass = distortionParameters.correlationLowPassSigma;
		double[] modelCorr;
		double[] modelCorr_new;
		if (numOfNeib > 1) {
			modelCorr = new double[thisCorrelationSize * thisCorrelationSize];
			modelCorr_new = new double[thisCorrelationSize * thisCorrelationSize];
			for (int i = 0; i < modelCorr.length; i++)
				modelCorr[i] = 0.0;
			for (int i = 0; i < modelCorr_new.length; i++)
				modelCorr_new[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)];
							modelCorr_new[(2 * y + dy) * thisCorrelationSize
									+ (2 * x + dx)] += modelCorrs_new[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
			modelCorr_new = modelCorrs_new[0]; // also - different size
		}

		if (debug_level > (debug_threshold + 0)) {
			System.out.println(">==========Showing modelCorr");
			ShowDoubleFloatArrays.showArrays(modelCorr, thisCorrelationSize, thisCorrelationSize, "modelCorr");
		}

		double[] centerXY_new;

		if (fast)
			centerXY_new = correlationMaximum( // maybe twice actual size if
					modelCorr_new, distortionParameters.correlationMaxOffset,
					(debug_level > (debug_threshold + 0)) && (numNeib == 0)); // low-pass filtering should already be
																				// done
		else
			centerXY_new = correlationMaximum(modelCorr_new, 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 > (debug_threshold + 0)));

		if (fast)
			centerXY = correlationMaximum( // maybe twice actual size if
					modelCorr, distortionParameters.correlationMaxOffset,
					(debug_level > (debug_threshold + 0)) && (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 > (debug_threshold + 0)));
		if (centerXY == null) {
			if (debug_level > (debug_threshold - 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;
			centerXY_new[0] *= 0.5;
			centerXY_new[1] *= 0.5;
			for (int i = 0; i < 2; i++)
				for (int j = 0; j < 2; j++)
					WVgreens[i][j] *= 0.5;
		}

		double[] contrasts_new = correlationContrast(modelCorr_new, greens, WVgreens, // wave vectors (same units as the
																						// pixels array)
				distortionParameters.contrastSelectSigmaCenter, // Gaussian sigma to select correlation centers (pixels,
																// 2.0)
				distortionParameters.contrastSelectSigma, // Gaussian sigma to select correlation centers (fraction of
															// UV period), 0.1
				centerXY[0], // x0, // center coordinates
				centerXY[1], // y0,
				"test-contrast-new"); // title base for optional plots names

		double[] contrasts = correlationContrast(modelCorr, greens, WVgreens, // wave vectors (same units as the pixels
																				// array)
				distortionParameters.contrastSelectSigmaCenter, // Gaussian sigma to select correlation (pixels, 2.0)
				distortionParameters.contrastSelectSigma, // Gaussian sigma to select correlation centers (fraction of
															// UV period), 0.1
				centerXY[0], // x0, // center coordinates
				centerXY[1], // y0,
				"test-contrast"); // title base for optional plots names
		if ((debug_level > (debug_threshold - 1))) {
			System.out.println("contrast_new = " + contrasts_new[0] + ", contrast = " + contrasts[0]);
		}
		contrast = contrasts[0];
		result[2] = contrast;
		if (Double.isNaN(contrasts[0]) || ((distortionParameters.correlationMinContrast > 0)
				&& (contrasts[0] < distortionParameters.correlationMinContrast))) {
			if ((debug_level > (debug_threshold - 1)))
				System.out.println(
						"Contrast too low - " + contrasts[0] + "<" + distortionParameters.correlationMinContrast);
			if (debug_level > (debug_threshold - 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 > (debug_threshold - 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 > (debug_threshold - 1))
				System.out.println("Absolute contrast too low - " + contrasts[1] + "<"
						+ distortionParameters.correlationMinAbsoluteContrast);
			if (debug_level > (debug_threshold - 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 > (debug_threshold - 0))
			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 > (debug_threshold + 0))
			System.out.println(">---correctedPatternCrossLocation: before x=" + IJ.d2s(beforeXY[0], 3) + " y="
					+ IJ.d2s(beforeXY[1], 3));
		if (debug_level > (debug_threshold + 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];
				}

			}
			ShowDoubleFloatArrays.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)
					ShowDoubleFloatArrays.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
						false);// boolean mono

				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)
					ShowDoubleFloatArrays.showArrays(greens.clone(), "greens-i" + i + "-j" + j);
				if (debugLevel > 2)
					ShowDoubleFloatArrays.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)
					ShowDoubleFloatArrays.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
			ShowDoubleFloatArrays.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)
				ShowDoubleFloatArrays.showArrays(corr1.clone(), "decimatedForFFT");
			fht_instance.swapQuadrants(corr1);
			if (!fht_instance.transform(corr1, false))
				return null; // direct FHT
			if (showDebug)
				ShowDoubleFloatArrays.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)
			 * ShowDoubleFloatArrays.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)
				ShowDoubleFloatArrays.showArrays(corr1.clone(), "FFT-masked");
			if (!fht_instance.transform(corr1, true))
				return null; // inverse FHT
			fht_instance.swapQuadrants(corr1);
			if (showDebug)
				ShowDoubleFloatArrays.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 };
			ShowDoubleFloatArrays.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 minGridPeriodLwir;
		public int    minGridFileSize = 15000; // Minimal file size (to overwrite) 
		public double maxGridPeriodLwir;
		public double debugX;
		public double debugY;
		public double debugRadius;
		// added for large cell findPattern (based on phase correlation)
		public boolean use_large_cells = false; // new method based on phase correlation should work with large cells,
		// so only first negative correlation (1/2 period) fits in window
		public double phaseCoeff = 0.5; // "phasiness" of correlation
		public double lowpass_sigma = .3; // for phase correlation - frequency fraction of maximal
		public double min_frac = 0.03; // do not use higher order autocorrelation if min/max
		// is weaker than this fraction of the zero maximum
		public double min_sin = 0.5; // minimal sine for the angle between two pattern vectors
		public boolean no_crazy = true; // fail if quadratic approximation fails or returns outside of +/- 1.5

		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 minGridPeriodLwir, double maxGridPeriodLwir, double debugX, double debugY, double debugRadius,
				boolean use_large_cells, double phaseCoeff, double lowpass_sigma, double min_frac, double min_sin,
				boolean no_crazy) {
			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.minGridPeriodLwir = minGridPeriodLwir;
			this.maxGridPeriodLwir = maxGridPeriodLwir;
			this.debugX = debugX;
			this.debugY = debugY;
			this.debugRadius = debugRadius;
			this.use_large_cells = use_large_cells;
			this.phaseCoeff = phaseCoeff;
			this.lowpass_sigma = lowpass_sigma;
			this.min_frac = min_frac;
			this.min_sin = min_sin;
			this.no_crazy = no_crazy;
		}

		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 + "minGridPeriodLwir", this.minGridPeriodLwir + "");
			properties.setProperty(prefix + "maxGridPeriodLwir", this.maxGridPeriodLwir + "");
			properties.setProperty(prefix + "debugX", this.debugX + "");
			properties.setProperty(prefix + "debugY", this.debugY + "");
			properties.setProperty(prefix + "debugRadius", this.debugRadius + "");

			properties.setProperty(prefix + "use_large_cells", this.use_large_cells + "");
			properties.setProperty(prefix + "phaseCoeff", this.phaseCoeff + "");
			properties.setProperty(prefix + "lowpass_sigma", this.lowpass_sigma + "");
			properties.setProperty(prefix + "min_frac", this.min_frac + "");
			properties.setProperty(prefix + "min_sin", this.min_sin + "");
			properties.setProperty(prefix + "no_crazy", this.no_crazy + "");
		}

		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 + "minGridPeriodLwir") != null)
				this.minGridPeriodLwir = Double.parseDouble(properties.getProperty(prefix + "minGridPeriodLwir"));
			else
				this.minGridPeriodLwir = 0.0;
			if (properties.getProperty(prefix + "maxGridPeriodLwir") != null)
				this.minGridPeriodLwir = Double.parseDouble(properties.getProperty(prefix + "maxGridPeriodLwir"));
			else
				this.maxGridPeriodLwir = 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"));

			if (properties.getProperty(prefix + "use_large_cells") != null)
				this.use_large_cells = Boolean.parseBoolean(properties.getProperty(prefix + "use_large_cells"));
			if (properties.getProperty(prefix + "phaseCoeff") != null)
				this.phaseCoeff = Double.parseDouble(properties.getProperty(prefix + "phaseCoeff"));
			if (properties.getProperty(prefix + "lowpass_sigma") != null)
				this.lowpass_sigma = Double.parseDouble(properties.getProperty(prefix + "lowpass_sigma"));
			if (properties.getProperty(prefix + "min_frac") != null)
				this.min_frac = Double.parseDouble(properties.getProperty(prefix + "min_frac"));

			if (properties.getProperty(prefix + "min_sin") != null)
				this.min_sin = Double.parseDouble(properties.getProperty(prefix + "min_sin"));
			if (properties.getProperty(prefix + "no_crazy") != null)
				this.no_crazy = Boolean.parseBoolean(properties.getProperty(prefix + "no_crazy"));
		}
	}

	/* ======================================================================== */

	public static class DistortionParameters {
		// TODO: make configurable
		public double threshold_contrast = 10.0;
		public int    threshold_number =  10; // grid should have this number of nodes with above-threshold contrast
		
		private int correlationSize; // FFTSize/4
		private int correlationSizeLwir;
		private int maximalCorrelationSize; // FFTSize/2
		private int maximalCorrelationSizeLwir;
		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
		private int FFTSize;
		private int FFTSize_lwir;
		private int FFTOverlap; // 32 used for aberration kernels, former FFT_OVERLAP
		private int FFTOverlap_lwir; // 4

		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 contrastSelectSigmaCenter; // Gaussian sigma to select correlation centers in pixels, 2.0 (center
													// spot)
		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

		private int minimalPatternCluster; // minimal pattern cluster size (0 - disable retries)
		private int minimalPatternClusterLwir; // 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  int errRefineFailed =    -12;
		
		public  boolean legacyMode = false; // legacy mode
		
		
		public int getCorrelationSize(int sensor_type) {
			switch (sensor_type) {
			case 1:  return correlationSizeLwir;
			default: return correlationSize;}
		}

		public void setCorrelationSize(int size, int sensor_type) {
			switch (sensor_type) {
			case 1:  correlationSizeLwir = size; break;
			default: correlationSize = size;}
		}
		
		
		
		public int getMaximalCorrelationSize(int sensor_type) {
			switch (sensor_type) {
			case 1:  return maximalCorrelationSizeLwir;
			default: return maximalCorrelationSize;}
		}
		
		public int getFFTSize(int sensor_type) {
			switch (sensor_type) {
			case 1:  return FFTSize_lwir;
			default: return FFTSize;}
		}
/*
		public void setFFTSize(int size, int sensor_type) {
			switch (sensor_type) {
			case 1:  FFTSize_lwir = size; break;
			default: FFTSize =      size; }
			
		}
*/
		
		public int getFFTOverlap(int sensor_type) {
			switch (sensor_type) {
			case 1:  return FFTOverlap_lwir;
			default: return FFTOverlap;}
		}

		public int getMinimalPatternCluster(int sensor_type) {
			switch (sensor_type) {
			case 1:  return minimalPatternClusterLwir;
			default: return minimalPatternCluster;}
		}

		public void setMinimalPatternCluster(int size, int sensor_type) {
			switch (sensor_type) {
			case 1:  minimalPatternClusterLwir = size; break;
			default: minimalPatternCluster = size;}
		}
		
		
		public boolean showDistortionDialog(int [] mdl) { //MatchSimulatedPattern.DistortionParameters distortionParameters) {
			MatchSimulatedPattern.DistortionParameters distortionParameters = this;
			int i;
			GenericDialog gd = new GenericDialog("Distrortion parameters");
			gd.addNumericField("FFTSize (Initial pattern and aberraton kernels):", distortionParameters.FFTSize, 0);      // 256
			gd.addNumericField("FFTSize for LWIR sensors):",                       distortionParameters.FFTSize_lwir, 0); // 32
			gd.addNumericField("FFTOverlap (aberration kernels):",                 distortionParameters.FFTOverlap, 0);      // 32
			gd.addNumericField("FFTOverlap for LWIR sensors):",                    distortionParameters.FFTOverlap_lwir, 0); // 4
			gd.addNumericField("FFT Gaussian width (relative):",                   distortionParameters.fftGaussWidth, 3);
			gd.addNumericField("Correlation size:",                                distortionParameters.correlationSize, 0); // 64
			gd.addNumericField("Correlation size LWIR:",                           distortionParameters.correlationSizeLwir, 0); // 16
			gd.addNumericField("Maximal correlation size:",                        distortionParameters.maximalCorrelationSize, 0); // 128
			gd.addNumericField("Maximal correlation size LWIR:",                   distortionParameters.maximalCorrelationSizeLwir, 0); // 16

			gd.addNumericField("Correlation Gauss width (relative):",              distortionParameters.correlationGaussWidth, 3);
			gd.addCheckbox("Keep Gaussian width absolute when increasing FFT size",distortionParameters.absoluteCorrelationGaussWidth);
//			/phaseCorrelationFraction
			//// leave this number of zeros on teh margins of the window (toatal from both sides). If correlationGaussWidth>0 will
	        // additionally multiply by Hamming
			gd.addNumericField("Leave zeros on the window margins (toatal numbedr from both sides)", distortionParameters.zeros, 0);

			gd.addNumericField("Phase correlation modifier (1.0 - phase corr., 0 - just corr.)", distortionParameters.phaseCorrelationFraction, 5);
			gd.addNumericField("Correlation high-pass sigma:",          distortionParameters.correlationHighPassSigma, 3);

			gd.addNumericField("Correlation low-pass sigma (fraction of sqrt(2)*Nyquist, lower - more filtering, 0 -none):",distortionParameters.correlationLowPassSigma, 3);
			gd.addNumericField("Correlation maximal offset from predicted:",distortionParameters.correlationMaxOffset, 3);
			gd.addNumericField("Detection ring width (fraction):",      distortionParameters.correlationRingWidth, 3);
			gd.addNumericField("Correlation minimal contrast (normalized)",         distortionParameters.correlationMinContrast, 3);
			gd.addNumericField("Correlation minimal contrast for initial search (normalized)", distortionParameters.correlationMinInitialContrast, 3);
			gd.addNumericField("Correlation minimal contrast (absolute)",         distortionParameters.correlationMinAbsoluteContrast, 3);
			gd.addNumericField("Correlation minimal contrast for initial search (absolute)", distortionParameters.correlationMinAbsoluteInitialContrast, 3);

			gd.addNumericField("Decrease contrast of cells that are too close to the border to be processed in refinement pass", distortionParameters.scaleFirstPassContrast, 3);
			gd.addNumericField("Gaussian sigma to select correlation center in pixels, 2.0", distortionParameters.contrastSelectSigmaCenter, 3);
			gd.addNumericField("Gaussian sigma to select correlation off-centers (fraction of UV period), 0.1", distortionParameters.contrastSelectSigma, 3);
			gd.addNumericField("Gaussian sigma to average correlation variations (as contrast reference), 0.5", distortionParameters.contrastAverageSigma, 3);

			gd.addNumericField("Minimal initial pattern cluster size (0 - disable retries)", distortionParameters.minimalPatternCluster, 0); // 40
			gd.addNumericField("Minimal initial LWIR pattern cluster size (0 - disable retries)", distortionParameters.minimalPatternClusterLwir, 0); // 10
			
			gd.addMessage("thresholdContrast=" + threshold_contrast);
			gd.addMessage("threshold_number=" + threshold_number);

			gd.addNumericField("Scale minimal contrast if the initial cluster is nonzero but smaller", distortionParameters.scaleMinimalInitialContrast, 3);
			gd.addNumericField("Overlap of FFT areas when searching for pattern", distortionParameters.searchOverlap, 3);

			gd.addNumericField("Pattern subdivision:",                  distortionParameters.patternSubdiv, 0); // 4
			gd.addNumericField("Blur pattern bitmap (sigma):       ",   distortionParameters.bPatternSigma, 3,5,"pattern cell"); // 0.02
			gd.addNumericField("Blur pattern (sigma):                ", distortionParameters.barraySigma, 3,5,"sensor pix");     // 0.5
			gd.addNumericField("Correlation weights (around maximum):", distortionParameters.correlationWeightSigma, 3,5,"nodes"); // 2.5
			gd.addNumericField("Correlation radius scale (0 - sharp sigma)", distortionParameters.correlationRadiusScale, 1,3,"sigmas"); //2.0

			gd.addNumericField("Correlation maximal radius to use",      distortionParameters.correlationRadius, 0,1,"pix"); // 2.0
			gd.addNumericField("Correlation maximum calculation threshold", distortionParameters.correlationThreshold*100, 2,5,"%"); // .8
			gd.addNumericField("Interpolate correlation (FFT*linear)",  distortionParameters.correlationSubdiv, 0,3,"x"); // 16
			gd.addNumericField("Interpolate correlation with FFT",      distortionParameters.correlationFFTSubdiv, 0,3,"x"); // 4

			gd.addNumericField("Correlation dx (debug)",                distortionParameters.correlationDx, 3);
			gd.addNumericField("Correlation dy (debug)",                distortionParameters.correlationDy, 3);
			gd.addNumericField("Maximal size of the pattern grid (square)", distortionParameters.gridSize, 0);
			gd.addCheckbox    ("Refine correlations",                   distortionParameters.refineCorrelations);
			gd.addCheckbox    ("Use fast correlation on first pass",    distortionParameters.fastCorrelationOnFirstPass);
			gd.addCheckbox    ("Use fast correlation on refine pass",   distortionParameters.fastCorrelationOnFinalPass);

			gd.addCheckbox    ("Average correlation measurements between neighbors (on refine)", distortionParameters.correlationAverageOnRefine);
			gd.addCheckbox    ("Update coordinates of the grid points as they are recalculated (false - then update all at once)", distortionParameters.refineInPlace);

			gd.addNumericField("Distance to ortho neighbors (for averaging)", distortionParameters.averageOrthoDist, 3,5,"sensor pix");
			gd.addNumericField("Combined weight of ortho neighbors (fraction of 1.0)", distortionParameters.averageOrthoWeight, 3);
			gd.addNumericField("Distance to diagonal neighbors (for averaging)", distortionParameters.averageDiagDist, 3,5,"sensor pix");
			gd.addNumericField("Combined weight of diagonal neighbors (fraction of 1.0)", distortionParameters.averageDiagWeight, 3);
			gd.addCheckbox    ("Use quadratic extrapolation (false - force linear)", distortionParameters.useQuadratic);

			gd.addCheckbox    ("Remove outer (unreliable) layer before extrapolation", distortionParameters.removeLast);
			gd.addNumericField("Number of extrapolated layers of nodes (final stage)", distortionParameters.numberExtrapolated, 0);
			gd.addNumericField("Sigma during final extrapolation stage", distortionParameters.extrapolationSigma, 3,5,"nodes");
			gd.addNumericField("Minimal UV span in correlation window to trigger FFT size increase", distortionParameters.minUVSpan, 3);

			gd.addCheckbox    ("Compensate uneven pattern intensity", distortionParameters.flatFieldCorrection);
			gd.addNumericField("Extrapolate pattern intensity map (relative to pattern period)", distortionParameters.flatFieldExtarpolate, 3);
			gd.addNumericField("Blur pattern intensity map (relative to pattern period)", distortionParameters.flatFieldBlur, 3);
			gd.addNumericField("Do not use areas where intensity map is below this part of maximal", distortionParameters.flatFieldMin, 3);

			gd.addNumericField("Shrink before extrapolating intensity map (relative to the average grid period)", distortionParameters.flatFieldShrink, 3);
			gd.addNumericField("Expand during extrapolation (relative to the average grid period)", distortionParameters.flatFieldExpand, 3);
			gd.addNumericField("Extrapolation weight effective radius (relative to the average grid period)", distortionParameters.flatFieldSigmaRadius, 3);
			gd.addNumericField("Consider pixels in a square with the side twice this (relative to flatFieldSigmaRadius)", distortionParameters.flatFieldExtraRadius, 3);
			gd.addNumericField("Multiply the average grid period to determine the area for averaging the grig brightness", distortionParameters.averagingAreaScale, 3);

			gd.addCheckbox    ("Legacy mode (deprecated)", distortionParameters.legacyMode);

			gd.addNumericField("Debug level inside the loop", distortionParameters.loop_debug_level, 0);


			gd.addNumericField("Debug Level:",                          mdl[0], 0);
		    WindowTools.addScrollBars(gd);
			gd.showDialog();
			if (gd.wasCanceled()) return false;
			distortionParameters.FFTSize =                     makePowerOfTwo((int) gd.getNextNumber());
			distortionParameters.FFTSize_lwir =                makePowerOfTwo((int) gd.getNextNumber());
			distortionParameters.FFTOverlap =                  makePowerOfTwo((int) gd.getNextNumber());
			distortionParameters.FFTOverlap_lwir =             makePowerOfTwo((int) gd.getNextNumber());
			distortionParameters.fftGaussWidth=                gd.getNextNumber();
			distortionParameters.correlationSize =             makePowerOfTwo((int) gd.getNextNumber());
			distortionParameters.correlationSizeLwir =         makePowerOfTwo((int) gd.getNextNumber());
			distortionParameters.maximalCorrelationSize =      makePowerOfTwo((int) gd.getNextNumber());
			distortionParameters.maximalCorrelationSizeLwir =  makePowerOfTwo((int) gd.getNextNumber());
			distortionParameters.correlationGaussWidth=        gd.getNextNumber();
			distortionParameters.absoluteCorrelationGaussWidth=gd.getNextBoolean();
			distortionParameters.zeros=                  (int) gd.getNextNumber();
			distortionParameters.phaseCorrelationFraction=     gd.getNextNumber();
			distortionParameters.correlationHighPassSigma=     gd.getNextNumber();
			distortionParameters.correlationLowPassSigma=      gd.getNextNumber();
			distortionParameters.correlationMaxOffset=         gd.getNextNumber();
			distortionParameters.correlationRingWidth=         gd.getNextNumber();
			distortionParameters.correlationMinContrast=       gd.getNextNumber();
			distortionParameters.correlationMinInitialContrast=  gd.getNextNumber();

			distortionParameters.correlationMinAbsoluteContrast=  gd.getNextNumber();
			distortionParameters.correlationMinAbsoluteInitialContrast=  gd.getNextNumber();

			distortionParameters.scaleFirstPassContrast=     gd.getNextNumber();
			distortionParameters.contrastSelectSigmaCenter=  gd.getNextNumber();
			distortionParameters.contrastSelectSigma=        gd.getNextNumber();
			distortionParameters.contrastAverageSigma=       gd.getNextNumber();

			distortionParameters.minimalPatternCluster=(int) gd.getNextNumber();
			distortionParameters.minimalPatternClusterLwir=(int) gd.getNextNumber();

			distortionParameters.scaleMinimalInitialContrast=gd.getNextNumber();
			distortionParameters.searchOverlap=              gd.getNextNumber();

			distortionParameters.patternSubdiv=     (int) gd.getNextNumber();
			distortionParameters.bPatternSigma=           gd.getNextNumber();
			distortionParameters.barraySigma=             gd.getNextNumber();
			distortionParameters.correlationWeightSigma=  gd.getNextNumber();
			distortionParameters.correlationRadiusScale=  gd.getNextNumber();
			distortionParameters.correlationRadius= (int) gd.getNextNumber();
			distortionParameters.correlationThreshold= 0.01*gd.getNextNumber();
			distortionParameters.correlationSubdiv= (int) gd.getNextNumber();
			distortionParameters.correlationFFTSubdiv=1;
			for (i=(int) gd.getNextNumber(); i >1; i>>=1) distortionParameters.correlationFFTSubdiv <<=1; /* make it to be power of 2 */
			distortionParameters.correlationDx=           gd.getNextNumber();
			distortionParameters.correlationDy=           gd.getNextNumber();
			distortionParameters.gridSize=          (int) gd.getNextNumber();
			distortionParameters.refineCorrelations=      gd.getNextBoolean();
			distortionParameters.fastCorrelationOnFirstPass=gd.getNextBoolean();
			distortionParameters.fastCorrelationOnFinalPass=gd.getNextBoolean();

			distortionParameters.correlationAverageOnRefine=gd.getNextBoolean();
			distortionParameters.refineInPlace=           gd.getNextBoolean();
			distortionParameters.averageOrthoDist=        gd.getNextNumber();
			distortionParameters.averageOrthoWeight=      gd.getNextNumber();
			distortionParameters.averageDiagDist=         gd.getNextNumber();
			distortionParameters.averageDiagWeight=       gd.getNextNumber();
			distortionParameters.useQuadratic=            gd.getNextBoolean();

			distortionParameters.removeLast=              gd.getNextBoolean();
			distortionParameters.numberExtrapolated=(int) gd.getNextNumber();
			distortionParameters.extrapolationSigma=      gd.getNextNumber();
			distortionParameters.minUVSpan=               gd.getNextNumber();
			distortionParameters.flatFieldCorrection=     gd.getNextBoolean();
			distortionParameters.flatFieldExtarpolate=    gd.getNextNumber();
			distortionParameters.flatFieldBlur=           gd.getNextNumber();
			distortionParameters.flatFieldMin=            gd.getNextNumber();
			distortionParameters.flatFieldShrink=         gd.getNextNumber();
			distortionParameters.flatFieldExpand=         gd.getNextNumber();
			distortionParameters.flatFieldSigmaRadius=    gd.getNextNumber();
			distortionParameters.flatFieldExtraRadius=    gd.getNextNumber();
			distortionParameters.averagingAreaScale=      gd.getNextNumber();
			distortionParameters.legacyMode=              gd.getNextBoolean();
			distortionParameters.loop_debug_level=  (int) gd.getNextNumber();
			mdl[0]=                                 (int) gd.getNextNumber();
			return true;
		}
		private int makePowerOfTwo(int v) {
			int v2 = 1;
			for (int i=v; i > 1; i>>=1 ) v2 <<=1; /* make it to be power of 2 */
			return v2;

		}
		
		
		
		public DistortionParameters(int correlationSize, int correlationSizeLwir, int maximalCorrelationSize,
				int maximalCorrelationSizeLwir, double correlationGaussWidth, boolean absoluteCorrelationGaussWidth,
				int zeros, int FFTSize, int FFTSize_lwir, int FFTOverlap, int FFTOverlap_lwir, 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 contrastSelectSigmaCenter, // Gaussian sigma to select correlation centers (fraction of UV
													// period), 0.02 (center spot)
				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)
				int minimalPatternClusterLwir, // 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.correlationSizeLwir = correlationSizeLwir;
			this.maximalCorrelationSize = maximalCorrelationSize;
			this.maximalCorrelationSizeLwir = maximalCorrelationSizeLwir;
			this.correlationGaussWidth = correlationGaussWidth;
			this.absoluteCorrelationGaussWidth = absoluteCorrelationGaussWidth;
			this.zeros = zeros;
			this.FFTSize = FFTSize;
			this.FFTSize_lwir = FFTSize_lwir;
			this.FFTOverlap = FFTOverlap;
			this.FFTOverlap_lwir = FFTOverlap_lwir;
			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.contrastSelectSigmaCenter = contrastSelectSigmaCenter; // Gaussian sigma to select correlation centers
																		// (pixels, 2.0)
			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.minimalPatternClusterLwir = minimalPatternClusterLwir; // 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;

		}

		@Override
		public DistortionParameters clone() {
			return new DistortionParameters(this.correlationSize, this.correlationSizeLwir, this.maximalCorrelationSize,
					this.maximalCorrelationSizeLwir, this.correlationGaussWidth, this.absoluteCorrelationGaussWidth,
					this.zeros, this.FFTSize, this.FFTSize_lwir, this.FFTOverlap, this.FFTOverlap_lwir,
					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.contrastSelectSigmaCenter, // Gaussian sigma to select correlation centers (pixels, 2.0)
					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.minimalPatternClusterLwir, // 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 + "correlationSizeLwir", this.correlationSizeLwir + "");
			properties.setProperty(prefix + "maximalCorrelationSize", this.maximalCorrelationSize + "");
			properties.setProperty(prefix + "maximalCorrelationSizeLwir", this.maximalCorrelationSizeLwir + "");
			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 + "FFTSize_lwir", this.FFTSize_lwir + "");
			properties.setProperty(prefix + "FFTOverlap", this.FFTOverlap + "");
			properties.setProperty(prefix + "FFTOverlap_lwir", this.FFTOverlap_lwir + "");
			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 + "contrastSelectSigmaCenter", this.contrastSelectSigmaCenter + "");
			properties.setProperty(prefix + "contrastSelectSigma", this.contrastSelectSigma + "");
			properties.setProperty(prefix + "contrastAverageSigma", this.contrastAverageSigma + "");
			properties.setProperty(prefix + "minimalPatternCluster", this.minimalPatternCluster + "");
			properties.setProperty(prefix + "minimalPatternClusterLwir", this.minimalPatternClusterLwir + "");
			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) {
//			EProperties properties = (EProperties) pproperties;
			if (properties.getProperty(prefix + "correlationSize") != null)
				this.correlationSize = Integer.parseInt(properties.getProperty(prefix + "correlationSize"));
			if (properties.getProperty(prefix + "correlationSizeLwir") != null)
				this.correlationSizeLwir = Integer.parseInt(properties.getProperty(prefix + "correlationSizeLwir"));
			if (properties.getProperty(prefix + "maximalCorrelationSize") != null)
				this.maximalCorrelationSize = Integer
						.parseInt(properties.getProperty(prefix + "maximalCorrelationSize"));
			if (properties.getProperty(prefix + "maximalCorrelationSizeLwir") != null)
				this.maximalCorrelationSizeLwir = Integer
						.parseInt(properties.getProperty(prefix + "maximalCorrelationSizeLwir"));
			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 + "FFTSize_lwir") != null)
				this.FFTSize_lwir = Integer.parseInt(properties.getProperty(prefix + "FFTSize_lwir"));

			if (properties.getProperty(prefix + "FFTOverlap") != null)
				this.FFTOverlap = Integer.parseInt(properties.getProperty(prefix + "FFTOverlap"));
			if (properties.getProperty(prefix + "FFTOverlap_lwir") != null)
				this.FFTOverlap_lwir = Integer.parseInt(properties.getProperty(prefix + "FFTOverlap_lwir"));

// finally shortened :
//			this.FFTOverlap=      properties.getProperty(prefix+"FFTOverlap",     this.FFTOverlap);
//			this.FFTOverlap_lwir= properties.getProperty(prefix+"FFTOverlap_lwir",this.FFTOverlap_lwir);

			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 + "contrastSelectSigmaCenter") != null)
				this.contrastSelectSigmaCenter = Double
						.parseDouble(properties.getProperty(prefix + "contrastSelectSigmaCenter"));
			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 + "minimalPatternClusterLwir") != null)
				this.minimalPatternClusterLwir = Integer
						.parseInt(properties.getProperty(prefix + "minimalPatternClusterLwir"));

			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"));
		}
	}
	/// ===== end of public static class DistortionParameters
	/// ==============================
	/* Use ROI */
	/* Supply rectangle */

	// Now accepts rectangles not completely contained in the image, pixels will be
	// copied from the image edge
	// getNoBayer() - replacement for splitBayer for monochrome images
	public double[] getNoBayer(ImagePlus imp, Rectangle r) {
		return getNoBayer(imp, 1, r);
	}

	// 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);
	}

	public double[] getNoBayer(ImagePlus imp, int sliceNumber, Rectangle r) {
		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();
		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);
		double[] dpixels = new double[r.height * r.width];
		int out_indx = 0;
		for (int y = 0; y < r.height; y++) {
			int y_in = y + r.y;
			if (y_in < 0)
				y_in = 0;
			else if (y_in >= full_height)
				y_in = full_height - 1;
			int base = y_in * full_width;
			for (int x = 0; x < r.width; x++) {
				int x_in = x + r.x;
				if (x_in < 0)
					x_in = 0;
				else if (x_in >= full_width)
					x_in = full_width - 1;
				dpixels[out_indx++] = pixels[base + x_in];
			}
		}
		return dpixels;
	}

	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 static double[][] splitBayer(ImagePlus imp, int sliceNumber, Rectangle r, boolean equalize_greens, int debug_level) {
		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 (debug_level > 10)
			IJ.showMessage("splitBayer", "r.width=" + r.width + "\nr.height=" + r.height + "\nr.x=" + r.x + "\nr.y="
					+ r.y + "\nlength=" + pixels.length);
		if ((debug_level > 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 (debug_level > 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;
	}
	//Assuming gr/bg
	public static double [][] simpleDemosaic(
			ImagePlus imp,
			double r2g,
			double b2g,
			double saturation,
			double gamma,
			double minlin_gamma, // do not apply gamma to lower values
			double hi            // map to 255, gamma will preserve 
			) {
		boolean debug_this = false;
		double kr= 0.299;
		double kb = 0.114;
		double kg = 1.0 - kr - kb;
		ImageProcessor ip = null;
		float[] pixels;
		ip = imp.getProcessor();
		pixels = (float[]) ip.getPixels(); // null pointer
		int width = imp.getWidth(); // full image width
		int height = imp.getHeight(); // full image height
		double [][] rgb = new double [3][width*height];
		double [] sg= {0.0,0.0};
		for (int y = 0; y < height; y+=2) {
			for (int x = 0; x < width; x+=2) {
				int indx0 = width*y+x;
				int indx3 = indx0+width+1;
				sg[0]+=pixels[indx0];
				sg[1]+=pixels[indx3];
			}
		}
		double g_av = Math.sqrt(sg[0] * sg[1]); 
		double g2g0 = g_av/sg[0];
		double g2g3 = g_av/sg[1];
		for (int y = 0; y < height; y+=2) {
			for (int x = 0; x < width; x+=2) {
				int indx0 = width*y+x;
				int indx1 = indx0+1;
				int indx2 = indx0+width;
				int indx3 = indx2+1;
				sg[0]+=pixels[indx0];
				sg[1]+=pixels[indx3];
				rgb[1][indx0] = pixels[indx0] * g2g0;
				rgb[1][indx3] = pixels[indx3] * g2g3;
				rgb[0][indx1] = pixels[indx1] * r2g;
				rgb[2][indx2] = pixels[indx2] * b2g;
			}
		}
		if (debug_this) {
			ShowDoubleFloatArrays.showArrays(rgb, width, height, true,imp.getTitle()+"split");
		}
		if (debug_this) { // remove
			System.out.println("sg="+sg[0]+", "+sg[1]+", g_av="+g_av);
		}
		// bi-linear interpolation
		for (int y = 0; y < height; y+=2) {
			for (int x = 0; x < width; x+=2) {
				int [][] ind = new int[4][4];
				int base = (y-1)*width+(x-1);
				for (int i = 0; i < 4;i++) {
					int ie = i;
					if ((i==0) && (y==0)) {
						ie = 2;
					} else if ((i==3) && (y==(height - 2))) {
						ie = 1;
					}	
					for (int j = 0; j < 4; j++) {
						int je = j;
						if ((j==0) && (x==0)) {
							je = 2;
						} else if ((j==3) && (x==(width - 2))) {
							je = 1;
						}
						ind[i][j] = base+ie*width+je;
					}
				}
				// red in top left
				rgb[0][ind[1][1]] = 0.5* (
						rgb[0][ind[1][0]] +
						rgb[0][ind[1][2]]);
				// blue in top left
				rgb[2][ind[1][1]] = 0.5* (
						rgb[2][ind[0][1]] +
						rgb[2][ind[2][1]]);
				// green in top right
				rgb[1][ind[1][2]] = 0.25*(
						rgb[1][ind[0][2]] +
						rgb[1][ind[1][1]] +
						rgb[1][ind[1][3]] +
						rgb[1][ind[2][2]]);
				// blue in top right
				rgb[2][ind[1][2]] = 0.25*(
						rgb[2][ind[0][1]] +
						rgb[2][ind[0][3]] +
						rgb[2][ind[2][1]] +
						rgb[2][ind[2][3]]);
				// red in bottom left
				rgb[0][ind[2][1]] = 0.25*(
						rgb[0][ind[1][0]] +
						rgb[0][ind[1][2]] +
						rgb[0][ind[3][0]] +
						rgb[0][ind[3][2]]);
				// green in bottom left
				rgb[1][ind[2][1]] = 0.25*(
						rgb[1][ind[1][1]] +
						rgb[1][ind[2][0]] +
						rgb[1][ind[2][2]] +
						rgb[1][ind[3][1]]);
				// red in bottom right
				rgb[0][ind[2][2]] = 0.5* (
						rgb[0][ind[1][2]] +
						rgb[0][ind[3][2]]);
				// blue in bottom right
				rgb[2][ind[2][2]] = 0.5* (
						rgb[2][ind[2][1]] +
						rgb[2][ind[2][3]]);
			}
		}
		if (debug_this) {
			ShowDoubleFloatArrays.showArrays(rgb, width, height, true,imp.getTitle()+"bilinear");
		}
		if (saturation != 1.0) {
			for (int i = 0; i < rgb[0].length; i++) {
				rgb[0][i] = rgb[1][i] * Math.pow(rgb[0][i]/rgb[1][i], saturation); 
				rgb[2][i] = rgb[1][i] * Math.pow(rgb[2][i]/rgb[1][i], saturation); 
			}
		}
		
		// gamma-correction
/*
			double gamma,
			double minlin_gamma, // do not apply gamma to lower values
			double hi            // map to 255, gamma will preserve 
		
 */	   
		boolean nogamma = (gamma == 1.0); 
		double out_range = 255.0;
		double lin_scale =  out_range/hi;
		if (nogamma) {
			minlin_gamma = hi;
		}
		double scale_out = out_range / (Math.pow(hi,  gamma) - Math.pow(minlin_gamma, gamma)*(1.0 - gamma));
//		double Y0 = scale_out * Math.pow(minlin_gamma, gamma)*(1.0 - gamma);
		double rY0 = Math.pow(minlin_gamma, gamma)*(1.0 - gamma);
		if (!nogamma) {
			lin_scale = scale_out* gamma*Math.pow(minlin_gamma, gamma - 1); 
		}
		for (int indx = 0; indx < rgb[0].length; indx++) {
			double Y = rgb[0][indx]*kr+rgb[1][indx]*kg+rgb[2][indx]*kb; // intensity to apply gamma
			double s = lin_scale;
			if (!nogamma && (Y > minlin_gamma)) {
				s = scale_out * (Math.pow(Y, gamma) - rY0)/Y;
			}
			for (int i = 0; i < 3; i++) {
				rgb[i][indx] *= s;
			}
		}
		if (debug_this) {
			ShowDoubleFloatArrays.showArrays(rgb, width, height, true,imp.getTitle()+"gamma");
		}
		
		return rgb;
	}
	
	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

}