import nom.tam.fits.*;
import nom.tam.util.*;
import nom.tam.image.*;

import java.io.*;
import java.net.*;
import java.util.*;
import java.text.*;


public class ReductionTool {
    ReducedData reduced, model;
    SharcScan[] scan;
    
    static boolean altaz = false, planetary = false, approximateSourceMap = false, verbose = true;
    static boolean writeIndex = false, writeGains=false, writeCovariance=false, writeHistogram=false, writeModels=true;
    static boolean writePixelCoeffs = false, writeResiduals = false;
    static boolean fitsCovariance = false, globalCovariance = false, spacialCorrelations = false;
    static boolean autoOrder = false, autoPlanetary = true;
    static boolean apertureOnlySource = false, linearResponse=false;
    static double minDegreesOfFreedom = 1.0;
    static double resolution = SharcHeader.pixelSize / 3.0, sourceDiameter = -1.0 * Unit.ArcSECOND;
    static double beamFWHM = 8.5 * Unit.ArcSECOND, smoothFWHM = 4.0 * Unit.ArcSECOND, intermediateSmoothFWHM = 0.0;
    static double histRange=20.0, histResolution=0.5;
    static int regionSizeX = 8, regionSizeY = 6, covarPixel=-1;
    static boolean chi2report = false, chi2all = false, debug = false, scramble = false;
    static boolean extended = true;
    static boolean simulatedChopped = false, measureEfficiency = false;
    static int extendedSourceGenerations = 2;
    static String mapUnit = "nV";
    static float curvature = 0.0F;
    static double defaultVperJy = 6.40e-7 * Unit.VOLT;
    static double timeConstantScaling = 1.0;
    static boolean sourceIsActive = false;

    int scans;
    int minActivePixels = 128;

    int iter=0, rounds = 7, faithfulTurn = 3, tauAdjustTurn = 0, gainRounds = -1, maxGainRounds = 20;
    double minRelativeExposure = 0.1, maxRelativeRMS = 3.0;
    double gainConvergenceGoal = 1.0e-3;
    String maskName, exposureClipDirective = "output", rmsClipDirective = "output";
    Vector pipeline = new Vector();
    Vector registry = new Vector();

    int sampleScan = 0, sampleFrame = -1;
   
    boolean sourceBeforeGain = false;

    boolean covar = false, noiseHistogram = false;

    final static int AUTO_GAIN_ROUNDS = -1;
    
    public ReductionTool() {
	driftModel.minTimeConstant = 32;
	driftModel.maxTimeConstant = 4096;

	// Do not attempt preliminary solutions for models
	// that have no independent flux measurement
	//gainModel.preliminary = false;	
	nonLinearityModel.preliminary = false;	
	timeWeightModel.preliminary = false;
	spikeModel.preliminary = false;
	spectralPixelTest.preliminary = false;
	temporalPixelTest.preliminary = false;
	simulatedChoppedModel.preliminary = false;

	registry.add(offsetModel);
	registry.add(correlatedModel);
	registry.add(gainModel);
	registry.add(nonLinearityModel);
	registry.add(driftModel);
	registry.add(pixelWeightModel);
	registry.add(skyPolynomialModel);
	registry.add(rowPolynomialModel);
	registry.add(multiplexerModel);
	registry.add(sourceModel);
	registry.add(timeWeightModel);	
	registry.add(spikeModel);
	registry.add(temporalPixelTest);
	registry.add(spectralPixelTest);
	registry.add(chopCoefficientModel);
	registry.add(accelerationModel);
	registry.add(regionalCorrelationModel);

	pipeline = (Vector) registry.clone();
    }
    
    public void init() {
	scans = scanBuffer.size();
	
	if(scans == 0) {
	    System.out.println(" No scans to reduce. Exiting...");
	    System.exit(1);
	}

	scan = new SharcScan[scans];

	sourceBeforeGain = pipeline.indexOf(sourceModel) < pipeline.indexOf(gainModel);

	for(int i=0; i<scans; i++) scan[i] = (SharcScan) scanBuffer.elementAt(i);

	for(int i=0; i<scans; i++) {
	    if(verbose) System.out.print("\r Preparing data for analysis. " + ((int)(100.0*(float)i/scans)) + "%    ");
	    if(verbose) System.out.flush();
	    scan[i].init();
	}
	if(verbose) System.out.println("\r Preparing data for analysis. Done.");

	if(autoPlanetary) {
	    planetary = isPlanetary(scan[0].sourceName);
	    if(planetary) if(verbose) System.out.println(" Planetary Object Detected. ");
	}

	createReduced();

	if(scramble) {
	    if(verbose) System.out.println("\r Scrambling Pixels (Do not expect to see source!!!).");
	    for(int i=0; i<scans; i++) scan[i].scramble();
	    for(int i=0; i<scans; i++) if(Math.random() < 0.5) scan[i].invert();
	}
			
	if(maskName != null) {
	    if(verbose) System.out.print(" Preparing Mask. ");
	    for(int i=0; i<scans; i++) {
		scan[i].makeMask(reduced);
		if(verbose) System.out.print(scan[i].masks + " ");
	    }
	    if(verbose) System.out.println(" points masked.");
	}

	if(verbose) System.out.print(" Velocity Clip -> ");
	for(int i=0; i<scans; i++) if(verbose) System.out.print(scan[i].vclipped + " ");
	if(verbose) System.out.println(" frames affected. ");
	
	if(verbose) System.out.print(" Acceleration Clip -> ");
	for(int i=0; i<scans; i++) if(verbose) System.out.print(scan[i].aclipped + " ");
	if(verbose) System.out.println(" frames affected. ");
	
	if(writeIndex) writeIndex();
    }

    public void defaults() {
    }

    static Hashtable tauCoefficients = new Hashtable();

    static {
	tauCoefficients.put("350um", new Vector2D(26.4, -0.08));
	tauCoefficients.put("225GHz", new Vector2D(1.0, 0.0));
	tauCoefficients.put("Sharc2", new Vector2D(26.4, -0.08));
    }

    public static double sharcTauOf(String id, double value) {
	Vector2D t = (Vector2D) tauCoefficients.get(id);
	Vector2D sharct = (Vector2D) tauCoefficients.get("Sharc2");
	return sharct.x / t.x * (value - t.y) + sharct.y;
    }

    public static double getTau(String id, double value) {
	Vector2D t = (Vector2D) tauCoefficients.get(id);
	Vector2D sharct = (Vector2D) tauCoefficients.get("Sharc2");
	return t.x / sharct.x * (value - sharct.y) + t.y;
    }

    public static double getTau(SharcScan sharcScan, String id) {
	Vector2D t = (Vector2D) tauCoefficients.get(id);
	Vector2D sharct = (Vector2D) tauCoefficients.get("Sharc2");
	return sharct.x / t.x * (sharcScan.tau - t.y) + sharct.y;
    }

    public boolean key(String key, String value) {
	if(key.equalsIgnoreCase("ALTAZ")) altaz = Util.parseBoolean(value);
	else if(key.equalsIgnoreCase("PLANETARY")) {
	    if(value.equalsIgnoreCase("auto")) autoPlanetary = true;
	    else { autoPlanetary = false; planetary = Util.parseBoolean(value); }
	}
	else if(key.equalsIgnoreCase("WRITE_MODELS")) writeModels = Util.parseBoolean(value);
	else if(key.equalsIgnoreCase("WRITE_RESIDUALS")) writeResiduals = Util.parseBoolean(value);
	else if(key.equalsIgnoreCase("WRITE_COVARIANCE")) {
	    writeCovariance = false; fitsCovariance = true; globalCovariance = false;
	    StringTokenizer tokens = new StringTokenizer(value, ",:");
	    covarPixel = -1;
	    if(tokens.countTokens() == 2)
		covarPixel = 32 * (Integer.parseInt(tokens.nextToken()) - 1) + (Integer.parseInt(tokens.nextToken()) - 1);
	    else if(value.equalsIgnoreCase("fits")) { writeCovariance = true; fitsCovariance = true; }
	    else if(value.equalsIgnoreCase("scan")) { writeCovariance = true; fitsCovariance = false; }
	    else if(value.equalsIgnoreCase("global")) { globalCovariance = true; }
	    else if(value.equalsIgnoreCase("none")) { writeCovariance = false; }
	    else { writeCovariance = Util.parseBoolean(value); fitsCovariance = true; }
	}
	else if(key.equalsIgnoreCase("WRITE_HISTOGRAM")) writeHistogram = Util.parseBoolean(value);
	else if(key.equalsIgnoreCase("DEFINE_TAU")) {
	    StringTokenizer tokens = new StringTokenizer(value, ",()");
	    int n = tokens.countTokens();
	    if(n < 2) System.err.println("Malformed tau scaling specification: " + value);
	    if(n == 2) tauCoefficients.put(tokens.nextToken(), new Vector2D(Double.parseDouble(tokens.nextToken()), 0.0) );
	    else tauCoefficients.put(tokens.nextToken(), new Vector2D
				     (Double.parseDouble(tokens.nextToken()), Double.parseDouble(tokens.nextToken())) );
	}
	else if(key.equalsIgnoreCase("ITERATIONS")) rounds = Integer.parseInt(value);
	else if(key.equalsIgnoreCase("GAIN_ITERATIONS")) {
	    if(value.equalsIgnoreCase("off")) gainRounds = 0;
	    else if(value.equalsIgnoreCase("auto")) gainRounds = -1;
	    else if(value.equalsIgnoreCase("converge")) gainRounds = -2;
	    else gainRounds = Integer.parseInt(value);
	}
	else if(key.equalsIgnoreCase("GAIN_CONVERGENCE_GOAL")) gainConvergenceGoal = Double.parseDouble(value);
	else if(key.equalsIgnoreCase("SCALE_TIME_CONSTANTS")) timeConstantScaling = Double.parseDouble(value);	
	else if(key.equalsIgnoreCase("HISTOGRAM_RANGE")) histRange = Double.parseDouble(value);
	else if(key.equalsIgnoreCase("HISTOGRAM_RESOLUTION")) histResolution = Double.parseDouble(value);
	else if(key.equalsIgnoreCase("PIPELINE_AUTO_REORDER")) autoOrder = Util.parseBoolean(value);
	else if(key.equalsIgnoreCase("pipeline.new")) {
	    if(Util.parseBoolean(value) == true) pipeline = new Vector();
	}
	else if(key.equalsIgnoreCase("pipeline.add")) {
	    boolean found = false;
	    for(int m=0; m<registry.size() && !found; m++) {
		Model model = (Model) registry.elementAt(m);
		if(value.equalsIgnoreCase(model.name)) { pipeline.add(model); found = true; }
	    }
	    if(!found) System.err.println("WARNING! Unknown pipeline element " + value);
	}
	else if(key.equalsIgnoreCase("pipeline.map")) {
	    StringTokenizer tokens = new StringTokenizer(value,":");
	    String placing = tokens.nextToken();
	    String modelName = tokens.hasMoreTokens() ? tokens.nextToken() : null;
	    
	    int index = -1;

	    // Remove source if already in pipeline
	    if(pipeline.indexOf(sourceModel) >= 0) pipeline.remove(sourceModel);

	    // Find the insertion point
	    if(modelName != null) {
		boolean found = false;
		for(int m=0; m<pipeline.size() && !found; m++) {
		    Model model = (Model) pipeline.elementAt(m);
		    if(modelName.equalsIgnoreCase(model.name)) { index=m; found = true; }
		}
		
		if(!found) System.err.println("WARNING! Unknown pipeline element " + model);
		
	   
		if(index < 0) {
		    System.out.println(" ERROR! Cannot insert source " + placing + ":" + model);
		    System.out.println(" ERROR! There is no pipeline element named " + model + ".");
		    System.out.println(" Exiting CRUSH... -- Bye!");
		    System.exit(1);
		}	    
	    }

	    // Reinsert source at the desired location
	    if(placing.equalsIgnoreCase("first")) pipeline.add(0, sourceModel);
	    else if(placing.equalsIgnoreCase("last")) pipeline.add(sourceModel);
	    else if(placing.equalsIgnoreCase("before")) pipeline.add(index, sourceModel);
	    else if(placing.equalsIgnoreCase("after")) {
		if((index + 1) >= pipeline.size()) pipeline.add(sourceModel);
		else pipeline.add(index+1, sourceModel);
	    }
	    else {
		System.out.println(" ERROR! Cannot insert source " + placing + ":" + model);
		System.out.println(" ERROR! Placement Directove " + placing + " is not understood.");
		System.out.println(" Exiting CRUSH... -- Bye!");
		System.exit(1);
	    }
	}	
	else if(key.equalsIgnoreCase("CHI2_REPORTING")) {
	    chi2report = false; chi2all = false;
	    if(value.equalsIgnoreCase("none") || value.equalsIgnoreCase("off"));
	    else if(value.equalsIgnoreCase("all") || value.equalsIgnoreCase("on")) { chi2report = true; chi2all = true; }
	    else if(value.equalsIgnoreCase("weight")) { chi2report = true; }
	}
	else if(key.equalsIgnoreCase("LINEAR_RESPONSE")) linearResponse = Util.parseBoolean(value);
	else {
	    for(int i=0; i<registry.size(); i++) {
		Model model = (Model) registry.elementAt(i);
		if(model.key(key, value)) return true;
	    }
	    return false;
	}
	return true;
    }

    public boolean option(String key, String value) {
	if(key.equalsIgnoreCase("-altaz")) altaz = true;
	else if(key.equalsIgnoreCase("-radec")) altaz = false;
	else if(key.equalsIgnoreCase("-planetary")) { autoPlanetary = false; planetary = true; }
	else if(key.equalsIgnoreCase("-static")) { autoPlanetary = false; planetary = false; }
	else if(key.equalsIgnoreCase("-index")) writeIndex = true;
	else if(key.equalsIgnoreCase("-noindex")) writeIndex = false;
	else if(key.equalsIgnoreCase("-gains")) writeGains = true;
	else if(key.equalsIgnoreCase("-nogains")) writeGains = false;
	else if(key.equalsIgnoreCase("-models")) writeModels = true;
	else if(key.equalsIgnoreCase("-nomodels")) writeModels = false;
	else if(key.equalsIgnoreCase("-residuals")) writeResiduals = true;
	else if(key.equalsIgnoreCase("-noresiduals")) writeResiduals = false;
	else if(key.equalsIgnoreCase("-covar")) key("WRITE_COVARIANCE", value);
	else if(key.equalsIgnoreCase("-spacial")) spacialCorrelations = true;
	else if(key.equalsIgnoreCase("-nospacial")) spacialCorrelations = false;
	else if(key.equalsIgnoreCase("-histogram")) writeHistogram = true;
	else if(key.equalsIgnoreCase("-nohist")) writeHistogram = false;
	else if(key.equalsIgnoreCase("-efficiency")) measureEfficiency = true;
	else if(key.equalsIgnoreCase("-tauScale")) key("DEFINE_TAU", value);
	else if(key.equalsIgnoreCase("-rounds")) rounds = Integer.parseInt(value);
	else if(key.equalsIgnoreCase("-gainRounds")) key("GAIN_ITERATIONS", value);
	else if(key.equalsIgnoreCase("-gainGoal")) gainConvergenceGoal = Double.parseDouble(value);
	else if(key.equalsIgnoreCase("-TCScale")) key("SCALE_TIME_CONSTANTS", value); 
	else if(key.equalsIgnoreCase("-reorder")) autoOrder = true;
	else if(key.equalsIgnoreCase("-noreorder")) autoOrder = false;
	else if(key.equalsIgnoreCase("-noflag")) {
	    spikeModel.turn = -1;
	    temporalPixelTest.turn = -1;
	    spectralPixelTest.turn = -1;
	}
	else if(key.equalsIgnoreCase("-mask")) {
	    if(value.equals("*") || value.equalsIgnoreCase("none")) maskName = null;
	    else maskName = value;
	}
	else if(key.equalsIgnoreCase("-aperture")) apertureOnlySource = true;
	
	else if(key.equalsIgnoreCase("-linear")) linearResponse = true;
	else if(key.equalsIgnoreCase("-nonlinear")) linearResponse = false;
	else if(key.equalsIgnoreCase("-chi2")) key("CHI2_REPORTING", value);
	else if(key.equalsIgnoreCase("-map")) key("pipeline.map", value);
	else if(key.equalsIgnoreCase("-model")) {
	    if(value.equalsIgnoreCase("none") || value.equalsIgnoreCase("off")) model = null;
	    else {
		try { model = new ReducedData(value); }
		catch(Exception e) {}
	    }
	}
	else if(key.equalsIgnoreCase("-slice")) {
	    StringTokenizer tokens = new StringTokenizer(value, ",:");
	    if(value.equalsIgnoreCase("off")) sampleFrame = -1;	
	    else {
		if(tokens.countTokens() > 1) sampleScan = Integer.parseInt(tokens.nextToken()) - 1;
		else sampleScan = 0;
		sampleFrame = Integer.parseInt(tokens.nextToken()) - 1 ;
	    }
	}
	else if(key.equalsIgnoreCase("-scramble")) { scramble = true; }
	else if(key.equalsIgnoreCase("-curve")) { curvature = Float.parseFloat(value); }
	else if(key.equalsIgnoreCase("-simchop")) { simulatedChopped = true; }
	else if(key.equalsIgnoreCase("-debug")) { debug = true; }
	else {
	    for(int i=0; i<registry.size(); i++) {
		Model model = (Model) registry.elementAt(i);
		if(model.option(key, value)) return true;
	    }
	    return false;
	}
	return true;
    }

    public void usage() {
	Enumeration tauCoeffNames = tauCoefficients.keys();
	String tauCoeffList = "";
	while(tauCoeffNames.hasMoreElements()) tauCoeffList += (String)tauCoeffNames.nextElement() + ",";
	if(tauCoeffList.length() > 0) tauCoeffList = tauCoeffList.substring(0,tauCoeffList.length() - 1);

	int sourceIndex = pipeline.indexOf(sourceModel);

	String info = "\n"
	    + " Reduction Options: " + "\n"
	    + "\n"

	    + " (These are global settings affecting the reduction pipeline.)" + "\n"
	    + "\n"

	    + "  -map=         Where to insert the source model. (" + 
	    ( sourceIndex == 0 ? "before:" + ((Model)pipeline.elementAt(1)).name : 
	     "after:" + ((Model)pipeline.elementAt(sourceIndex-1)).name ) + ")\n"
	    + "  -reorder      Determine Pipeline Order based on preliminary solution. " + (autoOrder ? "(*)" : "") + "\n"
	    + "  -noreorder    Leave pipeline as is. " + (!autoOrder ? "(*)" : "") + "\n"
	    + "  -radec        Reduce in RA/DEC coordinates. " + (!altaz ? "(*)" : "") + "\n"
	    + "  -altaz        Reduce in Alt/Az coordinates. " + (altaz ? "(*)" : "") + "\n"
	    + "  -static       Assume source fixed on RA/DEC sky. Use absolute coordinates " + (!planetary ? "(*)" : "") + "\n"
	    + "  -planetary    Use relative coordinates wrt tracking center - e.g. planets. " + (planetary ? "(*)" : "") + "\n"
	    + "  -linear       Use linear pixel response. " + (linearResponse ? "(*)" : "") + "\n"
	    + "  -nonlinear    Use non-linear pixel response. " + (!linearResponse ? "(*)" : "") + "\n"
	    + "  -index        Create coordinate index file at end. " + (writeIndex ? "(*)" : "") + "\n"
	    + "  -noindex      Do not create coordinate index file. " + (!writeIndex ? "(*)" : "") + "\n"
	    + "  -gains        Create standard pixel data file at end. " + (writeGains ? "(*)" : "") + "\n"
	    + "  -nogains      Do not create standard pixel data file. " + (!writeGains ? "(*)" : "") + "\n"
	    + "  -models       Add model data to FITS. " + (writeModels ? "(*)" : "") + "\n"
	    + "  -nomodels     Do not write model data. " + (!writeModels ? "(*)" : "") + "\n"
	    + "  -noflag       Disable all models that flag pixels. " + "\n"
	    + "  -residuals    Dump unmodelled residuals to file. " + (writeResiduals ? "(*)" : "") + "\n"
	    + "  -noresiduals  Do not write model residuals. " + (!writeResiduals ? "(*)" : "") + "\n"
	    + "  -covar=       [fits|scan|global|off|r:c] The way covariances are written. (" +
	    (writeCovariance ? (fitsCovariance ? "fits" : (globalCovariance ? "global" : "scan")) 
	     : covarPixel > 0 ? (covarPixel/32+1) + ":" + (covarPixel%32+1) : "off") + ")" + "\n"
	    + "  -spacial      Write spacial correlations of residuals. " + (spacialCorrelations ? "(*)" : "") + "\n"
	    + "  -nospacial    Do not write spacial correlations. " + (!spacialCorrelations ? "(*)" : "") + "\n"
	    + "  -histogram    Add residual pixel histogram data to FITS. " + (writeHistogram ? "(*)" : "") + "\n"
	    + "  -nohist       Do not add pixel histograms to FITS. " + (!writeHistogram ? "(*)" : "") + "\n"
	    + "  -efficiency   Measure relative beam efficiencies on the array." + (measureEfficiency ? "(*)" : "") + "\n"
	    + "  -chi2=        If and how chi-squared is reported. (" + (chi2report ? (chi2all ? "all" : "weight") : "off") + ")\n"
	    + "  -tauScale=    tau(id) = at + b as \"id(a,b)\". (" + tauCoeffList + ")\n"
	    + "  -rounds=      Set the number of iterations in the solution. (" + rounds + ")\n"
	    + "  -gainRounds=  Number of iterations for the initial gain solution. (" + 
	    (gainRounds == AUTO_GAIN_ROUNDS ? "auto" : (gainRounds < 0 ? "converge" : (gainRounds == 0 ? "off" : gainRounds + ""))) + ")\n"
	    + "  -gainGoal=    Gain convergence goal for 'auto' gain rounds. (" + 
	    new DecimalFormat("0.0E0").format(gainConvergenceGoal) + ")\n"
	    + "  -TCscale=     Scaling factor for all model time constants. (" + timeConstantScaling + ")\n" 
	    + "  -histbin=     The pixel histogram bin size in sigma units. (" + histResolution + ")\n"
	    + "  -histrange=   The pixel histogram range in sigma units. (" + histRange + ")\n"
	    + "  -mask=        The source mask file used. (" + (maskName == null ? "none" : maskName) + ")\n"
	    + "  -model=       Specify an initial source model. (" + (model == null ? "none" : model.fileName) + ")\n" 
	    + "  -aperture     Solve for sources inside the masked apertures. " + (apertureOnlySource ? "(*)" : "") + "\n"
	    + "  -slice=       Chose a scan and frame to sample models. (" + 
	    (sampleFrame >= 0 ? (sampleScan+1) + "," + (sampleFrame+1) : "off") + ")\n"
	    + "  -scramble     Scrable pixels (do not expect to see source!!!). " + (scramble ? "(*)" : "") + "\n"
	    + "  -debug        Look for anomalies in data. " + (debug ? "(*)" : "") + "\n";

	System.out.println(info);

	for(int i=0; i<pipeline.size(); i++) {
	    Model model = (Model) pipeline.elementAt(i);
	    model.usage();
	}
    }


    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException, IOException {
	System.out.print(" Adding FITS header information. ");
	
	Bolometer.editHeader(cursor);
	Crush.editHeader(cursor);	

	// ALTAZ is filled by ReducedData...
	//cursor.add(new HeaderCard("ALTAZ", altaz, "Whether to reduce un Alt/AZ vs the default RA/DEC system."));
	cursor.add(new HeaderCard("PLANET", planetary, "Whether planetary object is assumed (relative coordinates)."));
	cursor.add(new HeaderCard("ROUNDS", rounds, "The number of iterations used for the solution."));
	cursor.add(new HeaderCard("GNROUNDS", gainRounds, "The number of iterations for the initial gain solution."));
	if(gainRounds < 0) 
	    cursor.add(new HeaderCard("GNGOAL", gainConvergenceGoal, "The targeted gain convergence for initial gain sol."));
	cursor.add(new HeaderCard("LINEAR", linearResponse, "Assume linear pixel response (constant gains)."));
	cursor.add(new HeaderCard("MODELS", writeModels, "Whether to add scan and model info to FITS."));
	cursor.add(new HeaderCard("COVARIAN", writeCovariance && fitsCovariance, 
				  "Whether pixel-to-pixel covariance data is appended to scans"));
	cursor.add(new HeaderCard("HISTOGRM", writeHistogram, "Whether histogram data is appended to scans."));

	if(writeHistogram) {
	    cursor.add(new HeaderCard("HISTRANG", histRange, "The pixel histogram range in sigma units."));
	    cursor.add(new HeaderCard("HISTBIN", histResolution, "The pixel histogram binsize in sigma units."));
	}

	for(int m=0; m<pipeline.size(); m++) {
	    Model model = (Model) pipeline.elementAt(m);
	    if(model.isActive) model.editHeader(cursor);
	}

	System.out.println();
    }

    public void editHeader(Header header) throws FitsException, HeaderCardException, IOException {
	Cursor cursor = header.iterator();
	while(cursor.hasNext()) cursor.next();
	editHeader(cursor);
    }

    public void write() throws FitsException, HeaderCardException, IOException {
	Fits fits = reduced.getFits();
	editHeader(fits.getHDU(0).getHeader());
	addScanData(fits);	
	reduced.write(fits);
    }

    // Not very elegant but will do for now...
    public void writeScanMaps() throws FitsException, HeaderCardException, IOException {
	// Do not do anything unless multiple scans are processed.
	if(scans < 2) return;
	if(verbose) System.out.println(" Writing Images for all scans...");
	
	int[] activePixels = new int[scans];
	boolean wasVerbose = verbose;
	verbose = false;

	// Disactivate all scans...
	for(int i=0; i<scans; i++) {
	    activePixels[i] = scan[i].activePixels;
	    scan[i].activePixels = 0;
	}
	
	// Now re-activate them one by one, and write to a default file...
	for(int i=0; i<scan.length; i++) {
	    reduced.fileName = Crush.workPath + scan[i].sourceName + "." + scan[i].serialNo + ".fits";
	    scan[i].activePixels = activePixels[i];

	    sourceModel.extract();
	    
	    Fits fits = reduced.getFits();
	    editHeader(fits.getHDU(0).getHeader());
	    //addScanData(fits);	
	    reduced.write(fits);

	    scan[i].activePixels = 0;
	}
	
	// Re-activate the scans...
	for(int i=0; i<scans; i++) scan[i].activePixels = activePixels[i];
	reduced.fileName = null;

	verbose = wasVerbose;
    }

    Vector scanBuffer = new Vector();
    public void add(SharcScan aScan) throws Exception {
	if(verbose) aScan.printInfo();
	scanBuffer.add(aScan);
    }       
	
    public void createReduced() {
	if(verbose) System.out.print(" Initializing Source Map. ");
	
	reduced = new ReducedData();
	reduced.scan = scan;	
	reduced.options = Crush.options;
	reduced.delta = resolution;
	reduced.beamFWHM = beamFWHM;
	reduced.sourceName = scan[0].sourceName; 
	reduced.altaz = altaz;
	reduced.RA = scan[0].RA;
	reduced.DEC = scan[0].DEC;
	reduced.coordinateEpoch = scan[0].coordinateEpoch;
	reduced.Jy = defaultVperJy;
	reduced.init();
	
	reduced.setUnit(mapUnit);

	for(int i=0; i<scans; i++) scan[i].reduced = reduced;

	double mindRA=0.0, maxdRA=0.0; 
	double mindDEC=0.0, maxdDEC=0.0; 

	int[] corner = { 0, 31, 352, 383 };

	for(int i=0; i<scans; i++) for(int t=0; t<scan[i].frames; t++) for(int c=0; c<4; c++) {
	    int bol = corner[c];
	    Vector2D coord = scan[i].getMapOffsets(t, bol);
	    
	    if(coord.x < mindRA) mindRA = coord.x;
	    if(coord.x > maxdRA) maxdRA = coord.x;
	    if(coord.y < mindDEC) mindDEC = coord.y;
	    if(coord.y > maxdDEC) maxdDEC = coord.y;
	}

	mindRA = ((int)Math.floor(mindRA/resolution) - 0.5) * resolution;
	mindDEC = ((int)Math.floor(mindDEC/resolution) - 0.5) * resolution;

	double rangeRA = maxdRA - mindRA;
	double rangeDEC = maxdDEC - mindDEC;
	
	double areaFactor = SharcHeader.pixelArea / (resolution * resolution);  
		    
	int sizeRA = (int)Math.ceil(rangeRA/resolution) + 2;
	int sizeDEC = (int)Math.ceil(rangeDEC/resolution) + 2;

	reduced.dRA0 = mindRA - resolution;
	reduced.dDEC0 = mindDEC - resolution;	
	reduced.setSize(sizeRA, sizeDEC);

	if(maskName != null) {
	    if(maskName.equalsIgnoreCase("*")) maskName = null;	    
	    else if(maskName != null) reduced.readMask(maskName);
	}

	if(model != null) {
	    // Regrid the model to the same resolution if necessary
	    if(model.delta != resolution) model.regrid(resolution);

	    for(int i=0; i<model.sizeX; i++) for(int j=0; j<model.sizeY; j++) if(model.flag[i][j] == 0) {
		double w = 1.0/(model.rms[i][j]*model.rms[i][j]);
		reduced.addPointAt(model.getRAofIndex(i), model.getDECofIndex(j), model.clean[i][j], w, model.count[i][j]);
	    }
	    reduced.normalize();
	    model = reduced.getReducedData();
	    model.convolveTo(smoothFWHM);

	    if(verbose) System.out.print(" Synching with Initial Source Model. ");	    

	    reduced.scale(-1.0);
	    sourceModel.isActive = true;
	    sourceModel.inject();
	    // Reset explicitly just to be sure, though inject should do that too...
	    reduced.reset();
	}
	
	reduced.map = reduced.clean;
	
	reduced.info();	
    }


    public void setupPipeline() {
	if(verbose) System.out.println(" Initializing Reduction Pipeline. ");

	boolean chopped = false;
	for(int i=0; i<scans; i++) if(scan[i].chopped) chopped = true;
	if(!chopped) pipeline.remove(chopCoefficientModel);
	
	scans = 1;	
	consolidatePipeline();
	
	if(autoOrder || gainRounds == AUTO_GAIN_ROUNDS) {
	    preliminarySolution();
	    if(autoOrder) autoReorderPipeline();
	    if(gainRounds == AUTO_GAIN_ROUNDS) if(sourceModel.getTypicalFlux() > 0.01 * correlatedModel.getTypicalFlux()) gainRounds=0;
	    reset();
	}

	sourceBeforeGain = gainModel.turn >= 0 ? 
	    pipeline.indexOf(sourceModel) < pipeline.indexOf(gainModel) :
	    pipeline.indexOf(sourceModel) < pipeline.indexOf(correlatedModel);

	
	boolean guessTimeScales = false;
	for(int m=0; m<pipeline.size(); m++) {
	    Model model = (Model) pipeline.elementAt(m);
	    if(model.autoTimeScale) guessTimeScales = true;
	    else if(model.comparable) model.timeConstant = (int)Math.ceil(timeConstantScaling * model.timeConstant);
	}	
	
	if(guessTimeScales) {
	    preliminarySolution();
	    guessTimeConstants();
	    reset();
	}

	scans = scan.length;

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

    public void makeLookups() {
	for(int i=0; i<scans; i++) {
	    if(verbose) System.out.print("\r Generating lookup tables as necessary. " + ((int)(50.0*(float)i/scans)) + "%    ");
	    if(scan[i].lookup) scan[i].makeIndexLookup();;
	}

	Runtime VM = Runtime.getRuntime();
	VM.runFinalization();
	VM.gc();
	
	byte[] reserve = new byte[Crush.reserveMemory];
	
	boolean hasRoom = true;
	for(int i=0; i<scans; i++) {
	    if(verbose) System.out.print("\r Generating lookup tables as necessary. " + ((int)(50.0*(1.0+(float)i/scans))) + "%    ");
	    if(hasRoom) {
		try { scan[i].autoLookup(); }
		catch(OutOfMemoryError e) { hasRoom = false; }
	    }

	    if(!hasRoom) {
		scan[i].autoLookup = false;
		scan[i].lookup = false;
	    }
	}
	if(verbose) System.out.println("\r Generating lookup tables as necessary. Done.");	

	reserve = null;
	VM.runFinalization();
	VM.gc();
    }

    public void getInitialGainSolution() {
	boolean wasVerbose = verbose;
	boolean autoGainRounds = gainRounds < 0;

	if(autoGainRounds) {
	    if(sourceBeforeGain) return;
	    if(gainModel.turn < 0) return;
	    gainRounds = 1;	    
	}

	verbose = false;
	approximateSourceMap = false;

	int nModels = pipeline.indexOf(gainModel);
	if(nonLinearityModel.turn >= 0 && nonLinearityModel.turn <= rounds) 
	    nModels = Math.max(nModels, pipeline.indexOf(nonLinearityModel));

	for(int i=1; i<=gainRounds; i++) {
	    if(wasVerbose) System.out.print(" Initial Gain Solution -- Iteration " + i + ": ");
	    if(verbose) System.out.println();

	    for(int m=0; m <= nModels; m++) {
		Model model = (Model) pipeline.elementAt(m);
		if(verbose) if(chi2report && chi2all) {
		    DecimalFormat chif = new DecimalFormat("0.00");
		    
		    System.out.print(pixelWeightModel.isActive ? "  " : " ~");
		    System.out.print(chif.format(getChiSquared()) + "\r");
		}
		if(model.turn >= 0 && i >= model.turn) model.extract();
	    }

	    // Do pixel weighting if not done already
	    if(pipeline.indexOf(pixelWeightModel) > pipeline.indexOf(gainModel)) pixelWeightModel.extract();

	    if(autoGainRounds) if(meanGainChange > gainConvergenceGoal) {
		gainRounds++;
		if(i > maxGainRounds) {
		    System.err.println(" ERROR! Gains did not converge.in " + maxGainRounds + " iterations. ");
		    System.err.println("        Try relaxing convergence criterion or explicitly set the required");
		    System.err.println("        number of iterations ");
		    System.exit(1);
		}
	    }
	    
	    if(verbose) System.out.println();
	    else if(wasVerbose) System.out.println(" |dG| = " + new DecimalFormat("0.00E0").format(meanGainChange));
	}
	verbose = wasVerbose;
    }


    public ReducedData reduce() {

	approximateSourceMap = true;
	
	if(rounds > 0) {
	    setupPipeline();
	    initializePipeline();
	    makeLookups();
	}

	if(verbose) System.out.println(" Starting Pipeline Reduction. \n");	
	
	if(faithfulTurn == 0) faithfulTurn = (rounds + sourceModel.turn - 1) / 2; 

	if(gainModel.turn == 1 || !sourceBeforeGain) getInitialGainSolution();
	if(verbose) System.out.println();

	System.gc();

	approximateSourceMap = true;

	for(iter=1; iter <= rounds; iter++) {
	    if(verbose) System.out.println(" Iteration " + iter + ": ");

	    if(faithfulTurn >= 0) if(iter >= faithfulTurn) approximateSourceMap = false;
		
	    for(int m=0; m<pipeline.size(); m++) {
		Model model = (Model) pipeline.elementAt(m);
		if(verbose) if(chi2report && chi2all) {
		    DecimalFormat chif;		    
		    double chi2 = getChiSquared();

		    if(chi2 > 100) chif = new DecimalFormat("0.0E0");
		    else if(chi2 > 10)  chif = new DecimalFormat("00.0");
		    else chif = new DecimalFormat("0.00");
		    
		    System.out.print(pixelWeightModel.isActive ? "  " : " ~");
		    System.out.print(chif.format(chi2) + "\r");
		}
		if(model.turn >= 0 && iter >= model.turn) {
		    model.extract();
		    if(debug) if(hasNaNs()) {
			System.err.println("ERROR! In " + model.name);
			System.err.println("ERROR! Data has NaN element(s). Exiting!");
			System.exit(1);
		    }		    
		}
	    }

	    if(verbose) System.out.println();
	}
	
	approximateSourceMap = false;

	if(simulatedChopped) simulatedChoppedModel.extract();


	intermediateSmoothFWHM = 0.0;
	sourceModel.extract();

	if(exposureClipDirective.equalsIgnoreCase("output")) exposureClip();

	// Convolve to the desired smoothness
	if(model != null) reduced.math('+', model, false);

	reduced.convolveTo(smoothFWHM);
	
	if(rmsClipDirective.equalsIgnoreCase("output")) rmsClip();

	System.out.println();
	System.out.println(" Final Chi^2 = " + new DecimalFormat("0.000").format(getChiSquared()));

	if(sampleFrame >= 0) writeModelSlice(sampleScan, sampleFrame);

	slim();
	
	if(rounds > 0) summary();

	if(writeResiduals) writeResiduals();	
	if(writePixelCoeffs) for(int i=0; i<scans; i++) {
	    scan[i].calcGainCoefficients();
	    try { scan[i].writeGainCoefficients(); }
	    catch(IOException e) { System.out.println("Error writing gain coefficients."); }
	}

	if(writeGains) writeGains();
	if(covarPixel > 0) writeCovariances(covarPixel);
	if(writeCovariance && !fitsCovariance) writeCovariances();
	if(globalCovariance) writeGlobalCovariance();
	if(spacialCorrelations) writeSpacialCorrelations();	
	
	if(measureEfficiency) {
	    try { measureRelativeBeamEfficiency(); }
	    catch(Exception e) { e.printStackTrace(); }
	}

	return reduced;
    }

    public void slim() {
	for(int i=0; i<scans; i++) if(scan[i].mapIndexTable != null) {
	    scan[i].mapIndexTable = null;
	    scan[i].lookup = false;
	}
	System.gc();
    }	

    public void exposureClip() {
	double minIntTime = minRelativeExposure * reduced.getMax(reduced.count);
	for(int i=0; i<reduced.sizeX; i++) for(int j=0; j<reduced.sizeY; j++) if(reduced.flag[i][j] == 0)
	    if(reduced.count[i][j] < minIntTime) reduced.flag[i][j] = 1;
    }

    public void rmsClip() {
	double maxRMS = maxRelativeRMS * reduced.getMin(reduced.rms);
	for(int i=0; i<reduced.sizeX; i++) for(int j=0; j<reduced.sizeY; j++) if(reduced.flag[i][j] == 0)
	    if(reduced.rms[i][j] > maxRMS) reduced.flag[i][j] = 1;
    }


    public double getChiSquared() {
	double chi2 = 0.0;
	int n = 0;

	for(int i=0; i<scans; i++) if(scan[i].activePixels > 0 && scan[i].activeFrames > 0){
	    final Bolometer[] pixel = scan[i].pixel;

	    for(int t=0; t<scan[i].frames; t++) {
		final SharcFrame frame = scan[i].frame[t];		
		final float[] residual = frame.readout;
		final boolean[] flagged = frame.flagged;
		
		for(int bol=0; bol<scan[i].pixels; bol++) if(pixel[bol].isActive) if(!flagged[bol]) {
		    chi2 += frame.weight * pixel[bol].weight * residual[bol] * residual[bol];
		    n++;
		}	    
	    }
	}

	for(int m=0; m<pipeline.size(); m++) {
	    Model model = (Model) pipeline.elementAt(m);
	    if(model.isActive) n -= model.getNumberOfParameters();
	}	

	return chi2 / n ;
    }

    public boolean hasNaNs() {
	for(int i=0; i<scans; i++) {
	    final int nt = scan[i].frames;
	    final int nb = scan[i].pixels;
	    for(int t=0; t < nt; t++) {
		final float[] residual = scan[i].frame[t].readout; 
		for(int bol=0; bol<nb; bol++) if(Float.isNaN(residual[bol])) return true;
	    }
	}
	return false;
    }

    public void summary() {
	if(verbose) System.out.println("\n Approximated Model Fluxes:\n");

	int sourceIndex = pipeline.indexOf(sourceModel);
	pipeline.remove(sourceModel);
	
	DecimalFormat df = new DecimalFormat("0.000E0");

	for(int m=0; m<pipeline.size(); m++) {
	    Model model = (Model) pipeline.elementAt(m);
	    if(model.isActive) if(model.comparable) if(verbose) 
		System.out.println("  " + model.name + "\r\t\t\t" + df.format(model.getTypicalFlux() * 1e9) );
	}
	if(verbose) System.out.println();
	if(verbose) System.out.println("  " + sourceModel.name + "\r\t\t\t" + df.format(sourceModel.getTypicalFlux() * 1e9) );
	if(verbose) System.out.println();

	if(sourceIndex >= 0) pipeline.add(sourceIndex, sourceModel);
    }

    SharcScan[] backup;


    public void preliminarySolution() {	
	verbose = false;

	initializePipeline();
	
	gainModel.preliminary = pipeline.indexOf(sourceModel) > pipeline.indexOf(correlatedModel);

	// Save readout and bolometer data first
	backup = new SharcScan[scans];
	for(int i=0; i<scans; i++) backup[i] = scan[i].duplicate();	
	
	if(gainRounds == 1 || !sourceBeforeGain) {
	    System.out.print(" Solving for Pixel Gains. ");	    
	    getInitialGainSolution();
	    System.out.println();
	}

	for(int m=0; m<pipeline.size(); m++) {
	    Model model = (Model) pipeline.elementAt(m);

	    model.alwaysSolve = true;

	    if(model.turn >= 0 && model.turn < rounds) if(model.preliminary) model.extract();
	    
	    System.out.print("\r Obtaining Preliminary Solution. " + (int)(100.0 * (m+1)/(pipeline.size())) + "%                      ");
	    System.out.flush();
	}	

	System.out.println("\r Obtaining Preliminary Solution. Done.  ");	

	verbose = true;
    }

    public void reset() {
	verbose = false;

	System.out.print(" Resetting Data. ");
	for(int m=0; m<pipeline.size(); m++) {
	    Model model = (Model) pipeline.elementAt(m);
	    model.alwaysSolve = false;
	    if(model.isActive) {
		//model.inject();    
		model.reset();
	    }
	    System.out.print("\r Resetting Data. " + (int)(100.0 * (m+1)/pipeline.size()) + "%   ");
	    System.out.flush();
	}

	for(int i=0; i<scans; i++) scan[i] = backup[i];
	backup = null;

	System.out.println("\r Resetting Data. Done.   ");

	verbose = true;
    }
    
    public void consolidatePipeline() {
	for(int m=0; m<pipeline.size(); ) {
	    Model model = (Model) pipeline.elementAt(m);
	    if(model.turn < 0) pipeline.remove(m);
	    else m++;
	}
    }
      
    public void initializePipeline() {
	for(int m=0; m<pipeline.size(); m++) {
	    if(verbose) System.out.print("\r Creating Storage for Models. " + ((int)(100.0*(float)m/pipeline.size())) + "%    ");
	    Model model = (Model) pipeline.elementAt(m);
	    if(model.turn >= 0 && model.turn <= rounds) model.init();
	}
	if(verbose) System.out.println("\r Creating Storage for Models. Done.");	
    }
    
    public void autoReorderPipeline() {
	System.out.println(" Determining Optimal Pipeline Order. ");


	Model[] sorted = new Model[pipeline.size()];
	for(int m=0; m<pipeline.size(); m++) sorted[m] = (Model) pipeline.elementAt(m);
	
	Arrays.sort(sorted);

	pipeline = new Vector();
	for(int m=0; m < sorted.length; m++) {
	    System.out.println("\t" + (m+1) + " -> " + sorted[m].name);
	    pipeline.add(sorted[m]);
	}

	if(pipeline.indexOf(sourceModel) < pipeline.indexOf(correlatedModel)) {
	    System.out.println(" Source is too Bright! -- Reconfiguring with the \'-bright\' option. ");
	    Crush.option("-bright");
	}
    }

    
    public void guessTimeConstants() {
	System.out.println(" Estimating Time Constants: ");

	verbose = false;
	int numberOfScans = scans;

	for(int m=0; m<pipeline.size(); m++) {
	    Model model = (Model) pipeline.elementAt(m);

	    if(model.turn >= 0 && model.autoTimeScale) model.guessTimeConstant(0.05);
	}

	scans = numberOfScans;
	verbose = true;
    }

    public int maxHighPassFrames() {
	int maxFrames = 0;
	for(int i=0; i<scans; i++) if(scan[i].highPassFrames > maxFrames) maxFrames = scan[i].highPassFrames;

	return maxFrames;
    }

    public boolean allHighPassed() {
	for(int i=0; i<scans; i++) if(scan[i].highPass <= 0.0) return false;
	return true;
    }


    public double getPositionAngleRange() {
	double minPA = scan[0].frame[0].getPositionAngle();
	double maxPA = scan[0].frame[scan[0].frames-1].getPositionAngle();

	if(minPA > maxPA) {
	    double temp = minPA;
	    minPA = maxPA;
	    maxPA = temp;
	}

	for(int i=1; i<scans; i++) {
	    double PA = scan[i].frame[0].getPositionAngle();
	    if(PA < minPA) minPA=PA;
	    if(PA > maxPA) maxPA=PA;
	    
	    PA = scan[i].frame[scan[i].frames-1].getPositionAngle();
	    if(PA < minPA) minPA=PA;
	    if(PA > maxPA) maxPA=PA;
	}
	return maxPA - minPA;
    }

    public int getIndexOf(SharcScan aScan) {
	for(int i=0; i<scans; i++) if(scan[i] == aScan) return i;
	return -1;
    }

    // This is the full gain. It is only necessary for strong signals
    // e.g. background and occasionally source. For weak signals (all else)
    // the gain is adequately approximated by scan.gain * pixel.gain.
    // This is the gain from the input stage to the redout stage.
    public float gain(int i, int t, int bol) {
	if(linearResponse) return scan[i].gain * scan[i].pixel[bol].gain; 

	final Bolometer pixel = scan[i].pixel[bol];	
	
	float dI  = correlated[i][t];

	if(sourceBeforeGain) if(sourceModel.isActive) {
	    final short[] index = scan[i].getMapIndexes(t, bol);
	    dI += scan[i].gain * scan[i].frame[t].transmission * reduced.clean[index[0]][index[1]];
	}

	// The returned gain is simply the average gain over the
	// signal range. The small signal gain is on top of that...
	return scan[i].gain * pixel.gain * (1.0F - 0.5F * dI/scan[i].dIdT / (pixel.T0 - scan[i].loadT)); 
    }

    // This is the effective small signal gain at the current bolometer
    // loading. The effect of using this vs. the average relative gain as
    // determined for the scan should be minor.
    public float smallSignalGain(int i, int t, int bol) {
	if(linearResponse) return scan[i].gain * scan[i].pixel[bol].gain;

	final Bolometer pixel = scan[i].pixel[bol];
	
	float dI = correlated[i][t];

	if(sourceBeforeGain) if(sourceModel.isActive) {
	    final short[] index = scan[i].getMapIndexes(t, bol);
	    dI += scan[i].gain * scan[i].frame[t].transmission * reduced.clean[index[0]][index[1]];
	}

	return scan[i].gain * pixel.gain * (1.0F - dI/scan[i].dIdT / (pixel.T0 - scan[i].loadT));
    }

    public float beamEfficiency(int i, int t, int bol) {
	/*
	float dc = bol%32 - (float)scan[i].pointingCenter.x;
	float dr = bol/32 - (float)scan[i].pointingCenter.y;	
	return 1.0F - curvature * (dc * dc + dr * dr);
	*/
	return 1.0F;
    }
    
    public void adjustTransparencies(int i) {
	final double dtrans = scan[i].adjustTransparencies();

	final float G = scan[i].gain;
	final Bolometer[] pixel = scan[i].pixel;
	final float areaFactor = (float) (scan[i].pixelArea / SharcHeader.pixelArea);
	
	for(int t=0; t < scan[i].frames; t++) {
	    final float[] residual = scan[i].frame[t].readout;			
	    	    
	    // correct the residual by the effective transmission change on the source
	    // model.
	    if(sourceModel.isActive) for(int bol=0; bol < scan[i].pixels; bol++) {
		final float gain = sourceBeforeGain ? gain(i, t, bol) : G * pixel[bol].gain;
		final short[] index = scan[i].getMapIndexes(t, bol);
		residual[bol] -= dtrans * areaFactor * beamEfficiency(i, t, bol) * gain * reduced.clean[index[0]][index[1]];
	    }
	}
    }

    public float getTransmission(int i, int t) {
	return scan[i].transmission - scan[i].gain * correlated[i][t] / scan[i].I0;
    }


    public boolean isMapped(SharcScan aScan) {
	return aScan.activePixels >= minActivePixels && aScan.activeFrames * aScan.samplingInterval >= aScan.minIntTime;
    }

    // At the input stage.
    Model sourceModel = new Model("SourceMap", true) {
	    
	    public void init() {
		scanMap = reduced.getReducedData();
		oldImage = new double[reduced.sizeX][reduced.sizeY];
		if(fluxCorrectingFWHM <= 0.0) fluxCorrectingFWHM = beamFWHM;
	    }
	    
	    // Done with implicit injection...
	    public void extract() {		
		isActive = false;
		sourceIsActive = false;
		
		if(verbose) System.out.print("\tExtracting Source Map. ");
				    	      
		if(scanMap == null) init();
		else scanMap.reset();

		oldImage = (double[][]) reduced.imageCopy(reduced.clean);
		reduced.reset();

		final boolean tauAdjust = tauAdjustTurn >= 0 && iter >= tauAdjustTurn;
		for(int i=0; i<scans; i++) if(isMapped(scan[i])) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final Bolometer[] pixel = scan[i].pixel;
		    final double dt = scan[i].samplingInterval;
		    final float areaFactor = (float) (scan[i].pixelArea / SharcHeader.pixelArea);

		    for(int t=0; t < nt; t++) {
			final SharcFrame frame = scan[i].frame[t];
			final float[] residual = frame.readout;
			final boolean[] flagged = frame.flagged;
			final boolean[] masked = frame.masked;
			final float fw = frame.weight;		

			final float injectFrameGain = areaFactor * (atmosphericCorrection ? frame.transmission : 1.0F)
; 
			// Re-calculate the transmission for extraction.
			if(tauAdjust) frame.transmission = getTransmission(i, t);
			final float extractFrameGain = areaFactor * (atmosphericCorrection ? frame.transmission : 1.0F); 
			
			if(extractFrameGain > 0.0F) for(int bol=0; bol < nb; bol++) {
			    final short[] index = scan[i].getMapIndexes(t, bol);

			    float G = (sourceBeforeGain ? gain(i,t,bol) : scan[i].gain * pixel[bol].gain)
				* beamEfficiency(i, t, bol);
			    
			    // Inject the old image first to all data points
			    residual[bol] += G * injectFrameGain * oldImage[index[0]][index[1]];
			    
			    G *= extractFrameGain;
			    
			    // Now derive the new image on valid data points only
			    if(pixel[bol].isActive) if(apertureOnlySource ? masked[bol] : !flagged[bol] || masked[bol]) {
				final float w = G * G * fw * pixel[bol].weight;		
				scanMap.addPointAtIndex(index[0], index[1], residual[bol]/G, w, dt);
			    }		    
			}
		    }

		    scanMap.normalize();

		    if(approximateSourceMap) if(extendedFilter) if(!extended || generation >= extendedSourceGenerations) 
			scanMap.filterAbove(extendedScale);
		    if(generation == 0 || approximateSourceMap) scanMap.robustLevel();
		    if(calcScanWeights) scan[i].weight = (float) scanMap.getRobustWeight(false);

		    // Apply scan-wise point source corrections to compact sources...
		    if(!extended && !Double.isNaN(fluxCorrectingFWHM)) {
			double correction = 1.0;
			for(int m=0; m<pipeline.size(); m++) {
			    Model model = (Model) pipeline.get(m);
			    if(model.isActive) correction *= 1.0 - model.getScanCorrectionKappa(scan[i], fluxCorrectingFWHM);
			}
			scanMap.scale(1.0/correction);
		    }

		    reduced.addDirect(scanMap, scan[i].weight);

		    scanMap.reset();
		}

		reduced.normalize();
		reduced.fluxCorrectingFWHM = fluxCorrectingFWHM;
		
		if(exposureClipDirective.equalsIgnoreCase("always")) {
		    if(verbose) System.out.print("Exp-Clip. ");
		    exposureClip();
		}

		if(approximateSourceMap) if(extendedFilter) if(!extended || generation >= extendedSourceGenerations) { 
		    if(verbose) System.out.print("Filtering. ");
		    reduced.filterAbove(extendedScale);
		}

		if(approximateSourceMap) if(intermediateSmoothFWHM > 0.0) {
		    if(verbose) System.out.print("Smoothing. ");
		    reduced.convolveTo(intermediateSmoothFWHM);
		}	

		if(rmsClipDirective.equalsIgnoreCase("always")) {
		    if(verbose) System.out.print("RMS-Clip. ");
		    rmsClip();
		}

		if(verbose) System.out.print("Median Levelling. ");
		reduced.robustLevel();

		// Clip only if the level is > -1.0
		if(approximateSourceMap) if(clippingLevel > -1.0) {
		    if(verbose) System.out.print("Removing Negatives. ");
		    reduced.clip(clippingLevel);
		}

		remove();

		if(show) {
		    if(reduced.display == null) reduced.show();
		    else {
			reduced.setDefaults();
			reduced.display.repaint();
		    }
		}
		
		super.extract();
		isActive = true;
		sourceIsActive = true;

		if(verbose) if(calcScanWeights) {
		    System.out.println();
		    System.out.print("\t-> Derived Map Weight(s): ");	
		    DecimalFormat df = new DecimalFormat("0.00");
		    for(int i=0; i<scans; i++) System.out.print(df.format(scan[i].weight) + " ");
		}

		if(verbose) System.out.println();
	    }
	 
	    public void remove() { remove(reduced.clean); }

	    public void remove(double[][] image) {
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final float areaFactor = (float) (scan[i].pixelArea / SharcHeader.pixelArea);

		    for(int t=0; t < nt; t++) {
			final Bolometer[] pixel = scan[i].pixel;
			final SharcFrame frame = scan[i].frame[t];
			final float[] residual = frame.readout;
			final float frameGain = areaFactor * (atmosphericCorrection ? frame.transmission : 1.0F); 
			
			if(frameGain > 0.0F) for(int bol=0; bol < nb; bol++) {
			    final float pixelGain = sourceBeforeGain ? gain(i,t,bol) : scan[i].gain * pixel[bol].gain;
			    final short[] index = scan[i].getMapIndexes(t, bol);
			    residual[bol] -= pixelGain * frameGain * beamEfficiency(i, t, bol)
				* (float) image[index[0]][index[1]];
			}
		    }
		}
	    }
		
	    public void removeDifferenceOf(double[][] i1, double[][] i2) {
		double[][] diff = new double[reduced.sizeX][reduced.sizeY];
		for(int i=0; i<reduced.sizeX; i++) for(int j=0; j<reduced.sizeY; j++) diff[i][j] = i1[i][j] - i2[i][j];
		remove(diff);
	    }

	    public void inject() { inject(reduced.clean); }
	    
	    public void inject(double[][] image) {
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final float areaFactor = (float) (scan[i].pixelArea / SharcHeader.pixelArea);
		    
		    for(int t=0; t < nt; t++) {
			final Bolometer[] pixel = scan[i].pixel;
			final SharcFrame frame = scan[i].frame[t];
			final float[] residual = frame.readout;
			final float frameGain = areaFactor * (atmosphericCorrection ? frame.transmission : 1.0F); 
			
			if(frameGain > 0.0F) for(int bol=0; bol < nb; bol++) {
			    final float pixelGain = sourceBeforeGain ? gain(i,t,bol) : scan[i].gain * pixel[bol].gain;
			    final short[] index = scan[i].getMapIndexes(t, bol);
			    residual[bol] += pixelGain * frameGain * beamEfficiency(i, t, bol) 
				* (float) image[index[0]][index[1]];
			}
		    }
		}

		reduced.reset();
		sourceIsActive = false;
	    }

	    public void reset() {
		super.reset();
		reduced.reset();
		sourceIsActive = false;
		scanMap = null;
		oldImage = null;
	    }

	    public SharcFrame getFrame(SharcScan aScan, int t) {
		SharcFrame frame = super.getFrame(aScan, t);
		frame.readout = new float[aScan.pixels];
		final float areaFactor = (float) (aScan.pixelArea / SharcHeader.pixelArea);
		final float frameGain = areaFactor * (atmosphericCorrection ? frame.transmission : 1.0F); 
		final int i = getIndexOf(aScan);

		for(int bol=0; bol < aScan.pixels; bol++) {
		    final float pixelGain = sourceBeforeGain ? gain(i,t,bol) : scan[i].gain * aScan.pixel[bol].gain;
		    final short[] index = aScan.getMapIndexes(t, bol);
		    frame.readout[bol] = pixelGain * frameGain * beamEfficiency(i, t, bol) 
			* (float)(reduced.clean[index[0]][index[1]]);
		}
		return frame;
	    }

	    public int getNumberOfParameters() { return (int)reduced.countBeams(); }

	    public double getRMS() {
		double var = 0.0, sumw = 0.0;       
		
		for(int x=0; x<reduced.sizeX; x++) for(int y=0; y<reduced.sizeY; y++) if(reduced.flag[x][y] == 0) {
		    final float w = (float) reduced.weight[x][y];
		    final float value = (float)reduced.clean[x][y];
		    final float pointRMS = (float)reduced.rms[x][y];
		    var += w * (value * value - pointRMS * pointRMS );
		    sumw += w;
		}

		//return var > 0.0 ? Math.sqrt(var / sumw / reduced.pointsPerBeam) : 0.0;
		return var > 0.0 ? Math.sqrt(var / sumw) : 0.0;
	    }

	    public double getTypicalFlux() {
		float rmsGain = 0.0F;
		for(int i=0; i<scans; i++) {
		    final SharcFrame frame = scan[i].frame[scan[i].frames/2];
		    final float fgain = frame.transmission * scan[i].gain;
		    rmsGain += fgain * fgain;
		}
		rmsGain = (float)Math.sqrt(rmsGain / scans);

		double sizeCorrection = 1.0F;
		if(sourceDiameter > 0.0) {
		    double sourceArea = Math.PI / 4.0 * sourceDiameter * sourceDiameter;
		    sizeCorrection = Math.sqrt(reduced.getArea() / sourceArea);
		}

		return 0.1 * rmsGain * sizeCorrection * getRMS();
	    }

	    public void dGain(int i, int t, float[] dmodel) {
		if(!isActive) return;
		final SharcFrame frame = scan[i].frame[t];
		final Bolometer[] pixel = scan[i].pixel;
		final float areaFactor = (float) (scan[i].pixelArea / SharcHeader.pixelArea);
		    
		for(int bol=0; bol<scan[i].pixels; bol++) {			
		    final short[] index = scan[i].getMapIndexes(t, bol);
		    double model = areaFactor * beamEfficiency(i, t, bol) 
			* frame.transmission * reduced.clean[index[0]][index[1]];	       
			
		    float G = sourceBeforeGain ? gain(i,t,bol) : scan[i].gain * pixel[bol].gain;

		    dmodel[bol] += G * model;
		}
	    }
	    
	    public void weightAdjust(double factor) {
		double rmsFactor = 1.0 / Math.sqrt(factor);
		for(int x=0; x<reduced.sizeX; x++) for(int y=0; y<reduced.sizeY; y++) if(reduced.flag[x][y] == 0) {
		    reduced.weight[x][y] *= factor;
		    reduced.rms[x][y] *= rmsFactor;
		}
	    }

	    public Object[] getData() {
		float[][] transmission = new float[scans][];
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final SharcFrame[] frame = scan[i].frame;
		    transmission[i] = new float[scan[i].frames];
		    for(int t=0; t<nt; t++) transmission[i][t] = frame[t].transmission;
		}
		return transmission;
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "MAP_";
		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 + "TRUE", faithfulTurn, "Faithful source extraction iteration."));
		cursor.add(new HeaderCard(id + "XGEN", extendedSourceGenerations, "Undisturbed extended source generations."));
		cursor.add(new HeaderCard(id + "INDX", pipeline.indexOf(this), "The Model index inside the Pipeline."));
		cursor.add(new HeaderCard(id + "RMS", getRMS(), "The RMS of the Model Parameters."));
		cursor.add(new HeaderCard(id + "CUT", clippingLevel, "The level rel. to peak below which clipping when not faithful."));
		cursor.add(new HeaderCard(id + "CLPD", exposureClipDirective, "Directive for exposure clipping."));
		cursor.add(new HeaderCard(id + "CLIP", minRelativeExposure, "Relative Exposure under which to clip Source Map."));
		cursor.add(new HeaderCard(id + "BEAM", smoothFWHM/Unit.ArcSECOND, "The effective beam FWHM (arcsec)."));
		cursor.add(new HeaderCard(id + "DIAM", sourceDiameter/Unit.ArcSECOND, "The Assumed Source Diameter (arcsec)."));
		cursor.add(new HeaderCard(id + "FCOR", fluxCorrectingFWHM/Unit.ArcSECOND, "Flux correcting FWHM (arcsec)."));
		cursor.add(new HeaderCard(id + "XTND", extended, "Are extended structures retained?"));
		cursor.add(new HeaderCard(id + "XFLT", extendedFilter, "Is the map filtered for point sources?"));
		cursor.add(new HeaderCard(id + "XSCL", extendedScale/Unit.ArcSECOND, "Scale of extended structures (arcsec)."));
		cursor.add(new HeaderCard(id + "ATMC", atmosphericCorrection, "Are fluxes corrected for atmospheric absorbtion?"));
		cursor.add(new HeaderCard(id + "UNIT", mapUnit, "The Requested Mapping Unit"));
		cursor.add(new HeaderCard(id + "MNPX", minActivePixels, "The Minimim number of active pixels for mapping."));
	    }		

	    public boolean key(String key, String value) {
		
		if(key.equalsIgnoreCase("MIN_RELATIVE_EXPOSURE")) {
		    StringTokenizer tokens = new StringTokenizer(value, "@:,");
		    String firstArg = tokens.nextToken();

		    try { minRelativeExposure = Double.parseDouble(firstArg); }
		    catch(NumberFormatException e) { exposureClipDirective = firstArg; }

		    if(tokens.hasMoreTokens()) minRelativeExposure = Double.parseDouble(tokens.nextToken());
		}
		else if(key.equalsIgnoreCase("MAX_RELATIVE_NOISE")) {
		    StringTokenizer tokens = new StringTokenizer(value, "@:,");
		    String firstArg = tokens.nextToken();

		    try { maxRelativeRMS = Double.parseDouble(firstArg); }
		    catch(NumberFormatException e) { rmsClipDirective = firstArg; }

		    if(tokens.hasMoreTokens()) maxRelativeRMS = Double.parseDouble(tokens.nextToken());
		}
		else if(key.equalsIgnoreCase("MAP_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}		
		else if(key.equalsIgnoreCase("MAP_FAITHFUL_TURN")) {
		    if(value.equalsIgnoreCase("auto")) faithfulTurn = 0;
		    else if(value.equalsIgnoreCase("off")) faithfulTurn = -1;
		    else faithfulTurn = Integer.parseInt(value);
		}
		else if(key.equalsIgnoreCase("MAP_CUTOFF_LEVEL")) {
		    if(value.equalsIgnoreCase("off") || value.equalsIgnoreCase("none")) clippingLevel = -1.0;
		    else clippingLevel = Double.parseDouble(value);
		}
		else if(key.equalsIgnoreCase("MAP_VOLTS_PER_JY")) {
		    defaultVperJy = Double.parseDouble(value);
		}
		else if(key.equalsIgnoreCase("MAP_UNIT")) mapUnit = value;
		else if(key.equalsIgnoreCase("SHOW")) 
		    show = Util.parseBoolean(value);
		else if(key.equalsIgnoreCase("EXTENDED_PRESERVE_TURNS")) {
		    if(value.equalsIgnoreCase("off")) extended = false;
		    else {
			extended = true;
			extendedSourceGenerations = Integer.parseInt(value);
		    }
		}
		else if(key.equalsIgnoreCase("EXTENDED_FILTER_SCALE")) {
		    if(value.equalsIgnoreCase("off") || value.equalsIgnoreCase("false")) extendedFilter = false;
		    else {
			extendedFilter = true;
			if(value != null) extendedScale = Double.parseDouble(value) * Unit.ArcSECOND;
		    }
		}
		else if(key.equalsIgnoreCase("MAP_GRID_RESOLUTION")) {
		    if(value.equalsIgnoreCase("auto")) resolution = SharcHeader.pixelSize / 3.0;
		    else resolution = Double.parseDouble(value) * Unit.ArcSECOND;
		}
		else if(key.equalsIgnoreCase("MAP_INTERMEDIATE_SMOOTHING")) { 
		    if(value.equalsIgnoreCase("off")) intermediateSmoothFWHM = 0.0;
		    else intermediateSmoothFWHM = Double.parseDouble(value) * Unit.ArcSECOND;
		}
		else if(key.equalsIgnoreCase("MAP_SMOOTHING")) {
		    StringTokenizer directives = new StringTokenizer(value,",");
		    if(directives.countTokens() > 1) {
			while(directives.hasMoreTokens()) key("MAP_SMOOTHING", directives.nextToken());
			return true;
		    }
		    

		    StringTokenizer tokens = new StringTokenizer(value, ":");
		    String directive = "always";

		    if(tokens.countTokens() > 1) directive=tokens.nextToken();
		    String spec = tokens.nextToken();
		    double FWHM = spec.equalsIgnoreCase("off") || spec.equalsIgnoreCase("none") ? 
			0.0 : Double.parseDouble(spec) * Unit.ArcSECOND;
		    
		    if(directive.equalsIgnoreCase("internal") || directive.equalsIgnoreCase("always")) intermediateSmoothFWHM = FWHM;
		    if(directive.equalsIgnoreCase("final") || directive.equalsIgnoreCase("always")) smoothFWHM = FWHM;
		}
		else if(key.equalsIgnoreCase("SHARC2_BEAM_FWHM")) {
		    beamFWHM = Double.parseDouble(value) * Unit.ArcSECOND;
		}
		else if(key.equalsIgnoreCase("SOURCE_DIAMETER")) 
		    sourceDiameter = Double.parseDouble(value) * Unit.ArcSECOND;
		else if(key.equalsIgnoreCase("ATMOSPHERIC_CORRECTION")) 
		    atmosphericCorrection = Util.parseBoolean(value);
		else if(key.equalsIgnoreCase("MAP_MIN_PIXELS"))
		    minActivePixels = Integer.parseInt(value);
		else if(key.equalsIgnoreCase("MAP_ROBUST_WEIGHT")) {
		    calcScanWeights = Util.parseBoolean(value);
		} 
		else if(key.equalsIgnoreCase("FLUX_CORRECTING_FWHM")) {
		    if(value.equalsIgnoreCase("point")) fluxCorrectingFWHM = 0.0;
		    else if(value.equalsIgnoreCase("none") || value.equalsIgnoreCase("off")) fluxCorrectingFWHM = Double.NaN;
		    else fluxCorrectingFWHM = Math.max(fluxCorrectingFWHM, beamFWHM);
		}
		else return false;
		
		return true;
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-minexp")) key("MIN_RELATIVE_EXPOSURE", value);
		else if(key.equalsIgnoreCase("-maxRMS")) key("MAX_RELATIVE_NOISE", value);
		else if(key.equalsIgnoreCase("-Imap")) key("MAP_TURN", value);
		else if(key.equalsIgnoreCase("-Ifidel")) key("MAP_FAITHFUL_TURN", value);
		else if(key.equalsIgnoreCase("-Iextended")) key("EXTENDED_PRESERVE_TURNS", value);
		else if(key.equalsIgnoreCase("-cutoff")) key("MAP_CUTOFF_LEVEL", value);
		else if(key.equalsIgnoreCase("-mapWeight")) key("MAP_ROBUST_WEIGHT", value);
		else if(key.equalsIgnoreCase("-mapJy")) key("MAP_VOLTS_PER_JY", value);
		else if(key.equalsIgnoreCase("-unit")) mapUnit = value;
		else if(key.equalsIgnoreCase("-show")) show = true;
		else if(key.equalsIgnoreCase("-noshow")) show = false;
		else if(key.equalsIgnoreCase("-compact")) extended = false;
		else if(key.equalsIgnoreCase("-extended")) extended = true;
		else if(key.equalsIgnoreCase("-extFilter")) key("EXTENDED_FILTER_SCALE", value);
		else if(key.equalsIgnoreCase("-grid")) key("MAP_GRID_RESOLUTION", value);
		else if(key.equalsIgnoreCase("-beam")) key("SHARC2_BEAM_FWHM", value);
		else if(key.equalsIgnoreCase("-smooth")) key("MAP_SMOOTHING", value);
		else if(key.equalsIgnoreCase("-resolution")) {
		    System.err.println();
		    System.err.println(" WARNING! -resolution= flag is replaced by -smooth= flag and it may have been" +
				       " confusing, since the argument defines not the true resulting resolution of the" +
				       " map but rather the feature size that is optimally filtered for maximum" +
				       " signal-to-noise (i.e. a convolution by the argumented beam size). In the future" +
				       " please use -smooth= to that effect.");
		    System.err.println();
		    option("-smooth", value);
		}		    
		else if(key.equalsIgnoreCase("-diameter")) key("SOURCE_DIAMETER", value);
		else if(key.equalsIgnoreCase("-atm")) atmosphericCorrection = true;
		else if(key.equalsIgnoreCase("-noatm")) atmosphericCorrection = false;
		else if(key.equalsIgnoreCase("-minPixels")) key("MAP_MIN_PIXELS", value);
		else if(key.equalsIgnoreCase("-correct")) key("FLUX_CORRECTING_FWHM", value);
		else return false;

		return true;
	    }

	    public void usage() {
		DecimalFormat f2 = new DecimalFormat("0.0#");

		int sourceIndex = pipeline.indexOf(sourceModel);

		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Imap=        Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n"
		    + "  -Ifidel=      Faithful source removal from iteration. (" + 
		    (faithfulTurn == 0 ? "auto" : faithfulTurn < 0 ? "off" : faithfulTurn + "") + ")\n"
		    + "  -Iextended=   Source generations preserving all extended structure. (" + extendedSourceGenerations + ")\n"
		    + "  -cutoff=      Level rel. to peak for clipping when not faithful. (" + 
		    (clippingLevel > -1.0 ? clippingLevel + "" : "off") + ")\n"
		    + "  -unit=        The mapping units. (\"" + mapUnit + "\")\n"
		    + "  -mapJy=       Conversion factor from bolometer V to Jy/pixel. (" + defaultVperJy/Unit.VOLT + ")\n"
 		    + "  -minExp=      Clip positions with smaller relative exposure. (" + 
		    exposureClipDirective + ":" + f2.format(minRelativeExposure) + ")\n"
 		    + "  -maxRMS=      Clip positions with higher relative RMS. (" + 
		    rmsClipDirective + ":" + f2.format(maxRelativeRMS) + ")\n"
		    + "  -show         Display source map while reducing. " + (show ? "(*)" : "") + "\n"
		    + "  -noshow       Do not display source on-the-fly. " + (!show ? "(*)" : "") + "\n"
		    + "  -atm          Apply atmospheric flux correction. " + (atmosphericCorrection ? "(*)" : "") + "\n"
		    + "  -noatm        No atmospheric correction." + (!atmosphericCorrection ? "(*)" : "") + "\n"
		    + "  -compact      Cleaner maps but extended structures may be missing. " + (!extended ? "(*)" : "") + "\n"
		    + "  -extended     Retain extended structures in map. " + (extended ? "(*)" : "") + "\n"
		    + "  -extFilter=   Filter extended structures over scale. (" +
		    (extendedFilter ? f2.format(extendedScale/Unit.ArcSECOND) : "off") + ")\n"
		    + "  -beam=        The SHARC2 beam FWHM in arcsec. (" + f2.format(beamFWHM/Unit.ArcSECOND) + ")\n"
		    + "  -grid=        The map pixelization in arcsec. (" + f2.format(resolution/Unit.ArcSECOND) + ")\n"   
		    + "  -smooth=      Smooth map to beam size in arcsec. (" + 
		    "internal:" + f2.format(intermediateSmoothFWHM/Unit.ArcSECOND) + "," +
		    "final:" + f2.format(smoothFWHM/Unit.ArcSECOND)  + ")\n"
		    + "  -diameter=    The assumed source diameter in arcsec. (" + f2.format(sourceDiameter/Unit.ArcSECOND) + ")\n"
		    + "  -minPixels=   Minimum number of active pixels for mapping. (" + minActivePixels + ")\n"
		    + "  -mapWeight=   Derive robust map weights for each scan. (" + (calcScanWeights ? "true" : "false") + ")\n"
		    + "  -correct=     Flux correcting FWHM or 'point' or 'none', (" +
		    (Double.isNaN(fluxCorrectingFWHM) ? "none" : (fluxCorrectingFWHM <= 0.0 ? "point" : fluxCorrectingFWHM/Unit.ArcSECOND + "")) + ")\n"; 
		
		 
		System.out.println(info);
	    }

	    boolean show = false;
	    boolean atmosphericCorrection = true;
	    boolean extendedFilter = false;
	    boolean calcScanWeights = false;
	    double extendedScale = 35.0 * Unit.ArcSECOND;
	    double fluxCorrectingFWHM = Double.NaN;
	    double clippingLevel = 0.0;
	    ReducedData scanMap;
	    double[][] oldImage;
	};


    public float[][] correlated, correlatedVar; // scan, t;
    public float[][] correlatedParmsForBol;

    // At the input stage
    Model correlatedModel = new Model("CorrelatedNoise", true) {

	    public void init() {
		correlated = new float[scans][];
		correlatedVar = new float[scans][];
		correlatedParmsForBol = new float[scans][];

		for(int i=0; i<scans; i++) {
		    correlated[i] = new float[scan[i].frames];
		    correlatedVar[i] = new float[scan[i].frames];
		    correlatedParmsForBol[i] = new float[scan[i].pixels];
		}
	    }		

	    public void extract() {
		// Measure the correlated noise over zero mean bolometers
		// This way it does not pre-suppose a prior measurement of the
		// offsets.

		isActive = false;
		
	  
		if(verbose) System.out.print("\tEstimating Correlated Noise. ");		

		if(correlated == null) init();
		
		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 Bolometer[] scanPixel = scan[i].pixel;

		    double sumC = 0.0, sumfw = 0.0;

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

		    for(int t=0; t < nt; t++) if(scan[i].activeFrames > 0) {
			final SharcFrame frame = scan[i].frame[t];
			final float[] residual = frame.readout;
			final boolean[] flagged = frame.flagged;
			double sum = 0.0, sumpw = 0.0;

			for(int bol=0; bol < nb; bol++) if(scanPixel[bol].isActive) if(!flagged[bol]) {
			    final Bolometer pixel = scanPixel[bol];

			    final float G = gain(i, t, bol);
			    final float g = smallSignalGain(i, t, bol);
			    final float w = pixel.weight;

			    sum += w * g * residual[bol] ;
			    sumpw += w * G * g;
			}
			
			if(sumpw > 0.0) {
			    float dC = (float) (sum / sumpw);
			    
			    correlated[i][t] += dC;
			    correlatedVar[i][t] = (float) (1.0/sumpw);			    

			    sumC += frame.weight * correlated[i][t];
			    sumfw += frame.weight;

			    for(int bol=0; bol < nb; bol++) {
				final Bolometer pixel = scanPixel[bol];
				final float G = gain(i, t, bol);

				// Remove from data
				residual[bol] -= G * dC;
				
				if(pixel.isActive) if(!flagged[bol])
				    correlatedParmsForBol[i][bol] += G * G * pixel.weight / sumpw;
			    }

			    //frame.offset = correlated[i][t];
			}
			else correlatedVar[i][t] = 0.0F;
		    }

		    if(sumfw > 0.0) {
			float aveC = (float)(sumC / sumfw);
			for(int t=0; t < nt; t++) {
			    correlated[i][t] -= aveC;
			    scan[i].frame[t].offset = correlated[i][t];
			}
		    } 
		}
		
		super.extract();
		isActive = true;

		//Now do transparency adjustment directly in the sourceModel

		if(verbose) System.out.println();
	    }
	    
	    public void inject() {
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final Bolometer[] scanPixel = scan[i].pixel;

		    for(int t=0; t<scan[i].frames; t++) {
			final SharcFrame frame = scan[i].frame[t];
			final float[] residual = frame.readout;
			final float model = correlated[i][t];

			frame.offset -= model; 

			for(int bol=0; bol<nb; bol++) {
			    final Bolometer pixel = scanPixel[bol];
			    residual[bol] += gain(i, t, bol) * model;
			}
		    }
		}
		reset();
	    }

	    public void reset() {
		super.reset();
		if(tauAdjustTurn >= 0) for(int i=0; i<scans; i++) for(int t=0; t < scan[i].frames; t++) {
		    SharcFrame frame = scan[i].frame[t];
		    frame.calcTransmission();
		}
		correlated = null;
		correlatedVar = null;
		correlatedParmsForBol = null;
	    }
	    
	    public SharcFrame getFrame(SharcScan aScan, int t) {
		SharcFrame frame = super.getFrame(aScan, t);
		frame.readout = new float[aScan.pixels];
		Arrays.fill(frame.readout, correlated[getIndexOf(aScan)][t]);
		return frame;
	    }
	    
	    public int getNumberOfParameters() { 
		int n = 0;
		for(int i=0; i<scans; i++) n += scan[i].frames;
		return n; 
	    }

	    public double getRMS() {
		double var = 0.0, sumw = 0.0;
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final float G2 = scan[i].gain * scan[i].gain;
		    for(int t=0; t < nt; t++) {
			final float w = scan[i].frame[t].weight;
			if(correlatedVar[i][t] > 0.0) {
			    final float value = correlated[i][t];
			    var += w * G2 * (value * value - correlatedVar[i][t]);
			    sumw += w;
			}
		    }
		}
		return var > 0.0 ? Math.sqrt(var / sumw) : 0.0;
	    }

	    public void dGain(int i, int t, float[] dmodel) {
		if(!isActive) return;
		SharcFrame frame = scan[i].frame[t];
		for(int bol=0; bol<scan[i].pixels; bol++) 
		    dmodel[bol] += gain(i, t, bol) * correlated[i][t];
	    }

	    public void weightAdjust(double factor) {
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    for(int t=0; t < nt; t++) correlatedVar[i][t] /= factor;
		}
	    }

	    public Object[] getData() { return correlated; }

	    public Samples[] getSpectra(int i, int windowSize) {
		Samples tdata = new Samples(correlated[i]);
		tdata.dx = scan[i].samplingInterval;
		return new Samples[] { tdata.averagePower(windowSize) };
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {		
		String id = "COR_";
		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 + "RMS", getRMS(), "The RMS of the Model Parameters."));
		cursor.add(new HeaderCard(id + "ITAU", tauAdjustTurn, "The fast tau adjustment activation iteration."));
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("CORRELATED_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else if(key.equalsIgnoreCase("TAU_ADJUST_TURN")) {
		    if(value.equalsIgnoreCase("off")) tauAdjustTurn = -1;
		    else if(value.equalsIgnoreCase("auto")) tauAdjustTurn = 0;
		    else tauAdjustTurn = Integer.parseInt(value);
		}
		else return false;

		return true;		
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Icorr")) key("CORRELATED_TURN", value);
		else if(key.equalsIgnoreCase("-ItauAdjust")) key("TAU_ADJUST_TURN", value);
		else return false;

		return true;		
	    }

	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Icorr=       Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n"
		    + "  -ItauAdjust   Adjust transmission based on total power variation. (" + 
		    (tauAdjustTurn > 0 ? tauAdjustTurn + "" : (tauAdjustTurn == 0) ? "auto" : "off") + ")\n";

		System.out.println(info);
	    }
	};



    // Static relative gains per scan.
    // Gains measured from the input stage. but then renormalized.

    double meanGainChange = 0.0;
    
    Model gainModel = new Model("PixelGains") {
	    
	    public void init() {
		dgain = new float[scans][];
		for(int i=0; i < scans; i++) dgain[i] = new float[scan[i].pixels];
	    }

	    public void extract() {
		if(!isActive && turn == 0 && !alwaysSolve) {
		    if(sourceBeforeGain) return;
		    if(approximateSourceMap) if(sourceModel.getTypicalFlux() > 0.01 * correlatedModel.getTypicalFlux()) return;
		}

		if(!correlatedModel.isActive) {
		    if(verbose) System.out.println("\tCannot solve for gains without correlated noise model.");
		    return;
		}
	
		isActive = false;
		

		if(verbose) System.out.print("\tEstimating Pixel Gains. ");

		if(dgain == null) init();

		for(int i=0; i < scans; i++) if(scan[i].activeFrames > 0) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final SharcFrame[] scanFrame = scan[i].frame;
		    final float areaFactor = (float) (scan[i].pixelArea / SharcHeader.pixelArea);		    
		    final float G = scan[i].gain;

		    double validGainSum = 0.0;
		    int validGains = 0;
		    
		    // Get the gain correction factors based on the ratio of the cross-correlation
		    // to the autocorrelation per frame. Gain is calculated for all channels
		    for(int bol=0; bol < nb; bol++) if(!scan[i].pixel[bol].isExcluded()) {
			final Bolometer pixel = scan[i].pixel[bol];
			final double linearGain = scan[i].gain * pixel.gain;

			double RC = 0.0, C2 = 0.0;

			// Correlate the zero levelled bolometer signal with the correlated noise
			for(int t=0; t < nt; t++) {
			    final SharcFrame frame = scanFrame[t];
			    final double C = gain(i,t,bol) * correlated[i][t];

			    if(!frame.flagged[bol]) {
				RC += frame.weight * frame.readout[bol] * C;
				C2 += frame.weight * C * C;
			    }
			}
			
			if(C2 > 0.0) dgain[i][bol] += (float) (RC / C2);
		    }
		}	

		
		// Use the absolute value as 
		meanGainChange = 0.0;
		int N = 0;
		for(int i=0; i<scans; i++) for(int bol=0; bol<scan[i].pixels; bol++) if(scan[i].pixel[bol].isActive) {
		    meanGainChange += Math.abs(dgain[i][bol]);
		    N++;
		}
		meanGainChange /= N;

		super.extract();
		isActive = true;

		applyChanges();

		if(verbose) System.out.println("|dG| = " + new DecimalFormat("0.0E0").format(meanGainChange) + ". ");

	    }

	    public void inject() { dgain = null; }

	    public void reset() {
		dgain = null;
		super.reset(); 
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    final Bolometer[] pixel = scan[i].pixel;
		    for(int bol=0; bol < nb; bol++) {
			scan[i].unflag(bol, Bolometer.INVALID_GAIN);
			scan[i].pixel[bol].gain = pixel[bol].defaultGain;
		    }
		}
	    }
	    
	    public void applyChanges() {
		if(!isActive) return; 
		
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    float[] dmodel = new float[scan[i].pixels];

		    for(int t=0; t<scan[i].frames; t++) {
			Arrays.fill(dmodel, 0.0F);
			for(int m=0; m<pipeline.size(); m++) {
			    Model model = (Model) pipeline.elementAt(m);
			    if(model.isActive) model.dGain(i, t, dmodel);
			}
			for(int bol=0; bol<scan[i].pixels; bol++) 
			    scan[i].frame[t].readout[bol] -= dgain[i][bol] * dmodel[bol];
		    }

		    for(int bol=0; bol<scan[i].pixels; bol++) scan[i].pixel[bol].gain *= (1.0F + dgain[i][bol]);

		    scan[i].flagInvalidGains();
 		    scan[i].normalizeGains();
 
		    Arrays.fill(dgain[i], 0.0F);
		}
	    }


	    public int getNumberOfParameters() { 
		int n = 0;
		for(int i=0; i<scans; i++) n += scan[i].activePixels;
		return n; 
	    }

	    public double getRMS() {
		double rms = 0.0, sumw = 0.0;
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    final Bolometer[] scanPixel = scan[i].pixel;
		    for(int bol=0; bol < nb; bol++) if(scanPixel[bol].isActive) {
			final Bolometer pixel = scanPixel[bol];
			final float w = pixel.weight * pixel.gain;
			final float value = pixel.gain - 1.0F;
			rms += w * value * value;
			sumw += w;
		    }
		}
		return Math.sqrt(rms / sumw);
	    }

	    // Sort does not reorder elements that are equal. Therefore it is safe to keep the typical
	    // flux value for gain estimation equal to that of the correlated model, to make sure
	    // gain follows correlated.
	    public double getTypicalFlux() { return 0.1 * correlatedModel.getTypicalFlux(); }

	    public Object[] getData() { 
		float[][] gain = new float[scans][];
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    gain[i] = new float[scan[i].pixels];
		    for(int bol=0; bol < nb; bol++) gain[i][bol] = scan[i].pixel[bol].gain;
		}
		return gain;
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "GN_";
		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."));
	    }

	    public boolean key(String key, String value) {
		StringTokenizer tokens = new StringTokenizer(value);
		
		if(key.equalsIgnoreCase("GAIN_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else return false;

		return true;
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Igain")) key("GAIN_TURN", value);
		else return false;
		
		return true;
	    }

	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Igain=       Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n";

		System.out.println(info);
	    }

	    float[][] dgain;
	};
   
    Model nonLinearityModel = new Model("PixelNonlinearities") {
	    
	    public void init() {
		//dT0 = new float[scans][]; 
		//for(int i=0; i < scans; i++) dT0[i] = new float[scan[i].pixels];
		
		for(int i=0; i<scans; i++) {
		    scan[i].recalcPixelZeroLoadCoeffs();
		    scan[i].calcdIdT();
		}
	    }
	    
	    public void extract() {
		if(!isActive && turn == 0 && !alwaysSolve) {
		    if(sourceBeforeGain) return;
		    if(approximateSourceMap) if(sourceModel.getTypicalFlux() > 0.01 * correlatedModel.getTypicalFlux()) return;
		}
		
		if(correlated == null) {
		    if(verbose) System.out.println("\tCannot solve for gains without having correlated noise estimate.");
		    return;
		}
	
		isActive = false;
		

		if(verbose) System.out.print("\tEstimating Pixel Non-linearities. ");
		
		//if(dT0 == null) init();

		double sum = 0.0, sumw = 0.0;

		for(int i=0; i < scans; i++) if(scan[i].activeFrames > 0) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final double eps = 1.0 - scan[i].transmission;
		    final SharcFrame[] scanFrame = scan[i].frame;
		    final float areaFactor = (float) (scan[i].pixelArea / SharcHeader.pixelArea);
		    
		    double sumpw = 0.0;
		    for(int bol=0; bol < nb; bol++) sumpw += scan[i].pixel[bol].weight;

		    for(int bol=0; bol < nb; bol++) {
			final Bolometer pixel = scan[i].pixel[bol];
			double RC2 = 0.0, GC4 = 0.0;
			
			for(int t=0; t < nt; t++) {
			    final SharcFrame frame = scanFrame[t];
			    final double w = frame.weight;			    

			    double C = correlated[i][t];
			    if(sourceBeforeGain) if(sourceModel.isActive) {
				final short[] index = scan[i].getMapIndexes(t, bol);
				C += areaFactor * beamEfficiency(i,t,bol) * frame.transmission * reduced.clean[index[0]][index[1]];
			    }

			    final double C2 = C*C;

			    if(!frame.flagged[bol]) {
				RC2 += w * frame.readout[bol] * C2;
				GC4 += w * gain(i,t,bol) * C2 * C2;
			    }
			}
			
			final double dalpha = RC2 / GC4;

			// This part needs some fixing...
			//double T1 = pixel.T0 - scan[i].loadT;
			//pixel.T0 += dalpha * 0.5 * T1 * T1 * scan[i].dIdT;

			SharcFrame[] frame = scan[i].frame;
			for(int t=0; t < nt; t++) 
			    frame[t].readout[bol] -= scan[i].gain * pixel.gain * dalpha * correlated[i][t] * correlated[i][t];
			
			
			if(pixel.isActive) {
			    sum += pixel.weight * Math.abs(dalpha);
			    //sum += pixel.weight * pixel.T0;
			    sumw += pixel.weight;	
			}
		    }

		    scan[i].recalcPixelZeroLoadCoeffs();
		    scan[i].calcdIdT();
		    scan[i].calcOverallGain();
		}

		if(verbose) System.out.print(" |dG| = " + 
					     new DecimalFormat("0.0E0").format(sum/sumw * correlatedModel.getRMS()) + " ");
		
		//if(verbose) System.out.print(" |T0| = " + new DecimalFormat("0.0E0").format(sum/sumw) + " ");

		super.extract();				
		isActive = true;

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

	    public void inject() { }
	    
	    public int getNumberOfParameters() { 
		int n = 0;
		for(int i=0; i<scans; i++) n += scan[i].activePixels;
		return n; 
	    }

	    public double getRMS() {
		double var = 0.0, sumw = 0.0;
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    for(int bol=0; bol < nb; bol++) if(scan[i].pixel[bol].isActive) {
			Bolometer pixel = scan[i].pixel[bol];
			var += pixel.weight * pixel.T0 * pixel.T0;
			sumw += pixel.weight;
		    }
		}
		return sumw > 0.0 ? Math.sqrt(var / sumw) : 0.0;
		
	    }

	    // Sort does not reorder elements that are equal. Therefore it is safe to keep the typical
	    // flux value for non-linearity estimation equal to that of the correlated model, to make sure
	    // non-linearity follows correlated and gain.
	    public double getTypicalFlux() { return correlatedModel.getTypicalFlux(); }

	    public Object[] getData() { 
		float[][] T0 = new float[scans][];
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    T0[i] = new float[scan[i].pixels];
		    for(int bol=0; bol < nb; bol++) T0[i][bol] = scan[i].pixel[bol].T0;
		}
		return T0;
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "NLR_";
		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."));
	    }
	    public boolean key(String key, String value) {
		StringTokenizer tokens = new StringTokenizer(value);
		
		if(key.equalsIgnoreCase("NON_LINEAR_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else return false;

		return true;
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Inonlinear")) key("NON_LINEAR_TURN", value);
		else return false;
		
		return true;
	    }

	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Inonlinear=  Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n";

		System.out.println(info);
	    }

	    //float[][] dT0;
	};




    // Weighting is always performed at the readout stage. The weights then scale to the
    // appropriate stages by the proper gain correction.
    Model timeWeightModel = new Model("TimeWeighting") {

	    public void extract() {
		if(!isActive && turn == 0 && !alwaysSolve) {
		    //if(brightness >= pipeline.size() - pipeline.indexOf(correlatedModel)) return;		   
		    if(getChiSquared() > 2.0) return;
		    if(approximateSourceMap) if(pipeline.indexOf(sourceModel) < pipeline.indexOf(this)) return;
		}

		isActive = false;
		

		if(verbose) System.out.print("\tCalculating Frame Weights. ");

		double mapBeams = 0.0;
		if(sourceModel.isActive) mapBeams = reduced.countBeams();		
		
		for(int i=0; i<scans; i++) {
		    if(scan[i].activePixels > 0) {			
			final int nt = scan[i].frames;
			final int nb = scan[i].pixels;
			final SharcFrame[] scanFrame = scan[i].frame;
			final Bolometer[] pixel = scan[i].pixel;
			final int weightT = getT(scan[i]);
			final int nT = (int) Math.ceil( (float)scan[i].frames / weightT );		    
			
			double sum = 0.0; 
			for(int t=0; t < nt; t++) sum += scanFrame[t].weight;
			float norm = (float) sum;
			
			scan[i].activeFrames = 0;
			
			for(int T=0; T<nT; T++) {
			    double blockChiSquared = 0.0;
			    double blockParms = 0.0;
			    float blockWeight = 0.0F;
			    int points=0, blockFrames = 0;
			    
			    // Now calculate chi-squared for the time block
			    for(int blockt=0,t=T*weightT; blockt<weightT; blockt++,t++) if(t < nt) {
				final SharcFrame frame = scanFrame[t];
				final float[] residual = frame.readout;
				final boolean[] flagged = frame.flagged;
				final float frameDependance = norm > 0.0F ? frame.weight / norm : 0.0F;
				
				if(offsetModel.isActive) blockParms += frameDependance;
				if(correlatedModel.isActive) blockParms += 1.0;
				if(gainModel.isActive) blockParms += scan[i].pixels * frameDependance;
				if(driftModel.isActive) blockParms += driftParmsForFrame[i][t];
				if(skyPolynomialModel.isActive) blockParms += skyParmsForFrame[i][t];
				if(rowPolynomialModel.isActive) blockParms += rowCoeffParmsForFrame[i][t];
				if(sourceModel.isActive) blockParms += mapBeams * frameDependance;
				if(chopCoefficientModel.isActive) blockParms += frameDependance;
				if(accelerationModel.isActive) blockParms += frameDependance;
				if(regionalCorrelationModel.isActive) blockParms += regionParmsForFrame[i][t];
				if(multiplexerModel.isActive) blockParms += muxParmsForFrame[i][t];
				
				for(int bol=0; bol < nb; bol++) if(pixel[bol].isActive) if(!flagged[bol]) {
				    blockChiSquared += pixel[bol].weight * residual[bol] * residual[bol];
				    points++;
				}
				
				blockFrames++;
			    }
			    
			    final double dof = points - blockParms;			
			    
			    if(dof > minDegreesOfFreedom) {
				blockWeight = (float)(dof / blockChiSquared);
				
				// check if the block weight is valid			    
				if(!Float.isNaN(blockWeight)) scan[i].activeFrames += blockFrames;
				else blockWeight = 0.0F;
			    }
			    
			    for(int blockt=0,t=T*weightT; blockt<weightT; blockt++,t++) if(t < nt) { 
				scanFrame[t].weight = blockWeight;
			    }						
			}
			
			// Normalize to mean 1.0;
			double ave = 0.0;
			for(int t=0; t < nt; t++) ave += scanFrame[t].weight;
			ave /= nt;
			for(int t=0; t < nt; t++) scanFrame[t].weight /= ave;		    
			
		    }
		    
		    if(verbose) {
			System.out.print(scan[i].activeFrames + " ");
			System.out.flush();
		    }
		}
		

		super.extract();
		isActive = true;

		if(verbose) System.out.println("Active Frames. "); 
	    }
	    
	    public void inject() {}
	    
	    public void reset() { 
		super.reset();
		for(int i=0; i<scans; i++) for(int t=0; t<scan[i].frames; t++) scan[i].frame[t].weight = 1.0F;
	    }

	    public int getNumberOfParameters() { return 0; }

	    public double getRMS() { 
		double rms = 0.0;
		int n = 0;
		
		for(int i=0; i<scans; i++) {
		    final int weightT = getT(scan[i]);
		    final int nT = (int) Math.ceil( (float) scan[i].frames/weightT );
		    for(int T=0; T<nT; T++) {
			final float value = scan[i].frame[T*weightT].weight - 1.0F;
			rms += value * value;
			n++;
		    }
		}
		return Math.sqrt(rms / n);
	    }
	    
	    // To keep the relative order of pixel and time weights, set their fluxes equal.
	    public double getTypicalFlux() { return 0.1 * pixelWeightModel.getRMS(); }

	    public Object[] getData() {
		float[][] weight = new float[scans][];
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final SharcFrame[] frame = scan[i].frame;
		    weight[i] = new float[nt];
		    for(int t=0; t < nt; t++) weight[i][t] = frame[t].weight;
		}
		return weight;
	    }

	    public Samples[] getSpectra(int i, int windowSize) {
		Samples tdata = new Samples();
		tdata.data = new double[scan[i].frames];
		tdata.dx = scan[i].samplingInterval;
		for(int t=0; t<scan[i].frames; t++) tdata.data[t] = scan[i].frame[t].weight;
		return new Samples[] { tdata.averagePower(windowSize) };
	    }
	    
	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "TW_";
		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 + "RMS", getRMS(), "The RMS of the Model Parameters."));
		cursor.add(new HeaderCard(id + "T", timeConstant, "The Model Time Constant (frames).")); 
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("TIME_WEIGHT_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else if(key.equalsIgnoreCase("TIME_WEIGHT_T")) {
		    if(value.equals("*") || value.equalsIgnoreCase("auto")) timeConstant = -1;
		    else timeConstant = Integer.parseInt(value);
		}
		else return false;

		return true;		
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Itweight")) key("TIME_WEIGHT_TURN", value);
		else if(key.equalsIgnoreCase("-weightT")) key("TIME_WEIGHT_T", value);
		else return false;

		return true;		
	    }

	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Itweight=    Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n"
		    + "  -weightT=     Number of frames per weight value. (" + timeConstant + ")\n";

		System.out.println(info);
	    }
	};






    // The offset is at the readout stage.
    float[][] offset, offsetVar; // scan, bol

    // One must be really careful here. The average bolometer offset at over any time period
    // is a bad statistical indicator. Consider badly levelled bolometers, or faulty amplifier
    // modules. It is therefore best not to set the average offset to zero.
    Model offsetModel = new Model("PixelOffsets", true) {

	    public void init() {
		offset = new float[scans][];
		offsetVar = new float[scans][];

		for(int i=0; i<scans; i++) {
		    offset[i] = new float[scan[i].pixels];
		    offsetVar[i] = new float[scan[i].pixels];
		}
	    }

	    public void extract() {
		// if 'auto' turn if all scans are high-pass filtered than don't solve at all.
		if(!isActive && turn == 0 && !alwaysSolve) if(allHighPassed()) return;

		if(verbose) System.out.print("\tEstimating Overall Pixel Offsets. ");
		
		isActive = false;
		

		if(offset == null) init();

		for(int i=0; i<scans; i++) if(scan[i].activeFrames > 0) {

		    boolean solveNow = alwaysSolve || turn > 0 || scan[i].highPass <= 0.0;
		    
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final SharcFrame[] scanFrame = scan[i].frame;
		    final float areaFactor = (float) (scan[i].pixelArea / SharcHeader.pixelArea);

		    if(solveNow) for(int bol=0; bol < nb; bol++) if(!scan[i].pixel[bol].isExcluded()) {
			final Bolometer pixel = scan[i].pixel[bol];
			double sum = 0.0, sumfw = 0.0;
			
			for(int t=0; t < nt; t++) {
			    final SharcFrame frame = scanFrame[t];
			    
			    if(!frame.flagged[bol]) {
				sum += frame.weight * frame.readout[bol];
				sumfw += frame.weight;
			    }
			}
			
			if(sumfw > 0.0) {
			    final float doffset = (float) (sum / sumfw);

			    if(debug) if(Float.isNaN(doffset)) 
				System.out.println(" doffset is NaN at (" + scan[i].serialNo + "," + bol +")");
	    
			    pixel.offset -= doffset;
			    
			    offset[i][bol] += doffset;			
			    offsetVar[i][bol] = pixel.weight > 0.0 ? (float) (1.0 / (pixel.weight * sumfw)) : 0.0F;
			    
			    for(int t=0; t < nt; t++) scanFrame[t].readout[bol] -= doffset;
			}
			else offsetVar[i][bol] = 0.0F;
		    }
		}
		
		super.extract();
		isActive = true;
		
		applyChanges();

		if(verbose) System.out.println();
	    }			
	    
	    public void inject() {
		for(int i=0; i<scans; i++) for(int bol=0; bol < scan[i].pixels; bol++) {
		    final float model = offset[i][bol]; 
		    scan[i].pixel[bol].offset -= model;
		    for(int t=0; t < scan[i].frames; t++) scan[i].frame[t].readout[bol] += model;
		}
		reset();
	    }

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

	    public void applyChanges() {
		if(isActive) for(int i=0; i<scans; i++) if(scan[i].directGains || scan[i].directTau) {
		    final int nt = scan[i].frames; 
		    
		    float scanGainCorrection = scan[i].gain;
		    scan[i].calcOverallGain();
		    scanGainCorrection /= scan[i].gain;
		    
		    for(int t=0; t < nt; t++) correlated[i][t] *= scanGainCorrection;
		    
		    if(scan[i].directTau) { if(iter > tauAdjustTurn) scan[i].adjustTransparencies(); }
		    else scan[i].calcExcessLoad();
		}
	    }
	    
	    public SharcFrame getFrame(SharcScan aScan, int t) {
		SharcFrame frame = super.getFrame(aScan, t);
		frame.readout = new float[aScan.pixels];
		final int i = getIndexOf(aScan);

		for(int bol=0; bol < aScan.pixels; bol++) frame.readout[bol] = offset[i][bol];
	  
		return frame;
	    }

	    public int getNumberOfParameters() { 
		int n = 0;
		for(int i=0; i<scans; i++) n += scan[i].activePixels;
		return n; 
	    }

	    public double getRMS() {
		double var=0.0, sumw = 0.0; 
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    final Bolometer[] scanPixel = scan[i].pixel;

		    for(int bol=0; bol < nb; bol++) if(scanPixel[bol].isActive) {
			final float w = scanPixel[bol].weight;
			if(offsetVar[i][bol] > 0.0) {
			    final float value = offset[i][bol];
			    var += w * (value * value - offsetVar[i][bol]);
			    sumw += w;
			}
		    }
		}
		return var > 0.0 ? Math.sqrt(var / sumw) : 0.0;
	    }
	    
	    public void weightAdjust(double factor) {
		for(int i=0; i<scans; i++) for(int bol=0; bol<scan[i].pixels; bol++) offsetVar[i][bol] /= factor;
	    }
	    
	    public Object[] getData() { return offset; }
	    
	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "OFF_";
		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 + "RMS", getRMS(), "The RMS of the Model Parameters."));
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("OFFSET_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else return false;

		return true;		
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Ioffset")) key("OFFSET_TURN", value);
		else return false;

		return true;		
	    }

	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Ioffset=     Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n";

		System.out.println(info);
	    }
	
	};
    

    float[][][] drift, driftVar; // scan, T, bol;
    float[][] driftParmsForFrame; // scan, t

    // Drift is at the detector voltage stage
    Model driftModel = new Model("PixelDrifts", true) {

	    public void init() {
		drift = new float[scans][][];
		driftVar = new float[scans][][];
		driftParmsForFrame = new float[scans][];
		
		for(int i=0; i<scans; i++) {
		    final int driftT = getT(scan[i]);
		    final int nT = (int) Math.ceil( (float)scan[i].frames / driftT );
		    
		    drift[i] = new float[nT][scan[i].pixels];
		    driftVar[i] = new float[nT][scan[i].pixels];
		    driftParmsForFrame[i] = new float[scan[i].frames];
		}
	    }
		

	    public void extract() {
		if(!isActive && turn == 0 && !alwaysSolve) {
		    if(allHighPassed()) { 
			if(timeConstant > maxHighPassFrames()) return;
		    }
		    //if(brightness >= pipeline.size() - pipeline.indexOf(correlatedModel)) return;
		    if(approximateSourceMap) if(pipeline.indexOf(sourceModel) < pipeline.indexOf(this)) return;
		}
		
		isActive = false;
		

		if(verbose) System.out.print("\tEstimating Bolometer Drifts. ");
		
		if(drift == null) init();
		
		for(int i=0; i<scans; i++) if(scan[i].activeFrames > 0) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final int driftT = getT(scan[i]);
		    final int nT = (int) Math.ceil( (float)scan[i].frames / driftT );
		    
		    Arrays.fill(driftParmsForFrame[i], 0.0F);
		    
		    boolean solveNow = alwaysSolve || turn > 0 || scan[i].highPass <= 0.0 || timeConstant < scan[i].highPassFrames;		    
		    if(solveNow) {
			for(int bol=0; bol < nb; bol++) if(!scan[i].pixel[bol].isExcluded()) {
			    final Bolometer pixel = scan[i].pixel[bol];
			    final float G = pixel.gain;
			    final float G2 = G * G;
			    
			    for(int T=0; T < nT; T++) {		    
				double level = 0.0, sumfw = 0.0;			    
				
				for(int blockt=0,t=T*driftT; blockt < driftT; blockt++,t++) if(t < nt) {
				    final SharcFrame frame = scan[i].frame[t];
				    if(!frame.flagged[bol]) {
					level += G * frame.weight * frame.readout[bol];
					sumfw += G2 * frame.weight;
				    }			
				}
				
				if(sumfw > 0.0) {
				    level /= sumfw;
				    
				    for(int blockt=0,t=T*driftT; blockt < driftT; blockt++,t++) if(t < nt) {
					final SharcFrame frame = scan[i].frame[t];
					if(!frame.flagged[bol])
					    driftParmsForFrame[i][t] += G2 * frame.weight / sumfw;
					
					frame.readout[bol] -= G * level;				
				    }
				    
				    final float var =  (float) (1.0 / (sumfw * pixel.weight));
				    
				    drift[i][T][bol] += level;	
				    driftVar[i][T][bol] = var;
				}
				else driftVar[i][T][bol] = 0.0F;
			    }			
			}		    
		    }

		    // Remove the part that is degenerate w. correlated noise
		    // and add it to the correlated noise model.
		    for(int T=0; T < nT; T++) {
			double sum=0.0, sumw=0.0;
			for(int bol=0; bol < nb; bol++) if(!scan[i].pixel[bol].isExcluded()) if(driftVar[i][T][bol] > 0.0) {
			    final Bolometer pixel = scan[i].pixel[bol];
			    sum += drift[i][T][bol] / driftVar[i][T][bol];
			    sumw += 1.0 / driftVar[i][T][bol];
			}
			if(sumw > 0.0) {
			    final float aveDrift = (float) (sum / sumw);
			    for(int bol=0; bol < nb; bol++) if(!scan[i].pixel[bol].isExcluded()) drift[i][T][bol] -= aveDrift;
			    for(int blockt=0,t=T*driftT; blockt < driftT; blockt++,t++) if(t < nt)
				correlated[i][t] += aveDrift / scan[i].gain;
			}
		    }
		}
		
		super.extract();
		isActive = true;
		
		if(verbose) System.out.println();
	    }
	    
	    public void inject() {
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final int driftT = getT(scan[i]);
		    final int nT = (int) Math.ceil( (float)scan[i].frames / driftT );
		    final SharcFrame[] frame = scan[i].frame;
		    final Bolometer[] pixel = scan[i].pixel; 

		    for(int T=0; T<nT; T++)  for(int bol=0; bol< nb; bol++) {
			final float model = drift[i][T][bol];
			final float G = pixel[bol].gain;
			for(int blockt=0,t=T*driftT; blockt < driftT; blockt++,t++) if(t < nt) 
			    frame[t].readout[bol] += G * model;
		    }
		}
		reset();
	    }

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

	    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);
		
		for(int bol=0; bol < aScan.pixels; bol++) frame.readout[bol] = drift[i][T][bol];
	  
		return frame;
	    }

	    public int getNumberOfParameters() { 
		int n = 0;
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    final int nT = (int) Math.ceil((float) scan[i].frames / getT(scan[i]));
		    n += scan[i].activePixels * nT;
		}
		return n; 
	    }

	    public double getRMS() {
		double var = 0.0, sumw = 0.0;

		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    final int driftT = getT(scan[i]);
		    final int nT = (int) Math.ceil( (float)scan[i].frames / driftT );
		    for(int bol=0; bol < nb; bol++) if(scan[i].pixel[bol].isActive) {
			final Bolometer pixel = scan[i].pixel[bol];
			final float G2 = pixel.gain * pixel.gain;
			final float w = scan[i].pixel[bol].weight;
			
			for(int T=0; T<nT; T++) if(driftVar[i][T][bol] > 0.0) {		    
			    final float value = drift[i][T][bol];
			    var += w * G2 * (value * value - driftVar[i][T][bol]);
			    sumw += w;
			}
		       
		    }
		}
		
		return var > 0.0 ? Math.sqrt(var / sumw) : 0.0; 
	    } 

	    protected double getRMS(int i, int bol) {
		double var = 0.0, sumw = 0.0;

		final int driftT = getT(scan[i]);
		final int nT = (int) Math.ceil( (float)scan[i].frames / driftT );
		
		final Bolometer pixel = scan[i].pixel[bol];
		final float G2 = pixel.gain * pixel.gain;
		final float w = scan[i].pixel[bol].weight;
		for(int T=0; T<nT; T++) if(driftVar[i][T][bol] > 0.0) {		    
		    final float value = drift[i][T][bol];
		    var += w * G2 * (value * value - driftVar[i][T][bol]);
		    sumw += w;
		}		

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


	    public void dGain(int i, int t, float[] dmodel) {
		if(!isActive) return;
		float[] model = drift[i][t / getT(scan[i])];
		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 nb = scan[i].pixels;
		    final int driftT = getT(scan[i]);
		    final int nT = (int) Math.ceil( (float)scan[i].frames / driftT );
		    for(int T=0; T<nT; T++) for(int bol=0; bol < nb; bol++)
			driftVar[i][T][bol] /= factor;		    
		}
	    }
			    
	    public Object[] getData() { return drift; }

	    public Samples[] getSpectra(int i, int windowSize) { 
		Samples[] spectrum = new Samples[scan[i].pixels];
		for(int bol=0; bol<scan[i].pixels; bol++) {
		    Samples tdata = new Samples();
		    tdata.dx = getT(scan[i]) * scan[i].samplingInterval;
		    tdata.data = new double[drift[i].length];
		    for(int T=0; T<drift[i].length; T++) 
			//tdata.data[T] = driftVar[i][T][bol] > 0.0 ? drift[i][T][bol] / driftVar[i][T][bol] : 0.0;
			tdata.data[T] = drift[i][T][bol];
		    spectrum[bol] = tdata.averagePower(windowSize);
		}
		return spectrum;
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "BOL_";
		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 + "RMS", getRMS(), "The RMS of the Model Parameters."));
		cursor.add(new HeaderCard(id + "T", autoTimeScale ? "auto" : timeConstant + "", "The Model Time Constant (frames)."));
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("PIXEL_DRIFT_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else if(key.equalsIgnoreCase("BOLOMETER_T") || key.equalsIgnoreCase("PIXEL_DRIFT_T")) {
		    autoTimeScale = value.equals("*") || value.equalsIgnoreCase("auto");
		    if(!autoTimeScale) timeConstant = Integer.parseInt(value);
		}
		else return false;
		
		return true;		
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Idrift")) key("PIXEL_DRIFT_TURN", value);
		else if(key.equalsIgnoreCase("-bolT") || key.equalsIgnoreCase("-driftT")) key("PIXEL_DRIFT_T", value);
		else return false;
		
		return true;
	    }

	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Idrift=      Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n"
		    + "  -driftT=      Number of frames per drift offset value. (" + 
		    (autoTimeScale ? "auto" : timeConstant + "") + ")\n";

		System.out.println(info);
	    }
	    
	};
	    
	    
    // Weights are measured at the readout stage.
    Model pixelWeightModel = new Model("PixelWeighting") {

	    public void extract() {
		if(!isActive && turn == 0 && !alwaysSolve) {
		    //if(brightness >= pipeline.size() - pipeline.indexOf(correlatedModel)) return;
		    if(approximateSourceMap) if(pipeline.indexOf(sourceModel) < pipeline.indexOf(this)) return;
		}

		isActive = false;
		

		if(verbose) System.out.print("\tCalculating Pixel Weights. ");
		
		double mapBeams = 0.0;
		if(sourceModel.isActive) mapBeams = reduced.countBeams();
				
		for(int i=0; i<scans; i++) {
		    if(scan[i].activeFrames > 0) {
			final int nb = scan[i].pixels;
			final int nt = scan[i].frames;
			final Bolometer[] scanPixel = scan[i].pixel;
			final SharcFrame[] scanFrame = scan[i].frame;
			double sum = 0.0;
			
			// Get the normalization for the pixel weights
			for(int bol=0; bol<scan[i].pixels; bol++) if(scan[i].pixel[bol].isActive) sum += scan[i].pixel[bol].weight;
			final float norm = (float) sum;
			
			final int nPadded = ArrayUtil.getPaddedFFTSize(scan[i].frames);
			final double[] tdata = new double[nPadded]; 
			final int nfft = nPadded / 2;
			final Complex[] spectrum = new Complex[nfft+1];
			for(int f=0; f < spectrum.length; f++) spectrum[f] = new Complex();
			
			int fmin = (int) Math.floor(rangeFrom * nfft);
			int fmax = (int) Math.ceil(rangeTo * nfft);		    		    
			
			// Get RMS for all non dead bolometers 
			for(int bol=0; bol < nb; bol++) if(!scanPixel[bol].isExcluded()) {
			    final Bolometer pixel = scanPixel[bol];
			    double parms = 0.0;
			    
			    if(offsetModel.isActive) parms += 1.0;
			    if(gainModel.isActive) parms += 1.0;
			    if(correlatedModel.isActive) parms += correlatedParmsForBol[i][bol];
			    if(skyPolynomialModel.isActive) parms += skyParmsForBol[i][bol];
			    if(rowPolynomialModel.isActive) parms += rowCoeffParmsForBol[i][bol];
			    if(sourceModel.isActive) if(norm > 0.0) parms += mapBeams * scan[i].pixel[bol].weight / norm;  
			    if(driftModel.isActive) parms += drift[i].length;
			    if(chopCoefficientModel.isActive) parms += 1.0;
			    if(accelerationModel.isActive) parms += 1.0;
			    if(regionalCorrelationModel.isActive) parms += regionParmsForBol[i][bol];
			    if(multiplexerModel.isActive) parms += muxParmsForBol[i][bol];
			    
			    int points = 0;
			    
			    for(int t=0; t < nt; t++) {
				SharcFrame frame = scanFrame[t];
				if(!frame.flagged[bol]) {
				    tdata[t] = frame.weight * frame.readout[bol];
				    points++;
				}
				else tdata[t] = 0.0;
			    }
			    
			    // Pad the end with zeros to clear leftover spectral data
			    // (since arrays are recycled).
			    Arrays.fill(tdata, nt, tdata.length, 0.0);
			    
			    ArrayUtil.unsecureFFT(tdata, spectrum);
			    
			    double var = 0.0;
			    
			    for(int f=fmin; f<fmax; f++) var += spectrum[f].norm();
			    
			    // Just ignore the 13Hz filter altogether.
			    // This is rather crude, but oh well...
			    var /= fmax - fmin;
			    var *= nfft / 2.0;
			    
			    double DOF = points - parms - 2.0;
			    
			    // Correct for the missing real data in the padding
			    // as well as for the remaining degrees of freedom
			    if(DOF > minDegreesOfFreedom) {
				var *= (double)tdata.length / DOF;
				
				pixel.weight = (float) (1.0/var);
				pixel.rms = (float) Math.sqrt(var);
				
				scan[i].unflag(bol, Bolometer.INSUFFICIENT_DOF);
			    }
			    else {
				pixel.weight = 0.0F;
				pixel.rms = Float.POSITIVE_INFINITY;
				scan[i].flag(bol, Bolometer.INSUFFICIENT_DOF);
			    }			
			}
			
			// Do flagging by noise properties, if either
			// 1. pixel noise is in the highest X fraction of pixel noises
			// 2. pixel noise level is higher than f times the median noise
			boolean noiseFlagging = (!approximateSourceMap || pipeline.indexOf(sourceModel) >= pipeline.indexOf(this))
			    && (!extended || sourceModel.generation >= extendedSourceGenerations);
			
			if(noiseFlagging) if(flagHighestNoiseFraction > 0.0 || flagOverMedian > 0.0) {
			    int nIncluded = 0;
			    for(int bol=0; bol<nb; bol++) if(!scan[i].pixel[bol].isExcluded()) nIncluded++;
			    
			    float[] pweight = new float[nIncluded];
			    for(int bol=0, k=0; bol<nb; bol++) if(!scan[i].pixel[bol].isExcluded()) pweight[k++] = scan[i].pixel[bol].weight;
			    Arrays.sort(pweight);
			    
			    float medianWeight = nIncluded % 2 == 0 ? 
				(pweight[nIncluded/2-1] + pweight[nIncluded/2]) / 2.0F :
				pweight[(nIncluded-1)/2];
			    
			    float weightCutoff = 0.0F;
			    
			    if(flagHighestNoiseFraction > 0.0 && flagHighestNoiseFraction < 1.0)
				weightCutoff = pweight[(int)Math.floor(flagHighestNoiseFraction * nIncluded)];
			    if(flagOverMedian > 0.0)
				weightCutoff = (float)Math.max(weightCutoff, medianWeight / (flagOverMedian*flagOverMedian));
			    
			    for(int bol=0; bol<scan[i].pixels; bol++) {
				if(scan[i].pixel[bol].weight <= weightCutoff) scan[i].flag(bol, Bolometer.NOISY);
				else scan[i].unflag(bol, Bolometer.NOISY);
			    }
			}
		    
			// Get the normalization for the pixel weights
			double newNorm = 0.0;
			for(int bol=0; bol < nb; bol++) if(scanPixel[bol].isActive) 
			    newNorm += scanPixel[bol].weight;
			
			if(norm > 0.0 && newNorm > 0.0) for(int m=0; m<pipeline.size(); m++) {
			    Model model = (Model) pipeline.elementAt(m);
			    if(model.isActive) model.weightAdjust(newNorm / norm);
			}		   
		    }

		    if(verbose) System.out.print(scan[i].activePixels + " ");

		}
		if(verbose) System.out.print("Active Bolometers. ");
		
		DecimalFormat df = new DecimalFormat("0.000");

		if(verbose) {
		    if(chi2report && !chi2all) {
			System.out.print("Chi2 = " + df.format(getChiSquared()) + " ");
		    }
		    else {
			// Get the mean deviation in spectral density units.
			double nefd = getRMS() / ( 1.0e-9 * Math.sqrt(Unit.SECOND) );
			System.out.print("RMS = " + df.format(nefd) + " nV s^1/2 ");
		    }
		}
		
		super.extract();
		isActive = true;
		
		applyChanges();
		
		if(verbose) System.out.println();
	    }
	    
	    public void inject() {}
	    
	    public void reset() {
		super.reset();
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    final Bolometer[] pixel = scan[i].pixel;
		    for(int bol=0; bol < nb; bol++) {
			scan[i].unflag(bol, Bolometer.INSUFFICIENT_DOF);
			scan[i].unflag(bol, Bolometer.NOISY);
			pixel[bol].weight = (float) (pixel[bol].defaultWeight * scan[i].samplingInterval);
		    }
		}
	    }		    

	    public void applyChanges() {
		if(isActive) for(int i=0; i<scans; i++) {
		    if(scan[i].directGains || scan[i].directTau) {
			final int nt = scan[i].frames; 
		    
			float scanGainCorrection = scan[i].gain;
			scan[i].calcOverallGain();
			scanGainCorrection /= scan[i].gain;
			
			for(int t=0; t < nt; t++) correlated[i][t] *= scanGainCorrection;
			
			if(scan[i].directTau) { if(iter > tauAdjustTurn) scan[i].adjustTransparencies(); }
			else scan[i].calcExcessLoad();		    			
		    }

		    scan[i].calcI0();
		    scan[i].calcdIdT();
		}
	    }

	    public int getNumberOfParameters() { return 0; }

	    // RMS reported scaled to 36ms samples
	    public double getRMS() {
		double sumw = 0.0;
		int n=0;
		
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    final Bolometer[] pixel = scan[i].pixel;
		    for(int bol=0; bol < nb; bol++) if(pixel[bol].isActive) {
			sumw += pixel[bol].weight / scan[i].samplingInterval;
			n++;
		    }
		}

		return Math.sqrt(n/sumw);
	    }

	    public double getTypicalFlux() { return 1.0 * getRMS(); }

	    public Object[] getData() {
		float[][] weight = new float[scans][];
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    final Bolometer[] pixel = scan[i].pixel;
		    weight[i] = new float[scan[i].pixels];
		    for(int bol=0; bol < nb; bol++) weight[i][bol] = pixel[bol].weight;
		}
		return weight;
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "PW_";
		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 + "DOF", minDegreesOfFreedom, "The Minimum Required Degrees of Freedom."));
		cursor.add(new HeaderCard(id + "MINT", minTimeConstant, "The minimum window (frames) for weighting."));
		cursor.add(new HeaderCard(id + "MAXT", minTimeConstant, "The maximum window (frames) for weighting."));
		cursor.add(new HeaderCard(id + "XFRC", flagHighestNoiseFraction, "The fraction of highest noise pixels to flag."));
		cursor.add(new HeaderCard(id + "MXNZ", flagOverMedian, "The Maximum tolerated noise level over the median."));
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("PIXEL_WEIGHT_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else if(key.equalsIgnoreCase("PIXEL_WEIGHT_RANGE")) {
		    StringTokenizer tokens = new StringTokenizer(value, "-:");
		    rangeFrom = Double.parseDouble(tokens.nextToken());
		    rangeTo = tokens.hasMoreTokens() ? Double.parseDouble(tokens.nextToken()) : 1.0;
		    
		    if(rangeFrom < 0.0 || rangeFrom > 1.0) rangeFrom = 0.0;
		    if(rangeTo < 0.0 || rangeTo > 1.0) rangeTo = 1.0;
		    if(rangeFrom > rangeTo) { double temp = rangeFrom; rangeFrom=rangeTo; rangeTo=temp; }

		}
		else if(key.equalsIgnoreCase("MIN_DEGREES_OF_FREEDOM"))
		    minDegreesOfFreedom = Double.parseDouble(value);
		else if(key.equalsIgnoreCase("FLAG_NOISY_PIXEL_FRACTION")) {
		    if(value.equalsIgnoreCase("off")) flagHighestNoiseFraction = -1.0;
		    else flagHighestNoiseFraction = Double.parseDouble(value);
		}
		else if(key.equalsIgnoreCase("MAX_RELATIVE_PIXEL_NOISE")) {
		    if(value.equalsIgnoreCase("off")) flagOverMedian = -1.0;
		    else flagOverMedian = Double.parseDouble(value);
		}
		else return false;

		return true;
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Ipweight")) key("PIXEL_WEIGHT_TURN", value);
		else if(key.equalsIgnoreCase("-pwRange")) key("PIXEL_WEIGHT_RANGE", value);
		else if(key.equalsIgnoreCase("-mindof")) key("MIN_DEGREES_OF_FREEDOM", value);
		else if(key.equalsIgnoreCase("-flagFraction")) key("FLAG_NOISY_PIXEL_FRACTION", value);
		else if(key.equalsIgnoreCase("-maxNoise")) key("MAX_RELATIVE_PIXEL_NOISE", value);
		else return false;

		return true;
	    }

	    public void usage() {
		DecimalFormat df = new DecimalFormat("0.000");

		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Ipweight=     Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n"
		    + "  -pwRange=      Relative spectral interval for pixel weighting. (" + rangeFrom + "-" + rangeTo + ")\n" 
		    + "  -mindof=       Minimum degrees of freedom required for weighting. (" + minDegreesOfFreedom + ")\n"
		    + "  -flagFraction= Flag noisies fraction of pixels. (" + 
		    (flagHighestNoiseFraction < 0.0 ? "off" : df.format(flagHighestNoiseFraction) + "") + ")\n"
		    + "  -maxNoise=     Maximum tolerated noise over median pixel noise. (" + 
		    (flagOverMedian < 0.0 ? "off" : df.format(flagOverMedian) + "") + ")\n";

		System.out.println(info);
	    }
	    
	    double rangeFrom = 0.8, rangeTo = 1.0;
	    double flagHighestNoiseFraction = 0.0;
	    double flagOverMedian = 1.0;
	    //Numerical numerical;
	};
    
    
    int[] spikes;

    // De-spike at the readout stage
    Model spikeModel = new Model("ResidualSpikeRemoval") {

	    public void init() {
		spikes = new int[scans];
	    }

	    public void extract() {
		if(!isActive && turn == 0 && !alwaysSolve) {
		    if(!pixelWeightModel.isActive) return;
		    if(extended) if(sourceModel.generation < extendedSourceGenerations) return;
		    if(approximateSourceMap) if(pipeline.indexOf(sourceModel) < pipeline.indexOf(this)) return;
		    if(getChiSquared() > 2.0) return;
		}

		isActive = false;
		

		if(verbose) System.out.print("\tDespiking. ");
		
		if(spikes == null) init();
		
		int spikeCount = 0;
		
		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 SharcFrame[] scanFrame = scan[i].frame;
			
			scan[i].flags = 0;
			spikes[i] = 0;
			// Get spikes only over bolometers that have a calculated RMS and an OK gain
			final int flagPattern = scan[i].excludeFlags | Bolometer.INSUFFICIENT_DOF | Bolometer.INVALID_GAIN; 
			
			for(int bol=0; bol < nb; bol++) {
			    final Bolometer pixel = scan[i].pixel[bol];
			    if((pixel.flag & flagPattern) == 0) {
				int bolspikes = 0;
				
				for(int t=0; t < nt; t++) {
				    final SharcFrame frame = scanFrame[t];
				    if(!apertureOnlySource) frame.flagged[bol] = frame.masked[bol];
				    
				    if(!frame.masked[bol]) 
					if( Math.abs(frame.readout[bol]) > spikeLevel * pixel.rms ) {
					    frame.flagged[bol] = true; 
					    bolspikes++;
					    scan[i].flags++;
					}
				}
				
				// If not flagged for spikes, then flag, and mark as bad if not so already
				if((float)bolspikes / scan[i].frames > rejectSpikeRatio)
				    scan[i].flag(bol, Bolometer.TOO_MANY_SPIKES);
				else {
				    scan[i].unflag(bol, Bolometer.TOO_MANY_SPIKES);
				    spikes[i] += bolspikes;
				}
				
			    }
			}
			spikeCount += spikes[i];
		    }
		    if(verbose) System.out.print(scan[i].activePixels + " ");
		}
		if(verbose) System.out.print("Active Bolometers. ");
		
		if(verbose) System.out.println(spikeCount + " spikes above " + spikeLevel + " sigma.");
		
		super.extract();
		isActive = true;
	    }

	    public void inject() {}

	    public void reset() {
		super.reset();
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final SharcFrame[] scanFrame = scan[i].frame;

		    for(int bol=0; bol < nb; bol++) scan[i].unflag(bol, Bolometer.TOO_MANY_SPIKES);
			for(int t=0; t < nt; t++) scanFrame[t].flagged = (boolean[]) scanFrame[t].masked.clone();
		}
	    }
	    
	    public int getNumberOfParameters() { return 0; }

	    public double getRMS() {
		double rms = 0.0;
		int n = 0;
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    for(int t=0; t < nt; t++) {
			final SharcFrame frame = scan[i].frame[t];
			for(int bol=0; bol < nb; bol++) { 
			    if(frame.flagged[bol] && !frame.masked[bol]) {
				final float value = frame.readout[bol];
				rms += value * value;
				n++;
			    }
			}
		    }
		}
		return Math.sqrt(rms / n);
	    }

	    // To keep the relative order of de-spiking and pixel weighting, set their fluxes to equal values.
	    public double getTypicalFlux() { return pixelWeightModel.getTypicalFlux(); }

	    public Object[] getData() {
		int[][][] spikeList = new int[scans][][];
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final Bolometer[] scanPixel = scan[i].pixel;
		    if(spikes[i] > 0) {
			spikeList[i] = new int[spikes[i]][2];
			for(int t=0,k=0; t < nt; t++) {
			    SharcFrame frame = scan[i].frame[t];
			    for(int bol=0; bol < nb; bol++) {
				if(scanPixel[bol].isActive) if(frame.flagged[bol] && !frame.masked[bol]) {
				    spikeList[i][k][0] = t;
				    spikeList[i][k++][1] = bol;
				}
			    }
			}
		    }
		    else spikeList[i] = new int[][] {{ -1 , -1 }};
		}
		return spikeList;
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "SPK_";
		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 + "LEVL", spikeLevel, "The level (in sigmas) above which to remove residual spikes."));
		cursor.add(new HeaderCard(id + "FRAC", rejectSpikeRatio, "The fraction of spikes above which to flag bolometers."));
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("DESPIKE_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else if(key.equalsIgnoreCase("DESPIKE_LEVEL")) 
		    spikeLevel = Double.parseDouble(value);
		else if(key.equalsIgnoreCase("REJECT_SPIKE_FRACTION")) 
		    rejectSpikeRatio = Double.parseDouble(value);
		else return false;

		return true;
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Ispike")) key("DESPIKE_TURN", value);
		else if(key.equalsIgnoreCase("-spikeLevel")) key("DESPIKE_LEVEL", value);
		else if(key.equalsIgnoreCase("-spikeratio")) key("REJECT_SPIKE_FRACTION", value);
		else return false;

		return true;
	    }

	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Ispike=      Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n"
		    + "  -spikelevel=  Level (in sigmas) above which to remove residual spikes. (" + spikeLevel + ")\n"
		    + "  -spikeratio=  Maximum fraction of pixel spikes before flagging. (" + rejectSpikeRatio + ")\n";

		System.out.println(info);
	    }

	    double spikeLevel = 10.0, rejectSpikeRatio = 1.0e-4;
	};


    // Auto correlation at the readout stage
    Model spectralPixelTest = new Model("SpectralPixelAnalysis") {

	    public void extract() {
		if(!isActive && turn == 0 && !alwaysSolve) {
		    if(!pixelWeightModel.isActive) return;
		    if(extended) if(sourceModel.generation < extendedSourceGenerations) return;
		    if(approximateSourceMap) if(pipeline.indexOf(sourceModel) < pipeline.indexOf(this)) return;
		    if(getChiSquared() > 2.0) return;
		}
		
		isActive = false;
		

		if(verbose) System.out.print("\tTesting for Spectral Flukes. ");

		if(numerical == null) init();

		final Complex sum1 = new Complex(), sum2 = new Complex();

		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 SharcFrame[] frame = scan[i].frame;
			
			int nfft = 1;
			while(nfft < nt) nfft <<= 1;
			final double[] signal = new double[nfft];
			final int nf = nfft / 2 + 1;
			final Complex[] spectrum = new Complex[nf];
			for(int f=0; f<nf; f++) spectrum[f] = new Complex();
			
			for(int bol=0; bol < nb; bol++) if(!scan[i].pixel[bol].isExcluded()){
			    final Bolometer pixel = scan[i].pixel[bol];
			    
			    for(int t=0; t < nt; t++) signal[t] = frame[t].flagged[bol] ? 0.0 : frame[t].readout[bol];
			    
			    // Pad signal with zeros to clear leftover spectral data
			    // (since containers are recycled).
			    Arrays.fill(signal, nt, signal.length, 0.0);
			    
			    ArrayUtil.unsecureFFT(signal, spectrum);
			    
			    final double maxLevel = level*level / pixel.weight * nt / nf;
			    
			    boolean isBad = false;
			    
			    for(int F=1; F < nf; F *= 2) {
				for(int bin=0, f1=1, f2=f1+F/2; f2 < nf; bin++) {
				    sum1.zero(); sum2.zero();
				    int n1=0, n2=0;
				    for(int binf=0; binf<F; binf++, f1++, f2++) if(f1 < nf) {
					sum1.add(spectrum[f1]);
					n1++;
					if(f2 < nf) {
					    sum2.add(spectrum[f2]);
					    n2++;
					}
				    }
				    if(sum1.norm() > n1 * maxLevel || sum2.norm() > n2 * maxLevel) isBad = true;
				}
			    }
			    
			    if(isBad) scan[i].flag(bol, Bolometer.SPECTRAL_FEATURE);
			    else scan[i].unflag(bol, Bolometer.SPECTRAL_FEATURE);
			}
		    }
		    if(verbose) System.out.print(scan[i].activePixels + " ");
		}
		if(verbose) System.out.println("Active Bolometers.");

		super.extract();
		isActive = true;
	    }									    	    

	    public void inject() {}
	    
	    public void reset() {
		super.reset();
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    for(int bol=0; bol < nb; bol++) 
			scan[i].unflag(bol, Bolometer.SPECTRAL_FEATURE);
		}
	    }

	    public int getNumberOfParameters() { return 0; }

	    public double getRMS() { return 0.0; }

	    public double getTypicalFlux() {
		double aveFrames = 0;
		for(int i=0; i<scans; i++) aveFrames += scan[i].frames;
		aveFrames /= scans;
		
		return level * timeWeightModel.getTypicalFlux() / Math.pow(aveFrames, 0.5); 
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "SPC_";
		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 + "LEVL", level, "The max. level of spectral features before flagging (sigma)."));
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("SPECTRAL_ANALYSIS_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else if(key.equalsIgnoreCase("MAX_SPECTRAL_FEATURE")) {
		    level = Double.parseDouble(value);
		}
		else return false;
		
		return true;
	    }
	    
	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Ispectral")) key("SPECTRAL_ANALYSIS_TURN", value);
		else if(key.equalsIgnoreCase("-maxSpectral")) key("MAX_SPECTRAL_FEATURE", value);		
		else return false;
		
		return true;
	    }

	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Ispectral=   Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n"
		    + "  -maxSpectral= Maximum tolerated spectral feature before flagging. (" + level + ")\n";


		System.out.println(info);
	    }

	    double level = 3.0;
	    Numerical numerical;
	};

    // Auto correlation at the readout stage
    Model temporalPixelTest = new Model("TemporalPixelAnalysis") {

	    public void extract() {		
		if(!isActive && turn == 0 && !alwaysSolve) {
		    if(!pixelWeightModel.isActive) return;
		    if(extended) if(sourceModel.generation < extendedSourceGenerations) return;
		    //if(pipeline.indexOf(sourceModel) < pipeline.indexOf(this));
		    if(approximateSourceMap) if(pipeline.indexOf(sourceModel) < pipeline.indexOf(this)) return;		    
		    if(0.1 * sourceModel.getTypicalFlux() > getTypicalFlux()) return;
		    if(getChiSquared() > 2.0) return;
		}
		
		isActive = false;
		

		if(verbose) System.out.print("\tTesting for Time Domain Flukes. ");

		timeConstant = driftModel.timeConstant;

		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 SharcFrame[] frame = scan[i].frame;
			
			final double[] signal = new double[nt];
			
			for(int bol=0; bol < nb; bol++) if(!scan[i].pixel[bol].isExcluded()){
			    timeConstant = driftModel.timeConstant;
			    timeConstant /= 2;
			    
			    final Bolometer pixel = scan[i].pixel[bol];			
			    final int temporalT = getT(scan[i]);
			    final int minT = getT(scan[i]);;
			    
			    for(int t=0; t < nt; t++) signal[t] = frame[t].flagged[bol] ? 0.0 : frame[t].readout[bol];
			    
			    final double maxLevel = level*level / pixel.weight;
			    
			    boolean isBad = false;
			    
			    for(int T=minT; T <= temporalT; T *= 2) {
				for(int bin=0, t1=0, t2=t1+T/2; t2 < nt; bin++) {
				    double sum1=0.0, sum2 = 0.0;
				    int n1=0, n2=0;
				    for(int bint=0; bint<T; bint++, t1++, t2++) if(t1 < nt) {
					sum1 += signal[t1];
					n1++;
					if(t2 < nt) {
					    sum2 += signal[t2];
					    n2++;
					}
				    }
				    if(n1 > 1 && sum1*sum1 > n1 * maxLevel) isBad = true;
				    if(n2 > 1 && sum2*sum2 > n2 * maxLevel) isBad = true;
				}
			    }
			    
			    if(isBad) scan[i].flag(bol, Bolometer.TEMPORAL_FEATURE);
			    else scan[i].unflag(bol, Bolometer.TEMPORAL_FEATURE);
			}
		    }
		    if(verbose) System.out.print(scan[i].activePixels + " ");
		}
		if(verbose) System.out.println("Active Bolometers.");

		super.extract();
		isActive = true;
	    }									    	    

	    public void inject() {}
	    
	    public void reset() {
		super.reset();
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    for(int bol=0; bol < nb; bol++) 
			scan[i].unflag(bol, Bolometer.TEMPORAL_FEATURE);
		}
	    }

	    public int getNumberOfParameters() { return 0; }

	    public double getRMS() { return 0.0; }

	    public double getTypicalFlux() {
		double aveFrames = 0;
		for(int i=0; i<scans; i++) aveFrames += getT(scan[i]);
		aveFrames /= scans;
		
		return level * timeWeightModel.getTypicalFlux() / Math.pow(2 * aveFrames, 0.5); 
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "TMP_";
		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 Minimum Temporal Feature Width (frames)."));
		cursor.add(new HeaderCard(id + "LEVL", level, "The max. level of spectral features before flagging (sigma)."));
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("TEMPORAL_ANALYSIS_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else if(key.equalsIgnoreCase("TEMPORAL_ANALYSIS_MIN_T")) {
		    timeConstant = Integer.parseInt(value);
		}
		else if(key.equalsIgnoreCase("MAX_TEMPORAL_FEATURE")) {
		    level = Double.parseDouble(value);
		}
		else return false;
		
		return true;
	    }
	    
	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Itemporal")) key("TEMPORAL_ANALYSIS_TURN", value);
		else if(key.equalsIgnoreCase("-temporalT")) key("TEMPORAL_ANALYSIS_MIN_T", value);		
		else if(key.equalsIgnoreCase("-maxTemporal")) key("MAX_TEMPORAL_FEATURE", value);		
		else return false;
		
		return true;
	    }

	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Itemporal=   Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n"
		    + "  -temporalT=   Minimum feature size (frames). (" + timeConstant + ")\n"
		    + "  -maxTemporal= Maximum tolerated spectral feature before flagging. (" + level + ")\n";


		System.out.println(info);
	    }
	    double level = 3.0;
	};

    float[][][] muxOffset, muxOffsetVar; // scan, T, row
    float[][] muxParmsForBol; // scan, bol
    float[][] muxParmsForFrame; // scan, t

    // Multiplexer offsets at the readout stage
    Model multiplexerModel = new Model("MUXOffsets", true) {
	    
	    public void init() {
		muxOffset = new float[scans][][];
		muxOffsetVar = new float[scans][][];
		muxParmsForBol = new float[scans][];
		muxParmsForFrame = new float[scans][];

		for(int i=0; i<scans; i++) {
		    final int muxT = getT(scan[i]); 
		    final int nT = (int) Math.ceil( (float) scan[i].frames / muxT );

		    muxOffset[i] = new float[nT][12];
		    muxOffsetVar[i] = new float[nT][12];
		    muxParmsForBol[i] = new float[scan[i].pixels];
		    muxParmsForFrame[i] = new float[scan[i].frames];
		}

		minFieldRotation = Math.max(sourceDiameter, beamFWHM) / (32.0 - scan[0].leftEdge - scan[0].rightEdge);
	    }

	    public void extract() {
		if(!isActive && turn == 0 && !alwaysSolve) {
		    if(allHighPassed()) {
			if(timeConstant > maxHighPassFrames()) return;
		    }
		    if(extended) if(sourceModel.generation < extendedSourceGenerations) if(getPositionAngleRange() < minFieldRotation) return;
		    //if(brightness >= pipeline.size() - pipeline.indexOf(correlatedModel)) return;
		    if(approximateSourceMap) if(pipeline.indexOf(sourceModel) < pipeline.indexOf(this)) return;
		}

		if(verbose) System.out.print("\tEstimating Multiplexing Offsets.");

		isActive = false;
		

		if(muxOffset == null) init();
		
		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 muxT = getT(scan[i]); 
		    final int nT = (int) Math.ceil( (float) scan[i].frames / muxT );
		    final SharcFrame[] scanFrame = scan[i].frame;

		    Arrays.fill(muxParmsForBol[i], 0.0F);
		    Arrays.fill(muxParmsForFrame[i], 0.0F);
		    
		    boolean solveNow = alwaysSolve || turn > 0 || scan[i].highPass <= 0.0 || timeConstant < scan[i].highPassFrames;

		    if(solveNow) for(int T=0; T<nT; T++) for(int row=0; row<12; row++) {
			double sum=0.0, sumw=0.0;
			
			for(int col=0,bol=32*row; col<32; col++,bol++) if(scan[i].pixel[bol].isActive) {
			    final Bolometer pixel = scan[i].pixel[bol];
			    final float pw = pixel.weight;
			    final int sign = col < 16 ? 1 : -1;
			    
			    for(int blockt=0,t=T*muxT; blockt<muxT; blockt++,t++) if(t < nt) {
				final SharcFrame frame = scanFrame[t];

				if(!frame.flagged[bol]) {
				    final float w = pw * frame.weight;
				    sum += w * sign * frame.readout[bol];
				    sumw += w;
				}
			    }
			}
			
			if(sumw > 0.0) {
			    final float level = (float) (sum/sumw);
			    
			    for(int col=0,bol=32*row; col<32; col++,bol++) {
				final Bolometer pixel = scan[i].pixel[bol];
				final float model = (col < 16 ? 1 : -1) * level;
				final float pixelParmTerm = (float) (pixel.weight / sumw);
				
				for(int blockt=0,t=T*muxT; blockt<muxT; blockt++,t++) if(t < scan[i].frames) {
				    final SharcFrame frame = scan[i].frame[t];
				    if(pixel.isActive) if(!frame.flagged[bol]) {
					final double p = frame.weight * pixelParmTerm;
					muxParmsForBol[i][bol] += p;
					muxParmsForFrame[i][t] += p;
				    }
					
				    frame.readout[bol] -= model;
				}
			    }

			    muxOffset[i][T][row] += level;
			    muxOffsetVar[i][T][row] = (float) (1.0/sumw);
			}
			else muxOffsetVar[i][T][row] = 0.0F;
		    }	    
		}
		
		if(sourceModel.isActive) generationsWithSource++;

		super.extract();
		isActive = true;

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

	    public void inject() {
		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final int muxT = getT(scan[i]); 
		    final int nT = (int) Math.ceil( (float) scan[i].frames / muxT );
		    final SharcFrame[] frame = scan[i].frame;
		    for(int T=0; T<nT; T++) for(int row=0; row<12; row++) {
			final float value = muxOffset[i][T][row];
			for(int col=0,bol=32*row; col<32; col++,bol++) {
			    final float model = (col < 16 ? 1 : -1) * value;
			    for(int blockt=0,t=T*muxT; blockt<muxT; blockt++,t++) if(t < nt)
				frame[t].readout[bol] += model;
			}
		    }
		}
		reset();
	    }

	    public void reset() {
		super.reset();
		muxOffset = null;
		generationsWithSource = 0;
	    }

	    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);
		
		for(int row=0, bol=0; row < 12; row++) for(int col=0; col<32; col++,bol++)
		    frame.readout[bol] = (col < 16 ? 1 : -1) * muxOffset[i][T][row];
	  
		return frame;
	    }

	    public int getNumberOfParameters() { 
		int n = 0;
		for(int i=0; i<scans; i++) n += muxOffset[i].length;
		return n; 
	    }

	    public double getRMS() {
		double rms = 0.0;
		int n = 0;

		for(int i=0; i<scans; i++) {
		    final int muxT = getT(scan[i]); 
		    final int nT = (int) Math.ceil( (float) scan[i].frames / muxT );
		    for(int T=0; T<nT; T++) for(int row=0; row<12; row++) 
			if(muxOffsetVar[i][T][row] > 0.0) {
			    final float value = muxOffset[i][T][row];
			    rms += value * value - muxOffsetVar[i][T][row];
			    n++;
			}
		}
		return rms > 0.0 ? Math.sqrt(rms / n) : 0.0;
	    }

	    public void weightAdjust(double factor) {
		for(int i=0; i<scans; i++) {
		    final int muxT = getT(scan[i]); 
		    final int nT = (int) Math.ceil( (float) scan[i].frames / muxT );
		    for(int T=0; T<nT; T++) for(int row=0; row<12; row++) muxOffsetVar[i][T][row] /= factor;
		}
	    }

	    public Object[] getData() { return muxOffset; }

	    public Samples[] getSpectra(int i, int windowSize) { 
		Samples[] spectrum = new Samples[12];
		for(int row=0; row<12; row++) {
		    Samples tdata = new Samples();
		    tdata.dx = getT(scan[i]) * scan[i].samplingInterval;
		    tdata.data = new double[muxOffset[i].length];
		    for(int T=0; T<muxOffset[i].length; T++) 
			//tdata.data[T] = muxOffsetVar[i][T][row] > 0.0 ? muxOffset[i][T][row] / muxOffsetVar[i][T][row] : 0.0;
			tdata.data[T] = muxOffset[i][T][row];
		    spectrum[row] = tdata.averagePower(windowSize);
		}
		return spectrum;
	    }

	    public double getSourceCorrectionFactor(double FWHM) {
		if(!precedesSource) return 1.0;

		double rotation = getPositionAngleRange();
		// Calculate only if PA rotation is small
		if(rotation > 2.0/(32.0 - scan[0].leftEdge - scan[0].rightEdge)) return 1.0;

		// Estimate the bias reduction per iteration from the are spanned by 
		// the field rotation of a row over the area of the row.
		double reduction = 1.0 / (1.0 + 16.0 * rotation);

		double sumbias = 0.0, sumscanw = 0.0;

		for(int i=0; i<scans; i++) if(isMapped(scan[i])) {
		    sumbias += scan[i].weight * getScanCorrectionKappa(scan[i], FWHM);
		    sumscanw += scan[i].weight;
		}
		 
		double bias = sumscanw > 0.0 ? 1.0 / (1.0 - sumbias/sumscanw) : 1.0;
		return bias * Math.pow(reduction, generationsWithSource);
	    }

	    public double getScanCorrectionKappa(SharcScan aScan, double FWHM) {
		double sumbias = 0.0, sumw = 0.0;
		Bolometer[] pixel = aScan.pixel;
		double sigma = FWHM / 2.35 / aScan.pixelXSize;

		for(int row=0, bol=0; row<12; row++) {
		    int nl = 0, nr = 0;
		    double sumpw = 0.0;

		    for(int col=0; col<32; col++, bol++) if(pixel[bol].isActive) {
			sumpw += pixel[bol].weight;
			if(col < 16) nl++;
			else nr++;
		    }
		    
		    if(nl > 0 && nr > 0) {
			double avepw = sumpw / (nl + nr);
			double bias = Math.sqrt(2.0 * Math.PI) * sigma * avepw / sumpw;
			
			sumbias += avepw * bias;
			sumw += avepw;
		    }
		}
		return sumw > 0.0 ? sumbias/sumw : 0.0;
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "MUX_";
		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 + "PCOR", getSourceCorrectionFactor(beamFWHM), "Point source correction factor."));
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("MUX_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		} 
		else if(key.equalsIgnoreCase("MUX_T")) {
		    if(value.equals("*") || value.equalsIgnoreCase("auto")) autoTimeScale = true;
		    else {
			timeConstant = Integer.parseInt(value);
			autoTimeScale = false;
		    }
		}
		else return false;
		
		return true;
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Imux")) key("MUX_TURN", value);
		else if(key.equalsIgnoreCase("-muxT")) key("MUX_T", value);
		else return false;
		
		return true;
	    }
	    
	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Imux=        Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n"
		    + "  -muxT=        Number of frames per multiplexer offset value. (" + 
		    (autoTimeScale ? "auto" : timeConstant + "") + ")\n";

		System.out.println(info);
	    }

	    double minFieldRotation = 2.0 / 8.0; // A beam (i.e. 2 pixels) over half of the fitted range (16 pixels).
	    int generationsWithSource = 0;
	};


    float[][][][] rowCoeff, rowCoeffVar; // scan, t, row, power
    float[][] rowCoeffParmsForFrame; // scan, t
    float[][] rowCoeffParmsForBol; // scan, bol

    Model rowPolynomialModel = new Model("RowPolynomials", true) {

	    public void init() {
		if(order < 0) return;
		polynomial = new SharcRowPolynomialFit(order);

		final int fittedOrders = polynomial.countFitted();
		if(fittedOrders == 0) return;

		rowCoeff = new float[scans][][][];
		rowCoeffVar = new float[scans][][][];
		rowCoeffParmsForFrame = new float[scans][];
		rowCoeffParmsForBol = new float[scans][];
		
		value = new double[32];
		weight = new double[32];
		sigma = new double[32];

		termwsum = new double[order+1];

		for(int i=0; i<scans; i++) {
		    final int rowCoeffT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames / rowCoeffT);
    
		    rowCoeff[i] = new float[nT][12][order+1];
		    rowCoeffVar[i] = new float[nT][12][order+1];
		    rowCoeffParmsForFrame[i] = new float[scan[i].frames];
		    rowCoeffParmsForBol[i] = new float[scan[i].pixels];
		}

		minFieldRotation = Math.max(sourceDiameter, beamFWHM) / (32.0 - scan[0].leftEdge - scan[0].rightEdge);
	    }

	    public void extract() {
		if(!isActive && turn == 0 && !alwaysSolve) {
		    if(allHighPassed()) {
			if(timeConstant > maxHighPassFrames()) return;
		    }
		    if(extended) if(sourceModel.generation < extendedSourceGenerations) if(getPositionAngleRange() < minFieldRotation) return;
		    //if(brightness >= pipeline.size() - pipeline.indexOf(correlatedModel)) return;
		    if(approximateSourceMap) if(pipeline.indexOf(sourceModel) < pipeline.indexOf(this)) return;
		}

		final int fittedOrders = polynomial.countFitted();
		if(fittedOrders == 0) return;
		
		isActive = false;
		

		if(verbose) System.out.print("\tFitting Polynomial Row Model (order " + order + "). ");
		
		if(rowCoeff == null) init();

		for(int i=0; i<scans; i++) if(scan[i].activePixels > order && scan[i].activeFrames > 0) {
		    final int rowCoeffT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames / rowCoeffT);
		    final int nt = scan[i].frames;
		    final Bolometer[] scanPixel = scan[i].pixel;
		    final SharcFrame[] scanFrame = scan[i].frame;
    
		    Arrays.fill(rowCoeffParmsForFrame[i], 0.0F);
		    Arrays.fill(rowCoeffParmsForBol[i], 0.0F);
		 
		    boolean solveNow = alwaysSolve || turn > 0 || scan[i].highPass <= 0.0 || timeConstant < scan[i].highPassFrames;

		    if(solveNow) for(int T=0; T < nT; T++) {
			for(int row=0; row<12; row++) { 
			    Arrays.fill(value, 0.0);
			    Arrays.fill(weight, 0.0);

			    for(int col=0,bol=32*row; col<32; col++,bol++) {
				final Bolometer pixel = scanPixel[bol];
				final float G = pixel.gain;

				if(pixel.isActive) {
				    final float pw = pixel.weight;
				    
				    for(int blockt=0,t=T*rowCoeffT; blockt<rowCoeffT; blockt++,t++) if(t < nt) {
					final SharcFrame frame = scanFrame[t];
					
					if(!frame.flagged[bol]) {
					    final float w = pw * frame.weight;
					    value[col] += w * frame.readout[bol] * G;
					    weight[col] += w * G * G;
					}
				    }
				}
			    }
			    
			    int activeColumns = 0;

			    for(int col=0; col<32; col++) {
				if(weight[col] > 0.0) {
				    value[col] /= weight[col];
				    sigma[col] = Math.sqrt(1.0 / weight[col]);
				    activeColumns++;				
				}
				else sigma[col] = -1.0;
			    }
			    
			    
			    if(activeColumns > fittedOrders) {
				polynomial.fit(value, sigma);
				
				// Recalculate the fractional parameter normalization
				Arrays.fill(termwsum, 0.0);
				for(int p=0; p <= order; p++) for(int col=0; col<32; col++) 
				    termwsum[p] += weight[col] * polynomial.aterm[col][p];

				for(int col=0,bol=32*row; col<32; col++,bol++) {
				    final Bolometer pixel = scanPixel[bol];
				    final float G = pixel.gain;
				    
				    final double dmodel = G * polynomial.valueAt(col);
				    
				    // Calculated the average fractional parameter per data point
				    // for the time block.
				    float avePointParms = 0.0F;
				    for(int p=0; p<=order; p++) avePointParms += polynomial.aterm[col][p] / termwsum[p];
				    avePointParms *= pixel.weight * G * G;

				    for(int blockt=0,t=T*rowCoeffT; blockt<rowCoeffT; blockt++,t++) if(t < nt) {
					final SharcFrame frame = scanFrame[t];
					
					frame.readout[bol] -= dmodel;

					if(pixel.isActive) {
					    final float dp = frame.weight * avePointParms;					
					    rowCoeffParmsForFrame[i][t] += dp;
					    rowCoeffParmsForBol[i][bol] += dp;
					}
				    }
				}

				for(int p=0; p<=order; p++) {
				    rowCoeff[i][T][row][p] += (float) polynomial.parameter[p];
				    rowCoeffVar[i][T][row][p] = (float) polynomial.covar[p][p];
				}
			    }
			    else for(int p=0; p<=order; p++) rowCoeffVar[i][T][row][p] = Float.NaN;
			}
			
			// Remove the part degenerate with the correlated noise
			// and add it to the correlated noise model.
			double sum = 0.0, sumw=0.0;
			for(int row=0; row<12; row++) if(!Float.isNaN(rowCoeffVar[i][T][row][0])){
			    sum += rowCoeff[i][T][row][0] / rowCoeffVar[i][T][row][0];
			    sumw += 1.0 / rowCoeffVar[i][T][row][0];
			}
			if(sumw > 0.0) {
			    final float aveRowOffset = (float) (sum / sumw);
			    for(int row=0; row<12; row++) rowCoeff[i][T][row][0] -= aveRowOffset;
			    for(int blockt=0,t=T*rowCoeffT; blockt<rowCoeffT; blockt++,t++) if(t < nt) 
				correlated[i][t] += aveRowOffset / scan[i].gain;
			}
		    }
		}
		
		if(sourceModel.isActive) generationsWithSource++;
		super.extract();
		isActive = true;

		if(verbose) System.out.println();
	    }
	    
	    public void inject() {
		polynomial.reset();

		for(int i = 0; i < scans; i++) {
		    final int nt = scan[i].frames;
		    final int rowCoeffT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames / rowCoeffT);
		    final SharcFrame[] frame = scan[i].frame;
		    final Bolometer[] pixel = scan[i].pixel;
	       
		    for(int T=0; T<nT; T++) for(int row=0,bol=0; row<12; row++) for(int col=0; col<32; col++,bol++) {
			for(int p=0; p<=order; p++) polynomial.parameter[p] = rowCoeff[i][T][row][p];
			final float model = pixel[bol].gain * (float) polynomial.valueAt(col);

			for(int blockt=0,t=T*rowCoeffT; blockt<rowCoeffT; blockt++,t++) if(t < nt)
			    frame[t].readout[bol] += model;
		    }
		}
		reset();
	    }

	    public void reset() {
		super.reset();
		rowCoeff = null;
		generationsWithSource = 0;
	    }

	    public SharcFrame getFrame(SharcScan aScan, int t) {
		polynomial.reset();

		SharcFrame frame = super.getFrame(aScan, t);
		frame.readout = new float[aScan.pixels];
		final int i = getIndexOf(aScan);
		final int T = t / getT(aScan);
		
		for(int row=0, bol=0; row < 12; row++) for(int col=0; col<32; col++,bol++) {
		    for(int p=0; p<=order; p++) polynomial.parameter[p] = rowCoeff[i][T][row][p];
		    frame.readout[bol] = (float)polynomial.valueAt(col);
		}
	  
		return frame;
	    }

	    public int getNumberOfParameters() { 
		int n = 0;
		for(int i=0; i<scans; i++) n += 12 * (order+1) * rowCoeff[i].length;
		return n; 
	    }


	    public double getRMS() {
		polynomial.reset();

		double var = 0.0;
		int n = 0;

		for(int i = 0; i < scans; i++) {
		    final Bolometer[] pixel = scan[i].pixel;
		    final int rowCoeffT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames / rowCoeffT);
		    
		    for(int T=0; T<nT; T++) for(int row=0,bol=0; row<12; row++) {
			for(int p=0; p<=order; p++) {
			    polynomial.parameter[p] = rowCoeff[i][T][row][p];
			    polynomial.covar[p][p] = rowCoeffVar[i][T][row][p];
			}			
			
			for(int col=0; col<32; col++, bol++) {
			    final float G2 = pixel[bol].gain * pixel[bol].gain;
			    final double value = polynomial.valueAt(col);
			    final double pvar = polynomial.varianceAt(col);

			    if(!Double.isNaN(pvar)) {
				var += G2 * (value * value - pvar);
				n++;
			    }
			}
		    }
		}
		
		return var > 0.0 ? Math.sqrt(var / n) : 0.0;		
	    }
	    
	    public void dGain(int i, int t, float[] dmodel) {
		polynomial.reset();

		int rowT = getT(scan[i]);
		int T = t/rowT;
		float G = scan[i].gain;
		Bolometer[] pixel = scan[i].pixel;

		for(int row=0,bol=0; row<12; row++) {
		    for(int p=0; p<=order; p++) polynomial.parameter[p] = rowCoeff[i][T][row][p];
		    for(int col=0; col<32; col++, bol++) dmodel[bol] += G * pixel[bol].gain * polynomial.valueAt(col);
		}
	    }

	    public void weightAdjust(double factor) {
		for(int i = 0; i < scans; i++) {
		    final int rowCoeffT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames / rowCoeffT);
		    for(int T=0; T<nT; T++) for(int row=0; row<12; row++) for(int p=0; p<order; p++)
			rowCoeffVar[i][T][row][p] /= factor;
		}
	    }

	    public Object[] getData() { return rowCoeff; }

	    // provide the spectrum of the offset component only!
	    public Samples[] getSpectra(int i, int windowSize) { 
		Samples[] spectrum = new Samples[12];
		for(int row=0; row<12; row++) {
		    Samples tdata = new Samples();
		    tdata.dx = getT(scan[i]) * scan[i].samplingInterval;
		    tdata.data = new double[rowCoeff[i].length];
		    for(int T=0; T<rowCoeff[i].length; T++) 
			/*
			tdata.data[T] = !Float.isNaN(rowCoeffVar[i][T][row][0]) ? 
			    rowCoeff[i][T][row][0] / rowCoeffVar[i][T][row][0] : 0.0;
			*/
			tdata.data[T] = rowCoeff[i][T][row][0];
		    spectrum[row] = tdata.averagePower(windowSize);
		}
		return spectrum;
	    }

	    public double getSourceCorrectionFactor(double FWHM) {
		if(!precedesSource) return 1.0;

		double rotation = getPositionAngleRange();
		// Calculate only if PA rotation is small
		if(rotation > 2.0/(32.0 - scan[0].leftEdge - scan[0].rightEdge)) return 1.0;

		// Estimate the bias reduction per iteration from the are spanned by 
		// the field rotation of a row over the area of the row.
		double reduction = 1.0 / (1.0 + 16.0 * rotation);

		double sumbias = 0.0, sumscanw = 0.0;

		for(int i=0; i<scans; i++) if(isMapped(scan[i])) {
		    sumbias += scan[i].weight * getScanCorrectionKappa(scan[i], FWHM);
		    sumscanw += scan[i].weight;
		}
		 
		double bias = sumscanw > 0.0 ? 1.0 / (1.0 - sumbias/sumscanw) : 1.0;
		return bias * Math.pow(reduction, generationsWithSource);
	    }

	    public double getScanCorrectionKappa(SharcScan aScan, double FWHM) {
		double sumbias = 0.0, sumw = 0.0;
		Bolometer[] pixel = aScan.pixel;
		double sigma = FWHM / 2.35 / aScan.pixelXSize;
		
		for(int row=0, bol=0; row<12; row++) {
		    int n = 0;
		    double sumpw = 0.0;
		    
		    for(int col=0; col<32; col++, bol++) if(pixel[bol].isActive) {
			sumpw += pixel[bol].weight;
			n++;
		    }
		    
		    if(n > polynomial.countFitted()) {
			double avepw = sumpw / n;
			double bias = Math.sqrt(2.0 * Math.PI) * sigma * avepw / sumpw;
			
			sumbias += avepw * bias;
			sumw += avepw;
		    }
		}

		return sumw > 0.0 ? polynomial.countFitted() * sumbias/sumw : 0.0;
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "ROW_";
		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 + "RMS", getRMS(), "The RMS of the Model Parameters."));
		cursor.add(new HeaderCard(id + "T", timeConstant, "The Model Time Constant (frames).")); 
		cursor.add(new HeaderCard(id + "ORDR", order, "The polynomial order of the model.")); 
		cursor.add(new HeaderCard(id + "PCOR", getSourceCorrectionFactor(beamFWHM), "Point source correction factor."));
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("ROW_POLYNOMIAL_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else if(key.equalsIgnoreCase("ROW_POLYNOMIAL_T")) {
		    if(value.equals("*") || value.equalsIgnoreCase("auto")) autoTimeScale = true;
		    else {
			timeConstant = Integer.parseInt(value);
			autoTimeScale = false;
		    }
		}
		else if(key.equalsIgnoreCase("ROW_POLYNOMIAL_ORDER")) order = Integer.parseInt(value);
		else return false;
		
		return true;
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Irow")) key("ROW_POLYNOMIAL_TURN", value);
		else if(key.equalsIgnoreCase("-rowT")) key("ROW_POLYNOMIAL_T", value);
		else if(key.equalsIgnoreCase("-roworder")) key("ROW_POLYNOMIAL_ORDER", value);
		else return false;
		
		return true;
	    }
	    
	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Irow=       Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n"
		    + "  -rowT=       Number of frames per row offset value. (" + 
		    (autoTimeScale ? "auto" : timeConstant + "") + ")\n"
		    + "  -roworder=   Order of the polynomial row model. (" + order + ")\n";
		

		System.out.println(info);
	    }

	    SharcRowPolynomialFit polynomial;
	    double[] value, weight, sigma, termwsum;
	    int order = 2;
	    double minFieldRotation = 2.0 / 16.0; // 1 beam (2 pixels) over half a row (16 pixels).
	    int generationsWithSource = 0;
	};


    float[][][] sky; // scan, T, coeff
    float[][][] skyVar; // scan, T, coeff
    float[][] skyParmsForFrame; // scan, t
    float[][] skyParmsForBol; // scan, bol

    // Horizontal Skys at the detector voltage stage
    Model skyPolynomialModel = new Model("SkyPolynomials", true) {

	    public void init() {
		// -skyOrder=auto
		if(order <= 0) order = extended ? 1 : 3;

		polynomial = new Sharc2DPolynomialFit(order);		
		nParms = polynomial.parameter.length;

		sky = new float[scans][][];
		skyVar = new float[scans][][]; 
		skyParmsForBol = new float[scans][];
		skyParmsForFrame = new float[scans][];
	    				
		value = new double[32*12];
		weight = new double[32*12]; 
		sigma = new double[32*12];

		isColActive = new boolean[32];
		isRowActive = new boolean[12];

		termwsum = new double[nParms];
		
		for(int i=0; i<scans; i++) {
		    final int skyT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames/skyT);
	
		    sky[i] = new float[nT][nParms];
		    skyVar[i] = new float[nT][nParms];
		    skyParmsForBol[i] = new float[scan[i].pixels];
		    skyParmsForFrame[i] = new float[scan[i].frames];
		}
	    }

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

		if(order <= 0) return;
		
		isActive = false;
			    
		if(verbose) System.out.print("\tFitting Sky Polynomials (order " + order + ").");
		
		if(sky == null) init();


		for(int i=0; i<scans; i++) if(scan[i].activePixels > nParms && scan[i].activeFrames > 0) {
		    final int nt = scan[i].frames;
		    final int skyT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames/skyT);
		    final SharcFrame[] scanFrame = scan[i].frame;

		    Arrays.fill(skyParmsForBol[i], 0.0F);
		    Arrays.fill(skyParmsForFrame[i], 0.0F);
		    
		    boolean solveNow = alwaysSolve || turn > 0 || scan[i].highPass <= 0.0 || timeConstant < scan[i].highPassFrames;
		    
		    if(solveNow) for(int T=0; T < nT; T++) {
			Arrays.fill(value, 0.0);
			Arrays.fill(weight, 0.0);
			
			Arrays.fill(isColActive, false);
			Arrays.fill(isRowActive, false);

			for(int bol=0; bol<scan[i].pixels; bol++) if(scan[i].pixel[bol].isActive) {
			    final Bolometer pixel = scan[i].pixel[bol];
			    final float G = pixel.gain;
			    final float pwG2 = G * G * pixel.weight;
			    
			    for(int blockt=0,t=T*skyT; blockt<skyT; blockt++,t++) if(t < nt) {
				final SharcFrame frame = scanFrame[t];
				if(!frame.flagged[bol]) {
				    final float w = pwG2 * frame.weight;
				    value[bol] += w * frame.readout[bol] / G;
				    weight[bol] += w;
				}
			    }
			}		       
						
			int activePixels = 0;			
			for(int row=0, bol=0; row<12; row++) for(int col=0; col<32; col++, bol++) {
			    if(weight[bol] > 0.0) {
				value[bol] /= weight[bol];
				sigma[bol] = Math.sqrt(1.0 / weight[bol]);
				if(!isColActive[col]) isColActive[col] = true;
				if(!isRowActive[row]) isRowActive[row] = true;
				activePixels++;
			    }
			    else sigma[bol] = -1.0;
			}
			
			int activeRows = 0, activeCols = 0;
			for(int row=0; row<12; row++) if(isRowActive[row]) activeRows++;
			for(int col=0; col<32; col++) if(isColActive[col]) activeCols++;


			if(activePixels > (nParms + minDegreesOfFreedom) &&
			   activeRows > order && activeCols > order) {
			    
			    polynomial.fit(value, sigma); 
			    
			    // Recalculate the fractional parameter normalization
			    Arrays.fill(termwsum, 0.0);
			    for(int p=0; p<nParms; p++) for(int bol=0; bol<scan[i].pixels; bol++) if(scan[i].pixel[bol].isActive)
				termwsum[p] += weight[bol] * polynomial.aterm[bol][p];

			    for(int bol=0; bol<scan[i].pixels; bol++) {
				final Bolometer pixel = scan[i].pixel[bol];
				final float G = pixel.gain;
				final float dmodel = G * (float)polynomial.valueAt(bol);

				// Calculated the average fractional parameter per data point
				// for the time block.
				float avePointParms = 0.0F;
				for(int p=0; p<nParms; p++) avePointParms += polynomial.aterm[bol][p] / termwsum[p];
				avePointParms *= G * G * pixel.weight;

				for(int blockt=0,t=T*skyT; blockt<skyT; blockt++,t++) if(t < nt) {
				    final SharcFrame frame = scan[i].frame[t];
				    frame.readout[bol] -= dmodel;
				    
				    if(pixel.isActive) if(!frame.flagged[bol]) {
					final float dp = frame.weight * avePointParms; 
					skyParmsForFrame[i][t] += dp;
					skyParmsForBol[i][bol] += dp;
				    }
				    
				    // The constant offset is added to the correlated noise
				    // model.
				    if(correlatedModel.isActive) {
					correlated[i][t] += (float)polynomial.parameter[0] / scan[i].gain;
					polynomial.parameter[0] = 0.0;
				    }
				}	
			    }
			    
			    for(int p=0; p<nParms; p++) {
				sky[i][T][p] += (float)polynomial.parameter[p];
				skyVar[i][T][p] = (float)polynomial.covar[p][p];
			    }
			}
			else for(int p=0; p<nParms; p++) skyVar[i][T][p] = Float.NaN;
		    }
		}
		
		super.extract();
		isActive = true;

		if(verbose) System.out.println();
	    }
	    
	    public void inject() {		
		polynomial.reset();

		for(int i=0; i<scans; i++) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final int skyT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames / skyT);
		    final Bolometer[] pixel = scan[i].pixel;
		    final SharcFrame[] frame = scan[i].frame;

		    for(int T=0; T < nT; T++) {
			final float[] value = sky[i][T];

			for(int p=0; p<nParms; p++) polynomial.parameter[p] = sky[i][T][p];
			
			for(int bol=0; bol<scan[i].pixels; bol++) {
			    final float G = pixel[bol].gain;
			    final float model = pixel[bol].gain * (float)polynomial.valueAt(bol);

			    for(int blockt=0,t=T*skyT; blockt < skyT; blockt++,t++) if(t < nt)
				frame[t].readout[bol] += model;
			}
		    }
		}
		reset();
	    }
	    
	    public void reset() {
		super.reset();
		sky = null;
	    }

	    public SharcFrame getFrame(SharcScan aScan, int t) {
		polynomial.reset();

		SharcFrame frame = super.getFrame(aScan, t);
		frame.readout = new float[aScan.pixels];
		final int i = getIndexOf(aScan);
		final int T = t / getT(aScan);
		
		for(int p=0; p<nParms; p++) polynomial.parameter[p] = sky[i][T][p];
		for(int bol=0; bol < scan[i].pixels; bol++) frame.readout[bol] = (float)polynomial.valueAt(bol);

		return frame;
	    }

	    public int getNumberOfParameters() { 
		int n = 0;
		for(int i=0; i<scans; i++) n += nParms * sky[i].length;
		return n; 
	    }

	    public double getRMS() {
		polynomial.reset();

		double var = 0.0;
		int n = 0;
		
		for(int i=0; i<scans; i++) {
		    final int skyT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames/skyT);

		    for(int T=0; T<nT; T++) {
			for(int p=1; p<nParms; p++) {
			    polynomial.parameter[p] = sky[i][T][p];
			    polynomial.covar[p][p] = skyVar[i][T][p];
			}
			
			for(int bol=0; bol < scan[i].pixels; bol++) {
			    final double G = scan[i].pixel[bol].gain;
			    final double value = polynomial.valueAt(bol);
			    final double pvar = polynomial.varianceAt(bol);

			    if(!Double.isNaN(pvar)) {
				var += G * G * (value * value - pvar);
				n++;
			    }
			}
		    }
		}
		return var > 0.0 ? Math.sqrt(var / n) : 0.0;
	    }
	    
	    public void dGain(int i, int t, float[] dmodel) {
		if(!isActive) return;
		
		float G = scan[i].gain;
		Bolometer[] pixel = scan[i].pixel;

		float[] model = sky[i][t / getT(scan[i])];
		for(int p=0; p < nParms; p++) polynomial.parameter[p] = model[p];

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


	    public void weightAdjust(double factor) {
		for(int i=0; i<scans; i++) {
		    final int skyT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames/skyT);
		    for(int T=0; T<nT; T++) for(int p=0; p < nParms; p++)
			skyVar[i][T][p] /= factor;
		}
	    }

	    public Object[] getData() { return sky; }

	    // provide the spectrum of the offset component only!
	    public Samples[] getSpectra(int i, int windowSize) { 
		Samples[] spectrum = new Samples[2];

		Samples xdata = new Samples();
		xdata.dx = getT(scan[i]) * scan[i].samplingInterval;
		xdata.data = new double[sky[i].length];

		Samples ydata = new Samples();
		ydata.dx = getT(scan[i]) * scan[i].samplingInterval;
		ydata.data = new double[sky[i].length];
		 
		for(int T=0; T<sky[i].length; T++) {
		    //xdata.data[T] = !Float.isNaN(skyVar[i][T][1]) ? sky[i][T][1] / sky[i][T][1] : 0.0;
		    //ydata.data[T] = !Float.isNaN(skyVar[i][T][2]) ? sky[i][T][2] / sky[i][T][2] : 0.0;
		    xdata.data[T] = sky[i][T][1];
		    ydata.data[T] = sky[i][T][2];
		}
	    
		spectrum[0] = xdata.averagePower(windowSize);
		spectrum[1] = xdata.averagePower(windowSize);

		return spectrum;
	    }

	    public double getSourceCorrectionFactor(double FWHM) {
		if(!precedesSource) return 1.0;
		double sumbias = 0.0, sumscanw = 0.0;
		
		for(int i=0; i<scans; i++) if(isMapped(scan[i])) {
		    sumbias += scan[i].weight * getScanCorrectionKappa(scan[i], FWHM);
		    sumscanw += scan[i].weight;
		}
		 
		return sumscanw > 0.0 ? 1.0 / (1.0 - sumbias/sumscanw) : 1.0;
	    }

	    public double getScanCorrectionKappa(SharcScan aScan, double FWHM) {
		double sumbias = 0.0, sumw = 0.0;
		Bolometer[] pixel = aScan.pixel;
		double sigmaX = FWHM / 2.35 / aScan.pixelXSize;
		double sigmaY = FWHM / 2.35 / aScan.pixelYSize;
		    
		int n = 0;
		double sumpw = 0.0;
		
		for(int bol=0; bol<aScan.pixels; bol++) if(pixel[bol].isActive) {
		    sumpw += pixel[bol].weight;
		    n++;
		}
		
		if(n > polynomial.countFitted()) {
		    double avepw = sumpw / n;
		    double bias = 2.0 * Math.PI * sigmaX * sigmaY * avepw / sumpw;
		    
		    sumbias += avepw * bias;
		    sumw += avepw;
		}
		
		return sumw > 0.0 ? (nParms - 1) * sumbias/sumpw : 0.0;
	    }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "SKY_";
		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 + "RMS", getRMS(), "The RMS of the Model Parameters."));
		cursor.add(new HeaderCard(id + "T", timeConstant, "The Model Time Constant (frames).")); 
		cursor.add(new HeaderCard(id + "ORDR", order, "The Horizontal Sky Polynomial Order.")); 
		cursor.add(new HeaderCard(id + "PCOR", getSourceCorrectionFactor(beamFWHM), "Point source correction factor."));
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("SKY_POLYNOMIAL_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else if(key.equalsIgnoreCase("SKY_POLYNOMIAL_T")) {
		    if(value.equals("*") || value.equalsIgnoreCase("auto")) autoTimeScale = true;
		    else {
			timeConstant = Integer.parseInt(value);
			autoTimeScale = false;
		    }
		}
		else if(key.equalsIgnoreCase("SKY_POLYNOMIAL_ORDER")) {
		    if(value.equalsIgnoreCase("auto")) order = 0;
		    else order = Integer.parseInt(value);
		}
		else return false;
		
		return true;
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Isky")) key("SKY_POLYNOMIAL_TURN", value);
		else if(key.equalsIgnoreCase("-skyT")) key("SKY_POLYNOMIAL_T", value);
		else if(key.equalsIgnoreCase("-skyOrder")) key("SKY_POLYNOMIAL_ORDER", value); 
		else return false;
		
		return true;
	    }

	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Isky=      Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n"
		    + "  -skyT=      Number of frames per sky polynomial fit. (" + 
		    (autoTimeScale ? "auto" : timeConstant + "") + ")\n"
		    + "  -skyorder=  The polynomial order to fit for the sky. (" + (order > 0 ? order + "" : "auto") + "). ";

		System.out.println(info);
	    }
	    Sharc2DPolynomialFit polynomial;
	    int order = 1, nParms;

	    double[] value, weight, sigma, termwsum;
	    boolean[] isColActive, isRowActive;
	};



    float[][] chopCoeff, chopCoeffVar; // scan, bol

    // Chopping Offsets for Illumination correction
    // Levels are referred to at the temperature stage
    Model chopCoefficientModel = new Model("ChopResiduals", true) {
	    
	    public void init() {
		chopCoeff = new float[scans][];
		chopCoeffVar = new float[scans][];

		for(int i=0; i<scans; i++) {
		    chopCoeff[i] = new float[scan[i].pixels];
		    chopCoeffVar[i] = new float[scan[i].pixels];
		}
	    }

	    public void extract() {

		if(!isActive && turn == 0 && !alwaysSolve) {
		    boolean chopped = false;
		    for(int i=0; i<scans; i++) if(scan[i].chopped) chopped = true;
		    if(!chopped) return;
		    if(!correlatedModel.isActive) return;
		    if(approximateSourceMap) if(pipeline.indexOf(sourceModel) < pipeline.indexOf(this)) return;
		    if(getChiSquared() > 4.0) return;
		}

		if(verbose) System.out.print("\tEstimating Chopping Residuals.");

		isActive = false;
				

		if(chopCoeff == null) init();

		
		for(int i=0; i<scans; i++) if(scan[i].activeFrames > 0) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final SharcFrame[] scanFrame = scan[i].frame;
		    
		    boolean solveNow = alwaysSolve || turn > 0 || scan[i].chopped;
		    
		    if(solveNow) for(int bol=0; bol < nb; bol++) if(!scan[i].pixel[bol].isExcluded()) {
			final Bolometer pixel = scan[i].pixel[bol];
			final float pw = pixel.weight;
			double sum=0.0, sumfw=0.0;
			
			for(int t=0; t<nt; t++) {
			    final SharcFrame frame = scanFrame[t];

			    if(!frame.flagged[bol]) {
				final float w = frame.weight;
				final float alpha = (1.0F - frame.transmission) * frame.chopOffset;

				sum += w * alpha * frame.readout[bol];
				sumfw += w * alpha * alpha;
			    }
			}
			
			if(sumfw > 0.0) {
			    final float dcoeff = (float) (sum / sumfw);

			    for(int t=0; t<nt; t++) {				
				final SharcFrame frame = scanFrame[t];
				final float eps = 1.0F - frame.transmission;
				frame.readout[bol] -= (1.0F-frame.transmission) * frame.chopOffset * dcoeff;
			    }
			
			    chopCoeff[i][bol] += dcoeff;
			    chopCoeffVar[i][bol] = (float) (1.0 / (pw * sumfw));
			}
			else chopCoeffVar[i][bol] = 0.0F;
		    }
		}
		
		super.extract();
		isActive = true;

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

	    public void inject() {
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    final int nt = scan[i].frames;
		    final SharcFrame[] scanFrame = scan[i].frame;

		    for(int bol=0; bol < scan[i].pixels; bol++) {
			final float coeff = chopCoeff[i][bol];

			for(int t=0; t<nt; t++) {
			    final SharcFrame frame = scanFrame[t];
			    frame.readout[bol] += (1.0F - frame.transmission) * coeff * frame.chopOffset;
			}
		    }
		}
		reset();
	    }

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

	    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);
		float A = (1.0F - frame.transmission) * frame.chopOffset;

		for(int row=0, bol=0; row < 12; row++) for(int col=0; col<32; col++,bol++)
		    frame.readout[bol] = A * chopCoeff[i][bol];
	  
		return frame;
	    }

	    public int getNumberOfParameters() { 
		int n = 0;
		for(int i=0; i<scans; i++) n += scan[i].activePixels;
		return n; 
	    }

	    public double getRMS() {
		double sum = 0.0, sumw = 0.0;

		for(int i=0; i<scans; i++) {
		    final double chopperThrow = scan[i].chopperThrow;
		    final int nb = scan[i].pixels;
		    final Bolometer[] pixel = scan[i].pixel;
		    
		    for(int bol=0; bol < nb; bol++) if(pixel[bol].isActive) if(chopCoeffVar[i][bol] > 0.0) {
			final double alpha = (1.0F-scan[i].transmission) * chopperThrow;
			final double value = chopCoeff[i][bol];
			final float w = pixel[bol].weight;
			sum += w * alpha * alpha * (value * value - chopCoeffVar[i][bol]);
			sumw += w;
		    }
		}
		return sum > 0.0 ? Math.sqrt(sum / sumw) : 0.0;
	    }

	    public void weightAdjust(double factor) {
		for(int i=0; i<scans; i++) for(int bol=0; bol<scan[i].pixels; bol++) chopCoeffVar[i][bol] /= factor;
	    }

	    public Object[] getData() { return chopCoeff; }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "CHP_";
		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 + "RMS", getRMS(), "The RMS of the Model Parameters."));
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("CHOP_RESIDUAL_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		} 
		else return false;
		
		return true;
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Ichop")) key("CHOP_RESIDUAL_TURN", value);
		else return false;
		
		return true;
	    }
	    
	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Ichop=        Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n";

		System.out.println(info);
	    }
	};

    
    // I ~ eps * alpha * acceleration
    // alpha -> I / (eps * acceleration)
    
    float[][] accelCoeff, accelCoeffVar; // scan, bol

    Model accelerationModel = new Model("AccelerationResponse", true) {

	    public void init() {
		accelCoeff = new float[scans][];
		accelCoeffVar = new float[scans][];

		for(int i=0; i<scans; i++) {
		    accelCoeff[i] = new float[scan[i].pixels];
		    accelCoeffVar[i] = new float[scan[i].pixels];
		}
	    }		

	    public void extract() {

		if(!isActive && turn == 0 && !alwaysSolve) {
		    if(approximateSourceMap) if(pipeline.indexOf(sourceModel) < pipeline.indexOf(this)) return;
		    if(getChiSquared() > 1.1) return;
		}

		if(verbose) System.out.print("\tEstimating Acceleration Response.");
		isActive = false;
		
		if(accelCoeff == null) init();

		
		for(int i=0; i<scans; i++) if(scan[i].activeFrames > 0) {
		    final int nt = scan[i].frames;
		    final int nb = scan[i].pixels;
		    final SharcFrame[] scanFrame = scan[i].frame;
		    
		    boolean solveNow = true;
		    
		    if(solveNow) for(int bol=0; bol < nb; bol++) if(!scan[i].pixel[bol].isExcluded()) {
			final Bolometer pixel = scan[i].pixel[bol];
			final float pw = pixel.weight;
			double sum=0.0, sumfw=0.0;
			
			for(int t=0; t<nt; t++) {
			    final SharcFrame frame = scanFrame[t];
			    final float a = frame.acceleration;

			    if(!frame.flagged[bol] && !Float.isNaN(a)) {
				final float w = frame.weight;
				final float alpha = (1.0F - frame.transmission) * a;
			    
				sum += w * alpha * frame.readout[bol];
				sumfw += w * alpha * alpha;
			    }
			}
			
			if(sumfw > 0.0) {
			    final float dcoeff = (float) (sum / sumfw);

			    for(int t=0; t<nt; t++) {				
				final SharcFrame frame = scanFrame[t];
				final float a = frame.acceleration;

				if(!Float.isNaN(a)) frame.readout[bol] -= (1.0F-frame.transmission) * dcoeff * a;
			    }
			
			    accelCoeff[i][bol] += dcoeff;
			    accelCoeffVar[i][bol] = (float) (1.0 / (pw * sumfw));
			}
			else accelCoeffVar[i][bol] = 0.0F;
		    }
		}
		
		super.extract();
		isActive = true;

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

	    public void inject() {
		for(int i=0; i<scans; i++) {
		    final int nb = scan[i].pixels;
		    final int nt = scan[i].frames;
		    final SharcFrame[] scanFrame = scan[i].frame;

		    for(int bol=0; bol < scan[i].pixels; bol++) {
			final float coeff = accelCoeff[i][bol];

			for(int t=0; t<nt; t++) {
			    final SharcFrame frame = scanFrame[t];
			    frame.readout[bol] += (1.0F-frame.transmission) * coeff * frame.acceleration;
			}
		    }
		}
		reset();
	    }

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

	    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);
		float A = (1.0F - frame.transmission) * frame.acceleration;

		for(int row=0, bol=0; row < 12; row++) for(int col=0; col<32; col++,bol++)
		    frame.readout[bol] = A * accelCoeff[i][bol];
	  
		return frame;
	    }

	    public int getNumberOfParameters() { 
		int n = 0;
		for(int i=0; i<scans; i++) n += scan[i].activePixels;
		return n; 
	    }

	    public double getRMS() {
		double sum = 0.0, sumw = 0.0;

		for(int i=0; i<scans; i++) {
		    final double maxAccel = scan[i].aclip > 0.0 ? scan[i].aclip : scan[i].maxAcceleration;
		    final int nb = scan[i].pixels;
		    final Bolometer[] pixel = scan[i].pixel;
		    
		    for(int bol=0; bol < nb; bol++) if(pixel[bol].isActive) if(accelCoeffVar[i][bol] > 0.0F) { 
			final double alpha = (1.0F-scan[i].transmission) * maxAccel;
			final float value = accelCoeff[i][bol];
			final float w = pixel[bol].weight;

			sum += w * alpha * alpha * (value * value - accelCoeffVar[i][bol]);
			sumw += w;
		    }
		}
		return sum > 0.0 ? Math.sqrt(sum / sumw / 2.0) : 0.0;
	    }

	    public void weightAdjust(double factor) {
		for(int i=0; i<scans; i++) for(int bol=0; bol<scan[i].pixels; bol++) accelCoeffVar[i][bol] /= factor;
	    }

	    public Object[] getData() { return accelCoeff; }

	    public void editHeader(Cursor cursor) throws FitsException, HeaderCardException {
		String id = "ACL_";
		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 + "RMS", getRMS(), "The RMS of the Model Parameters."));
	    }

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("ACCELERATION_RESPONSE_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		} 
		else return false;
		
		return true;
	    }

	    public boolean option(String key, String value) {
		if(key.equalsIgnoreCase("-Iaccel")) key("ACCELERATION_RESPONSE_TURN", value);
		else return false;
		
		return true;
	    }
	    
	    public void usage() {
		String info = "\n"
		    + " Options to " + name + ":\n"
		    + "\n"
		    + "  -Iaccel=       Activate model at iteration. (" + (turn == 0 ? "auto" : turn < 0 ? "off" : turn + "") + ")\n";


		System.out.println(info);
	    }
	};

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

    // Add to configs

    Model regionalCorrelationModel = new Model("RegionalCorrelations", true) {

	    public void init() {
		regionComponent = new float[scans][][][][];
		regionVar = new float[scans][];
		regionParmsForFrame = new float[scans][];
		regionParmsForBol = new float[scans][];
		
		ctemp = new Complex[32];
		for(int r=0; r<32; r++) ctemp[r] = new Complex(); 
		
		dtemp = new double[128];
		
		makeFilter();
		    
		mean = new Complex[32][64];
		weight = new double[32][64];
		correction = new Complex[32][64];
		
		for(int row=0; row<32; row++) for(int col=0; col<64; col++) {		   
		    mean[row][col] = new Complex();
		    correction[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;
		    // Below is no longer needed since proper FFT padding takes care of wrapping.
		    //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 Regional Correlations. ");
		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;
		    
		    calcCorrection(scan[i]);

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

			for(int row=0; row<32; row++) {
			    Arrays.fill(weight[row], 0.0);
			    for(int col=0; col<64; 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++) sumw += weight[row][col];
			
			double avew = sumw / scan[i].activePixels;

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

			// 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<32; wy++) for(int wx=0; wx<64; 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][64-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 / correction[row][col].x;
				
				if(pixel.isActive) {
				    float dp = frame.weight * aveFrameParms;
				    regionParmsForFrame[i][t] += dp;
				    regionParmsForBol[i][bol] += dp;
				}	
			    }
			}

			// Since sinusoidals have <x^2> = 1/2...
			regionVar[i][T] = sumw > 0.0 ? (float)(0.5 / 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 / correction[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 / correction[row][col].x);
  
		return frame;
	    }

	    protected void loadComponents(float[][][] component) {
		for(int row=0; row<32; row++) for(int col=0; col<64; 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[32-wy][wx].set(value[2], value[3]);
		    if(wx != 0) mean[wy][64-wx].set(value[2], value[3]);
		    if(wx*wy != 0) mean[32-wy][64-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 > 32) nx = 32;
		if(ny > 16) ny = 16;
		return new int[] {nx, ny};
	    }

	    public Vector2D getAxes() {
		// d/N*D ~ 1  -> D ~ N/d
		// go to 2*D

		double d = 2.0 * radius / SharcHeader.pixelSize;
		
		if(filterFunction == F_KILL) return new Vector2D(32.0/d, 16.0/d);	          
		return new Vector2D(64.0/d, 32.0/d);	      
	    }

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

		double r0 = radius / SharcHeader.pixelSize;
		double maxR2 = r0 * r0;
		double sigma = 2.0 * r0 / 2.35;
		double sigma2 = sigma * sigma;
		
		double FWHM = Math.sqrt(beamFWHM * beamFWHM + smoothFWHM * smoothFWHM);
		double beamSigma = FWHM / SharcHeader.pixelSize / 2.35;
		double beamSigma2 = beamSigma * beamSigma;

		Vector2D axes = getAxes();
		double a2 = axes.x * axes.x;
		double b2 = axes.y * axes.y;

		for(int row=0; row<32; row++) for(int col=0; col<64; col++) {
		    int dx = col > 32 ? 64 - col : col;
		    int dy = row > 16 ? 32 - 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;
		    case F_KILL :
			filter[row][col].x = 1.0; break;
		    }
			
		    //if(filterFunction != F_KILL) filter[row][col].x -= Math.exp(-r2 / (2.0 * beamSigma2));
		}
		
		if(filterFunction != F_KILL) ArrayUtil.unsecureFFT(filter, true, ctemp, dtemp);
		    		
		parameters = 0.0F; 

		double norm = filter[0][0].x;
	      
		// Set the filter components to zero outside the preserved range
		for(int row=0; row<32; row++) for(int col=0; col<64; col++) {
		    int dx = col > 32 ? 64 - col : col;
		    int dy = row > 16 ? 32 - 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 calcCorrection(SharcScan aScan) {
		for(int row=0,bol=0; row<32; row++) for(int col=0; col<64; col++,bol++) correction[row][col].zero();

		for(int row=0,bol=0; row<12; row++) for(int col=0; col<32; col++,bol++) 
		    correction[row][col].x = aScan.pixel[bol].isActive ? 1.0 : 0.0;

		ArrayUtil.unsecureFFT(correction, true, ctemp, dtemp);
		for(int row=0; row<32; row++) for(int col=0; col<64; col++) correction[row][col].math('*', filter[row][col]);
		ArrayUtil.unsecureFFT(correction, false, ctemp, dtemp);				
	    }

	    public double getSourceCorrectionFactor(double FWHM) {
		if(!precedesSource) return 1.0;
		double d = 2.0 * radius;
		return 1.0 / (1.0 - FWHM*FWHM/(d*d));
	    }

	    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."));
		cursor.add(new HeaderCard(id + "PCOR", getSourceCorrectionFactor(beamFWHM), "Point source correction factor."));
		
	    }

	    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, correction;
	    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 int F_KILL = 3;
	    final String[] typeName = { "flat", "Gaussian", "exponential", "killer" };
	};
 
   

    

    Model simulatedChoppedModel = new Model("SimulatedChopping", false) {
	    public void extract() {
		if(verbose) System.out.print("\tApproximating Legacy Chopped Reduction. ");
		isActive = false;

		timeConstant = 3;
		
		for(int i=0; i<scans; i++) {
		    final int chopT = getT(scan[i]);

		    for(int t=0, t2=chopT; t2 < scan[i].frames; t++, t2++) {
			final SharcFrame frame = scan[i].frame[t];			
			final SharcFrame f2 = scan[i].frame[t2];
			final float[] residual = frame.readout;
			final float[] r2 = f2.readout;
			final Bolometer[] pixel = scan[i].pixel;

			for(int bol=8; bol<24; bol++) if(pixel[bol].isActive) {
			    int b2 = bol % 8;
			    int b3 = b2 + 16;
			    
			    // If the off pixel is not good try a neighboring one
			    while(!pixel[b2].isActive) {
				b2++; 
				b2 %= scan[i].pixels; 
			    }
			    
			    residual[bol] -= pixel[bol].gain * (r2[b2] / pixel[b2].gain + correlated[i][t] - correlated[i][t2]);
			}			
		    }
		    scan[i].frames -= chopT;
		}

		System.out.print("Reset. ");
		offsetModel.reset();
		correlatedModel.reset();

		System.out.print("Inject. ");
		for(int m=0; m<pipeline.size(); m++) {
		    Model model = (Model) pipeline.elementAt(m);
		    if(model.isActive) model.inject();
		}

		System.out.print("Remove. ");
		if(pipeline.contains(gainModel)) pipeline.remove(gainModel);
		if(pipeline.contains(nonLinearityModel)) pipeline.remove(nonLinearityModel);

		super.extract();
		isActive = true;

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

	    public void inject() { }

	    public void reset() { super.reset(); }

	    public long getMemoryEstimate() { return 0L; }

	    public int getNumberOfParameters() { return 0; }

	    public double getRMS() { return sourceModel.getRMS(); }
	    
	    public void usage() {}

	    public boolean key(String key, String value) { return false; }

	    public boolean option(String flag, String value) { return false; }
	};

    public void measureRelativeBeamEfficiency() throws FitsException, HeaderCardException, IOException {
	Fits fits = new Fits();
	
	double[][] efficiency = new double[12][32];
	double[][] sumw = new double[12][32];
	
	double[] value = new double[12*32];
	double[] sigma = new double[12*32];

	double imageMax = reduced.getMax();
	
	float[][] image = reduced.getFloatImageClone();

	// Keep only the bright models and the source
	for(int m=pipeline.indexOf(correlatedModel)+1; m<pipeline.size(); m++) {
	    Model model = (Model) pipeline.elementAt(m);
	    if(model.isActive) if(model != sourceModel) model.inject();
	}

	for(int i=0; i<scans; i++) {
	    final Bolometer[] pixel = scan[i].pixel;
	    final float areaFactor = (float) (scan[i].pixelArea / SharcHeader.pixelArea);
	    	    
	    for(int t=0; t<scan[i].frames; t++) {
		final SharcFrame frame = scan[i].frame[t];
		final float[] residual = frame.readout;
		final boolean[] flagged = frame.flagged;
		final boolean[] masked = frame.masked;
		final float frameGain = areaFactor * frame.transmission; 
		final float fw = scan[i].weight * frame.weight;
		
		final SharcFrame sourceFrame = sourceModel.getFrame(scan[i],t);

		if(fw > 0.0F) for(int row=0, bol=0; row<12; row++) for(int col=0; col<32; col++, bol++) 
		    if(scan[i].pixel[bol].isActive) if(!flagged[bol]) {
			final float pixelGain = sourceBeforeGain ? gain(i,t,bol) : scan[i].gain * pixel[bol].gain;
			final float G = pixelGain * frameGain * beamEfficiency(i, t, bol);
			final float w = G * G * fw * pixel[bol].weight;		
			final short[] index = scan[i].getMapIndexes(t, bol);
			
			if(image[index[0]][index[1]] > 0.1 * imageMax) {
			    double imageValue = pixelGain * frameGain * image[index[0]][index[1]];
			    double measurement = sourceFrame.readout[bol] + residual[bol];
			    efficiency[row][col] += w * measurement / imageValue;
			    sumw[row][col] += w;
			}
		    }		    
	    }
       
	    for(int row=0, bol=0; row<12; row++) for(int col=0; col<32; col++, bol++) {
		efficiency[row][col] /= sumw[row][col];
		value[bol] = efficiency[row][col];
		sigma[bol] = sumw[row][col] > 0.0 ? 0.1 : -1.0;
		// Also discard points from the fitting that have too low measured efficiency
		if(value[bol] < 0.75) sigma[bol] = -1.0;
	    }
	    
	    BasicHDU hdu = Fits.makeHDU(efficiency);
	    fits.addHDU(hdu);
	}
	
       

	fits.write(new BufferedDataOutputStream(new FileOutputStream(Crush.workPath + "beam-efficiency.fits")));
	System.out.println(" Written Relative Beam Efficiency data.");
    }
    
    
    public void addScanData(Fits fits) throws FitsException, HeaderCardException, IOException {
	System.out.print(" Adding Models to FITS. ");

	// Rewrite to call scan.getHDU()
	// then add model information (skip models that are present by default)
	
	Vector models = new Vector(); 
	Vector names = new Vector(); 

	if(writeModels) {
	    for(int m=0; m<pipeline.size(); m++) {
		Model  model = (Model) pipeline.elementAt(m);
		if(model.isActive) {
		    Object[] data = model.getData();
		    if(data != null) {
			models.add(data);
			names.add(model.name);
		    }
		}
	    }
	}
	    
	else {
	    models.add(gainModel.getData());
	    names.add(gainModel.name);
	    
	    models.add(pixelWeightModel.getData());
	    names.add(pixelWeightModel.name);
	}

	System.out.println();

	models.add(getPixelFlagData());
	names.add("[Pixel Flags]");

	if(writeCovariance && fitsCovariance) {
	    models.add(getCovarianceData());
	    names.add("[Pixel Covariance]");
	}

	if(writeHistogram) {
	    models.add(getHistogramData());
	    names.add("[Pixel Histogram]");
	}

	for(int i=0; i<scans; i++) {
	    Object[][] scanData = new Object[1][models.size()];
	    for(int m=0; m<models.size(); m++) scanData[0][m] = ((Object[]) models.elementAt(m))[i];       
	    
	    BasicHDU hdu = Fits.makeHDU(scanData);	    
	    Header header = hdu.getHeader();
	    Cursor cursor = header.iterator();	
	    
	    for(int m=0; m<models.size(); m++) {		
		cursor.setKey("TFORM" + (m+1));
		cursor.add(new HeaderCard("TTYPE" + (m+1), (String)names.get(m), "Model Type"));
	    }

	    scan[i].editHeader(header);

	    fits.addHDU(hdu);
	}
    }


    void writeResiduals() {
	for(int i=0; i<scans; i++) {
	    System.out.print("\r Writing Model Residuals. " + (int)(100.0 * i/scans) + "%   ");
	    System.out.flush();
	    scan[i].writeResiduals();
	}
	System.out.print("\r Writing Model Residuals. Done.   ");
    }

    void writeIndex() {
	System.out.print(" Writing Index Tables. ");
	for(int i=0; i<scans; i++) scan[i].writeIndex();
	System.out.println();
    }

    
    void writeGains() {
	System.out.print(" Writing Gain Data.");
	for(int i=0; i<scans; i++) scan[i].writeGains();
	System.out.println();
    }

    void writeCovariances() {
	for(int i=0; i<scans; i++) {
	    System.out.print("\r Writing Covariance Data. " + (int)(100.0 * i/scans) + "%   ");
	    System.out.flush();
	    try { scan[i].writeCovariance(); }
	    catch(Exception e) {}
	}
	System.out.println("\r Writing Covariance Data. Done.  ");
    }

    void writeCovariances(int bol) {
	System.out.print(" Writing Covariance Data for Pixel [" + (covarPixel/32+1) + ":" + (covarPixel%32+1) + "]. ");
	for(int i=0; i<scans; i++) {
	    try { scan[i].writeCovariance(bol); }
	    catch(Exception e) {}
	}
	System.out.println();
    }
    

    int[][] getPixelFlagData() {
	int[][] pixelFlag = new int[scans][];
	for(int i=0; i<scans; i++) {
	    pixelFlag[i] = new int[scan[i].pixels];
	    for(int bol=0; bol<scan[i].pixels; bol++) pixelFlag[i][bol] = scan[i].pixel[bol].flag;
	}
	return pixelFlag;
    }

    
    float[][][] getCovarianceData() {
	float[][][] covar = new float[scans][][];
	for(int i=0; i<scans; i++) {
	    System.out.print("\r Adding Covariance Data to FITS. " + (int)(100.0 * i/scans) + "%    ");
	    System.out.flush();
	    covar[i] = new float[scan[i].pixels][scan[i].pixels];
	    double[][] dcovar = scan[i].getCovariance();
	    for(int b1=0; b1<scan[i].pixels; b1++) for(int b2=0; b2<scan[i].pixels; b2++) 
		covar[i][b1][b2] = (float) dcovar[b1][b2];
	}
	System.out.println("\r Adding Covariance Data to FITS. Done.");
	return covar;
    }

    void writeGlobalCovariance() {
	try {
	    Fits fits = new Fits();
	    fits.addHDU(Fits.makeHDU(getGlobalCovariance()));
	    fits.addHDU(Fits.makeHDU(N));
	    fits.write(new BufferedDataOutputStream(new FileOutputStream(Crush.workPath + "covar.global.fits")));
	    System.out.println(" Written Global Covariances.");
	}
	catch(Exception e) { e.printStackTrace(); }
    }


    int N[][];

    float[][] getGlobalCovariance() {
	float[][] covar = new float[scan[0].pixels][scan[0].pixels];
	N = new int[scan[0].pixels][scan[0].pixels];

	for(int i=0; i<scans; i++) {
	    System.out.print("\r Measuring Global Covariances. " + (int)(100.0 * i/scans) + "%   ");
	    System.out.flush();

	    double[][] scancovar = scan[i].getCovariance();
	    int[][] n = scan[i].n;

	    for(int b1 = 0; b1 < scan[i].pixels-1; b1++) for(int b2=b1+1; b2<scan[i].pixels; b2++)
		if(!Double.isNaN(scancovar[b1][b2])) {
		    covar[b1][b2] += n[b1][b2] * scancovar[b1][b2];
		    N[b1][b2] += n[b1][b2];
		}
	}

	for(int b1 = 0; b1 < scan[0].pixels-1; b1++) for(int b2=b1+1; b2<scan[0].pixels; b2++) {
	    if(N[b1][b2] > 0) covar[b1][b2] /= N[b1][b2];
	    covar[b2][b1] = covar[b1][b2];
	    N[b2][b1] = N[b1][b2];
	}

	System.out.println("\r Measuring Global Covariances. Done.       ");

	return covar;
    }

    double[][] getSpacialCorrelations() {
	double[] covar = new double[68];
	double[] weight = new double[68];
	double[] rms = new double[68];

	for(int i=0; i<scans; i++) {
	    System.out.print("\r Calculating Spacial Correlations. " + (int)(100.0*i/scans) + "%    ");
	    System.out.flush();

	    final int nt = scan[i].frames;
	    final int nb = scan[i].pixels;

	    byte[][] distance = new byte[nb][nb];
	    
	    for(int b1=0; b1<nb-1; b1++) {
		final int r1 = b1/32;
		final int c1 = b1%32;

		for(int b2=b1+1; b2<nb; b2++) {
		    final int dr = b2/32 - r1;
		    final int dc = b2%32 - c1;

		    distance[b1][b2] = (byte)Math.round(2.0 * Math.sqrt(dr*dr + dc*dc));
		}
	    }

	    for(int t=0; t<nt; t++) {
		final SharcFrame frame = scan[i].frame[t];
		final float[] residual = frame.readout;		

		for(int b1=0; b1<nb-1; b1++) if(scan[i].pixel[b1].isActive) if(!frame.flagged[b1]) {
		    final Bolometer pixel1 = scan[i].pixel[b1];
		    final float G1 = scan[i].gain * pixel1.gain;
		    
		    int r1 = b1/32;

		    //for(int b2=b1+1; b2<nb; b2++) if(scan[i].pixel[b2].isActive) if(!frame.flagged[b2]) {
		    for(int b2=b1+1; b2<nb; b2++) if(scan[i].pixel[b2].isActive) if(!frame.flagged[b2]) {
			final Bolometer pixel2 = scan[i].pixel[b2];
			final float G2 = scan[i].gain * pixel2.gain;

			final int d = distance[b1][b2];
			
			final float w = G1 * pixel1.weight * G2 * pixel2.weight;
		       
			covar[d] += w * residual[b1] * residual[b2]; 
			weight[d] += w * G1 * G2; 
		    }
		}
	    }
	    System.out.print("\r Calculating Spacial Correlations. Done.       ");
	}
	
	for(int d=0; d<covar.length; d++) if(weight[d] > 0.0) {
	    covar[d] /= weight[d];
	    rms[d] = Math.sqrt(1/weight[d]);
	}

	return new double[][] { covar, rms };
    }

    void writeSpectra(String modelName, int i, int windowSize) throws IOException {
	Model model = null;
	for(int m=0; m<pipeline.size(); m++) {
	    model = (Model) pipeline.elementAt(m);
	    if(model.name.equalsIgnoreCase(modelName)) break;
	    model = null;
	}

	if(model == null) { 
	    System.err.println(" No model " + modelName + " in pipeline. No specta written. ");
	    return;
	}

	Samples[] spectrum = model.getSpectra(i, (int)Math.ceil(windowSize/model.timeConstant));
	
	if(spectrum == null) { 
	    System.err.println(" Model " + model.name + " provides no spectral information. ");
	    return;
	}
       

	DecimalFormat df = new DecimalFormat("0.00000E0");
	String outName = Crush.workPath + model.name + "." + scan[i].serialNo + ".spec";
	PrintWriter out = new PrintWriter(new FileOutputStream(outName));

	out.println("# Power Spectral Density");
	out.println("# Creator: crush-" + Crush.version);
	out.println("#");
	out.println("# f(Hz)     \tPSD(V/Hz^0.5)");
	out.println("# ----------\t-------------");
	
	double pUnit = Unit.VOLT * Unit.VOLT / Unit.Hz;

	int nf = spectrum[0].data.length;
	for(int f=0; f<nf; f++) {
	    out.print(df.format(f * spectrum[0].dx / Unit.Hz));
	    for(int k=0; k<spectrum.length; k++)
		out.print("\t" + df.format(Math.sqrt(spectrum[k].data[f] / spectrum[k].dx / pUnit)));
	    out.println();
	}

	out.flush();
	out.close();
	
	System.out.println(" Writtem spectra to " + outName + ".");
    }


    void writeSkySpectrum(int timeWindowSize) {	
	if(skyPolynomialModel.isActive) skyPolynomialModel.inject();
	if(regionalCorrelationModel.isActive) regionalCorrelationModel.inject();
	if(sourceModel.isActive) sourceModel.inject();
	
	
	Bolometer[] pixel = scan[0].pixel;
	
	int nt = ArrayUtil.getPaddedFFTSize(timeWindowSize);
	int nT = scan[0].frames / timeWindowSize;		  

	Complex[][] tdata = new Complex[16][nt];
	for(int c=0; c<16; c++) for(int t=0; t<tdata[c].length; t++) tdata[c][t] = new Complex();
		  
	Complex[] ctemp = new Complex[16];
	double[] dtemp = new double[2*nt];
	for(int c=0; c<16; c++) ctemp[c] = new Complex();

	double[][] avepwG2 = new double[12][2];
	for(int row=0,bol=0; row<12; row++) for(int half=0; half<2; half++) {
	    for(int col=0; col<16; col++, bol++) if(pixel[bol].isActive) 
		avepwG2[row][half] += pixel[bol].weight * pixel[bol].gain * pixel[bol].gain;
	    avepwG2[row][half] /= scan[0].activePixels;
	}

	double[][] spectrum = new double[9][nt/2+1];

	for(int T=0; T<nT; T++) for(int row=0; row<12; row++) for(int half=0; half<2; half++) {
	    for(int blockt=0; blockt<nt; blockt++) tdata[row][blockt].zero();

	    for(int blockt=0, t=T*timeWindowSize; blockt<timeWindowSize; blockt++,t++) {
		SharcFrame frame = scan[0].frame[t];
		float[] readout = frame.readout;	    
		boolean[] flagged = frame.flagged;
		for(int col=0, bol=32*row+16*half; col<16; col++, bol++) if(pixel[bol].isActive) if(!flagged[bol])
		    tdata[col][blockt].x = pixel[bol].weight * pixel[bol].gain / avepwG2[row][half] * readout[bol];
	    }
	    
	    ArrayUtil.unsecureFFT(tdata, true, ctemp, dtemp);
	    
	    for(int blockt=0; blockt<nt/2; blockt++) {		
		for(int c=0; c<8; c++) spectrum[c][blockt] += tdata[c][blockt].norm();
		for(int c=8; c<16; c++) spectrum[16-c][blockt] += tdata[c][blockt].norm();
	    }
	    for(int blockt=nt/2; blockt<nt; blockt++) {		
		for(int c=0; c<8; c++) spectrum[c][nt-blockt] += tdata[c][blockt].norm();
		for(int c=8; c<16; c++) spectrum[16-c][nt-blockt] += tdata[c][blockt].norm();
	    }
	}
	
	double norm = 1.0 / (12 * 2 * nT);
	for(int F=0; F<=8; F++) for(int f=0; f<=nt/2; f++) {	
	    spectrum[F][f] *= norm;
	    if(F % 8 == 0) spectrum[F][f] *= 2.0;
	    if(f % (nt/2) == 0) spectrum[F][f] *= 2.0;
	}

	try {
	    Fits fits = new Fits();
	    fits.addHDU(Fits.makeHDU(spectrum));
	    fits.write(new BufferedDataOutputStream
		       (new FileOutputStream(Crush.workPath + "sky-psd." + scan[0].serialNo + ".fits")));
	    
	    System.out.println(" Written 2-D Sky Spectrum.");
	} catch(Exception e) {}
    }
    

    void writeModelSlice(int i, int t) {
	try {
	    Fits fits = new Fits();
	    for(int m=0; m < pipeline.size(); m++) {
		Model model = (Model) pipeline.elementAt(m);
		if(model.isActive && model.comparable) {
		    SharcFrame frame = model.getFrame(scan[i], t);
		    float[][] image = new float[12][32];
		    for(int row=0, bol=0; row<12; row++) for(int col=0; col<32; col++, bol++) {
			image[row][col] = frame.readout[bol];
		    }	
		    BasicHDU hdu = Fits.makeHDU(image);
		    hdu.addValue("EXTNAME", model.name, "The Model Name.");
		    fits.addHDU(hdu);
		}
	    }
	    fits.write(new BufferedDataOutputStream
		       (new FileOutputStream(Crush.workPath + "slice-" + scan[i].serialNo + "-" + (t+1) + ".fits")));
	    System.out.println("\tWritten Model Time Slice.");
	}
	catch(Exception e) { e.printStackTrace(); }
    }

    public void writeSpacialCorrelations() {
	double[][] data = getSpacialCorrelations();

	DecimalFormat df = new DecimalFormat("0.000000E0");	
	
	try {
	    File file = new File(Crush.workPath + "spacial-correlations.dat");
	    if(file.exists()) file.delete();
	    RandomAccessFile spacialFile = new RandomAccessFile(file, "rw");
	    
	    String header = "# Spacial Correlations Data File. \n" +
		"#\n" +
		"#D(arcsec)\tC\t\trms\n";
	    
	    spacialFile.writeBytes(header);


	    for(int d=0; d<data[0].length; d+=2) {
		spacialFile.writeBytes(0.5 * d + "\t");
		spacialFile.writeBytes(df.format(data[0][d]) + "\t");
		spacialFile.writeBytes(df.format(data[1][d]) + "\n");
	    }

	    spacialFile.close();
	}
	catch(IOException e) { e.printStackTrace(); }
       
    }

    int[][][] getHistogramData() {
	int[][][] histogram = new int[scans][][];
	for(int i=0; i<scans; i++) {
	    System.out.print("\r Adding Histogram Data to FITS. " + (int)(100.0*i/scans) + "%    ");
	    System.out.flush();
	    histogram[i] = scan[i].getHistogram(histRange, histResolution);
	}
	System.out.println("\r Adding Histogram Data to FITS. Done.   ");
	return histogram;
    }

    public boolean isPlanetary(String object) {
	for(int i=0; i<planetaryBody.length; i++) if(object.equalsIgnoreCase(planetaryBody[i])) return true;
	return false;
    }
    
    static String[] planetaryBody = { "Sun", "Moon", 
				      "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto", "Sedna",
				      
				      // The moons of Mars
				      "Deimos", "Phobos",
				      
				      // The moons of Jupiter
				      "Io", "Europa", "Ganymede", "Callisto",
				      "Metis", "Adrastea", "Amalthea", "Thebe", "Leda", "Himalia", "Lysithea", 
				      "Elara", "Ananke", "Carme", "Pasiphae", "Sinope",
				      
				      // The moons of Saturn
				      "Pan", "Atlas", "Prometheus", "Pandora", "Epimetheus", "Janus", "Mimas", "Enceladus",
				      "Tethys", "Telesto", "Calypso", "Dione", "Helene" , "Rhea", "Titan", "Hyperion", "Iapetus", "Phoebe",
				      
				      // The moons of Uranus
				      "Cordelia", "Ophelia", "Bianca", "Cressida", "Desdemona", "Juliet", "Portia", 
				      "Rosalind", "Belinda", "1986U10", "Puck", "Miranda", "Ariel", "Umbriel", "Titania",
				      "Oberon", "Caliban", "Sycorax",
				      
				      // The moons of Neptune
				      "Naiad", "Thalassa", "Despina", "Galatea", "Larissa", "Proteus", "Triton", "Nereid",
				      
				      // the moon of Pluto
				      "Charon",
				      
				      // Some Asteroids
				      "Ceres", "Pallas", "Vesta", "Hygiea", "Davida", "Interamnia",
				      "Europa", "Eunomia", "Sylvia", "Psyche", "Themis", "Euphrosyne",
				      "Cybele", "Juno", "Bamberga", "Camilla", "Hektor", "Herculina",
				      "Patientia", "Doris",
				      
				      "Hebe", "Iris", "Flora", "Metis",
				      "Parthenope", "Victoria", "Egeria", "Irene",
				      "Thetis", "Melpomene",
				      "Fortuna", "Massalia",
				      "Eros", "Gaspra",
				      "Amor", "Apollo", "Aten",

				      // Some Comets
				      "Biela", "Encke", "Faye", "Hale-Bopp", "Halley", "Hyakutake", "Kohoutek", "Swift-Tuttle", "Kegler" };
    
}

    
    

