Index: trunk/ppImage/src/ppConfigLoad.c
===================================================================
--- trunk/ppImage/src/ppConfigLoad.c	(revision 5857)
+++ 	(revision )
@@ -1,34 +1,0 @@
-# include "ppImage.h"
-
-bool ppConfigLoad (ppConfig *config, int argc, char **argv) {
-
-    ppConfig config;
-
-    // Parse the configurations
-    config->site = NULL;            // Site configuration
-    config->camera = NULL;          // Camera configuration
-    config->recipe = NULL;          // Recipe configuration
-
-    if (! pmConfigRead(&config->site, &config->camera, &config->recipe, &argc, argv, RECIPE)) {
-        psErrorStackPrint(stderr, "Can't find site configuration!\n");
-        exit(EXIT_FAILURE);
-    }
-
-    // Parse other command-line arguments
-    config->arguments = psMetadataAlloc(); // The arguments, with default values
-
-    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-bias", 0, "Name of the bias image", "");
-    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-dark", 0, "Name of the dark image", "");
-    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-flat", 0, "Name of the flat-field image", "");
-    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-mask", 0, "Name of the mask image", "");
-    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "-chip", 0, "Chip number to process (if positive)", -1);
-    if (! psArgumentParse(config->arguments, &argc, argv) || argc != 3) {
-        printf("\nPan-STARRS Phase 2 processing\n\n");
-        printf("Usage: %s INPUT.fits OUTPUT.fits\n\n", argv[0]);
-        psArgumentHelp(config->arguments);
-        psFree(config->arguments);
-        exit(EXIT_FAILURE);
-    }
-    config->database = pmConfigDB(config->site);  // Database handle
-    return true;
-} 
Index: trunk/ppImage/src/ppDataLoadMeta.c
===================================================================
--- trunk/ppImage/src/ppDataLoadMeta.c	(revision 5857)
+++ 	(revision )
@@ -1,54 +1,0 @@
-# include "ppImage.h"
-
-bool ppDataLoadMeta (ppData *data, ppConfig *config, char **argv) {
-
-    const char *inputName = argv[1];    // Name of input image
-    const char *outputName = argv[2];   // Name of output image
-
-    // Open the input image
-    psLogMsg("phase2", PS_LOG_INFO, "Opening input image: %s\n", inputName);
-    data->inputFile = psFitsOpen(inputName, "r"); // File handle for FITS file
-    if (! data->inputFile) {
-        psErrorStackPrint(stderr, "Can't open input image: %s\n", inputName);
-        exit(EXIT_FAILURE);
-    }
-    data->header = psFitsReadHeader(NULL, data->inputFile); // FITS header
-
-    // Open the output image
-    // We do it here so that we don't process the whole lot and then find out we can't open the output file
-    data->outputFile = psFitsOpen(outputName, "w");
-    if (! data->outputFile) {
-        psErrorStackPrint(stderr, "Can't open output image: %s\n", outputName);
-        exit(EXIT_FAILURE);
-    }
-
-    // Open the output mask
-    // XXX EAM : not done
-
-    // Get camera configuration from header if not already defined
-    if (! config->camera) {
-        config->camera = pmConfigCameraFromHeader(config->site, config->header);
-        if (! config->camera) {
-            psErrorStackPrint(stderr, "Can't find camera configuration!\n");
-            exit(EXIT_FAILURE);
-        }
-   } else if (! pmConfigValidateCamera(config->camera, config->header)) {
-        psError(PS_ERR_IO, true, "%s does not seem to be from the camera.\n", inputName);
-        exit(EXIT_FAILURE);
-    }
-
-    // determine the correct recipe to use
-    if (! config->recipe && !(config->recipe = pmConfigRecipeFromCamera(config->camera, RECIPE))) {
-        psErrorStackPrint(stderr, "Can't find recipe configuration!\n");
-        exit(EXIT_FAILURE);
-    }
-
-    // Construct camera in preparation for reading
-    data->input = pmFPAConstruct(camera);
-    data->mask  = pmFPAConstruct(camera);
-    data->bias  = pmFPAConstruct(camera);
-    data->dark  = pmFPAConstruct(camera);
-    data->flat  = pmFPAConstruct(camera);
-
-    return true;
-}
Index: trunk/ppImage/src/ppDataLoadPixels.c
===================================================================
--- trunk/ppImage/src/ppDataLoadPixels.c	(revision 5857)
+++ 	(revision )
@@ -1,137 +1,0 @@
-# include "ppImage.h"
-
-// XXX EAM : this function reads in all data from all chips in one pass
-bool ppDataLoadPixels (ppData *data, ppRecipe *options, ppConfig *config) {
-
-    // if we are using a single chip, select it
-    if (options->chipNum >= 0) {
-        if (! pmFPASelectChip(data->input, options->chipNum)) {
-            psErrorStackPrint(stderr, "Chip number %d doesn't exist in camera.\n", options->chipNum);
-            exit(EXIT_FAILURE);
-        }
-	if (! pmFPASelectChip(data->bias, options->chipNum)) psAbort (__func__, "invalid missing bias chip");
-	if (! pmFPASelectChip(data->dark, options->chipNum)) psAbort (__func__, "invalid missing dark chip");
-	if (! pmFPASelectChip(data->mask, options->chipNum)) psAbort (__func__, "invalid missing mask chip");
-
-        psLogMsg("phase2", PS_LOG_INFO, "Operating only on chip %d\n", options->chipNum);
-    }
-    
-    // Read in the input pixels
-    if (! pmFPARead(data->input, data->inputFile, data->header, config->database)) {
-        psErrorStackPrint(stderr, "Unable to populate camera from input FITS file\n");
-        exit(EXIT_FAILURE);
-    }
-    psFitsClose(data->inputFile);
-
-    // Generate input mask and weight frame
-    // ** note the distinction between the input->mask and the external mask **
-    // XXX is this a sensible distinction? **
-    p_pmHDU *hdu = NULL;
-    if (data->input->hdu) { hdu = data->input->hdu; }
-    psArray *chips = data->input->chips;
-    for (int chipNum = 0; chipNum < chips->n; chipNum++) {
-	pmChip *chip = chips->data[chipNum];
-	if (! chip->valid) { continue; } 
-	if (chip->hdu) { hdu = chip->hdu; }
-
-	psArray *cells = chip->cells;
-	for (int cellNum = 0; cellNum < cells->n; cellNum++) {
-	    pmCell *cell = cells->data[cellNum];
-	    if (! cell->valid) { continue; }
-	    if (cell->hdu) { hdu = cell->hdu; }
-
-	    hdu->masks = psArrayAlloc(hdu->images->n);
-	    hdu->weights = psArrayAlloc(hdu->images->n);
-	    psArray *readouts = cell->readouts;
-	    for (int readNum = 0; readNum < hdu->images->n; readNum++) {
-		psImage *image = hdu->images->data[readNum];
-		psImage *mask = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
-		psImage *weight = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
-		pmReadout *readout = readouts->data[readNum];
-
-		// XXX EAM : this needs a gain and read-noise for the weight
-		// XXX EAM : should weight be stdev or var ?
-		for (int j = 0; j < image->numRows; j++) {
-		    for (int i = 0; i < image->numCols; i++) {
-			mask->data.U8[j][i] = 0;
-			weight->data.F32[j][i] = sqrtf(image->data.F32[j][i]);
-		    }
-		}
-		hdu->masks->data[readNum] = mask;
-		hdu->weights->data[readNum] = weight;
-		readout->mask = psMemIncrRefCounter(mask);
-		readout->weight = psMemIncrRefCounter(weight);
-	    }
-	}
-    }
-
-    // Load the calibration frames, if required
-    // XXX : by using the entries like data->biasFile we can read in the inner loop
-    if (options->doBias) {
-        psFits *biasFile = psFitsOpen(options->biasName, "r"); // File handle for bias
-        psMetadata *biasHeader = psFitsReadHeader(NULL, biasFile); // FITS header for bias
-        if (! pmConfigValidateCamera(config->camera, biasHeader)) {
-            psError(PS_ERR_IO, true, "Bias (%s) does not seem to be from the same camera.\n", options->biasName);
-            exit(EXIT_FAILURE);
-        }
-        psLogMsg("phase2", PS_LOG_INFO, "Opening bias image: %s\n", options->biasName);
-        if (! pmFPARead(data->bias, biasFile, biasHeader, config->database)) {
-            psErrorStackPrint(stderr, "Unable to populate bias camera from fits FITS: %s\n", options->biasName);
-            exit(EXIT_FAILURE);
-        }
-        psFree(biasHeader);
-        psFitsClose(biasFile);
-    }
-
-    if (options->doDark) {
-        psFits *darkFile = psFitsOpen(options->darkName, "r"); // File handle for dark
-        psMetadata *darkHeader = psFitsReadHeader(NULL, darkFile); // FITS header for dark
-        if (! pmConfigValidateCamera(config->camera, darkHeader)) {
-            psError(PS_ERR_IO, true, "Dark (%s) does not seem to be from the same camera.\n", options->darkName);
-            exit(EXIT_FAILURE);
-        }
-        psLogMsg("phase2", PS_LOG_INFO, "Opening dark image: %s\n", options->darkName);
-        if (! pmFPARead(data->dark, darkFile, darkHeader, config->database)) {
-            psErrorStackPrint(stderr, "Unable to populate dark camera from fits FITS: %s\n", options->darkName);
-            exit(EXIT_FAILURE);
-        }
-        psFree(darkHeader);
-        psFitsClose(darkFile);
-    }
-
-    if (options->doMask) {
-        psFits *maskFile = psFitsOpen(options->maskName, "r"); // File handle for mask
-        psMetadata *maskHeader = psFitsReadHeader(NULL, maskFile); // FITS header for mask
-        if (! pmConfigValidateCamera(config->camera, maskHeader)) {
-            psError(PS_ERR_IO, true, "Mask (%s) does not seem to be from the same camera.\n", options->maskName);
-            exit(EXIT_FAILURE);
-        }
-        psLogMsg("phase2", PS_LOG_INFO, "Opening mask image: %s\n", options->maskName);
-        if (! pmFPARead(data->mask, maskFile, maskHeader, config->database)) {
-            psErrorStackPrint(stderr, "Unable to populate mask camera from fits FITS: %s\n", options->maskName);
-            exit(EXIT_FAILURE);
-        }
-        psFree(maskHeader);
-        psFitsClose(maskFile);
-    }
-
-    if (options->doFlat) {
-        psFits *flatFile = psFitsOpen(options->flatName, "r"); // File handle for flat
-        if (! flatFile) {
-            psErrorStackPrint(stderr, "Can't open flat image: %s\n", options->flatName);
-            exit(EXIT_FAILURE);
-        }
-        psMetadata *flatHeader = psFitsReadHeader(NULL, flatFile); // FITS header for flat
-        if (! pmConfigValidateCamera(config->camera, flatHeader)) {
-            psError(PS_ERR_IO, true, "Flat (%s) does not seem to be from the same camera.\n", options->flatName);
-            exit(EXIT_FAILURE);
-        }
-        psLogMsg("phase2", PS_LOG_INFO, "Opening flat image: %s\n", options->flatName);
-        if (! pmFPARead(data->flat, flatFile, flatHeader, config->database)) {
-            psErrorStackPrint(stderr, "Unable to populate flat camera from fits FITS: %s\n", options->flatName);
-            exit(EXIT_FAILURE);
-        }
-        psFree(flatHeader);
-        psFitsClose(flatFile);
-    }
-
Index: trunk/ppImage/src/ppImage.c
===================================================================
--- trunk/ppImage/src/ppImage.c	(revision 5857)
+++ trunk/ppImage/src/ppImage.c	(revision 5858)
@@ -5,23 +5,21 @@
     ppData data;
     ppConfig config;
-    ppRecipe options;
+    ppOptions options;
 
     psTimerStart("phase2");
 
     // Parse the configuration and arguments
-    ppConfigLoad (&config, argc, argv);
+    ppImageConfig (&config, argc, argv);
 
     // Open the input image, output image, output mask
     // Get camera configuration from header if not already defined
     // Construct camera in preparation for reading
-    ppDataLoadMeta (&data, &config, argv);
+    ppImageParseCamera (&data, &config, argv);
 
     // Set various tasks (define optional operations)
-    ppRecipeInit (&options, &config)
+    ppImageOptions (&data, &options, &config);
 
-    // Read in the input pixels
-    // Generate mask and weight frame
-    // Read in detrend pixels
-    ppDataLoadPixels (&data, &options, &config);
+    // open detrend images, load headers, optionally load pixels
+    ppImageParseDetrend (&data, &options, &config);
 
     // Image Arithmetic Loop
@@ -29,5 +27,5 @@
 
     // output options
-    ppImageOutput (&data, &options, &config);
+    // ppImageOutput (&data, &options, &config);
 
     exit (0);
Index: trunk/ppImage/src/ppImage.h
===================================================================
--- trunk/ppImage/src/ppImage.h	(revision 5857)
+++ trunk/ppImage/src/ppImage.h	(revision 5858)
@@ -23,4 +23,11 @@
 #define RECIPE "PHASE2"                 // Name of the recipe to use
 
+typedef enum {
+    PP_LOAD_NONE,
+    PP_LOAD_FPA,
+    PP_LOAD_CHIP,
+    PP_LOAD_CELL,
+} ppImageLoadDepth;
+
 typedef struct {
     psMetadata *site;
@@ -33,12 +40,13 @@
 typedef struct {
     bool doMask;			// Mask bad pixles
-    bool doNonLin;			// Non-linearity correction
     bool doBias;			// Bias subtraction
     bool doDark;			// Dark subtraction
-    bool doOverscan;			// Overscan subtraction
     bool doFlat;			// Flat-field normalisation
     bool doFringe;			// Fringe subtraction
     bool doSource;			// Source identification and photometry
     bool doAstrom;			// Astrometry
+    bool preLoad;
+
+    bool doOverscan;			// Overscan subtraction
     int overscanBins;			// Number of pixels per bin for overscan
     psStats *overscanStats;		// Statistics for overscan
@@ -47,27 +55,56 @@
     pmOverscanAxis overscanMode;	// Axis for overscan
 
-    char *maskName;
-    char *biasName;
-    char *darkName;
-    char *flatName;
-} ppRecipe;
+    bool doNonLin;			// Non-linearity correction
+    psDataType nonLinearType;
+    psMetadataItem *nonLinearData;
+    void *nonLinearSource;
+
+    ppImageLoadDepth imageLoadDepth;
+
+} ppOptions;
 
 typedef struct {
+    char *filename;
+    pmFPA *fpa;
+    psFits *fits;
     psMetadata *header;
+} ppFPA; 
 
-    pmFPA *input;
-    pmFPA *weight;
-    pmFPA *mask;
-    pmFPA *bias;
-    pmFPA *dark;
-    pmFPA *flat;
-    pmFPA *fringe;
+typedef struct {
+    ppFPA *input;
+    ppFPA *mask;
+    ppFPA *bias;
+    ppFPA *dark;
+    ppFPA *flat;
+    ppFPA *fringe;
+    ppFPA *process;
+} ppData;
 
-    psFits *inputFile;
-    psFits *weightFile;
-    psFits *maskFile;
-    psFits *biasFile;
-    psFits *darkFile;
-    psFits *flatFile;
-    psFits *fringeFile;
-} ppData;
+typedef struct {
+    pmCell *input;
+    pmCell *mask;
+    pmCell *bias;
+    pmCell *dark;
+    pmCell *flat;
+} ppDetrend;
+
+bool ppImageConfig (ppConfig *config, int argc, char **argv);
+bool ppImageLoadPixels (ppFPA *input, ppFPA *process, psDB *db, int nChip, int nCell);
+bool ppImageLoop (ppData *data, ppOptions *options, ppConfig *config);
+bool ppImageOptions (ppData *data, ppOptions *options, ppConfig *config);
+bool ppImageParseCamera (ppData *data, ppConfig *config, char **argv);
+bool ppImageParseDetrend (ppData *data, ppOptions *options, ppConfig *config);
+
+bool ppReadoutWeights (pmReadout *readout);
+
+bool ppDetrendCell (ppDetrend *detrend, ppOptions *options, ppConfig *config);
+
+bool ppDetrendMask (pmCell *cell, pmReadout *input, pmReadout *mask);
+bool ppDetrendNonLinear (pmCell *cell, pmReadout *input, ppOptions *options);
+bool ppDetrendNonLinearLookup (pmReadout *input, psMetadataItem *dataItem);
+bool ppDetrendNonLinearPolynomial (pmReadout *input, psMetadataItem *dataItem);
+bool ppDetrendBias (pmCell *inputCell, pmReadout *inputReadout, pmReadout *pedestal, ppOptions *options);
+pmReadout* ppDetrendPedestal (pmReadout *pedestal, pmCell *input, pmReadout *bias, pmReadout *dark, float darkTime, ppOptions *options);
+pmReadout* ppDetrendSelectFirst (pmCell *cell, char *name, bool doThis);
+
+bool ppFPAOpen (ppFPA *fpa, psMetadata *camera, char *name, bool doThis);
Index: trunk/ppImage/src/ppImageConfig.c
===================================================================
--- trunk/ppImage/src/ppImageConfig.c	(revision 5858)
+++ trunk/ppImage/src/ppImageConfig.c	(revision 5858)
@@ -0,0 +1,42 @@
+# include "ppImage.h"
+
+bool ppImageConfig (ppConfig *config, int argc, char **argv) {
+
+    // Parse the configurations
+    config->site = NULL;            // Site configuration
+    config->camera = NULL;          // Camera configuration
+    config->recipe = NULL;          // Recipe configuration
+
+    // XXX - this should be split into a function to parse argc,argv 
+    //       and a second to read the config files.  
+    //       all argc,argv should be interpretted before work is done.
+    if (! pmConfigRead(&config->site, &config->camera, &config->recipe, &argc, argv, RECIPE)) {
+        psErrorStackPrint(stderr, "Can't find site configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // Parse other command-line arguments
+    config->arguments = psMetadataAlloc(); // The arguments, with default values
+
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-bias", 0, "Name of the bias image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-dark", 0, "Name of the dark image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-flat", 0, "Name of the flat-field image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-mask", 0, "Name of the mask image", "");
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "-chip", 0, "Chip number to process (if positive)", -1);
+
+    if (! psArgumentParse(config->arguments, &argc, argv) || argc != 3) {
+        printf("\nPan-STARRS Phase 2 processing\n\n");
+        printf("Usage: %s INPUT.fits OUTPUT.fits\n\n", argv[0]);
+        psArgumentHelp(config->arguments);
+        psFree(config->arguments);
+        exit(EXIT_FAILURE);
+    }
+
+    // add the input and output images to the arguments list
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "-input",  0, "Name of the input image",  argv[1]);
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "-output", 0, "Name of the output image", argv[2]);
+
+    // define Database handle, if used
+    config->database = pmConfigDB(config->site);
+    return true;
+} 
Index: trunk/ppImage/src/ppImageDetrendBias.c
===================================================================
--- trunk/ppImage/src/ppImageDetrendBias.c	(revision 5857)
+++ trunk/ppImage/src/ppImageDetrendBias.c	(revision 5858)
@@ -1,57 +1,54 @@
 # include "ppImage.h"
 
-bool ppDetrendBias (pmCell *input, psImage *bias, psImage *dark, ppRecipe *options) {
+bool ppDetrendBias (pmCell *inputCell, pmReadout *inputReadout, pmReadout *pedestal, ppOptions *options) {
 
     psPolynomial1D *poly;
     psSpline1D *spline;
+    psString coeffs;
 
-    // XXX EAM : this is an odd trigger for this
-    if (options->overscanMode == PM_OVERSCAN_ROWS || options->overscanMode == PM_OVERSCAN_COLUMNS) {
+    // XXX EAM : renamed the general edge overscan concept
+    if (options->overscanMode == PM_OVERSCAN_EDGE) {
 	// Need to get the read direction
 	int readdir = psMetadataLookupS32(NULL, inputCell->concepts, "CELL.READDIR");
 	if (readdir == 1) {
-// psmodule-0.8.0 has confused PM_OVERSCAN_ROWS and PM_OVERSCAN_COLUMNS; bug 608.
-#ifdef PRODUCTION
 	    options->overscanMode = PM_OVERSCAN_ROWS;
-#else
+	} else if (readdir == 2) {
 	    options->overscanMode = PM_OVERSCAN_COLUMNS;
-#endif
-	} else if (readdir == 2) {
-#ifdef PRODUCTION
-	    options->overscanMode = PM_OVERSCAN_COLUMNS;
-#else
-	    options->overscanMode = PM_OVERSCAN_ROWS;
-#endif
 	} else {
-	    psErrorStackPrint(stderr, "CELL.READDIR (%d) is not 1 or 2 --- assuming 1.\n",
-			      readdir);
-	    options->overscanMode = PM_OVERSCAN_ROWS;
+	    psAbort("phase2", "CELL.READDIR (%d) is not 1 or 2", readdir);
 	}
     }
 
-    psList *inputOverscans = pmReadoutGetBias(inputReadout); // List of overscan bias regions
-#ifdef PRODUCTION
-    (void)pmSubtractBias(inputReadout, overscanFit, inputOverscans, overscanMode,
-			 overscanStats, overscanBins, overscanFitType, pedestal);
-#else
-    (void)pmSubtractBias(inputReadout, overscanFit, inputOverscans, overscanMode,
-			 overscanStats, overscanBins, overscanFitType, biasReadout);
-#endif
+    // XXX : should this be a psArray?
+    psList *inputOverscans = NULL;
+    if (options->doOverscan) {
+	inputOverscans = pmReadoutGetBias(inputReadout); // List of overscan bias regions
+    }
+
+    // supplies the fit result in 'overscanFit'
+    pmSubtractBias(inputReadout, 
+		   options->overscanFit, 
+		   inputOverscans, 
+		   options->overscanMode, 
+		   options->overscanStats, 
+		   options->overscanBins, 
+		   options->overscanFitType, 
+		   pedestal);
     psFree(inputOverscans);
-
+    
     // Output overscan fit results, if required
-    if (!doOverscan) { 
+    if (! options->doOverscan) { 
 	return true;
     }
 
-    if (! overscanFit) {
+    if (! options->overscanFit) {
 	psLogMsg("phase2", PS_LOG_WARN, "No fit generated!\n");
 	return true;
     } 
 
-    switch (overscanFitType) {
+    switch (options->overscanFitType) {
       case PM_FIT_POLYNOMIAL:
-	poly = (psPolynomial1D*)overscanFit; // The polynomial
-	psString coeffs = NULL;     // String containing the coefficients
+	poly = (psPolynomial1D *) options->overscanFit; // The polynomial
+	coeffs = NULL;     // String containing the coefficients
 	for (int i = 0; i < poly->nX; i++) {
 	    psStringAppend(&coeffs, "%e ", poly->coeff[i]);
@@ -63,6 +60,6 @@
 
       case PM_FIT_SPLINE:
-	spline = (psSpline1D*)overscanFit; // The spline
-	psString coeffs = NULL;     // String containing the coefficients
+	spline = (psSpline1D *) options->overscanFit; // The spline
+	coeffs = NULL;     // String containing the coefficients
 	for (int i = 0; i < spline->n; i++) {
 	    psPolynomial1D *poly = spline->spline[i]; // i-th polynomial
Index: trunk/ppImage/src/ppImageDetrendCell.c
===================================================================
--- trunk/ppImage/src/ppImageDetrendCell.c	(revision 5858)
+++ trunk/ppImage/src/ppImageDetrendCell.c	(revision 5858)
@@ -0,0 +1,68 @@
+# include "ppImage.h"
+
+// mask, bias, dark, flat are defined per Cell
+// pedestal is constructed for each readout, which may have different exposure times
+
+pmReadout *ppDetrendSelectFirst (pmCell *cell, char *name, bool doThis) {
+
+    if (!doThis) {
+	return NULL;
+    }
+
+    if (cell->readouts->n > 1) {
+	psLogMsg("phase2", PS_LOG_WARN, "%s contains multiple readouts: only the first will be used.", name);
+    }
+
+    pmReadout *readout = cell->readouts->data[0]; // Readout of interest in this cell
+
+    return readout;
+}
+
+bool ppDetrendCell (ppDetrend *detrend, ppOptions *options, ppConfig *config) {
+
+    pmReadout *mask = ppDetrendSelectFirst (detrend->mask, "mask", options->doMask);
+    pmReadout *bias = ppDetrendSelectFirst (detrend->bias, "bias", options->doBias);
+    pmReadout *dark = ppDetrendSelectFirst (detrend->dark, "dark", options->doDark);
+    // pmReadout *flat = ppDetrendSelectFirst (detrend->flat, "flat", options->doFlat);
+
+    // Dark time for dark image
+    float darkTime = 1.0;       
+    if (options->doDark) {
+	darkTime = psMetadataLookupF32(NULL, detrend->dark->concepts, "CELL.DARKTIME");
+	if (darkTime <= 0.0) {
+	    psErrorStackPrint(stderr, "DARKTIME for dark image (%f) is non-positive.\n", darkTime);
+	    exit(EXIT_FAILURE);
+	}
+    }
+
+    // the pedestal combines the bias and dark contributions for a single readout
+    pmReadout *pedestal = NULL;
+
+    for (int k = 0; k < detrend->input->readouts->n; k++) {
+
+	pmReadout *input = detrend->input->readouts->data[k]; // Readout of interest in input image
+
+	// Mask bad pixels
+	if (options->doMask) {
+	    ppDetrendMask (detrend->input, input, mask);
+	}
+
+	// Non-linearity correction
+	if (options->doNonLin) {
+	    ppDetrendNonLinear (detrend->input, input, options);
+	}
+
+	// generate the pedestal image (passes through the old pedestal if current exptime == last exptime)
+	pedestal = ppDetrendPedestal (pedestal, detrend->input, bias, dark, darkTime, options);
+
+	// Bias, dark and overscan subtraction are all merged.
+	ppDetrendBias (detrend->input, input, pedestal, options);
+
+	// Flat-field correction (no options used?)
+	if (options->doFlat) {
+	    psLogMsg("phase2", PS_LOG_INFO, "flat field disabled.\n");
+	    // pmFlatField (input, flat);
+	}
+    }
+    return true;
+}
Index: trunk/ppImage/src/ppImageDetrendFlat.c
===================================================================
--- trunk/ppImage/src/ppImageDetrendFlat.c	(revision 5857)
+++ trunk/ppImage/src/ppImageDetrendFlat.c	(revision 5858)
@@ -1,7 +1,12 @@
 # include "ppImage.h"
 
-bool ppDetrendFlat () {
+// why does this use the flat pmReadout?
+
+bool ppDetrendFlat (pmReadout *inputReadout, pmReadout *flat, ppOptions *options) {
 
     psLogMsg("phase2", PS_LOG_INFO, "Performing flat field.\n");
+
+    
+
 #ifndef PRODUCTION
     psImage *dummyImage = psImageAlloc(inputReadout->image->numCols,
Index: trunk/ppImage/src/ppImageDetrendMask.c
===================================================================
--- trunk/ppImage/src/ppImageDetrendMask.c	(revision 5857)
+++ trunk/ppImage/src/ppImageDetrendMask.c	(revision 5858)
@@ -1,14 +1,17 @@
 # include "ppImage.h"
 
-bool ppDetrendMask (pmCell *cell, pmReadout *readout, pmImage *mask) {
+# define MASK_MODE (PM_MASK_TRAP | PM_MASK_BADCOL | PM_MASK_SAT)
+
+// XXX pass 'concepts' not 'cell' to this function?
+
+bool ppDetrendMask (pmCell *cell, pmReadout *input, pmReadout *mask) {
 
     float saturation = psMetadataLookupF32(NULL, cell->concepts, "CELL.SATURATION");
 
     // Need to change this later to grow the mask by the size of the convolution kernel
-    (void)pmMaskBadPixels(readout, mask, 
-			  PM_MASK_TRAP | PM_MASK_BADCOL | PM_MASK_SAT, saturation,
-			  PM_MASK_TRAP, 0);
+    // XXX does this function respect the mask / image cell region information?
+    // XXX should probably take pmRegion *mask
+    pmMaskBadPixels(input, mask->image, MASK_MODE, saturation, PM_MASK_TRAP, 0);
 
     return true;
 }
-
Index: trunk/ppImage/src/ppImageDetrendNonLinear.c
===================================================================
--- trunk/ppImage/src/ppImageDetrendNonLinear.c	(revision 5857)
+++ trunk/ppImage/src/ppImageDetrendNonLinear.c	(revision 5858)
@@ -1,71 +1,65 @@
 # include "ppImage.h"
 
-bool ppDetrendNonLinear (pmCell *cell, pmReadout *readout, psMetadata *recipe) {
+bool ppDetrendNonLinearPolynomial (pmReadout *input, psMetadataItem *dataItem) {
 
-    psString name;
-    psVector *coeff;
-    psMetadata *options;
+    // These are the polynomial coefficients
+    psVector *coeff = dataItem->data.V; // The coefficient vector
+    if (coeff->type.type != PS_TYPE_F64) {
+	psVector *temp = psVectorCopy(NULL, coeff, PS_TYPE_F64); // F64 version
+	psFree (coeff);
+	coeff = temp;
+    }
+    psPolynomial1D *correction = psPolynomial1DAlloc(coeff->n - 1, PS_POLYNOMIAL_ORD);
+    psFree(correction->coeff);
+    correction->coeff = psMemIncrRefCounter(coeff->data.F64);
+    pmNonLinearityPolynomial(input, correction);
+    psFree(coeff);
+    psFree(correction);
+    return true;
+}    
 
-    psMetadataItem *dataItem = psMetadataLookup(recipe, "NONLIN.DATA");
-    if (! dataItem) {
-	psLogMsg("phase2", PS_LOG_WARN, "Non-linearity correction desired, but unable to "
-		 "find NONLIN.DATA in recipe --- ignored.\n");
+bool ppDetrendNonLinearLookup (pmReadout *input, psMetadataItem *dataItem) {
+
+    // This is a filename: lookup table
+    char *name = dataItem->data.V;       // Filename
+    psLookupTable *table = psLookupTableAlloc(name, "%f %f", 0);
+    if (psLookupTableRead(table) <= 0) {
+	psErrorStackPrint(stderr, "Unable to read non-linearity correction file "
+			  "%s --- ignored\n", name);
 	return false;
     } 
+#ifdef PRODUCTION
+    pmNonLinearityLookup(input, table);
+#else
+    psVector *influx = table->values->data[0];
+    psVector *outflux = table->values->data[1];
+    pmNonLinearityLookup(input, influx, outflux);
+#endif
+    psFree(table);
+    return true;
+}    
 
-    switch (dataItem->type) {
+bool ppDetrendNonLinear (pmCell *cell, pmReadout *input, ppOptions *options) {
+
+    psMetadataItem *concept;
+
+    switch (options->nonLinearType) {
       case PS_DATA_VECTOR:
-	// These are the polynomial coefficients
-	coeff = dataItem->data.V; // The coefficient vector
-	if (coeff->type.type != PS_TYPE_F64) {
-	    psVector *temp = psVectorCopy(NULL, coeff, PS_TYPE_F64); // F64 version
-	    coeff = temp;
-	}
-	psPolynomial1D *correction = psPolynomial1DAlloc(coeff->n - 1,
-							 PS_POLYNOMIAL_ORD);
-	psFree(correction->coeff);
-	correction->coeff = psMemIncrRefCounter(coeff->data.F64);
-	(void)pmNonLinearityPolynomial(inputReadout, correction);
-	psFree(coeff);
-	psFree(correction);
+	ppDetrendNonLinearPolynomial (input, options->nonLinearData);
 	return true;
 
       case PS_DATA_STRING:
-	// This is a filename
-	name = dataItem->data.V;       // Filename
-	psLookupTable *table = psLookupTableAlloc(name, "%f %f", 0);
-	if (psLookupTableRead(table) <= 0) {
-	    psErrorStackPrint(stderr, "Unable to read non-linearity correction file "
-			      "%s --- ignored\n", name);
-	    return false;
-	} 
-#ifdef PRODUCTION
-	(void)pmNonLinearityLookup(inputReadout, table);
-#else
-	psVector *influx = table->values->data[0];
-	psVector *outflux = table->values->data[1];
-	(void)pmNonLinearityLookup(inputReadout, table->values->data[0],
-				   table->values->data[1]);
-#endif
-	psFree(table);
+	ppDetrendNonLinearLookup (input, options->nonLinearData);
 	return true;
 
       case PS_DATA_METADATA:
-	// This is a menu
-	options = dataItem->data.V; // Options with concept values as keys
-	bool mdok = false; // Success of MD lookup
-	psString concept = psMetadataLookupStr(&mdok, recipe, "NONLIN.SOURCE");
-	if (! mdok || ! concept) {
-	    psLogMsg("phase2", PS_LOG_WARN, "Non-linearity correction desired, but "
-		     "unable to find NONLIN.SOURCE in recipe --- ignored.\n");
+	// XXX this is somewhat confusing : let's wrap in a function when i understand it
+	concept = pmCellGetConcept(cell, options->nonLinearSource);
+	if (! concept) {
+	    psLogMsg("phase2", PS_LOG_WARN, "Unable to find value of concept %s "
+		     "for non-linearity correction --- ignored.\n", options->nonLinearSource);
 	    return false;
 	} 
-	psMetadataItem *conceptValueItem = pmCellGetConcept(inputCell, concept);
-	if (! conceptValueItem) {
-	    psLogMsg("phase2", PS_LOG_WARN, "Unable to find value of concept %s "
-		     "for non-linearity correction --- ignored.\n", concept);
-	    return false;
-	} 
-	if (conceptValueItem->type != PS_DATA_STRING) {
+	if (concept->type != PS_DATA_STRING) {
 	    psLogMsg("phase2", PS_LOG_WARN, "Type for concept %s isn't STRING, as"
 		     " expected for non-linearity correction --- ignored.\n",
@@ -74,7 +68,8 @@
 	} 
 
-	psString conceptValue = conceptValueItem->data.V;
 	// Get the value of the concept
-	psMetadataItem *optionItem = psMetadataLookup(options, conceptValue);
+	psString conceptValue = concept->data.V;
+	psMetadata *folder = (psMetadata *)options->nonLinearData->data.V;
+	psMetadataItem *optionItem = psMetadataLookup(folder, conceptValue);
 	if (!optionItem) {
 	    psLogMsg("phase2", PS_LOG_WARN, "Unable to find %s in NONLIN.DATA"
@@ -82,52 +77,22 @@
 	    return false;
 	} 
-	if (optionItem->type == PS_DATA_VECTOR) {
-	    // These are the polynomial coefficients
-	    coeff = optionItem->data.V; // The coefficient vector
-	    if (coeff->type.type != PS_TYPE_F64) {
-		psVector *temp = psVectorCopy(NULL, coeff, PS_TYPE_F64);
-		coeff = temp;
-	    }
-	    // Polynomial correction
-	    psPolynomial1D *correction = psPolynomial1DAlloc(coeff->n - 1,
-							     PS_POLYNOMIAL_ORD);
-	    psFree(correction->coeff);
-	    correction->coeff = psMemIncrRefCounter(coeff->data.F64);
-	    (void)pmNonLinearityPolynomial(inputReadout, correction);
-	    psFree(coeff);
-	    psFree(correction);
+
+	switch (optionItem->type) {
+	  case PS_DATA_VECTOR:
+	    ppDetrendNonLinearPolynomial (input, optionItem);
 	    return true;
-	} 
-	if (optionItem->type == PS_DATA_STRING) {
-	    // This is a filename
-	    psString tableName = optionItem->data.V; // The filename
-	    psLookupTable *table = psLookupTableAlloc(tableName, "%f %f", 0);
-	    int numLines = 0; // Number of lines read from table
-	    if ((numLines = psLookupTableRead(table)) <= 0) {
-		psErrorStackPrint(stderr, "Unable to read non-linearity "
-				  "correction file %s --- ignored\n",
-				  tableName);
-		return false;
-	    } 
-#ifdef PRODUCTION
-	    (void)pmNonLinearityLookup(inputReadout, table);
-#else
-	    printf("XXX: Non-linearity correction from lookup table not "
-		   "yet implemented.\n");
-#endif
-	    psFree(table);
+	  case PS_DATA_STRING:
+	    ppDetrendNonLinearLookup (input, optionItem);
 	    return true;
+	  default:
+	    psLogMsg("phase2", PS_LOG_WARN, "Non-linearity correction "
+		     "desired but unable to interpret NONLIN.DATA for %s"
+		     " --- ignored\n", conceptValue);
+	    return false;
 	}
-
-	psLogMsg("phase2", PS_LOG_WARN, "Non-linearity correction "
-		 "desired but unable to interpret NONLIN.DATA for %s"
-		 " --- ignored\n", conceptValue);
-	return false;
-  
       default:
-	psLogMsg("phase2", PS_LOG_WARN, "Non-linearity correction desired, but "
-		 "NONLIN.DATA is of invalid type --- ignored.\n");
+	psAbort("phase2", "Invalid options->nonLinearType");
     }
-    return false;
+    return true;
 }
 
Index: trunk/ppImage/src/ppImageDetrendPedestal.c
===================================================================
--- trunk/ppImage/src/ppImageDetrendPedestal.c	(revision 5857)
+++ trunk/ppImage/src/ppImageDetrendPedestal.c	(revision 5858)
@@ -1,9 +1,15 @@
 # include "ppImage.h"
 
-psImage *ppDetrendPedestal (pmCell *input, psImage *bias, psImage *dark, ppRecipe *options) {
+// XXX check on image overlaps, as well as image sizes
+// XXX what constraints do we put on the detrend images?
+// XXX this function is much easier if we can assume all detrend images
+//     should have the same size and represent the same pixels...
 
-    psImage *pedestal = NULL;       // Pedestal image (bias + scaled dark)
+pmReadout *ppDetrendPedestal (pmReadout *pedestal, pmCell *input, pmReadout *bias, pmReadout *dark, float darkTime, ppOptions *options) {
 
 #ifdef PRODUCTION
+    static float lastExptime = 0.0;
+    bool reusePedestal = true;
+
     if (options->doDark) {
 	// Dark time for input image
@@ -14,23 +20,55 @@
 	    exit(EXIT_FAILURE);
 	}
-	pedestal = (psImage*)psBinaryOp(NULL, dark, "*", psScalarAlloc(inputTime * darkTime, PS_TYPE_F32));
+
+	if (inputTime != lastExptime) {
+	    // this will create a new pedestal if the input one is NULL
+	    // XXX EAM : this function had darkTime * inputTime, which is wrong, yes?
+	    if (pedestal == NULL) {
+		pedestal = pmReadoutAlloc (NULL);
+	    }
+	    pedestal->col0    = dark->col0;
+	    pedestal->row0    = dark->row0;
+	    pedestal->colBins = dark->colBins;
+	    pedestal->rowBins = dark->rowBins;
+
+	    pedestal->image   = (psImage *) psBinaryOp(pedestal->image, dark->image, "*", psScalarAlloc(inputTime / darkTime, PS_TYPE_F32));
+	    reusePedestal = false;
+	    lastExptime = inputTime
+	}
     }
-    if (options->doBias) {
-	if (pedestal) {
-	    if (bias->numRows != dark->numRows ||
-		bias->numCols != dark->numCols) {
-		psError(PS_ERR_IO, true, "Bias and dark images have different dimensions: "
-			"%dx%d vs %dx%d\n", bias->numCols, bias->numRows,
-			dark->numCols, dark->numRows);
-		exit(EXIT_FAILURE);
-	    }
-	    (void)psBinaryOp(pedestal, pedestal, "+", bias);
-	} else {
+
+    // no bias image, return dark pedestal or NULL
+    if (!options->doBias) {
+	return pedestal;
+    }
+
+    // no dark image, return bias pedestal, or reuse
+    if (!options->doDark) {
+	if (!pedestal) {
 	    pedestal = psMemIncrRefCounter(bias);
 	}
+	return pedestal;
     }
+
+    if (reusePedestal) {
+	return pedestal;
+    }
+
+    if (bias->image->numRows != dark->image->numRows ||
+	bias->image->numCols != dark->image->numCols) {
+	psError(PS_ERR_IO, true, "Bias and dark images have different dimensions: %dx%d vs %dx%d\n", 
+		bias->image->numCols, bias->image->numRows,
+		dark->image->numCols, dark->image->numRows);
+	exit(EXIT_FAILURE);
+    }
+
+    psBinaryOp(pedestal->image, pedestal->image, "+", bias->image);
+    return pedestal;
+#else
+    return NULL;
 #endif
-
-    return pedestal;
 }
 
+// creates a new pedestal image for this readout
+// if the current exptime is the same as the last exptime, 
+// reuses the supplied pedestal
Index: trunk/ppImage/src/ppImageLoadPixels.c
===================================================================
--- trunk/ppImage/src/ppImageLoadPixels.c	(revision 5858)
+++ trunk/ppImage/src/ppImageLoadPixels.c	(revision 5858)
@@ -0,0 +1,57 @@
+# include "ppImage.h"
+
+bool ppImageLoadPixels (ppFPA *input, ppFPA *process, psDB *db, int nChip, int nCell) {
+
+    // an input chip is valid if:
+    // (((nChip == i) || (nChip == -1)) && process.valid)
+
+    // if we have not opened the file, skip it
+    if (input->fits == NULL) {
+	return false;
+    }
+
+    fprintf (stderr, "loading %d,%d for %s\n", nChip, nCell, input->filename);
+
+    // set input:valid flags according to process and nChip/nCell
+    for (int i = 0; i < input->fpa->chips->n; i++) {
+	pmChip *pChip = process->fpa->chips->data[i];
+	pmChip *iChip = input->fpa->chips->data[i];
+
+	iChip->valid = pChip->valid;
+	iChip->valid &= (nChip == i) || (nChip == -1);
+
+        for (int j = 0; j < iChip->cells->n; j++) {
+
+	    pmCell *pCell = pChip->cells->data[j];
+	    pmCell *iCell = iChip->cells->data[j];
+
+	    iCell->valid =  pCell->valid;
+	    iCell->valid &= iChip->valid;
+	    iCell->valid &= (nCell == i) || (nCell == -1);
+	}
+    }
+
+    // Read in the input pixels
+    if (! pmFPARead(input->fpa, input->fits, input->header, db)) {
+        psErrorStackPrint(stderr, "Unable to populate camera from input FITS file\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // reset input:valid flags to true
+    for (int i = 0; i < input->fpa->chips->n; i++) {
+
+	pmChip *iChip = input->fpa->chips->data[i];
+	iChip->valid = true;
+        for (int j = 0; j < iChip->cells->n; j++) {
+
+	    pmCell *iCell = iChip->cells->data[j];
+	    iCell->valid =  true;
+	}
+    }
+
+    return true;
+}
+
+// XXX this is not very efficient with fseeks : each pmFPARead is randomly accessing the file
+// XXX does this handle multi-file data?
+// XXX this does NOT preserve the state of the input valid flags
Index: trunk/ppImage/src/ppImageLoop.c
===================================================================
--- trunk/ppImage/src/ppImageLoop.c	(revision 5857)
+++ trunk/ppImage/src/ppImageLoop.c	(revision 5858)
@@ -1,39 +1,52 @@
 # include "ppImage.h"
 
-bool ppImageLoop (ppData *data, ppRecipe *options, ppConfig *config) {
+bool ppImageLoop (ppData *data, ppOptions *options, ppConfig *config) {
 
-    psArray *inputChips = data->input->chips; // Array of chips in input image
-    psArray *biasChips 	= data->bias->chips;  // Array of chips in bias image
-    psArray *darkChips 	= data->dark->chips;  // Array of chips in dark image
-    psArray *maskChips 	= data->mask->chips;  // Array of chips in mask image
-    psArray *flatChips 	= data->flat->chips;  // Array of chips in flat image
+    ppDetrend detrend;
 
-    for (int i = 0; i < inputChips->n; i++) {
-        pmChip *inputChip = inputChips->data[i]; // Chip of interest in input image
-        pmChip *biasChip  = biasChips->data[i];  // Chip of interest in bias image
-        pmChip *darkChip  = darkChips->data[i];	 // Chip of interest in dark image
-        pmChip *maskChip  = maskChips->data[i];	 // Chip of interest in mask image
-        pmChip *flatChip  = flatChips->data[i];  // Chip of interest in flat image
+    if (options->imageLoadDepth == PP_LOAD_FPA) {
+	ppImageLoadPixels (data->input, data->process, config->database, -1, -1);
+	ppImageLoadPixels (data->bias, 	data->process, config->database, -1, -1);
+	ppImageLoadPixels (data->dark, 	data->process, config->database, -1, -1);
+	ppImageLoadPixels (data->mask, 	data->process, config->database, -1, -1);
+	ppImageLoadPixels (data->flat, 	data->process, config->database, -1, -1);
+    }
+    for (int i = 0; i < data->input->fpa->chips->n; i++) {
+        pmChip *inputChip = data->input->fpa->chips->data[i]; // Chip of interest in input image
+        pmChip *biasChip  = data->bias->fpa->chips->data[i];  // Chip of interest in bias image
+        pmChip *darkChip  = data->dark->fpa->chips->data[i];  // Chip of interest in dark image
+        pmChip *maskChip  = data->mask->fpa->chips->data[i];  // Chip of interest in mask image
+        pmChip *flatChip  = data->flat->fpa->chips->data[i];  // Chip of interest in flat image
 
         if (! inputChip->valid) { continue; }
 
-        psArray *inputCells = inputChip->cells; // Array of cells in input image
-        psArray *biasCells  = biasChip->cells; 	// Array of cells in bias image
-        psArray *darkCells  = darkChip->cells; 	// Array of cells in dark image
-        psArray *maskCells  = maskChip->cells; 	// Array of cells in mask image
-        psArray *flatCells  = flatChip->cells; 	// Array of cells in flat image
+	if (options->imageLoadDepth == PP_LOAD_CHIP) {
+	    ppImageLoadPixels (data->input, data->process, config->database, i, -1);
+	    ppImageLoadPixels (data->bias,  data->process, config->database, i, -1);
+	    ppImageLoadPixels (data->dark,  data->process, config->database, i, -1);
+	    ppImageLoadPixels (data->mask,  data->process, config->database, i, -1);
+	    ppImageLoadPixels (data->flat,  data->process, config->database, i, -1);
+	}
+        for (int j = 0; j < inputChip->cells->n; j++) {
+            detrend.input = inputChip->cells->data[j]; // Cell of interest in input image
+            detrend.bias  = biasChip->cells->data[j];  // Cell of interest in bias image
+            detrend.dark  = darkChip->cells->data[j];  // Cell of interest in dark imag
+            detrend.mask  = maskChip->cells->data[j];  // Cell of interest in mask image
+            detrend.flat  = flatChip->cells->data[j];  // Cell of interest in flat image
 
-        for (int j = 0; j < inputCells->n; j++) {
-            pmCell *inputCell = inputCells->data[j]; // Cell of interest in input image
-            pmCell *biasCell  = biasCells->data[j];  // Cell of interest in bias image
-            pmCell *darkCell  = darkCells->data[j];  // Cell of interest in dark imag
-            pmCell *maskCell  = maskCells->data[j];  // Cell of interest in mask image
-            pmCell *flatCell  = flatCells->data[j];  // Cell of interest in flat image
+            if (! detrend.input->valid) { continue; }
 
-            if (! inputCell->valid) { continue; }
-
-	    ppReadoutDetrend (inputCell, maskCell, biasCell, darkCell, flatCell);
+	    if (options->imageLoadDepth == PP_LOAD_CELL) {
+		ppImageLoadPixels (data->input, data->process, config->database, i, j);
+		ppImageLoadPixels (data->bias,  data->process, config->database, i, j);
+		ppImageLoadPixels (data->dark,  data->process, config->database, i, j);
+		ppImageLoadPixels (data->mask,  data->process, config->database, i, j);
+		ppImageLoadPixels (data->flat,  data->process, config->database, i, j);
+	    }
+	    ppDetrendCell (&detrend, options, config);
 	}
     }
     return true;
 }
+
+/* if we allow per-readout loads, we need to load detrend images here */
Index: trunk/ppImage/src/ppImageOptions.c
===================================================================
--- trunk/ppImage/src/ppImageOptions.c	(revision 5858)
+++ trunk/ppImage/src/ppImageOptions.c	(revision 5858)
@@ -0,0 +1,178 @@
+# include "ppImage.h"
+
+// XXX EAM : this needs signficant work to choose the detrend images based on the detrend database
+
+bool ppImageOptions (ppData *data, ppOptions *options, ppConfig *config) {
+
+    bool status;
+
+    // default initial values
+    options->doMask = false;		// Mask bad pixels
+    options->doNonLin = false;		// Non-linearity correction
+    options->doBias = false;		// Bias subtraction
+    options->doDark = false;		// Dark subtraction
+    options->doOverscan = false;	// Overscan subtraction
+    options->doFlat = false;		// Flat-field normalisation
+    options->doFringe = false;		// Fringe subtraction
+    options->doSource = false;		// Source identification and photometry
+    options->doAstrom = false;		// Astrometry
+    options->overscanBins = 1;		// Number of pixels per bin for overscan
+    options->overscanStats = NULL;      // Statistics for overscan
+    options->overscanFit = NULL;	// Overscan fit (polynomial or spline)
+    options->overscanFitType = PM_FIT_NONE; // Fit type for overscan
+    options->overscanMode = PM_OVERSCAN_NONE; // Axis for overscan
+
+    options->doNonLin = false;
+    options->nonLinearType = false;
+    options->nonLinearData = NULL;
+    options->nonLinearSource = NULL;
+
+    options->imageLoadDepth = PP_LOAD_NONE;
+    char *depth = psMetadataLookupPtr(NULL, config->recipe, "LOAD.DEPTH");
+    if (depth == NULL) {
+	psAbort ("phase2", "load depth not specified");
+    }
+    if (!strcasecmp(depth, "FPA")) {
+	options->imageLoadDepth = PP_LOAD_FPA;
+    }
+    if (!strcasecmp(depth, "CHIP")) {
+	options->imageLoadDepth = PP_LOAD_CHIP;
+    }
+    if (!strcasecmp(depth, "CELL")) {
+	options->imageLoadDepth = PP_LOAD_CELL;
+    }
+    if (options->imageLoadDepth == PP_LOAD_NONE) {
+	psAbort ("phase2", "load depth not specified");
+    }
+
+    // mask - recipe options
+    if (psMetadataLookupBool(NULL, config->recipe, "MASK")) {
+	data->mask->filename = psMetadataLookupStr(NULL, config->arguments, "-mask");
+        if (strlen(data->mask->filename) > 0) {
+            options->doMask = true;
+        } else {
+            psLogMsg("phase2", PS_LOG_WARN, "Masking is desired, but no mask was supplied"
+		     " --- no masking will be performed.\n");
+        }
+    }
+
+    // non-linear - recipe options
+    if (psMetadataLookupBool(NULL, config->recipe, "NONLIN")) {
+	psMetadataItem *dataItem = psMetadataLookup(config->recipe, "NONLIN.DATA");
+	if (! dataItem) {
+	    psAbort("phase2", "Non-linearity correction desired, but unable to "
+		     "find NONLIN.DATA in recipe");
+	} 
+
+        options->doNonLin = true;
+	options->nonLinearType = dataItem->type;
+	options->nonLinearData = dataItem;
+
+	switch (dataItem->type) {
+	  case PS_DATA_VECTOR:
+	  case PS_DATA_STRING:
+	    break;
+
+	  case PS_DATA_METADATA:
+	    // This is a menu
+	    options->nonLinearSource = psMetadataLookupStr(&status, config->recipe, "NONLIN.SOURCE");
+	    if (! status || ! options->nonLinearSource) {
+		psAbort("phase2", "Non-linearity correction desired, but unable to "
+			 "find NONLIN.SOURCE in recipe");
+	    } 
+	    break;
+
+	  default:
+	    psAbort("phase2", "Non-linearity correction desired, but "
+		    "NONLIN.DATA is of invalid type.");
+	}
+
+    }
+
+    // bias - recipe options
+    if (psMetadataLookupBool(NULL, config->recipe, "BIAS")) {
+	data->bias->filename = psMetadataLookupStr(NULL, config->arguments, "-bias");
+        if (strlen(data->bias->filename) > 0) {
+            options->doBias = true;
+        } else {
+            psLogMsg("phase2", PS_LOG_WARN, "Bias subtraction is desired, but no bias was supplied --- "
+                     "no bias subtraction will be performed.\n");
+        }
+    }
+
+    // dark - recipe options
+    if (psMetadataLookupBool(NULL, config->recipe, "DARK")) {
+	data->dark->filename = psMetadataLookupStr(NULL, config->arguments, "-dark");
+        if (strlen(data->dark->filename) > 0) {
+            options->doDark = true;
+        } else {
+            psLogMsg("phase2", PS_LOG_WARN, "Dark subtraction is desired, but no dark was supplied --- "
+                     "no dark subtraction will be performed.\n");
+        }
+    }
+
+    // overscan - recipe options
+    // XXX EAM : we should abort on invalid options. default options?
+    if (psMetadataLookupBool(NULL, config->recipe, "OVERSCAN")) {
+	// XXX EAM : does 'overscanMode = NONE' mean doOverscan = false?
+        options->doOverscan = true;
+        psString mode = psMetadataLookupStr(NULL, config->recipe, "OVERSCAN.MODE");
+        if (! strcasecmp(mode, "INDIVIDUAL")) {
+            options->overscanMode = PM_OVERSCAN_EDGE;
+        } else if (! strcasecmp(mode, "ALL")) {
+            options->overscanMode = PM_OVERSCAN_ALL;
+        } else if (strcasecmp(mode, "NONE")) {
+            psLogMsg("phase2", PS_LOG_WARN, "OVERSCAN.MODE (%s) is not one of NONE, INDIVIDUAL, or ALL:"
+                     " assuming NONE.\n", mode);
+        }
+        psString fit = psMetadataLookupStr(NULL, config->recipe, "OVERSCAN.FIT");
+        if (! strcasecmp(fit, "POLYNOMIAL")) {
+            options->overscanFitType = PM_FIT_POLYNOMIAL;
+            int order = psMetadataLookupS32(NULL, config->recipe, "OVERSCAN.ORDER"); // Order of polynomial fit
+            options->overscanFit = psPolynomial1DAlloc(order, PS_POLYNOMIAL_ORD);
+        } else if (! strcasecmp(fit, "SPLINE")) {
+            options->overscanFit = NULL;
+            options->overscanFitType = PM_FIT_SPLINE;
+            // int order = psMetadataLookupS32(NULL, config->recipe, "OVERSCAN.ORDER"); // Order of polynomial fit
+	    // XXX : not in psLib yet : options->overscanFit = psSpline1DAlloc();
+        } else if (strcasecmp(fit, "NONE")) {
+            psLogMsg("phase2", PS_LOG_WARN, "OVERSCAN.FIT (%s) is not one of NONE, POLYNOMIAL, or SPLINE:"
+                     " assuming NONE.\n", fit);
+        }
+        options->overscanBins = psMetadataLookupS32(NULL, config->recipe, "OVERSCAN.BIN");
+        if (options->overscanBins <= 0) {
+            psErrorStackPrint(stderr, "OVERSCAN.BIN (%d) is non-positive --- assuming 1.\n", options->overscanBins);
+            options->overscanBins = 1;
+        }
+        psString stat = psMetadataLookupStr(NULL, config->recipe, "OVERSCAN.STAT");
+        if (! strcasecmp(stat, "MEAN")) {
+            options->overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        } else if (! strcasecmp(stat, "MEDIAN")) {
+            options->overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
+        } else {
+            psErrorStackPrint(stderr, "OVERSCAN.STAT (%s) is not one of MEAN, MEDIAN: assuming MEAN\n", stat);
+            options->overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        }
+    }
+
+    // flat-field - recipe options
+    if (psMetadataLookupBool(NULL, config->recipe, "FLAT")) {
+	data->flat->filename = psMetadataLookupStr(NULL, config->arguments, "-flat");
+        if (strlen(data->flat->filename) > 0) {
+            options->doFlat = true;
+        } else {
+            psLogMsg("phase2", PS_LOG_WARN, "Flat-fielding is desired, but no flat was supplied --- "
+                     "no flat-fielding will be performed.\n");
+        }
+    }
+
+    // XXX need to add the following:
+
+    // fringe - recipe options
+
+    // photom - recipe options
+
+    // astrom - recipe options
+
+    return true;
+}
Index: trunk/ppImage/src/ppImageParseCamera.c
===================================================================
--- trunk/ppImage/src/ppImageParseCamera.c	(revision 5858)
+++ trunk/ppImage/src/ppImageParseCamera.c	(revision 5858)
@@ -0,0 +1,87 @@
+# include "ppImage.h"
+
+static void ppFPA_Free (ppFPA *fpa) {
+    return;
+}
+
+ppFPA *ppFPA_Alloc (void) {
+
+    ppFPA *fpa = psAlloc (sizeof(ppFPA));
+    psMemSetDeallocator(fpa, (psFreeFunc) ppFPA_Free);
+
+    fpa->filename = NULL;
+    fpa->fpa = NULL;
+    fpa->fits = NULL;
+    fpa->header = NULL;
+
+    return fpa;
+}
+
+bool ppImageParseCamera (ppData *data, ppConfig *config, char **argv) {
+    
+    bool status;
+
+    data->input   = ppFPA_Alloc ();
+    data->mask 	  = ppFPA_Alloc ();
+    data->bias 	  = ppFPA_Alloc ();
+    data->dark 	  = ppFPA_Alloc ();
+    data->flat 	  = ppFPA_Alloc ();
+    data->fringe  = ppFPA_Alloc ();
+    data->process = ppFPA_Alloc ();
+    
+    data->input->filename = psMetadataLookupPtr (NULL, config->arguments, "-input");
+
+    // Open the input image
+    psLogMsg("phase2", PS_LOG_INFO, "Opening input image: %s\n", data->input->filename);
+    data->input->fits = psFitsOpen(data->input->filename, "r"); // File handle for FITS file
+    if (! data->input->fits) {
+        psErrorStackPrint(stderr, "Can't open input image: %s\n", data->input->filename);
+        exit(EXIT_FAILURE);
+    }
+    data->input->header = psFitsReadHeader(NULL, data->input->fits); // FITS header
+
+    // Get camera configuration from header if not already defined
+    if (! config->camera) {
+        config->camera = pmConfigCameraFromHeader(config->site, data->input->header);
+        if (! config->camera) {
+            psErrorStackPrint(stderr, "Can't find camera configuration!\n");
+            exit(EXIT_FAILURE);
+        }
+   } else if (! pmConfigValidateCamera(config->camera, data->input->header)) {
+        psError(PS_ERR_IO, true, "%s does not seem to be from the camera.\n", data->input->filename);
+        exit(EXIT_FAILURE);
+    }
+
+    // determine the correct recipe to use
+    if (! config->recipe && !(config->recipe = pmConfigRecipeFromCamera(config->camera, RECIPE))) {
+        psErrorStackPrint(stderr, "Can't find recipe configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // Construct camera in preparation for reading
+    data->input->fpa   = pmFPAConstruct(config->camera);
+    data->mask->fpa    = pmFPAConstruct(config->camera);
+    data->bias->fpa    = pmFPAConstruct(config->camera);
+    data->dark->fpa    = pmFPAConstruct(config->camera);
+    data->flat->fpa    = pmFPAConstruct(config->camera);
+    data->fringe->fpa  = pmFPAConstruct(config->camera);
+    data->process->fpa = pmFPAConstruct(config->camera);
+
+    // XXX EAM : extend this to allow an array of selected chips by name
+    // Chip selection: if we are using a single chip, select it for each FPA
+    // data->process acts as a process mask for the full FPA
+    int chipNum = psMetadataLookupS32(&status, config->arguments, "-chip"); // Chip number to work on
+    if (chipNum >= 0) {
+        if (! pmFPASelectChip(data->process->fpa, chipNum)) {
+            psErrorStackPrint(stderr, "Chip number %d doesn't exist in camera.\n", chipNum);
+            exit(EXIT_FAILURE);
+        }
+        psLogMsg("phase2", PS_LOG_INFO, "Operating only on chip %d\n", chipNum);
+    }
+    return true;
+}
+
+// XXX EAM : I dropped the pre-open of the output files; replace with a stats test of output permissions
+
+// XXX : for split data (data spread across multiple files), this step is only operating on a component 
+//       of the FPA.  the other components should be set to 'invalid' in this case?
Index: trunk/ppImage/src/ppImageParseDetrend.c
===================================================================
--- trunk/ppImage/src/ppImageParseDetrend.c	(revision 5858)
+++ trunk/ppImage/src/ppImageParseDetrend.c	(revision 5858)
@@ -0,0 +1,33 @@
+# include "ppImage.h"
+
+// open all needed detrend files
+// read in primary headers
+// validate camera for each detrend image
+
+// XXX : keep the primary headers in data?
+// XXX : add fringe frame
+
+bool ppFPAOpen (ppFPA *fpa, psMetadata *camera, char *name, bool doThis) {
+
+    if (!doThis) {
+	return false;
+    }
+
+    psLogMsg("phase2", PS_LOG_INFO, "Opening %s image: %s\n", name, fpa->filename);
+    fpa->fits = psFitsOpen(fpa->filename, "r"); // File handle
+    fpa->header = psFitsReadHeader(NULL, fpa->fits); // primary header
+    if (! pmConfigValidateCamera(camera, fpa->header)) {
+	psError(PS_ERR_IO, true, "%s (%s) does not seem to be from the correct camera.\n", name, fpa->filename);
+	exit(EXIT_FAILURE);
+    }
+    return true;
+}
+
+bool ppImageParseDetrend (ppData *data, ppOptions *options, ppConfig *config) {
+
+    ppFPAOpen (data->mask, config->camera, "mask", options->doMask);
+    ppFPAOpen (data->bias, config->camera, "bias", options->doBias);
+    ppFPAOpen (data->dark, config->camera, "dark", options->doDark);
+    ppFPAOpen (data->flat, config->camera, "flat", options->doFlat);
+    return true;
+}
Index: trunk/ppImage/src/ppImageWeights.c
===================================================================
--- trunk/ppImage/src/ppImageWeights.c	(revision 5858)
+++ trunk/ppImage/src/ppImageWeights.c	(revision 5858)
@@ -0,0 +1,29 @@
+# include "ppImage.h"
+
+bool ppReadoutWeights (pmReadout *readout) {
+
+    psImage *image  = readout->image;
+    readout->mask   = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+    readout->weight = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+
+    // XXX : we need to determine rdnoise and gain from the appropriate location
+    float rdnoise = 5.0;  // in electrons
+    float gain = 2.0;     // in e/ADU
+    
+    float rgain = 1.0 / gain;
+    float rnoise = PS_SQR (rdnoise / gain);
+
+    psF32 **vI = readout->image->data.F32;
+    psU8  **vM = readout->mask->data.U8;
+    psF32 **vW = readout->weight->data.F32;
+
+    for (int j = 0; j < image->numRows; j++) {
+	for (int i = 0; i < image->numCols; i++) {
+	    vM[j][i] = 0;
+	    vW[j][i] = PS_MAX (rgain * vI[j][i] + rnoise, 0.0);
+	}
+    }
+    return true;
+}
+
+// XXX : I am defining the 'weight' to be the variance, not the stdev
Index: trunk/ppImage/src/ppReadoutDetrend.c
===================================================================
--- trunk/ppImage/src/ppReadoutDetrend.c	(revision 5857)
+++ 	(revision )
@@ -1,81 +1,0 @@
-# include "ppImage.h"
-
-bool ppReadoutDetrend (pmCell *input, pmCell *mask, pmCell *bias, pmCell *dark, pmCell *flat, ppRecipe *options, ppConfig *config) {
-
-    psArray *inputReadouts = inputCell->readouts; // Array of readouts in input image
-
-    pmReadout *maskReadout = NULL; // Mask readout
-    psImage *maskImage = NULL;  // Mask pixels
-    if (doMask) {
-	maskReadout = maskCell->readouts->data[0]; // Readout of interest in mask image
-	maskImage = maskReadout->image;
-	if (maskCell->readouts->n > 1) {
-	    psLogMsg("phase2", PS_LOG_WARN, "Mask contains multiple readouts: only the first will be used.");
-	}
-    }
-
-    pmReadout *biasReadout = NULL; // Bias readout
-    psImage *biasImage = NULL;  // Bias pixels
-    if (doBias) {
-	biasReadout = biasCell->readouts->data[0]; // Readout of interest in bias image
-	biasImage = biasReadout->image;
-	if (biasCell->readouts->n > 1) {
-	    psLogMsg("phase2", PS_LOG_WARN, "Bias contains multiple readouts: only the first will be used.");
-	}
-    }
-
-    pmReadout *darkReadout = NULL; // Readout of interest in dark image
-    psImage *darkImage = NULL;  // Dark pixels
-    float darkTime = 1.0;       // Dark time for dark image
-    if (doDark) {
-	pmReadout *darkReadout = darkCell->readouts->data[0]; // Readout of interest in dark image
-	darkImage = darkReadout->image;
-	darkTime = psMetadataLookupF32(NULL, darkCell->concepts, "CELL.DARKTIME");
-	if (darkTime <= 0.0) {
-	    psErrorStackPrint(stderr, "DARKTIME for dark image (%f) is non-positive.\n", darkTime);
-	    exit(EXIT_FAILURE);
-	}
-	if (darkCell->readouts->n > 1) {
-	    psLogMsg("phase2", PS_LOG_WARN, "Dark contains multiple readouts: only the first will be used.");
-	}
-    }
-
-    pmReadout *flatReadout = NULL; // Flat readout
-    psImage *flatImage = NULL;  // Flat pixels
-    if (doFlat) {
-	flatReadout = flatCell->readouts->data[0]; // Readout of interest in flat image
-	flatImage = flatReadout->image;
-	if (flatCell->readouts->n > 1) {
-	    psLogMsg("phase2", PS_LOG_WARN, "Flat contains multiple readouts: only the first will be"
-		     " used.");
-	}
-    }
-
-    for (int k = 0; k < inputReadouts->n; k++) {
-	pmReadout *inputReadout = inputReadouts->data[k]; // Readout of interest in input image
-	psImage *inputImage = inputReadout->image; // The actual image
-
-	// Mask bad pixels
-	if (options->doMask) {
-	    ppDetrendMask (inputCell, inputReadout, maskImage);
-	}
-
-	// Non-linearity correction
-	if (options->doNonLin) {
-	    ppDetrendNonLinear (inputCell, inputReadout, config->recipe);
-	}
-
-	// Changed the definition of pmSubtractBias to do the dark subtraction as well, but
-	// it's not in there yet.  Once it is, we can get rid of "pedestal".
-	// generate the pedestal image
-	psImage *pedestal = ppDetrendPedestal (inputCell, biasImage, darkImage, options);
-
-	// Bias, dark and overscan subtraction are all merged.
-	if (options->doBias || options->doOverscan || options->doDark) {
-	    ppDetrendBias (inputCell, pedestal, options);
-	}
-
-	// Flat-field correction
-	if (doFlat) {
-	    ppDetrendFlat (inputCell, flatImage, options);
-	}
Index: trunk/ppImage/src/ppReciptInit.c
===================================================================
--- trunk/ppImage/src/ppReciptInit.c	(revision 5857)
+++ 	(revision )
@@ -1,125 +1,0 @@
-# include "ppImage.h"
-
-bool ppRecipeInit (ppRecipe *options, ppConfig *config) {
-
-    // default initial values
-    options->doMask = false;		// Mask bad pixles
-    options->doNonLin = false;		// Non-linearity correction
-    options->doBias = false;		// Bias subtraction
-    options->doDark = false;		// Dark subtraction
-    options->doOverscan = false;	// Overscan subtraction
-    options->doFlat = false;		// Flat-field normalisation
-    options->doFringe = false;		// Fringe subtraction
-    options->doSource = false;		// Source identification and photometry
-    options->doAstrom = false;		// Astrometry
-    options->overscanBins = 1;		// Number of pixels per bin for overscan
-    options->overscanStats = NULL;      // Statistics for overscan
-    options->overscanFit = NULL;	// Overscan fit (polynomial or spline)
-    options->overscanFitType = PM_FIT_NONE; // Fit type for overscan
-    options->overscanMode = PM_OVERSCAN_NONE; // Axis for overscan
-
-    // Chip selection
-    options->chipNum  = psMetadataLookupS32(NULL, config->arguments, "-chip"); // Chip number to work on
-    
-    // mask - recipe options
-    options->maskName = psMetadataLookupStr(NULL, config->arguments, "-mask");
-    if (psMetadataLookupBool(NULL, config->recipe, "MASK")) {
-        if (strlen(options->maskName) > 0) {
-            options->doMask = true;
-        } else {
-            psLogMsg("phase2", PS_LOG_WARN, "Masking is desired, but no mask was supplied"
-		     " --- no masking will be performed.\n");
-        }
-    }
-
-    // non-linear - recipe options
-    if (psMetadataLookupBool(NULL, config->recipe, "NONLIN")) {
-        options->doNonLin = true;
-    }
-
-    // bias - recipe options
-    options->biasName = = psMetadataLookupStr(NULL, config->arguments, "-bias");
-    if (psMetadataLookupBool(NULL, config->recipe, "BIAS")) {
-        if (strlen(biasName) > 0) {
-            options->doBias = true;
-        } else {
-            psLogMsg("phase2", PS_LOG_WARN, "Bias subtraction is desired, but no bias was supplied --- "
-                     "no bias subtraction will be performed.\n");
-        }
-    }
-
-    // dark - recipe options
-    options->darkName = psMetadataLookupStr(NULL, config->arguments, "-dark");
-    if (psMetadataLookupBool(NULL, config->recipe, "DARK")) {
-        if (strlen(options->darkName) > 0) {
-            options->doDark = true;
-        } else {
-            psLogMsg("phase2", PS_LOG_WARN, "Dark subtraction is desired, but no dark was supplied --- "
-                     "no dark subtraction will be performed.\n");
-        }
-    }
-
-    // overscan - recipe options
-    if (psMetadataLookupBool(NULL, config->recipe, "OVERSCAN")) {
-        options->doOverscan = true;
-        psString mode = psMetadataLookupStr(NULL, config->recipe, "OVERSCAN.MODE");
-        if (strcasecmp(mode, "INDIVIDUAL") == 0) {
-            options->overscanMode = PM_OVERSCAN_ROWS;
-            // By "ROWS", I mean either rows or columns --- will check the READDIR for each chip
-        } else if (strcasecmp(mode, "ALL") == 0) {
-            options->overscanMode = PM_OVERSCAN_ALL;
-        } else if (strcasecmp(mode, "NONE") != 0) {
-            psLogMsg("phase2", PS_LOG_WARN, "OVERSCAN.MODE (%s) is not one of NONE, INDIVIDUAL, or ALL:"
-                     " assuming NONE.\n", mode);
-        }
-        psString fit = psMetadataLookupStr(NULL, config->recipe, "OVERSCAN.FIT");
-        if (strcasecmp(fit, "POLYNOMIAL") == 0) {
-            options->overscanFitType = PM_FIT_POLYNOMIAL;
-            int order = psMetadataLookupS32(NULL, config->recipe, "OVERSCAN.ORDER"); // Order of polynomial fit
-            options->overscanFit = psPolynomial1DAlloc(order, PS_POLYNOMIAL_ORD);
-        } else if (strcasecmp(fit, "SPLINE") == 0) {
-            options->overscanFitType = PM_FIT_SPLINE;
-            int order = psMetadataLookupS32(NULL, config->recipe, "OVERSCAN.ORDER"); // Order of polynomial fit
-            options->overscanFit = NULL;
-	    // XXX : not in psLib yet : options->overscanFit = psSpline1DAlloc();
-        } else if (strcasecmp(fit, "NONE") != 0) {
-            psLogMsg("phase2", PS_LOG_WARN, "OVERSCAN.FIT (%s) is not one of NONE, POLYNOMIAL, or SPLINE:"
-                     " assuming NONE.\n", fit);
-        }
-        options->overscanBins = psMetadataLookupS32(NULL, config->recipe, "OVERSCAN.BIN");
-        if (options->overscanBins <= 0) {
-            psErrorStackPrint(stderr, "OVERSCAN.BIN (%d) is non-positive --- assuming 1.\n", options->overscanBins);
-            options->overscanBins = 1;
-        }
-        psString stat = psMetadataLookupStr(NULL, config->recipe, "OVERSCAN.STAT");
-        if (strcasecmp(stat, "MEAN")) {
-            options->overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
-        } else if (strcasecmp(stat, "MEDIAN")) {
-            options->overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
-        } else {
-            psErrorStackPrint(stderr, "OVERSCAN.STAT (%s) is not one of MEAN, MEDIAN: assuming MEAN\n", stat);
-            options->overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
-        }
-    }
-
-    // flat-field - recipe options
-    options->flatName = psMetadataLookupStr(NULL, config->arguments, "-flat");
-    if (psMetadataLookupBool(NULL, config->recipe, "FLAT")) {
-        if (strlen(options->flatName) > 0) {
-            options->doFlat = true;
-        } else {
-            psLogMsg("phase2", PS_LOG_WARN, "Flat-fielding is desired, but no flat was supplied --- "
-                     "no flat-fielding will be performed.\n");
-        }
-    }
-
-    // XXX need to add the following:
-
-    // fringe - recipe options
-
-    // photom - recipe options
-
-    // astrom - recipe options
-
-    return true;
-}
