float[][][][][] regionComponent; // scan, T, wx, wy, {a,b}
    float[][] regionVar; //scan, T
    float[][] regionParmsForBol, regionParmsForFrame;

    // Add to configs

    Model regionalCorrelationModel = new Model("Regional-Correlations", true) {

	    public void init() {
		regionComponent = new float[scans][][][][];
		regionVar = new float[scans][];
		regionParmsForFrame = new float[scans][];
		regionParmsForBol = new float[scans][];
		
		ctemp = new Complex[16];
		for(int r=0; r<16; r++) ctemp[r] = new Complex(); 
		
		dtemp = new double[64];
		
		makeFilter();
		    
		mean = new Complex[16][32];
		weight = new double[16][32];
		
		for(int row=0; row<16; row++) for(int col=0; col<32; col++) mean[row][col] = new Complex();
	    	    
		size = getBoundingSize();

		for(int i=0; i<scans; i++) {
		    final int regionT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames/regionT);
		    
		    regionComponent[i] = new float[nT][size[1]][size[0]][4];
		    regionVar[i] = new float[nT];
		    regionParmsForFrame[i] = new float[scan[i].frames];
		    regionParmsForBol[i] = new float[scan[i].pixels];
		}
	    }

	    public void extract() {

		if(!isActive && turn == 0 && !alwaysSolve) {
		    if(allHighPassed()) {
			if(timeConstant > maxHighPassFrames()) return;
		    }
		    if(!sourceModel.isActive) return;
		    if(!skyPolynomialModel.isActive) return;
		    if(sourceModel.generation < 3) return;
		    if(extended) if(sourceModel.generation < extendedSourceGenerations) return;		    
		    if(approximateSourceMap) if(pipeline.indexOf(sourceModel) < pipeline.indexOf(this)) return;
		}

		if(verbose) System.out.print("\tEstimating Correlation Components ");
		isActive = false;
		
		if(regionComponent == null) init();

		if(verbose) System.out.print("(" + new DecimalFormat("0.0").format(parameters/2.0) + " complex components) ");
		
		for(int i=0; i<scans; i++) if(scan[i].activePixels > 0 && scan[i].activeFrames > 0) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final int regionT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames/regionT);

		    Arrays.fill(regionParmsForFrame[i], 0.0F);
		    Arrays.fill(regionParmsForBol[i], 0.0F);

		    boolean solveNow = true;
		    
		    if(solveNow) for(int T=0; T<nT; T++) {

			for(int row=0; row<16; row++) {
			    Arrays.fill(weight[row], 0.0);
			    for(int col=0; col<32; col++) mean[row][col].zero();
			}
			
			for(int row=0, bol=0; row<12; row++) for(int col=0; col<32; col++, bol++) if(scan[i].pixel[bol].isActive) {
			    Bolometer pixel = scan[i].pixel[bol];
			    float G = pixel.gain;
			    float G2 = G * G;
			    float pw = pixel.weight;

			    for(int blockt=0,t=T*regionT; blockt<regionT && t<nt; blockt++, t++) {
				SharcFrame frame = scan[i].frame[t];

				if(!frame.flagged[bol]) {
				    float w = pw * frame.weight * G2;
				    mean[row][col].x += w * frame.readout[bol] / G;
				    weight[row][col] += w;
				}
			    }
			}

			double sumw = 0.0;
			for(int row=0; row<12; row++) for(int col=0; col<32; col++) if(weight[row][col] > 0.0) {
			    mean[row][col].x /= weight[row][col];
			    sumw += weight[row][col];
			}			

			// Do the convolution in Fourier domain
			ArrayUtil.unsecureFFT(mean, true, ctemp, dtemp);		
			
			// Apply the filter on the residual array spectrum
			for(int wy=0; wy<16; wy++) for(int wx=0; wx<32; wx++) mean[wy][wx].math('*', filter[wy][wx]);

			// Calculate the differencial change.
			for(int wy=0; wy<size[1]; wy++) for(int wx=0; wx<size[0]; wx++) {
			    float[] component = regionComponent[i][T][wy][wx];
			    Complex F = filter[wy][wx];
			    Complex right = mean[wy][wx];

			    right.x += component[0] * (F.x - 1.0);
			    right.y += component[1] * (F.y - 1.0);

			    component[0] += (float)right.x;
			    component[1] += (float)right.y;

			    if(wx > 0) {
				Complex left = mean[wy][32-wx];
				
				left.x += component[2] * (F.x - 1.0);
				left.y += component[3] * (F.y - 1.0);

				component[2] += (float)left.x;
				component[3] += (float)left.y;
			    }
			}
			
			if(scan[i].activePixels > 0) {
			    // Add the constant component to the correlated noise
			    // The true array offset is measure over active pixels only
			    // with zero pads elsewhere. Thus, estimate the true array offset as
			    float offset = regionComponent[i][T][0][0][0] * (32*16) / scan[i].activePixels;
			    regionComponent[i][T][0][0][0] -= offset;
			    
			    // Scale to input gain stage
			    offset /= scan[i].gain;
			    for(int blockt=0,t=T*regionT; blockt<regionT && t<nt; blockt++, t++) correlated[i][t] += offset;
			}

			ArrayUtil.unsecureFFT(mean, false, ctemp, dtemp);

			// Now remove the filtered components
			if(sumw > 0.0) for(int row=0, bol=0; row<12; row++) for(int col=0; col<32; col++, bol++) {
			    Bolometer pixel = scan[i].pixel[bol];
			    float G = pixel.gain;
			    float aveFrameParms = parameters * (float)(pixel.weight * G * G / sumw);

			    for(int blockt=0,t=T*regionT; blockt<regionT && t<nt; blockt++, t++) {
				SharcFrame frame = scan[i].frame[t];
				frame.readout[bol] -= G * mean[row][col].x;
				
				if(pixel.isActive) {
				    float dp = frame.weight * aveFrameParms;
				    regionParmsForFrame[i][t] += dp;
				    regionParmsForBol[i][bol] += dp;
				}	
			    }
			}

			regionVar[i][T] = sumw > 0.0 ? (float)(1.0 / sumw) : Float.NaN;
		    }
		}
		
		super.extract();
		isActive = true;

		if(verbose) System.out.println();
	    }

	    public void inject() {
		if(!isActive) return;

		float[] model = new float[12*32];
		
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final int regionT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames/regionT);

		    for(int T=0; T<nT; T++) {
			loadComponents(regionComponent[i][T]);
			ArrayUtil.unsecureFFT(mean, false, ctemp, dtemp);

			for(int row=0, bol=0; row<12; row++) for(int col=0; col<32; col++, bol++) model[bol] = (float)mean[row][col].x;
			
			for(int bol=0; bol<scan[i].pixels; bol++) {
			    Bolometer pixel = scan[i].pixel[bol];
			    float G = pixel.gain;

			    for(int blockt=0, t=T*regionT; blockt < regionT && t < nt; blockt++, t++)
				scan[i].frame[t].readout[bol] -= G * model[bol];
			}
		    }
		}

		reset();
	    }

	    public void reset() {
		super.reset();
		regionComponent = null;
	    }

	    private SharcFrame lastFrame = new SharcFrame();
	    private int lastT = -1;
	    public SharcFrame getFrame(SharcScan aScan, int t) {
		SharcFrame frame = super.getFrame(aScan, t);
		frame.readout = new float[aScan.pixels];
		final int i = getIndexOf(aScan);
		final int T = t / getT(aScan);

		if(lastT == T) return lastFrame;
		else lastT = T;

		int[] size = getBoundingSize();
		loadComponents(regionComponent[i][T]);

		ArrayUtil.unsecureFFT(mean, false, ctemp, dtemp);

		for(int row=0, bol=0; row<12; row++) for(int col=0; col<32; col++, bol++) frame.readout[bol] = (float)mean[row][col].x;
  
		return frame;
	    }

	    protected void loadComponents(float[][][] component) {
		for(int row=0; row<16; row++) for(int col=0; col<32; col++) mean[row][col].zero();

		int[] size = getBoundingSize();

		for(int wy=0; wy<size[1]; wy++) for(int wx=0; wx<size[0]; wx++) {
		    float[] value = component[wy][wx];
		    mean[wy][wx].set(value[0], value[1]);
		    if(wy != 0) mean[16-wy][wx].set(value[2], value[3]);
		    if(wx != 0) mean[wy][32-wx].set(value[2], value[3]);
		    if(wx*wy != 0) mean[16-wy][32-wx].set(value[0], value[1]);
		}
	    }
		
	    public int getNumberOfParameters() { 
		int n = 0;
		for(int i=0; i<scans; i++) {
		    final int regionT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames/regionT);

		    n += nT * parameters;
		}
		return n; 
	    }

	    public double getRMS() {
		if(!isActive) return 0.0;

		double var = 0.0;
		int n = 0;

		int[] size = getBoundingSize();

		double[][] F = ArrayUtil.norm(filter);

		for(int i=0; i<scans; i++) {
		    final int regionT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames/regionT);
		    final float G2 = scan[i].gain * scan[i].gain;
		    
		    for(int T=0; T<nT; T++) if(!Float.isNaN(regionVar[i][T])) {
			for(int wy=0; wy<size[1]; wy++) for(int wx=0; wx<size[0]; wx++) {
			    float[] value = regionComponent[i][T][wy][wx];
			    
			    var += G2 * (value[0]*value[0] + value[1]*value[1]);
			    var += G2 * (value[2]*value[2] + value[3]*value[3]);
			    
			    var -= 2.0 * G2 * regionVar[i][T] * F[wy][wx];
			}
			n += 2;
		    }
		}

		return var > 0.0 ? Math.sqrt(var / n) : 0.0;
	    }

	    public void dGain(int i, int t, float[] dmodel) {
		if(!isActive) return;

		SharcFrame frame = getFrame(scan[i],t);
		float[] model = frame.readout;
		float G = scan[i].gain;
		Bolometer[] pixel = scan[i].pixel;

		for(int bol=0; bol < scan[i].pixels; bol++) dmodel[bol] += G * pixel[bol].gain * model[bol];
	    }

	    public void weightAdjust(double factor) {
		for(int i=0; i<scans; i++) {
		    final int regionT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames/regionT);
		    
		    for(int T=0; T<nT; T++) regionVar[i][T] /= factor;
		}
	    }
	    
	    public Object[] getData() { return regionComponent; }

	    public int[] getBoundingSize() {
		Vector2D axes = getAxes();
		int nx = (int)Math.ceil(axes.x);
		int ny = (int)Math.ceil(axes.y);
		if(nx > 16) nx = 16;
		if(ny > 8) ny = 8;
		return new int[] {nx, ny};
	    }

	    public Vector2D getAxes() {
		double pradius = radius / SharcHeader.pixelSize;
		return new Vector2D(32.0/pradius, 16.0/pradius);
	    }

	    protected void makeFilter() {
		filter = new Complex[16][32];

		double r0 = radius / SharcHeader.pixelSize;
		double maxR2 = r0 * r0;
		double sigma2 = 2.0 * r0 / 2.35;
		sigma2 *= sigma2;

		double FWHM = Math.sqrt(beamFWHM * beamFWHM + imageFWHM * imageFWHM);
		double beamSigma2 = FWHM / SharcHeader.pixelSize / 2.35;
		beamSigma2 *= beamSigma2;

		for(int row=0; row<16; row++) for(int col=0; col<32; col++) {
		    int dx = col > 16 ? 32 - col : col;
		    int dy = row > 8 ? 16 - row : row;
		    double r2 = dx * dx + dy * dy;
 
		    filter[row][col] = new Complex();

		    switch(filterFunction) {
		    case FLAT_FILTER :
			filter[row][col].x = r2 < maxR2 ? 1.0 : 0.0; break;
		    case GAUSSIAN_FILTER : 
			filter[row][col].x = Math.exp(-r2 / (2.0 * sigma2)); break;
		    case EXPONENTIAL_FILTER :
			filter[row][col].x = Math.exp(-Math.sqrt(r2) / r0); break;
		    }

		    // Take out the beam-sized correlations
		    filter[row][col].x -= filter[row][col].x * Math.exp(-r2 / (2.0 * beamSigma2));
		}

		ArrayUtil.unsecureFFT(filter, true, ctemp, dtemp);
		
		Vector2D axes = getAxes();
		double a2 = axes.x * axes.x;
		double b2 = axes.y * axes.y;

		parameters = 0.0F; 

		double norm = filter[0][0].x;
		// Set the filter components to zero outside the preserved range
		for(int row=0; row<16; row++) for(int col=0; col<32; col++) {
		    int dx = col > 16 ? 32 - col : col;
		    int dy = row > 8 ? 16 - row : row;
		    double r2 = dx * dx / a2 + dy * dy / b2;
		    
		    if(r2 < 1.0) filter[row][col].math('/', norm);
		    else filter[row][col].zero();

		    // The number of fitted parameters. (removed degrees of freedom).
		    parameters += Math.abs(filter[row][col].x);
		    parameters += Math.abs(filter[row][col].y);
		}

		// Symmetry halfs the number of independent parameters
		parameters /= 2;
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "REG_";
		cursor.add(new HeaderCard(id + "NAME", name, "The Model Name."));
		cursor.add(new HeaderCard(id + "STRT", turn, "The iteration at which the model is activated."));
		cursor.add(new HeaderCard(id + "INDX", pipeline.indexOf(this), "The Model index inside the Pipeline."));
		cursor.add(new HeaderCard(id + "T", timeConstant, "The Model Time Constant (frames).")); 
		cursor.add(new HeaderCard(id + "FORM", typeName[filterFunction], "The correlation function to use.")); 
		cursor.add(new HeaderCard(id + "SIZE", 2.0 * radius / Unit.ArcSECOND, "The scale (FWHM) of the correlations in arcsec.")); 
		cursor.add(new HeaderCard(id + "RMS", getRMS(), "The RMS of the Model Parameters."));
		
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("REGIONAL_CORRELATION_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		} 
		else if(key.equalsIgnoreCase("REGIONAL_CORRELATION_T")) {
		    if(value.equals("*") || value.equalsIgnoreCase("auto")) autoTimeScale = true;
		    else {
			timeConstant = Integer.parseInt(value);
			autoTimeScale = false;
		    }
		}
		else if(key.equalsIgnoreCase("REGIONAL_CORRELATION_FORM")) {
		    filterFunction = 0;
		    for(int i=0; i<typeName.length; i++) {
			if(value.equalsIgnoreCase(typeName[i])) { filterFunction = i; break; }
		    } 
		}
		else if(key.equalsIgnoreCase("REGIONAL_CORRELATION_SIZE")) {
		    radius = Double.parseDouble(value) * Unit.ArcSECOND / 2.0F;
		}
		else return false;
		
		return true;
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Iregion")) key("REGIONAL_CORRELATION_TURN", value);
		else if(key.equalsIgnoreCase("-regionT")) key("REGIONAL_CORRELATION_T", value); 
		else if(key.equalsIgnoreCase("-regionType")) key("REGIONAL_CORRELATION_FORM", value); 
		else if(key.equalsIgnoreCase("-regionSize")) key("REGIONAL_CORRELATION_SIZE", value); 
		else return false;
		
		return true;
	    }
	    
	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Iregion=      Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n"
		    + "  -regionalT=    Temporal resolution in frames. (" + timeConstant + ")\n"
		    + "  -regionType=   Form of the regional correlations. (" + typeName[filterFunction] + ")\n"
		    + "  -regionSize=   Scale or FWHM (in arcsec) of the regions. (" + 
		    new DecimalFormat("0.0").format(2.0 * radius/Unit.ArcSECOND) + ")\n";

		System.out.println(info);
	    }

	    int[] size;
	    Complex[][] filter, mean;
	    double[][] weight;
	    Complex[] ctemp = null;
	    double[] dtemp = null;
	    double radius = 15.0 * Unit.ArcSECOND;
	    float parameters = 0.0F;
	    int filterFunction = 0;

	    final int FLAT_FILTER = 0;
	    final int GAUSSIAN_FILTER = 1;
	    final int EXPONENTIAL_FILTER = 2;
	    final String[] typeName = { "flat", "Gaussian", "exponential" };
	};
   
