Optimal Spectrum Extraction Package for IDL

	Design, supervision, contact:
		Dr. Joseph Harrington
		326 Space Sciences Building
		Cornell University
		Ithaca, NY 14853-6801
		jh@oobleck.astro.cornell.edu

	Programmers:
                Dara (Cornell)                          
		John Dermody (Cornell)	Version 1.02 	17 Sep 03
			Initial Implementation

OVERVIEW: 
	This package optimally extracts the object spectrum from a reduced
	data frame using the algorithm described in "An Optimal Extraction
	Algorithm for CCD Spectroscopy" (K. Horne, 1986, PASP 98:609-617).  The
	initial flattening and bias subtraction are to be done outside the
	package, along with any sky subtraction and calculations of the initial
	variance estimates.  The base procedure fits the background, generates
	a profile estimate, masks cosmic rays, and extracts the optimal
	spectrum.  Extensions to the paper include Gaussian and running average
	fits for the profile construction.

INSTALLATION:
	Copy the optspecextr directory to your idl function directory, where it
	can be seen by the IDL program.

	To test your installation, change into a directory where you can write
	files and run testoptextr.  The following four lines should appear on
	your scree if things are working normally:

	Creating simple test data
	Creating default test data
	PASS - Simple Frame
	PASS - Default Frame



The input frames and result plots from the testoptextr procedure

PREPARING DATA: 
        The optimal spectrum extraction package requires at least six
	inputs: a data image, an initial variance estimate, the gain and read
	noise of the array, and pixel locations bounding the spectrum in the
	spatial direction. The data image is the object frame from which you
	wish to extract the spectrum. The spectrum must run in the vertical
	direction, and lines of constant wavelength are assumed to be parallel
	to the horizontal direction.  The initial variance estimate is an array
	the same size as the data image, but containing each pixel's variance
	estimate.  Horne suggests abs(Data) / EPADU + ReadNoise ^ 2 as a good
	estimate.  The spatial bounds should be set widely because the optimal
	extraction program minimizes the influence of noisy pixels far from the
	peak of the profile.
	
	If part of your preprocessing is sky subtraction, set skyvarim and
	rnsky to the sky variance image and sky read noise, respectfully.
	These values will be added to the variance image as it is refined to
	reflect the increase in noise attributed to sky subtraction.

	The importance in an accurate variance image can not be over-stressed.
	The variance image is used to calculate which pixels are off because
	of noise, and which are cosmic rays.  It is also used to weight the
	pixels for the final optimal extraction.  To accuratly predict the
	noise of the data, the skyvarim must include levels for all
	pre-processing steps that might introduce noise.  Any noise source
	must be included except readnoise for the data image, and photon noise
	from the calculated background and object spectrum.  If the data has
	additional noise sources that are not represented, the routine will
	mask good pixels.  If it overestimates noise sources, cosmic rays can
	slip through, and the final spectrum will be inaccurate.

	Any high variance loctions (such as unilluminated sections) where
	extraction of the spectrum is impossible should be trimed.  If that is
	not possible, then mask off that area, or make sure the variance image
	is correspondingly high.  Otherwise the routine will incorrectly try
	to fit using these data.

EXPLANATION:
        This package implements steps 3 through 8 of that Horne's algorithm.  
	The user is responsible for steps 1 and 2. The steps and 
        implementation follow.

        Step 1: Initial image processing - The user is responsible
        Step 2: Initial variance estimates - The user is responsible.
                Horne suggests (RMS readnoise)^2 + (Reduced frame)/(Epadu)
        Step 3: Fit sky background - fitbg.pro
                This function uses procvect.pro on each wavelength using
                the initial variance estimates as weights, and ignoring
                the data within the object spectrum
        Step 4: Extract standard spectrum - stdspec.pro
                Sums each wavelength within the limits
        Step 5: Construct spatial profile
        Step 6: Revise variance estimates - fitprof.pro
                Initially creates the spatial profile by 
                (reduced - background) / (standard spectrum)
                Then uses procvect.pro on each spatial column, using the
                variance / spectrum^2 as weights.  All values are then
                made positive, and each wavelength sums to 1
        Step 7: Mask cosmic ray hits
        Step 8: Extract optimal spectrum -
                Uses procvect.pro to optimally extract the spectrum for 
                each wavelength.
        Step 9: Iteration - The iteration scheme we used is suggested by
                Horne.  Iterate 3 by itself to find cosmic rays in the 
                background section. Iterate 5 and 6 to find the spatial
                profile image, but do not use the mask found in step 5 for
                the extraction. Rather, next iterate 6, 7, and 8 to
                mask cosmic rays and optimally extract the spectrum.  Also, by
		setting REPMAIN, the entire function will loop until a self-
		consistent solution is found.

        Procvect.pro: Iteratively fits a function, removing one extreme pixel
		      per iteration until none are found.  It begins by either
		      fitting the data or optimally extracting the spectrum.
		      The pixel with the largest residual (if it exceeds
		      THRESH) is marked as bad, variances are recalculated, and
		      if another bad pixels is found, the process loops.  Once
		      no bad pixels are found, either the fit is evaluated at
		      all values and returned, or a final extraction is
		      returned.
                
	Fitbg.pro:    Sends each spectral vector to procvect.pro, using a
		      polynomial fit and ignoring the data within the object
		      limits. This background fit is evaluated and subtracted
		      at all locations.

        Fitprof.pro: Driver for spatial profile fitting.  This can be done
		      either using a Gaussian fit along the spatial dimension,
		      or a polynomial fit or boxcar average along the spectral
		      dimension, based on the user's decision. The routine sets
		      up the calculation and calls procvect.pro on each vector
		      in the data, building a 2D frame.

	Stdextr.pro:  Calculates standard extraction, totaling each spectral
		      row within the spectrum bounds.

        Extrspec.pro: Sends each row of constant wavelength procvect.pro, which
	              optimally estimates the spectrum amplitude at that point,
		      and calculates the variance.

IDL FUNCTIONS:
	optspecextr.pro - driver routine
        fitbg.pro       - Step 3
        stdspec.pro     - Step 4
        fitprof.pro     - Steps 5,6
        extrspec.pro    - Steps 6,7,8
        procvect.pro    - Smooths the vector, or extracts the spectrum value
	
EXAMPLE/TEST DATA:
	In the optspecextr/images directory there are four example.fits 
	files.  These files provide input files for the following three
	example runs.  Run each of these tutorials from the optspecextr/images
	directory


	1) Frame with flat background and perpendicular trace

	This is the most basic case. First load the FITS file into data:
	IDL> frame1 = readfits('ex1.fits', header1)

	Next, locate the read noise and gain levels for the CCD array.  This
	FITS file contains that information in the header.
	IDL> Q =  sxpar(header1, 'EPADU') 
	IDL> rn = sxpar(header1, 'RDNOISE') / Q 

	Now we need to select the object spectrum limits.  This can be done
	interactively using a plot of the frame.  The trace is perpendicular,
	any row will do.
	IDL> plot, frame1[*,0]
	IDL> plot, frame1[200:300,0]

	It seems the spectrum spans columns 240 to 270.
	IDL> x1 = 240
	IDL> x2 = 270
	
	Now we need to prepare the data and variance images.  Since there is
	no sky frame, the data image is the object frame.  The inital estimate
	for the variance image is easily calculated.
	IDL> dataim = frame1
	IDL> varim = abs(frame1) / Q + rn^2

	With all of the data now prepared, run optspecextr.
	IDL> opspec1 = optspecextr(dataim, varim, rn, Q, x1, x2)
	IDL> plot, opspec1
	

Object frame and extracted spectrum from a simple frame
	
	2) Object and Sky frame
	
	When working with IR data and a sky frame is to be used for sky
	subtraction, that information needs to be passed onto the optimal
	extraction procedure.

	The preperation is identical to example 1.
	IDL> objframe2 = readfits('ex2obj.fits', objheader2)
	IDL> skyframe2 = readfits('ex2sky.fits', skyheader2)
	IDL> Q =     sxpar(objheader2, 'EPADU') 
	IDL> rn =    sxpar(objheader2, 'RDNOISE') / Q 
	IDL> rnsky = sxpar(skyheader2, 'RDNOISE') / Q 
	IDL> x1 =    240
	IDL> x2 =    270

	The data image is object frame - sky frame.  An additional sky
	variance image must also be created.  The initial variance estimate
	must reflect the additional image processing step.
	IDL> dataim =   objframe2 - skyframe2
	IDL> skyvarim = abs(skyframe) / Q + rnsky^2
	IDL> varim =    abs(skyframe + objframe) / Q + rnsky^2 + rn^2

	The call for optspecextr uses the SKYVARIM keyword.
	IDL> opspec2 = optspecextr(dataim, varim, rn, Q, x1, x2, $
	                           skyvarim=skyvarim)
	IDL> plot, opspec2
	

Object and sky frames, with resulting plot

	3) Frame with curved trace

	A frame with a curved trace can not fit the profile using a polynomial
	fit along the wavelength dimension because the profile changes to
	quickly.  Instead try either the boxcar filter, or gaussian fit.

	The preperation begins the same.
	IDL> frame3 = readfits('ex3.fits', header3)
	IDL> Q =  sxpar(header3, 'EPADU') 
	IDL> rn = sxpar(header3, 'RDNOISE') / Q 
	
	However, the curved trace makes setting spectrum limits more
	difficult.  Take a look at the frame to get the general idea.
	IDL> tv, frame3
	
	The minimum occurs and the top and bottom of the frame, while the
	maximum occurs about halfway down the frame.
	IDL> plot, frame3[200:300,0]
	IDL> plot, frame3[200:300,255]
	IDL> plot, frame3[200:300,511]

	It seems to span from 240 to 280
	IDL> x1 = 240
	IDL> x2 = 280

	Now run optspecextr with either the FITGAUSS or FITBOXCAR keyword.
	IDL> dataim = frame3
	IDL> varim = abs(frame3) / Q + rn^2
	IDL> opspec3gauss = optspecextr(dataim, varim, rn, Q, x1, x2, $
	                                /fitgauss)
	IDL> dataim = frame3
	IDL> varim = abs(frame3) / Q + rn^2
	IDL> opspec3rsa = optspecextr(dataim, varim, rn, Q, x1, x2, $
	                              /fitboxcar)



Exctraction using FITGAUSS and FITRSA repsectfully

	4) Straightening

	A curved frame may also be corrected using the STRAIGHTEN keyword.
        This technique upsamples the data frame by 10 pixels, shifts each
        row by an estimated center correction.  Then the usual fitting methods
        can be used to fit the expanded data.  Before use the profile is
        downsampled.

	The preperation begins the same.
	IDL> frame4 = readfits('ex4.fits', header4)
	IDL> Q =  sxpar(header4, 'EPADU') 
	IDL> rn = sxpar(header4, 'RDNOISE') / Q 
	
	In order to show the correction more clearly this frame is only curved
	in the x direction.  The shift4.fits file contains the shift amount
	IDL> traceact = readfits('shift4.fits')
        IDL> plot, traceact
	
	It seems to span from 235 to 270
	IDL> x1 = 235
	IDL> x2 = 270

	Now run optspecextr with the straighten keyword.  The TRACEEST keyword
	return the estimated shift amount.  With the expantion, straightening
	may take much longer than other techniques.
	IDL> dataim = frame4
	IDL> varim = abs(frame4) / Q + rn^2
	IDL> opspec4 = optspecextr(dataim, varim, rn, Q, x1, x2, $
	                           /straighten, traceest=traceest)
	
        IDL> plot, traceest
        IDL> plot, (traceest - 4) - traceact


Comparison between actual center shift and estmated



DEBUGGING ASSISTANCE:
        If the program does not work as expected the routine has specific
        keywords designed to assist in debugging. 
     
        The VERBOSE keyword controls the level of output to the user.  By
        default it displays only fatal errors. With the level at 1, it will
        collect stats on the execution of the program, and if neccissary
        outputs them on a single line at the conclusion.  If VERBOSE is set to
        level 2, it will update the user on its location throughout the
        program.  Setting VERBOSE to 3 will give you warnings as the program
        believes the data is not acting according to plan.  
        
        With VERBOSE at 4 the program will attempt to plot the data as it
        executes.  The PLOTTYPE keyword controls what type of plot to use.  1
        will plot shaded surfaces of the raw data about to be processed and
        the fitted data recently analyzed.  PLOTTYPE at 2 will give a summary
        of each vector before processing, and PLOTTYPE 3 will plot each
        iteration of cosmic ray identification.

        Setting VERBOSE to 5 keyword stops the exectution during the
        background fitting and profile fitting sections of the algorithm.  The
        keyword stops exectution right before entering the procvect routine
        (which actually does the fitting) and triggers procvect to stop after
        every iteration of the fit. The when stopped, the idl ".continue" or
        ".c" will continue execution until the next stop is encountered.
        When verbose is at 5 it will plot both PLOTTYPE 2 and 3 plots.


FAQ: 
        1) Q: Why is everything in double precision when the input data is 
	most likely single precision?

	A: The short answer is it prevents a systematic bias is the final
	output.  The cause of this bias was traced to an error in floating
	point representation of the profile image.  When a perfect (no noise,
	straight trace, no bad pixels, ...) image was tested, there was
	symetric error at machine level precision in the profile image created
	by looping procvect.pro.  However, as this image was scaled to make
	sure each wavelength summed to 1, the error became off-center.  This
	error was componded in later steps and eventually showed up in the
	final spectrum.  There was no way to scale a floating point array
	without this occuring, so instead everything is done in double
	precision.

	2) Q: Help! The program keeps masking all of my data points. What's
	wrong?

	A: The probable answer lies in the variance image creation.  The
	routine uses and iterative sigma-cliping calculation using: 
	  Residual = (Data - Background - Spectrum*Profile)^2 / Variance 
	If the variance estimate is wrong, the procvect routine can end up
	clipping way too many or too few pixels easily.  The background fiting
	and the first pass of the profile fitting use the inital variance
	estimates that are passed in.  However, each subsequent pass in
	profile fitting, and in the optimal extraction iteration, the variance
	image used is recalculated based on: 
	  Variance = Readnoise^2 + Abs(Spectrum*Profile + Background) / EPADU 
	Therefore, if your estimates for read noise or gain are off, each
	revisted estimate will be inaccurate.  The sigma-clipping routine is
	sensitve to both rdnoise and gain. If background pixels are wrongly
	masked, the readnoise is proabably off.  If the high-intensity pixels
	are masked, then it is probably the gain.  

	Another problem could stem from inaccurate estimation of the fit.  If
	the profile image is changing more rapidly than can be modeled by a
	third degree polynomial, the degree must be increased, or a different
	fit used.  Similarly, if skylines are curved too much (more than three
	pixels spell distator for polynomial fitting) the background fitting
	routine won't be able to model the background.