    float[][][] wave; // scan, T, coeff
    float[][][] waveVar; // scan, T, coeff
    float[][] waveParmsForFrame; // scan, t
    float[][] waveParmsForBol; // scan, bol

    // Horizontal Waves at the detector voltage stage
    Model waveletModel = new Model("Wavelets", true) {

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

		wavelet = new SharcWaveFit(order);		
		order = wavelet.parameter.length;

		wave = new float[scans][][];
		waveVar = new float[scans][][]; 
		waveParmsForBol = new float[scans][];
		waveParmsForFrame = new float[scans][];
	    				
		value = new double[32];
		weight = new double[32]; 
		sigma = new double[32];

		termwsum = new double[order];
		
		for(int i=0; i<scans; i++) {
		    final int waveT = getT(scan[i]);
		    final int nT = (int) Math.ceil((float)scan[i].frames/waveT);
	
		    wave[i] = new float[nT][order];
		    waveVar[i] = new float[nT][order];
		    waveParmsForBol[i] = new float[scan[i].pixels];
		    waveParmsForFrame[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 Wavelets (order " + order + ").");
		
		if(wave == null) init();


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

		    Arrays.fill(waveParmsForBol[i], 0.0F);
		    Arrays.fill(waveParmsForFrame[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);
			
			for(int row=0,bol=0; row<12; row++) for(int col=0; col<32; col++,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*waveT; blockt<waveT; blockt++,t++) if(t < nt) {
				final SharcFrame frame = scanFrame[t];
				if(!frame.flagged[bol]) {
				    final float w = pwG2 * frame.weight;
				    value[col] += w * frame.readout[bol] / G;
				    weight[col] += w;
				}
			    }
			}		       
						
			for(int col=0; col<32; col++) {
			    if(weight[col] > 0.0) {
				value[col] /= weight[col];
				sigma[col] = Math.sqrt(1.0 / weight[col]);
			    }
			    else sigma[col] = -1.0;
			}
			
			
			wavelet.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++) if(weight[col] > 0.0)
			    termwsum[p] += weight[col] * wavelet.aterm[col][p];

			for(int row=0,bol=0; row<12; row++) for(int col=0; col<32; col++,bol++) {
			    final Bolometer pixel = scan[i].pixel[bol];
			    final float G = pixel.gain;
			    final float dmodel = G * (float)wavelet.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 += wavelet.aterm[col][p] / termwsum[p];
			    avePointParms *= G * G * pixel.weight;
			    
			    for(int blockt=0,t=T*waveT; blockt<waveT; 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; 
				    waveParmsForFrame[i][t] += dp;
				    waveParmsForBol[i][bol] += dp;
				}				
			    }
			}
			
			for(int p=0; p<order; p++) {
			    wave[i][T][p] += (float)wavelet.parameter[p];
			    waveVar[i][T][p] = (float)wavelet.covar[p][p];
			}
		    }
		    
		}
		
		super.extract();
		isActive = true;

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

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

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

			for(int p=0; p<order; p++) wavelet.parameter[p] = wave[i][T][p];
			
			for(int row=0,bol=0; row<12; row++) for(int col=0; col<32; col++,bol++) {
			    final float G = pixel[bol].gain;
			    final float model = pixel[bol].gain * (float)wavelet.valueAt(col);

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

	    public SharcFrame getFrame(SharcScan aScan, int t) {
		wavelet.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<order; p++) wavelet.parameter[p] = wave[i][T][p];
		for(int row=0,bol=0; row<12; row++) for(int col=0; col<32; col++,bol++) frame.readout[bol] = (float)wavelet.valueAt(col);

		return frame;
	    }

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

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

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

		    for(int T=0; T<nT; T++) for(int p=1; p<order; p++) {
			double value = wave[i][T][p];
			var += 0.5 * G2 * (value*value - waveVar[i][T][p]);
		    }
		}
		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 = wave[i][t / getT(scan[i])];
		for(int p=0; p < order; p++) wavelet.parameter[p] = model[p];

		for(int row=0,bol=0; row<12; row++) for(int col=0; col<32; col++,bol++) dmodel[bol] += G * pixel[bol].gain * wavelet.valueAt(col);
	    }


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

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

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

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

	    public boolean key(String key, String value) {
		if(key.equalsIgnoreCase("WAVELET_TURN")) {
		    if(value.equalsIgnoreCase("auto")) turn = 0;
		    else if(value.equalsIgnoreCase("off")) turn = -1;
		    else turn = Integer.parseInt(value);
		}
		else if(key.equalsIgnoreCase("WAVELET_T")) {
		    if(value.equals("*") || value.equalsIgnoreCase("auto")) autoTimeScale = true;
		    else {
			timeConstant = Integer.parseInt(value);
			autoTimeScale = false;
		    }
		}
		else if(key.equalsIgnoreCase("WAVE_WAVELET_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("-Iwave")) key("WAVELET_TURN", value);
		else if(key.equalsIgnoreCase("-waveT")) key("WAVELET_T", value);
		else if(key.equalsIgnoreCase("-waveOrder")) key("WAVELET_ORDER", value); 
		else return false;
		
		return true;
	    }

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

		System.out.println(info);
	    }
	    SharcWaveFit wavelet;
	    int order = 1;

	    double[] value, weight, sigma, termwsum;
	};
