package com.elphel.imagej.calibration;
import java.awt.Rectangle;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;

import javax.swing.SwingUtilities;

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

import ij.IJ;

/*
 **
 ** SimulationPattern.java - Generate simulated pattern
 **
 ** Copyright (C) 2010-2011 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  SimulationPattern.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/>.
 ** -----------------------------------------------------------------------------**
 **
 */

public class SimulationPattern {
	//	private double []  bPattern; // pattern bitmap (does not change)
	//	private double[][] barray;   // high resolution boolean pattern array (specific to distortions in each area)
	public double []  bPattern=null; // pattern bitmap (does not change)
	public int        bPatternSize=0;
	///	public double[][] barray;   // high resolution boolean pattern array (specific to distortions in each area)
	public double[] barray;   // high resolution boolean pattern array (specific to distortions in each area)
	public double bPatternSigma=0.0;
	public double barraySigma=0.0;
	public  int         debugLevel=2;
	private DoubleGaussianBlur gb = new DoubleGaussianBlur();
	private ShowDoubleFloatArrays SDFA_INSTANCE= new ShowDoubleFloatArrays(); // just for debugging?

	public SimulationPattern (){
		this.bPattern=null;
	}
	public SimulationPattern (double [] bPattern){ // reuse the same barray
		this.bPattern=bPattern;
	}
	public SimulationPattern(
			SimulParameters simulParameters ) {
		this.bPatternSigma=simulParameters.bPatternSigma;
		this.barraySigma=simulParameters.barraySigma;
		patternGenerator(simulParameters);
	}
	public SimulationPattern(
			int size,
			int patternNumber,
			double patternModifier) {
		patternGenerator(size,patternNumber,patternModifier);
	}
	/* ======================================================================== */
	public double [] patternGenerator(
			SimulParameters simulParameters ) {
		return patternGenerator(
				simulParameters.patternSize,
				simulParameters.pattern_type,
				simulParameters.pattern_modifier);
	}
	public double [] patternGenerator(int size,
			int patternNumber,
			double patternModifier) {
		this.bPattern=new double [size*size];
		this.bPatternSize=size;
		int i,j,index,k;
		double p;
		double a,r,r2,h;
		double qSize=size/4;
		switch (patternNumber) {
		case 1:
			a=patternModifier*(Math.sqrt(2)-1.0);
			r=(a*a+1)/(2*a)*qSize;
			r2=r*r;
			h=Math.sqrt(r2-qSize*qSize);
			if (a>1.0) h=-h;
			double [][] pattern1Centers={{qSize,         -h},
					{ size+h,       qSize},
					{ size-qSize,   size+h},
					{-h,            size-qSize}};
			index=0;
			for (i=0;i<size;i++) for (j=0;j<size;j++) {
				p=1.0;
				for (k=0;k<pattern1Centers.length;k++) if ((((i-pattern1Centers[k][1])*(i-pattern1Centers[k][1])+(j-pattern1Centers[k][0])*(j-pattern1Centers[k][0])))<r2) p=0.0;
				this.bPattern[index++]=p;
			}
			break;
		case 2:
			index=0;
			for (i=0;i<size;i++) for (j=0;j<size;j++) {
				p= ((i>=0.3*size) && (i<0.7*size) && (j>=0.3*size) && (j<0.7*size))?1.0:0.0;
				this.bPattern[index++]=p;
			}
			break;
		case 3:
			index=0;
			for (i=0;i<size;i++) for (j=0;j<size;j++) {
				p= ((i>=0.1*size) && (i<0.9*size) && (j>=0.1*size) && (j<0.9*size))?1.0:0.0;
				this.bPattern[index++]=p;
			}
			break;
		default: for (index=0;index<this.bPattern.length;index++) this.bPattern[index]=1.0;
		}
		// blur pattern
		if (this.bPatternSigma>0) {
			if (this.bPatternSigma>0.25) this.bPatternSigma=0.25;
			// 1 - add margins around the pattern
			int i1,j1;
			int margin= (int) Math.ceil(3*size*this.bPatternSigma);
			int sizeM=size+2*margin;
			boolean invertY,invertX;
			double [] bPatternM=new double [sizeM*sizeM];
			for (i=0;i<sizeM;i++) {
				i1= (i+size-margin)%size;
				invertY=(((i+size-margin)/size)&1)==0;
				for (j=0;j<sizeM;j++) {
					invertX=(((j+size-margin)/size)&1)==0;
					j1= (j+size-margin)%size;
					bPatternM[i*sizeM+j]= (invertX ^ invertY)?(1.0-this.bPattern[i1*size+j1]):this.bPattern[i1*size+j1];
				}
			}
			// apply blur
			if (this.debugLevel>3) SDFA_INSTANCE.showArrays(bPatternM,sizeM,sizeM, "bPatternM");
			this.gb.blurDouble(bPatternM,sizeM,sizeM,size*this.bPatternSigma,size*this.bPatternSigma, 0.01);
			if (this.debugLevel>3) SDFA_INSTANCE.showArrays(bPatternM,sizeM,sizeM, "bPatternM-blured");
			// remove margins
			for (i=0;i<size;i++) for (j=0;j<size;j++) {
				this.bPattern[i*size+j]= bPatternM[(i+margin)*sizeM+(j+margin)];
			}
		}
		return this.bPattern;
	}

	/* ======================================================================== */
	public void simulatePatternFullPattern(
			double freqX1,
			double freqY1,
			double phase1,
			double freqX2,
			double freqY2,
			double phase2,
			double [] corr,
			int subdiv,
			int size,
			boolean center_for_g2,
			boolean mono) {// takes precedence over center_for_g2, makes symmetrical around center of array (between pixels)
		this.barray=simulatePatternFullPatternSafe(
				freqX1,
				freqY1,
				phase1,
				freqX2,
				freqY2,
				phase2,
				corr,
				subdiv,
				size,
				center_for_g2,
				mono);

	}


	public double [] simulatePatternFullPatternSafe(
			double freqX1,
			double freqY1,
			double phase1,
			double freqX2,
			double freqY2,
			double phase2,
			double [] corr,
			int subdiv,
			int size,
			boolean center_for_g2,
			boolean mono) {// takes precedence over center_for_g2, makes symmetrical around center of array (between pixels)
		int patternSize= (this.bPattern!=null)?((int) Math.sqrt(this.bPattern.length)):0;
		double twicePatternSize=2*patternSize;
		int i,j;
		int fullSize=subdiv*(size+4)*2;
		double [] localBarray=new double [fullSize*fullSize];
		double xl,yl; //,x,y;//,p1,p2;


		double [][] xy2uv= {{freqX1,freqY1},
				{freqX2,freqY2}};

		if (this.debugLevel>2) {
			System.out.println("simulatePatternFullPattern:");
			System.out.println(" Ax="+IJ.d2s(corr[0],5)+" Bx="+IJ.d2s(corr[1],5)+" Cx="+IJ.d2s(corr[2],5)+" Dx="+IJ.d2s(corr[6],5)+" Ex="+IJ.d2s(corr[7],5));
			System.out.println(" Ay="+IJ.d2s(corr[3],5)+" By="+IJ.d2s(corr[4],5)+" Cy="+IJ.d2s(corr[5],5)+" Dy="+IJ.d2s(corr[8],5)+" Ey="+IJ.d2s(corr[9],5));
		}

		if (this.debugLevel>2) {
			System.out.println("simulatePatternFullPattern:  xy2uv[0][0]="+IJ.d2s(xy2uv[0][0],4)+" xy2uv[0][1]="+IJ.d2s(xy2uv[0][1],4));
			System.out.println("simulatePatternFullPattern:  xy2uv[1][0]="+IJ.d2s(xy2uv[1][0],4)+" xy2uv[1][1]="+IJ.d2s(xy2uv[1][1],4));
		}

		double []uv, xy;
		xy=new double [2];

		double [] phases={phase1/(Math.PI*2)+0.25,phase2/(Math.PI*2)+0.25}; // period=1.0;
		int iu,iv;
		boolean invert;

		double offset = mono? 0.0: ((center_for_g2?0.5:1.0));
		double center = 0.5*fullSize; //  +(mono?-0.5:  0.0); // see if it is needed!
		// in mono mode result barray is symmetrical around [fullSize/2,fullSize/2], but
		// after extractSimulMono result will be symmetrical around [size/2-0.5, size/2 - 0.5]
		for (i=0; i<fullSize; i++) {
			yl=(i - center) / subdiv - offset; // center in the middle of Bayer
			for (j=0; j<fullSize; j++) {
				xl=(j - center) / subdiv - offset; // center in the middle of Bayer
				/* apply second order polynomial correction to x,y
    x=xl+Ax*xl^2+Bx*yl^2+2*Cx*xl*yl;
    y=xl+Ay*xl^2+By*yl^2+2*Cy*xl*yl; */
				if (corr==null) {
					xy[0]=xl;
					xy[1]=yl;
				} else {
					xy[0]=xl + corr[0]*xl*xl + corr[1]*yl*yl + 2* corr[2]*xl*yl + corr[6]*xl + corr[7]*yl;
					xy[1]=yl + corr[3]*xl*xl + corr[4]*yl*yl + 2* corr[5]*xl*yl + corr[8]*xl + corr[9]*yl;
				}
				uv= matrix2x2_mul(xy2uv, xy);
				uv= vector_add(uv,phases);
				uv[0]-=Math.floor(uv[0]);
				uv[1]-=Math.floor(uv[1]);
				invert=false;
				if (uv[0]>=0.5){
					invert=!invert;
					uv[0]-=0.5;
				}
				if (uv[1]>=0.5){
					invert=!invert;
					uv[1]-=0.5;
				}
				if (this.bPattern==null) {
					localBarray[i*fullSize+j]=invert?0.0:1.0; //!invert;
				} else {
					iu= (int) Math.round(uv[0]*twicePatternSize);
					iv= (int) Math.round(uv[1]*twicePatternSize);
					if ((iu<0) || (iu>=patternSize)) {
						invert=!invert;
						iu=(iu+patternSize)% patternSize;
					}
					if ((iv<0) || (iv>=patternSize)) {
						invert=!invert;
						iv=(iv+patternSize)% patternSize;
					}
					localBarray[i*fullSize+j]=invert?(1.0-this.bPattern[iv*patternSize + iu]): this.bPattern[iv*patternSize + iu];
				}
			}
		}
		// Blur barray pattern if sigma >0
		if (this.barraySigma>0) {
			double sigma=this.barraySigma*subdiv; //*/ 2?
			if (this.debugLevel>3) SDFA_INSTANCE.showArrays(localBarray, "localBarray");
			this.gb.blurDouble(localBarray,fullSize,fullSize,sigma,sigma, 0.01);
			if (this.debugLevel>3) SDFA_INSTANCE.showArrays(localBarray, "localBarray-blured");
		}
		return localBarray;
	}
	/* ======================================================================== */
	public double [] recursiveFillPixels ( // invert pattern in the caller, return signed value (-1..1 - pattern is 0..1)
			SimulParameters  simulParameters,
			double [] xy,  // top-left corner
			double [] dxy, // increments to other corners
			double [][][] cornersXY, // xy pairs for the 4 corners of the square in UV (pattern) coordinates (u0v0,u1v0,u0v1,u1v1)
			double [] uv,  // UV value for the top-left corner (matching cornersXY[0][0])
			double [] duv,  // distances to the opposite corner in UV
			//			   final boolean   maskOnly, // just mark defined cells
			int debug
			){ //use this.bPattern, this.bPatternSize (side of the square)

		double [][][] cornersUV=new double [2][2][];
		double [] xy4=new double[2];
		double [] result ={0.0,0.0};
		int numInside=0;
		for (int i=0;i<2;i++) for (int j=0;j<2;j++) {
			xy4[0]=xy[0]+j*dxy[0];
			xy4[1]=xy[1]+i*dxy[1];
			cornersUV[i][j]=bilinearXY2UV(cornersXY,xy4,debug);
			if ((cornersUV[i][j][0]>=0.0) && (cornersUV[i][j][0]<=1.0) && (cornersUV[i][j][1]>=0.0) && (cornersUV[i][j][1]<=1.0)) numInside++;
		}
		if (debug>21){
			String dbgStr="";
			//			 IJ.d2s(quarter_patterns[iq][0][0],4)
			dbgStr+="xy={"+IJ.d2s(xy[0],2)+","+IJ.d2s(xy[1],2)+"} ";
			dbgStr+=" dxy={"+IJ.d2s(dxy[0],2)+","+IJ.d2s(dxy[1],2)+"} ";
			dbgStr+=" uv={"+IJ.d2s(uv[0],2)+","+IJ.d2s(uv[1],2)+"} ";
			dbgStr+=" duv={"+IJ.d2s(duv[0],2)+","+IJ.d2s(duv[1],2)+"} ";
			dbgStr+=" cornersXY={{{"+IJ.d2s(cornersXY[0][0][0],5)+","+IJ.d2s(cornersXY[0][0][1],5)+"},";
			dbgStr+=             "{"+IJ.d2s(cornersXY[0][1][0],5)+","+IJ.d2s(cornersXY[0][1][1],5)+"}},";
			dbgStr+=            "{{"+IJ.d2s(cornersXY[1][0][0],5)+","+IJ.d2s(cornersXY[1][0][1],5)+"},";
			dbgStr+=             "{"+IJ.d2s(cornersXY[1][1][0],5)+","+IJ.d2s(cornersXY[1][1][1],5)+"}}}";
			dbgStr+=" cornersUV={{{"+IJ.d2s(cornersUV[0][0][0],3)+","+IJ.d2s(cornersUV[0][0][1],3)+"},";
			dbgStr+=             "{"+IJ.d2s(cornersUV[0][1][0],3)+","+IJ.d2s(cornersUV[0][1][1],3)+"}},";
			dbgStr+=            "{{"+IJ.d2s(cornersUV[1][0][0],3)+","+IJ.d2s(cornersUV[1][0][1],3)+"},";
			dbgStr+=             "{"+IJ.d2s(cornersUV[1][1][0],3)+","+IJ.d2s(cornersUV[1][1][1],3)+"}}}";
			dbgStr+=" numInside="+numInside;
			System.out.println(dbgStr);
		}

		if (numInside==0) return result; // all corners outside of the (sub)pattern cell
		//		if (maskOnly) {
		if (simulParameters==null) {
			result[1]=dxy[0]*dxy[1];
			result[0]=result[1];
			return result;
		}
		// recalculate to the full uv
		boolean cornersInvert;
		double  [][] cornerValue=new double [2][2];
		int  []   iPat=new int [2];
		double min=1.0,max=-1.0;
		for (int i=0;i<2;i++) for (int j=0;j<2;j++) {
			//			cornersUV[i][j][0]=uv[0]+j*cornersUV[i][j][0]*duv[0];
			//			cornersUV[i][j][1]=uv[1]+i*cornersUV[i][j][1]*duv[1];
			cornersUV[i][j][0]=uv[0]+cornersUV[i][j][0]*duv[0];
			cornersUV[i][j][1]=uv[1]+cornersUV[i][j][1]*duv[1];
			cornersInvert=false;
			for (int k=0;k<2;k++) {
				//				iPat[k] = (int) Math.floor(cornersUV[i][j][k]*this.bPatternSize*2.0); // 0.5 ->bPatternSize
				iPat[k] = (int) Math.floor(cornersUV[i][j][k]*this.bPatternSize); // 1.0 ->bPatternSize
				if (iPat[k]<0){
					iPat[k]+=this.bPatternSize;
					cornersInvert=!cornersInvert;
				} else if (iPat[k]>=this.bPatternSize){
					iPat[k]-=this.bPatternSize;
					cornersInvert=!cornersInvert;
				}
				if ((iPat[k]<0) || (iPat[k]>=this.bPatternSize)) {
					if (debug>0) System.out.println("Too far, cornersUV["+i+"]["+j+"]["+k+"]="+cornersUV[i][j][k]);
					return result; // {0,0} here
				}
			}
			cornerValue[i][j]=(2.0*this.bPattern[this.bPatternSize*iPat[1]+iPat[0]]-1.0)*(cornersInvert?1.0:-1.0);
			if (max<cornerValue[i][j]) max=cornerValue[i][j];
			if (min>cornerValue[i][j]) min=cornerValue[i][j];
		}
		if (((max-min)>simulParameters.bitmapNonuniforityThreshold) &&
				(dxy[0]>simulParameters.smallestSubPix) &&
				(dxy[1]>simulParameters.smallestSubPix)) {

			// divide this square into 4 quadrants, return sum of the recursively called method on them
			double [][] quadrants={{0.0,0.0},{0.5,0.0},{0.0,0.5},{0.5,0.5}};
			double [] subResult;
			double [] subxy=new double [2];
			double [] subdxy={0.5*dxy[0],0.5*dxy[1]};
			if (debug>1){
				System.out.println("---> Subdividing into "+subdxy[0]+"x"+subdxy[0]+" (max="+IJ.d2s(max,3)+" min="+IJ.d2s(min,3)+
						" max-min="+IJ.d2s(max-min,3)+
						" cornerValue: [0][0]="+IJ.d2s(cornerValue[0][0],3)+
						" [0][1]="+IJ.d2s(cornerValue[0][1],3)+
						" [1][0]="+IJ.d2s(cornerValue[1][0],3)+
						" [1][1]="+IJ.d2s(cornerValue[1][1],3)+

						" cornersUV={{{"+IJ.d2s(cornersUV[0][0][0],3)+","+IJ.d2s(cornersUV[0][0][1],3)+"},"+
						"{"+IJ.d2s(cornersUV[0][1][0],3)+","+IJ.d2s(cornersUV[0][1][1],3)+"}},"+
						"{{"+IJ.d2s(cornersUV[1][0][0],3)+","+IJ.d2s(cornersUV[1][0][1],3)+"},"+
						"{"+IJ.d2s(cornersUV[1][1][0],3)+","+IJ.d2s(cornersUV[1][1][1],3)+"}}}");
			}
			for (int i=0;i<quadrants.length;i++) {
				subxy[0]=xy[0]+dxy[0]*quadrants[i][0];
				subxy[1]=xy[1]+dxy[1]*quadrants[i][1];
				subResult=	recursiveFillPixels ( // invert pattern in the caller, return signed value (-1..1 - pattern is 0..1)
						simulParameters,
						subxy,  // top-left corner
						subdxy, // increments to other corners
						cornersXY, // xy pairs for the 4 corners of the square in UV (pattern) coordinates (u0v0,u1v0,u0v1,u1v1)
						uv,  // UV value for the top-left corner (matching cornersXY[0][0])
						duv,  // distances to the opposite corner in UV
						//						maskOnly, // just mark defined cells //always false - will never get here
						debug
						);
				result[0]+=subResult[0];
				result[1]+=subResult[1];
			}
			if (debug>1){
				if (result[1]==0.0) System.out.println("<--- Combined results "+IJ.d2s(result[0],3)+" / "+IJ.d2s(result[1],3));
				else                System.out.println("<--- Combined results "+IJ.d2s(result[0],3)+" / "+IJ.d2s(result[1],3)+"="+IJ.d2s(result[0]/result[1],3));
			}

		} else { // no more subdivisions - calculate average value, taking into account partial pixels
			for (int i=0;i<2;i++) for (int j=0;j<2;j++)  result[0]+=0.25*cornerValue[i][j];
			result[1]=dxy[0]*dxy[1];
			result[0]*=result[1];
			if (numInside <4) {
				double f=((double) numInside)/4; // estimate fraction of the pixel - start with simple number of corners
				result[0]*=f;
				result[1]*=f;
			}
			if (debug>1){
				if (result[1]==0.0)System.out.println("< === Returnimg "+IJ.d2s(result[0],3)+" / "+IJ.d2s(result[1],3)+" ("+numInside+" corners inside)");
				else System.out.println("< === Returning "+IJ.d2s(result[0],5)+" / "+IJ.d2s(result[1],5)+"="+IJ.d2s(result[0]/result[1],3)+" ("+numInside+" corners inside)");
			}
		}
		return result;
	}
	/* ======================================================================== */
	/**
	 * @param cornersXY first index V, second index U, third index:0 - x, 1-y
	 * @param xy 0-x,1-y of the point, for which UV should be generated
	 * @return UV pair
	 */
	public double [] bilinearXY2UV(
			double [][][] cornersXY, // first index V, second index U, third index:0 - x, 1-y
			double []     xy,         // 0-x,1-y of the point, for which
			int debug
			) {
		return bilinearXY2UV(
				cornersXY, // first index V, second index U, third index:0 - x, 1-y
				xy,         // 0-x,1-y of the point, for which
				1E-6,
				debug);
	}
	/**
	 * @param cornersXY first index V, second index U, third index:0 - x, 1-y
	 * @param xy 0-x,1-y of the point, for which UV should be generated
	 * @param quadThreshold if abs(4*a*c)/b^2 is less than this, use linear, not quadratic equations
	 * @return UV pair
	 */
	public double [] bilinearXY2UV(
			double [][][] cornersXY, // first index V, second index U, third index:0 - x, 1-y
			double []     xy,         // 0-x,1-y of the point, for which
			double quadThreshold,      // if abs(4*a*c)/b^2 is less than this, use linear, not quadratic equations
			int debug
			) {
		/*
x,y -> u,v

(1) x= v*u*Ax + v*Bx + u*Cx + Dx
(2) y= v*u*Ay + v*By + u*Cy + Dy

Ax=x11-x10-x01+x00
Bx=x10-x00
Cx=x01-x00
Dx=x00

Ay=y11-y10-y01+y00
By=y10-y00
Cy=y01-y00
Dy=y00

u*u*(Cy*Ax-Cx*Ay)+u*((-Cx*By-Ay*Dx+Cy*Bx+Ax*Dy)+Ay*x-Ax*y)+(By*x-Bx*y)+(-By*Dx+Bx*Dy)=0

Au*u*u+Bu*u+Cu=0
Av*v*v+Bv*v+Cv=0

Au=(Cy*Ax-Cx*Ay)
Bu=((-Cx*By-Ay*Dx+Cy*Bx+Ax*Dy)+Ay*x-Ax*y)
Cu=(By*x-Bx*y)+(-By*Dx+Bx*Dy)

Av=(By*Ax-Bx*Ay)
Bv=((-Bx*Cy-Ay*Dx+By*Cx+Ax*Dy)+Ay*x-Ax*y)
Cv=(Cy*x-Cx*y)+(-Cy*Dx+Cx*Dy)


		 */
		double Ax=cornersXY[1][1][0]-cornersXY[1][0][0]-cornersXY[0][1][0]+cornersXY[0][0][0];
		double Bx=cornersXY[1][0][0]-cornersXY[0][0][0];
		double Cx=cornersXY[0][1][0]-cornersXY[0][0][0];
		double Dx=cornersXY[0][0][0];

		double Ay=cornersXY[1][1][1]-cornersXY[1][0][1]-cornersXY[0][1][1]+cornersXY[0][0][1];
		double By=cornersXY[1][0][1]-cornersXY[0][0][1];
		double Cy=cornersXY[0][1][1]-cornersXY[0][0][1];
		double Dy=cornersXY[0][0][1];

		double Au=(Cy*Ax-Cx*Ay);
		double Bu=((-Cx*By-Ay*Dx+Cy*Bx+Ax*Dy)+Ay*xy[0]-Ax*xy[1]);
		double Cu=(By*xy[0]-Bx*xy[1])+(-By*Dx+Bx*Dy);

		double Av=(By*Ax-Bx*Ay);
		double Bv=((-Bx*Cy-Ay*Dx+By*Cx+Ax*Dy)+Ay*xy[0]-Ax*xy[1]);
		double Cv=(Cy*xy[0]-Cx*xy[1])+(-Cy*Dx+Cx*Dy);
		//		double [] UV={-Cv/Bv,-Cu/Bu}; // linear solution - use for linear grid
		double [] UV={-Cu/Bu,-Cv/Bv}; // linear solution - use for linear grid
		double au=0.0,bu=0.0,av=0.0,bv=0.0;
		if (Math.abs(Au*Cu)/(Bu*Bu)>quadThreshold) { // use quadratic equation for U
			au=-Bu/(2*Au);
			bu=Math.sqrt(Bu*Bu-4*Au*Cu)/Math.abs(2*Au);
			// Use solution that is closer to linear one
			if (UV[0]>au) UV[0]=au+bu;
			else          UV[0]=au-bu;
		}
		if (Math.abs(Av*Cv)/(Bv*Bv)>quadThreshold) { // use quadratic equation for V
			av=-Bv/(2*Av);
			bv=Math.sqrt(Bv*Bv-4*Av*Cv)/Math.abs(2*Av);
			// Use solution that is closer to linear one
			if (UV[1]>av) UV[1]=av+bv;
			else          UV[1]=av-bv;
		}
		if (debug>2){
			String dbgStr="";
			//			 IJ.d2s(quarter_patterns[iq][0][0],4)
			dbgStr+=" Ax="+IJ.d2s(Ax,5)+", Bx="+IJ.d2s(Bx,5)+", Cx="+IJ.d2s(Cx,5)+", Dx="+IJ.d2s(Dx,5);
			dbgStr+=" Ay="+IJ.d2s(Ay,5)+", By="+IJ.d2s(By,5)+", Cy="+IJ.d2s(Cy,5)+", Dy="+IJ.d2s(Dy,5);
			dbgStr+=" Au="+IJ.d2s(Au,5)+", Bu="+IJ.d2s(Bu,5)+", Cu="+IJ.d2s(Cu,5);
			dbgStr+=" Av="+IJ.d2s(Av,5)+", Bv="+IJ.d2s(Bv,5)+", Cv="+IJ.d2s(Cv,5);
			dbgStr+=" LinU="+IJ.d2s(-Cu/Bu,3)+", LinV="+IJ.d2s(-Cv/Bv,5);
			dbgStr+=" au="+IJ.d2s(au,5)+", bu="+IJ.d2s(bu,5);
			dbgStr+=" av="+IJ.d2s(av,5)+", bv="+IJ.d2s(bv,5);
			System.out.println(dbgStr);
		}

		return UV;
	}
	/* ======================================================================== */
	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));
       }
	 */
	public float [] combineWithCanvas(
			double canvasFill,
			int width,
			int height,
			Rectangle woi,
			float [] selection ){
		float []canvas=new float[width*height];
		for (int i=0;i<canvas.length;i++)canvas[i]= (float) canvasFill;
		return combineWithCanvas(canvas, width, woi, selection );
	}
	public float [] combineWithCanvas(
			float [] canvas,
			int width,
			Rectangle woi,
			float [] selection ){
		// debug
		if (selection==null) System.out.println("combineWithCanvas(): selection==null");
		if (woi==null) System.out.println("combineWithCanvas(): woi==null");
		if (selection.length!=(woi.width*woi.height)) throw new IllegalArgumentException ("selection.length="+selection.length+", woi.width="+woi.width+", woi.height="+woi.height);
		int i0=0;
		int i1=width*woi.y+woi.x;
		for (int y=0;y<woi.height;y++){
			for (int x=0;x<woi.width;x++){
				if ((i1>canvas.length) ||(i0>=selection.length)){
					System.out.println("canvas.length="+canvas.length+" width="+width+" selection.length="+selection.length+" y="+y+" x="+x+" i0="+i0+" i1="+i1+
							" woi.x="+woi.x+" woi.y="+woi.y+" woi.width="+woi.width+" woi.height="+woi.height);
				}
				canvas[i1++]=selection[i0++]; // OOB 18720

			}
			i1+=(width-woi.width);
		}
		return canvas;
	}

	public double [] combineWithCanvas(
			double canvasFill,
			int width,
			int height,
			Rectangle woi,
			double [] selection ){
		double []canvas=new double[width*height];
		for (int i=0;i<canvas.length;i++)canvas[i]= canvasFill;
		return combineWithCanvas(canvas, width, woi, selection );
	}
	public double [] combineWithCanvas(
			double [] canvas,
			int width,
			Rectangle woi,
			double [] selection ){
		if (selection.length!=(woi.width*woi.height)) throw new IllegalArgumentException ("selection.length="+selection.length+", woi.width="+woi.width+", woi.height="+woi.height);
		int i0=0;
		int i1=width*woi.y+woi.x;
		for (int y=0;y<woi.height;y++){
			for (int x=0;x<woi.width;x++) canvas[i1++]=selection[i0++];
			i1+=(width-woi.width);
		}
		return canvas;
	}
	//===================== Moved from Aberration_Calibration
	public float[][] simulateGridAll (
			int width, // extend to full image, width, height - original (not scaled) image size
			int height,
			MatchSimulatedPattern matchSimulatedPattern,
			//				double [][][][] patternGrid, // should be aligned to gridFrac
			int gridFrac, // number of grid steps per pattern full period
			SimulParameters  simulParameters,
			int       threadsMax,
			boolean   updateStatus,
			int globalDebugLevel,
			int debug_level){// debug level used inside loops
		//			SimulationPattern simulationPattern=new SimulationPattern(simulParameters);
		float [][] simArray0=simulateGridAll (
				matchSimulatedPattern,
				//					patternGrid, // should be aligned to gridFrac
				gridFrac, // number of grid steps per pattern full period
				simulParameters,
				//					simulationPattern,
				threadsMax,
				updateStatus,
				globalDebugLevel,
				debug_level);
		Rectangle woi=matchSimulatedPattern.getWOI();
		if ((woi.x==0) && (woi.y==0) && (woi.width==width) && (woi.height==height)) return simArray0;
		int k=simulParameters.subdiv/2;
		Rectangle scaledWoi=new Rectangle(k*woi.x, k*woi.y, k*woi.width, k*woi.height);
		float [][] simArray=new float [2][];
		simArray[0]=(new SimulationPattern(simulParameters)).combineWithCanvas(0.0,  k*width, k*height, scaledWoi,simArray0[0]);
		simArray[1]=(new SimulationPattern(simulParameters)).combineWithCanvas(0.0,  k*width, k*height, scaledWoi,simArray0[1]);
		if (globalDebugLevel>0) SDFA_INSTANCE.showArrays(simArray,width*k,height*k,true, "full-simulation");
		return simArray;
	}

	public float[][] simulateGridAll (
			MatchSimulatedPattern matchSimulatedPattern,
			//				double [][][][] patternGrid, // should be aligned to gridFrac
			int gridFrac, // number of grid steps per pattern full period
			SimulationPattern.SimulParameters  simulParameters,
			//				SimulationPattern simulationPattern, // or null
			int       threadsMax,
			boolean   updateStatus,
			int globalDebugLevel,
			int debug_level){// debug level used inside loops
		long 	  startTime=System.nanoTime();
		double [][] xy0={{simulParameters.offsetX,simulParameters.offsetY},{simulParameters.offsetX-0.5,simulParameters.offsetY-0.5}} ;
		//			if (simulationPattern==null) simulationPattern=new SimulationPattern(simulParameters);
		float[][] simArray=new float[2][];
		simArray[0]=  simulateGrid (
				matchSimulatedPattern.getDArray(),
				2, // gridFrac, // number of grid steps per pattern full period
				simulParameters,
				matchSimulatedPattern.getWOI(),
				simulParameters.subdiv/2,
				xy0[0],    // add to patterGrid xy
				threadsMax,
				updateStatus,
				debug_level); // debug level
		simArray[1]=  simulateGrid (
				matchSimulatedPattern.getDArray(),
				2, // gridFrac, // number of grid steps per pattern full period
				simulParameters,
				matchSimulatedPattern.getWOI(),
				simulParameters.subdiv/2,
				xy0[1],    // add to patterGrid xy
				threadsMax,
				updateStatus,
				debug_level); // debug level
		if (globalDebugLevel>2) SDFA_INSTANCE.showArrays(simArray,matchSimulatedPattern.getWOI().width*simulParameters.subdiv/2,matchSimulatedPattern.getWOI().height*simulParameters.subdiv/2,true, "a-simulation");
		if (globalDebugLevel>1) System.out.println("Grid simulation is finished at "+ IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
		return simArray;
	}

	//========================
	public float [] simulateGrid (
			final double [][][][] patternGrid, // should be aligned to gridFrac
			final int gridFrac, // number of grid steps per pattern full period: black+white
			final SimulParameters  simulParameters, // Try to use null here for maskOnly
			final Rectangle woi,
			final int       subdiv,    // subdivide output array from woi (normally 2)
			double[]  shift_xy,    // add to patterGrid xy, null OK
			//			   final boolean   maskOnly, // just mark defined cells
			final int       threadsMax,
			final boolean   updateStatus,
			final int debug_level){// debug level used inside loops
		double []xy_zero={0.0,0.0};
		if (patternGrid==null) return null;
		final double [] xy0=(shift_xy==null)?xy_zero:shift_xy;

		if ((simulParameters!=null) && (this.bPattern==null)){
			System.out.println("simulateGrid(), running patternGenerator(simulParameters )");
			patternGenerator(simulParameters ); // generate bPattern if it was not done yet
		}
		final Rectangle woiOut=new Rectangle(subdiv*woi.x,subdiv*woi.y,subdiv*woi.width,subdiv*woi.height);
		final float [] pixels=new float[woiOut.width*woiOut.height];
		final float [] pixelsDenom=new float[woiOut.width*woiOut.height];
		for (int i=0;i<pixels.length;i++ ){
			pixels[i]= 0.0F;
			pixelsDenom[i]= 0.0F;
		}
		final Thread[] threads = newThreadArray(threadsMax);
		final AtomicInteger cellNum = new AtomicInteger(0);
		final int [] series =  new int[1];
		final int uvhwidth=(patternGrid[0].length+1)/2;
		final int uvwidth=  patternGrid[0].length;
		final int uvhheight=(patternGrid.length+1)/2;
		final int numInSeries=(uvhwidth-1)*(uvhheight-1);
		final AtomicInteger debugCellNum = new AtomicInteger(0);
		final AtomicInteger finishedAtomic = new AtomicInteger(1);
		final int cellsToProcess=numInSeries*4;
		final int debugCellNum0=0;
		IJ.showStatus("Generating simulated pattern...");
		for (series[0]=0;series[0]<4;series[0]++) { // split processing in 4 series (odd/even row/column to avoid races between threads
			if (debug_level>2)System.out.println("**** series[0]="+series[0]);
			cellNum.set(0);
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					@Override
					public void run() {
						//	    					 String dbgStr="";
						int [][][] iUV=new int [2][2][2];
						double [][][] dUV=new double [2][2][2];
						double [][][] xy=new double [2][2][2];
						boolean invPattern;
						double [] pixDXY={1.0,1.0};
						for (int ncell=cellNum.getAndIncrement(); ncell<numInSeries;ncell=cellNum.getAndIncrement()){
							iUV[0][0][0]=2*(ncell%(uvhwidth-1))+ (series[0]      & 1);
							iUV[0][0][1]=2*(ncell/(uvhwidth-1))+ ((series[0]>>1) & 1);
							if ((updateStatus) && (debugLevel>1)) IJ.showStatus("Generating simulated pattern, series "+series[0]+" (of 4), row "+(iUV[0][0][1]/2+1)+"(of "+(uvhheight-1)+")");
							if (debugLevel>2) System.out.println("Generating pattern, series "+series[0]+" (of 4), row "+(iUV[0][0][1]/2+1)+"(of "+(uvhheight-1)+")");
							iUV[0][1][0]=iUV[0][0][0]+1;
							iUV[0][1][1]=iUV[0][0][1];
							iUV[1][0][0]=iUV[0][0][0];
							iUV[1][0][1]=iUV[0][0][1]+1;
							iUV[1][1][0]=iUV[0][0][0]+1;
							iUV[1][1][1]=iUV[0][0][1]+1;
							if ((isCellValid(patternGrid,iUV[0][0])) &&
									(isCellValid(patternGrid,iUV[0][1])) &&
									(isCellValid(patternGrid,iUV[1][0])) &&
									(isCellValid(patternGrid,iUV[1][1]))){



								// All 4 corners are valid
								invPattern=((iUV[0][0][0]%gridFrac)>=(gridFrac/2))^((iUV[0][0][1]%gridFrac)>=(gridFrac/2));
								if (debug_level>2)System.out.println("iUV[0][0][1]="+iUV[0][0][1]+" iUV[0][0][0]="+iUV[0][0][0]+"    invert="+invPattern);


								for (int i=0;i<2;i++) for (int j=0;j<2;j++) for (int k=0;k<2;k++) {
									xy[i][j][k]=subdiv*patternGrid[iUV[i][j][1]][iUV[i][j][0]][0][k]+xy0[k];
								}
								for (int k=0;k<2;k++) {
									dUV[0][0][k]=((double) (iUV[0][0][k]%(gridFrac/2)))/(gridFrac/2);
								}
								dUV[0][1][0]=dUV[0][0][0]+1.0/(gridFrac/2);
								dUV[0][1][1]=dUV[0][0][1];
								dUV[1][0][0]=dUV[0][0][0];
								dUV[1][0][1]=dUV[0][0][1]+1.0/(gridFrac/2);
								dUV[1][1][0]=dUV[0][1][0];
								dUV[1][1][1]=dUV[1][0][1];

								double [] minXY={xy[0][0][0],xy[0][0][1]};
								double [] maxXY={xy[0][0][0],xy[0][0][1]};
								for (int i=0;i<2;i++) for (int j=0;j<2;j++) for (int k=0;k<2;k++) {
									if (minXY[k]>xy[i][j][k]) minXY[k]=xy[i][j][k];
									if (maxXY[k]<xy[i][j][k]) maxXY[k]=xy[i][j][k];
								}
								Rectangle rcell=new Rectangle((int)minXY[0], // contains all pixels
										(int)minXY[1],
										((int) Math.ceil(maxXY[0]))-((int)minXY[0]) ,
										((int) Math.ceil(maxXY[1]))-((int)minXY[1]));
								double [] cornersDUV={1.0/(gridFrac/2),1.0/(gridFrac/2)};
								boolean debugNow=debugCellNum.getAndIncrement()==debugCellNum0;
								if (woiOut.intersects (rcell)) // do not bother if no
									for (int iy=rcell.y;iy<(rcell.y+rcell.height);iy++) for (int ix=rcell.x;ix<(rcell.x+rcell.width);ix++)
										if (woiOut.contains (ix,iy))
										{
											double [] pixXY={ix,iy};
											double [] pixData=recursiveFillPixels ( // invert pattern in the caller, return signed value (-1..1 - pattern is 0..1)
													simulParameters,
													pixXY,  // top-left corner
													pixDXY, // increments to other corners
													xy, // xy pairs for the 4 corners of the square in UV (pattern) coordinates (u0v0,u1v0,u0v1,u1v1)
													dUV[0][0],  // UV value for the top-left corner (matching cornersXY[0][0])
													cornersDUV,  // distances to the opposite corner in UV
													//	    												  maskOnly, // just mark defined cells
													debugNow?debug_level: debug_level-2
													);
											//	    										 int index=woiOut.width*iy+ix;
											int index=woiOut.width*(iy-woiOut.y)+(ix-woiOut.x);
											//	    										 pixels[index]+=((invPattern || maskOnly)?1.0:-1.0)*pixData[0];
											if (index>pixels.length){
												//											         final float [] pixels=new float[woiOut.width*woiOut.height];

												System.out.println("simulateGrid(), pixels.length="+pixels.length+
														" index="+index+
														" iy="+iy+" ix="+ix+
														" ncell="+ncell+
														" subdiv="+subdiv+
														" woiOut.x="+woiOut.x+
														" woiOut.y="+woiOut.y+
														" woiOut.width="+woiOut.width+
														" woiOut.height="+woiOut.height+
														" woi.x="+woi.x+
														" woi.y="+woi.y+
														" woi.width="+woi.width+
														" woi.height="+woi.height+
														" rcell.x="+rcell.x+
														" rcell.y="+rcell.y+
														" rcell.width="+rcell.width+
														" rcell.height="+rcell.height);

											}
											//	    										 if (maskOnly) pixels[index]=patternGrid.length*iUV[0][0][1]+iUV[0][0][0]; // out of bounds
											// OLD NASTY BUG!
											//	    										 if (maskOnly) pixels[index]=uvwidth*iUV[0][0][1]+iUV[0][0][0]; // Should be width, not height!
											if (simulParameters==null) pixels[index]=uvwidth*iUV[0][0][1]+iUV[0][0][0]; // Should be width, not height!
											else          pixels[index]+=(invPattern?1.0:-1.0)*pixData[0];

											pixelsDenom[index]+=pixData[1];
										}
							}
							final int numFinished=finishedAtomic.getAndIncrement();
							SwingUtilities.invokeLater(new Runnable() {
								@Override
								public void run() {
									IJ.showProgress(numFinished,cellsToProcess);
								}
							});

						}
					}
				};
			}
			startAndJoin(threads);
		}
		for (int i=0;i<pixels.length;i++ ) {
			//				/(simulParameters!=null)
			//				if (maskOnly) {
			if (simulParameters==null) {
				if (pixelsDenom[i]==0.0F) pixels[i]=-1;
			} else {
				if (pixelsDenom[i]!=0.0F){
					pixels[i]/= pixelsDenom[i];
					pixels[i]=(float) ((pixels[i]+1.0)/2); // convert from -1..+1 to 0..1.0
				}
			}
		}

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

	/* ======================================================================== */
	/* make it faster when outSubdiv =2*n (usually so) */
	/* TODO: cleanup shifts - they seem now to work correctly */
	public double [][] extractSimulPatterns (
			SimulParameters  simulParameters,
			int outSubdiv,  // subdivide output pixels
			int size,    // number of Bayer cells in width of the square selection (half number of pixels)
			double x0,    // selection center, X (in pixels)
			double y0) {
		return extractSimulPatterns (
				this.barray,
				simulParameters,
				outSubdiv,  // subdivide output pixels
				size,    // number of Bayer cells in width of the square selection (half number of pixels)
				x0,    // selection center, X (in pixels)
				y0);
	}

	public double [][] extractSimulPatterns (
			double [] localbArray,
			SimulParameters  simulParameters,
			int outSubdiv,  // subdivide output pixels
			int size,    // number of Bayer cells in width of the square selection (half number of pixels)
			double x0,    // selection center, X (in pixels)
			double y0) {
		int sampleWidth=(int) (Math.sqrt(simulParameters.fill)*simulParameters.subdiv);
		int sampleN=sampleWidth*sampleWidth;
		if      (sampleWidth<1)     sampleWidth=1;
		else if (sampleWidth>simulParameters.subdiv)sampleWidth=simulParameters.subdiv;
		double sampleAverage=0.5*sampleN;

		int n,i,j;
		int fullSize=(int) Math.sqrt(localbArray.length);
		double [][] simul_pixels=new double [5][size*size];
		int ix,iy, iy0,ix0,px,py;
		double bx,by;
		double s;
		double span=((double) size)/outSubdiv;
		int sampLow=-sampleWidth/2;
		int sampHigh=sampLow+sampleWidth;
		for (n=0;n<4;n++) {
			bx=(n&1)-0.5+0.5;        // last 0.5 to make same center as for dual greens
			by=((n>>1) & 1)-0.5-0.5;// last 0.5 to make same center as for dual greens
			for (iy=0;iy<size;iy++) {
				iy0=(fullSize/2) + (int) ((-span+y0+by  +1.5  +2.0*iy/outSubdiv)*simulParameters.subdiv);
				for (ix=0;ix<size;ix++) {
					ix0=(fullSize/2) + (int) ((-span+x0+bx+0.5   +2.0*ix/outSubdiv)*simulParameters.subdiv);
					s=0.0;
					for (py=iy0+sampLow;py<iy0+sampHigh;py++) for (px=ix0+sampLow;px<ix0+sampHigh;px++) {
						try {
							s+=localbArray[py*fullSize+px];
						} catch (Exception e){
							System.out.println("Bug in extractSimulPatterns(): px="+px+" py="+py+" fullSize="+fullSize+" size="+size+" x0="+x0+" y0="+y0);
							e.printStackTrace();
							return null;
						}
					}
					simul_pixels[n][iy*size+ix]= (s-sampleAverage)/sampleAverage;
				}
			}

		}
		if (outSubdiv>1) {
			if (this.debugLevel>2)System.out.println("Generating combined greens pattern greens from scratch");
			n=4;
			bx=0.0;
			by=0.0;
			for (iy=0;iy<size;iy++) {
				for (ix=0;ix<size;ix++) {
					iy0=(fullSize/2) + (int) ((-span+y0+by-1+1.5  +1.0*(size+iy-ix)/outSubdiv)*simulParameters.subdiv);
					ix0=(fullSize/2) + (int) ((-span+x0+bx  +0.5  +1.0*(iy+ix)/outSubdiv)*simulParameters.subdiv);
					s=0.0;
					for (py=iy0+sampLow;py<iy0+sampHigh;py++) for (px=ix0+sampLow;px<ix0+sampHigh;px++) {
						s+=localbArray[py*fullSize+px];
					}
					simul_pixels[n][iy*size+ix]= (s-sampleAverage)/sampleAverage;
				}

			}
		} else { // just reuse available greens
			if (this.debugLevel>2)System.out.println("Generating combined greens pattern from individual greens");
			/* now combine greens - same as in splitBayer() */

			int base, base_b;
			base_b=0;
			for (i=0;i<size/2; i++){
				base=size*size/2+ i* (size+1);
				for (j=0; j<size/2; j++) {
					simul_pixels[4][base_b++]=simul_pixels[0][base];
					base-=size;
					simul_pixels[4][base_b++]=simul_pixels[3][base++];
				}
				base=size*size/2+ i* (size+1);
				for (j=0; j<size/2; j++) {
					//System.out.println("2:y="+y+" x="+x+" base_b="+base_b+" base="+base);
					simul_pixels[4][base_b++]=simul_pixels[3][base++];
					simul_pixels[4][base_b++]=simul_pixels[0][base];
					base-=size;
				}
			}
		}
		if (this.debugLevel>2) {
			System.out.println("extractSimulPatterns, x0="+x0+" y0="+y0+" fullSize="+fullSize+" size="+size+" subdiv="+simulParameters.subdiv+" outSubdiv="+outSubdiv);
			System.out.println(" sampLow="+sampLow+" sampHigh="+sampHigh+" span="+span+" size="+size);
			for (n=0;n<simul_pixels.length;n++) {
				s=0.0;
				for (i=0;i<simul_pixels[n].length;i++) s+=simul_pixels[n][i];
				System.out.println(" component="+i+" sum of pixels="+s);
			}
		}

		if (this.debugLevel>2) SDFA_INSTANCE.showArrays(simul_pixels,size,size, "SIMUL");

		return simul_pixels;
	}

	public double [] extractSimulMono (
			SimulParameters  simulParameters,
			int outSubdiv,  // subdivide output pixels
			int size,    // number of Bayer cells in width of the square selection (half number of pixels)
			double x0,    // selection center, X (in pixels)
			double y0) {
		return extractSimulMono (
				this.barray,
				simulParameters,
				outSubdiv,  // subdivide output pixels
				size,    // number of Bayer cells in width of the square selection (half number of pixels)
				x0,    // selection center, X (in pixels)
				y0);
	}

	public double [] extractSimulMono ( // TODO: can use twice smaller barray
			double [] localbArray,
			SimulParameters  simulParameters,
			int outSubdiv,  // subdivide output pixels - now 4
			int size,    // number of Bayer cells in width of the square selection (half number of pixels)
			double x0,    // selection center, X (in pixels)
			double y0) {
		return extractSimulMono ( // TODO: can use twice smaller barray
				localbArray,
				false, // boolean invert,
				simulParameters,
				outSubdiv,  // subdivide output pixels - now 4
				size,    // number of Bayer cells in width of the square selection (half number of pixels)
				x0,    // selection center, X (in pixels)
				y0);
	}

	public double [] extractSimulMono ( // TODO: can use twice smaller barray
			double [] localbArray,
			boolean invert,
			SimulParameters  simulParameters,
			int outSubdiv,  // subdivide output pixels - now 4
			int size,    // number of Bayer cells in width of the square selection (half number of pixels)
			double x0,    // selection center, X (in pixels)
			double y0) {
		double pattern_sign = invert? -1.0 : 1.0;
		int sampleWidth=(int) (Math.sqrt(simulParameters.fill)*simulParameters.subdiv);
		int sampleN=sampleWidth*sampleWidth;
		if      (sampleWidth<1)     sampleWidth=1;
		else if (sampleWidth>simulParameters.subdiv)sampleWidth=simulParameters.subdiv;
		double sampleAverage=0.5*sampleN;
		int fullSize=(int) Math.sqrt(localbArray.length);
		double [] simul_pixels=new double [size*size];
		int ix,iy, iy0,ix0,px,py;
		double s;
		double span=((double) size)/outSubdiv;
		int sampLow=-sampleWidth/2;
		int sampHigh=sampLow+sampleWidth;
		for (iy=0;iy<size;iy++) {
			iy0=(fullSize/2) + (int) ((    -span/2 + y0 + 0.5 + 1.0 * iy / outSubdiv)*simulParameters.subdiv);
			for (ix=0;ix<size;ix++) {
				ix0=(fullSize/2) + (int) ((-span/2 + x0 + 0.5 + 1.0 * ix / outSubdiv)*simulParameters.subdiv);
				s=0.0;
				for (py=iy0+sampLow;py<iy0+sampHigh;py++) for (px=ix0+sampLow;px<ix0+sampHigh;px++) {
					try {
						s+=localbArray[py*fullSize+px];
					} catch (Exception e){
						System.out.println("Bug in extractSimulMono(): px="+px+" py="+py+" fullSize="+fullSize+" size="+size+" x0="+x0+" y0="+y0);
						e.printStackTrace();
						return null;
					}
				}
				simul_pixels[iy*size+ix]= pattern_sign * (s - sampleAverage) / sampleAverage;
			}
		}
		if (this.debugLevel>2) {
			System.out.println("extractSimulMono, x0="+x0+" y0="+y0+" fullSize="+fullSize+" size="+size+" subdiv="+simulParameters.subdiv+" outSubdiv="+outSubdiv);
			System.out.println(" sampLow="+sampLow+" sampHigh="+sampHigh+" span="+span+" size="+size);
			s=0.0;
			for (int i=0;i<simul_pixels.length;i++) s+=simul_pixels[i];
			System.out.println("sum of pixels="+s);
		}
		if (this.debugLevel>2) SDFA_INSTANCE.showArrays(simul_pixels,size,size, "SIMUL");
		return simul_pixels;
	}

	private 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;
	}
	private 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;
	}

	// colorComp == -1 = mono, positive, colorComp == -2 - mono, negative
	public double[] extractBayerSim (
			float [][] spixels, // [0] - regular pixels, [1] - shifted by 1/2 diagonally, for checker greens
			int full_width,
			Rectangle woi,
			int bayerPeriod, // 4
			int colorComp) {
		Rectangle r=new Rectangle(woi); // clone
		int full_height=spixels[0].length/full_width; // 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="+spixels[0].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);
		if (colorComp==5) colorComp=0; // for compatibility, combined grees and green 0 generate the same result
		double []result=new double[r.width*r.height];
		int index;
		if (colorComp==4) { // checkerboard greens
			r.y+=r.width/2; // now it is the "top left" corner of the diagonal greens
			for (index=0;index<result.length;index++){
				//				int iy=r.y+(index / r.width);
				//				int ix=r.x+(index % r.width);
				int iyi=index / r.width;
				int ixi=index % r.width;
				int iy=r.y+(iyi+ixi)/2 -ixi;
				int ix=r.x+(iyi+ixi)/2;
				if (iy<0) iy=0;
				else if (iy>=full_height) iy=full_height-1;
				if (ix<0) ix=0;
				else if (ix>=full_width) iy=full_width-1;
				result[index]=spixels[(ixi+iyi) & 1][iy*full_width+ix];
			}
		} else { // components 0..3
			int ser;
			double pattern_sign = (colorComp == -2)? -1.0: 1.0;
			if (colorComp < 0) {
				ser = 1; // offset by 1/2 pix? should it be so? // FIXME: verify and fix if needed - compare to extractSimulMono()
			} else {
				ser = 0;
			r.x+=(bayerPeriod/2)*(colorComp &1);
			r.y+=(bayerPeriod/2)*((colorComp>>1) &1);
			}
			if (debugLevel>2) System.out.println(">>> r.width="+r.width+
					" r.height="+r.height+
					" r.x="+r.x+
					" r.y="+r.y+
					" colorComp="+colorComp);
			for (index=0;index<result.length;index++){
				int iy = r.y + (index / r.width);
				int ix = r.x + (index % r.width);
				if (iy < 0) iy = 0;
				else if (iy >= full_height) iy = full_height - 1;
				if (ix < 0) ix = 0;
				else if (ix >= full_width) iy = full_width - 1;
				result[index] = pattern_sign * spixels[ser][iy * full_width + ix];
			}
		}
		return result;
	}

	//=====================
	public static class SimulParameters {
		public int    patternSize;
		public int    pattern_type;
		public double pattern_modifier;
		public double freq_x1;
		public double freq_y1;
		public double phase1;
		public double freq_x2;
		public double freq_y2;
		public double phase2;
		public int    subdiv;
		public double fill;
		public boolean center_for_g2;
		public double bPatternSigma; // blur bPattern with this sigma
		public double barraySigma; // blur barray with this sigma, multiplied by subdiv
		public double smallestSubPix; // subdivide pixels down to that fraction when simulating
		public double bitmapNonuniforityThreshold; // subdivide pixels until difference between the corners is below this value
		public double offsetX; // debug - add to X during simulation, in pixels
		public double offsetY; // debug - add to Y during simulation, in pixels


		public SimulParameters(
				int    patternSize,
				int    pattern_type,
				double pattern_modifier,
				double freq_x1,
				double freq_y1,
				double phase1,
				double freq_x2,
				double freq_y2,
				double phase2,
				int    subdiv,
				double fill,
				boolean center_for_g2,
				double bPatternSigma, // blur bPattern with this sigma
				double barraySigma, // blur barray with this sigma, multiplied by subdiv
				double smallestSubPix, // subdivide pixels down to that fraction when simulating
				double bitmapNonuniforityThreshold, // subdivide pixels until difference between the corners is below this value
				double offsetX, // debug - add to X during simulation, in pixels
				double offsetY // debug - add to Y during simulation, in pixels
				) {

			this.patternSize=    patternSize;
			this.pattern_type=    pattern_type;
			this.pattern_modifier=pattern_modifier;
			this.freq_x1=         freq_x1;
			this.freq_y1=         freq_y1;
			this.phase1=          phase1;
			this.freq_x2=         freq_x2;
			this.freq_y2=         freq_y2;
			this.phase2=          phase2;
			this.subdiv=          subdiv;
			this.fill=            fill;
			this.center_for_g2=   center_for_g2;
			this.bPatternSigma=   bPatternSigma;
			this.barraySigma=barraySigma;
			this.smallestSubPix=  smallestSubPix;
			this.bitmapNonuniforityThreshold=bitmapNonuniforityThreshold;
			this.offsetX=         offsetX;
			this.offsetY=         offsetY;

		}
		@Override
		public SimulParameters clone() {
			return new SimulParameters(
					this.patternSize,
					this.pattern_type,
					this.pattern_modifier,
					this.freq_x1,
					this.freq_y1,
					this.phase1,
					this.freq_x2,
					this.freq_y2,
					this.phase2,
					this.subdiv,
					this.fill,
					this.center_for_g2,
					this.bPatternSigma,
					this.barraySigma,
					this.smallestSubPix,
					this.bitmapNonuniforityThreshold,
					this.offsetX,
					this.offsetY
					);
		}

		public void setProperties(String prefix,Properties properties){
			properties.setProperty(prefix+"patternSize",this.patternSize+"");
			properties.setProperty(prefix+"pattern_type",this.pattern_type+"");
			properties.setProperty(prefix+"pattern_modifier",this.pattern_modifier+"");
			properties.setProperty(prefix+"freq_x1",this.freq_x1+"");
			properties.setProperty(prefix+"freq_y1",this.freq_y1+"");
			properties.setProperty(prefix+"phase1",this.phase1+"");
			properties.setProperty(prefix+"freq_x2",this.freq_x2+"");
			properties.setProperty(prefix+"freq_y2",this.freq_y2+"");
			properties.setProperty(prefix+"phase2",this.phase2+"");
			properties.setProperty(prefix+"subdiv",this.subdiv+"");
			properties.setProperty(prefix+"fill",this.fill+"");
			properties.setProperty(prefix+"center_for_g2",this.center_for_g2+"");
			properties.setProperty(prefix+"bPatternSigma",this.bPatternSigma+"");
			properties.setProperty(prefix+"barraySigma",this.barraySigma+"");
			properties.setProperty(prefix+"smallestSubPix",this.smallestSubPix+"");
			properties.setProperty(prefix+"bitmapNonuniforityThreshold",this.bitmapNonuniforityThreshold+"");
			properties.setProperty(prefix+"offsetX",this.offsetX+"");
			properties.setProperty(prefix+"offsetY",this.offsetY+"");
		}
		public void getProperties(String prefix,Properties properties){
			if (properties.getProperty(prefix+"patternSize")!=null) this.patternSize=Integer.parseInt(properties.getProperty(prefix+"patternSize"));
			if (properties.getProperty(prefix+"pattern_type")!=null) this.pattern_type=Integer.parseInt(properties.getProperty(prefix+"pattern_type"));
			if (properties.getProperty(prefix+"pattern_modifier")!=null) this.pattern_modifier=Double.parseDouble(properties.getProperty(prefix+"pattern_modifier"));
			if (properties.getProperty(prefix+"freq_x1")!=null) this.freq_x1=Double.parseDouble(properties.getProperty(prefix+"freq_x1"));
			if (properties.getProperty(prefix+"freq_y1")!=null) this.freq_y1=Double.parseDouble(properties.getProperty(prefix+"freq_y1"));
			if (properties.getProperty(prefix+"phase1")!=null) this.phase1=Double.parseDouble(properties.getProperty(prefix+"phase1"));
			if (properties.getProperty(prefix+"freq_x2")!=null) this.freq_x2=Double.parseDouble(properties.getProperty(prefix+"freq_x2"));
			if (properties.getProperty(prefix+"freq_y2")!=null) this.freq_y2=Double.parseDouble(properties.getProperty(prefix+"freq_y2"));
			if (properties.getProperty(prefix+"phase2")!=null) this.phase2=Double.parseDouble(properties.getProperty(prefix+"phase2"));
			if (properties.getProperty(prefix+"subdiv")!=null) this.subdiv=Integer.parseInt(properties.getProperty(prefix+"subdiv"));
			if (properties.getProperty(prefix+"fill")!=null) this.fill=Double.parseDouble(properties.getProperty(prefix+"fill"));
			if (properties.getProperty(prefix+"center_for_g2")!=null) this.center_for_g2=Boolean.parseBoolean(properties.getProperty(prefix+"center_for_g2"));
			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+"smallestSubPix")!=null) this.smallestSubPix=Double.parseDouble(properties.getProperty(prefix+"smallestSubPix"));
			if (properties.getProperty(prefix+"bitmapNonuniforityThreshold")!=null) this.bitmapNonuniforityThreshold=Double.parseDouble(properties.getProperty(prefix+"bitmapNonuniforityThreshold"));
			if (properties.getProperty(prefix+"offsetX")!=null) this.offsetX=Double.parseDouble(properties.getProperty(prefix+"offsetX"));
			if (properties.getProperty(prefix+"offsetY")!=null) this.offsetY=Double.parseDouble(properties.getProperty(prefix+"offsetY"));
		}

	}
}