Index: /trunk/ppImage/src/ppImage.c
===================================================================
--- /trunk/ppImage/src/ppImage.c	(revision 5859)
+++ /trunk/ppImage/src/ppImage.c	(revision 5860)
@@ -15,5 +15,5 @@
     // Get camera configuration from header if not already defined
     // Construct camera in preparation for reading
-    ppImageParseCamera (&data, &config, argv);
+    ppImageParseCamera (&data, &config);
 
     // Set various tasks (define optional operations)
Index: /trunk/ppImage/src/ppImage.h
===================================================================
--- /trunk/ppImage/src/ppImage.h	(revision 5859)
+++ /trunk/ppImage/src/ppImage.h	(revision 5860)
@@ -46,5 +46,4 @@
     bool doSource;			// Source identification and photometry
     bool doAstrom;			// Astrometry
-    bool preLoad;
 
     bool doOverscan;			// Overscan subtraction
@@ -61,5 +60,4 @@
 
     ppImageLoadDepth imageLoadDepth;
-
 } ppOptions;
 
@@ -93,5 +91,5 @@
 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 ppImageParseCamera (ppData *data, ppConfig *config);
 bool ppImageParseDetrend (ppData *data, ppOptions *options, ppConfig *config);
 
Index: /trunk/ppImage/src/ppImageParseCamera.c
===================================================================
--- /trunk/ppImage/src/ppImageParseCamera.c	(revision 5859)
+++ /trunk/ppImage/src/ppImageParseCamera.c	(revision 5860)
@@ -18,5 +18,5 @@
 }
 
-bool ppImageParseCamera (ppData *data, ppConfig *config, char **argv) {
+bool ppImageParseCamera (ppData *data, ppConfig *config) {
     
     bool status;
Index: /trunk/ppMerge/src/ppMerge.c
===================================================================
--- /trunk/ppMerge/src/ppMerge.c	(revision 5860)
+++ /trunk/ppMerge/src/ppMerge.c	(revision 5860)
@@ -0,0 +1,32 @@
+# include "ppImage.h"
+
+int main (int argc, char **argv) {
+
+    ppData data;
+    ppConfig config;
+    ppOptions options;
+
+    psTimerStart("merge");
+
+    // Parse the configuration and arguments
+    ppMergeConfig (&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
+    ppMergeParseCamera (&data, &config, argv);
+
+    // Set various tasks (define optional operations)
+    ppMergeOptions (&data, &options, &config);
+
+    // open detrend images (mask), load headers
+    ppMergeParseDetrend (&data, &options, &config);
+
+    // Image Merge Loop
+    ppMergeLoop (&data, &options, &config);
+
+    // output options
+    // ppImageOutput (&data, &options, &config);
+
+    exit (0);
+}
Index: /trunk/ppMerge/src/ppMerge.h
===================================================================
--- /trunk/ppMerge/src/ppMerge.h	(revision 5860)
+++ /trunk/ppMerge/src/ppMerge.h	(revision 5860)
@@ -0,0 +1,96 @@
+#include <stdio.h>
+#include <strings.h>
+
+#include "pslib.h"
+
+#include "psAdditionals.h"
+
+#include "pmAstrometry.h"
+#include "pmReadout.h"
+#include "pmConfig.h"
+#include "pmFPAConstruct.h"
+#include "pmFPARead.h"
+#include "pmFPAConceptsGet.h"
+#include "pmFPAWrite.h"
+
+#include "pmFlatField.h"
+#include "pmMaskBadPixels.h"
+#include "pmNonLinear.h"
+#include "pmSubtractBias.h"
+#include "pmChipMosaic.h"
+//#include "pmFPAMorph.h"
+
+#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;
+    psMetadata *camera;
+    psMetadata *recipe;
+    psMetadata *arguments;
+    psDB       *database;
+} ppConfig;
+
+typedef struct {
+    char *filename;
+    pmFPA *fpa;
+    psFits *fits;
+    psMetadata *header;
+    double zero;
+    double scale;
+} ppFPA; 
+
+typedef struct {
+    psArray *data;
+    ppFPA *process;
+    ppFPA *mask;
+} ppData;
+
+typedef struct {
+    ppImageLoadDepth imageLoadDepth;
+
+    psStats *mergeStats;		// Statistics for overscan
+    void *overscanFit;			// Overscan fit (polynomial or spline)
+    pmFit overscanFitType;		// Fit type for overscan
+    pmOverscanAxis overscanMode;	// Axis for overscan
+
+    bool doNonLin;			// Non-linearity correction
+    psDataType nonLinearType;
+    psMetadataItem *nonLinearData;
+    void *nonLinearSource;
+} ppOptions;
+
+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/ppMerge/src/ppMergeCell.c
===================================================================
--- /trunk/ppMerge/src/ppMergeCell.c	(revision 5860)
+++ /trunk/ppMerge/src/ppMergeCell.c	(revision 5860)
@@ -0,0 +1,45 @@
+# include "ppMerge.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("merge", 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 ppMergeCell (psArray *cellList, pmCell *maskCell, ppOptions *options, ppConfig *config) {
+
+    pmCell *sample = cellList->data[0];
+    pmReadout *mask   = ppDetrendSelectFirst (maskCell, "mask", options->doMask);
+
+    // push all readouts from each image onto the list
+    // not clear that this is the right process -- how to merge readouts?
+    // are all readouts from this cell equivalent? (ie, across images?)
+    // XXX need to set / carry the zero,scale values
+
+    readouts = psListAlloc ();
+
+    for (int i = 0; i < cellList->n; i++) {
+	pmCell *cell = cellList->data[i];
+	for (int j = 0; j < cell->readouts->n; j++) {
+	    psListAdd (cell->readouts->data[j], PS_LIST_TAIL);
+	}
+    }
+
+    // XXX : need to consider the output
+    
+    psImage *output = pmReadoutCombine (NULL, readouts, params,...);
+
+    return true;
+}
Index: /trunk/ppMerge/src/ppMergeConfig.c
===================================================================
--- /trunk/ppMerge/src/ppMergeConfig.c	(revision 5860)
+++ /trunk/ppMerge/src/ppMergeConfig.c	(revision 5860)
@@ -0,0 +1,56 @@
+# include "ppImage.h"
+
+bool ppMergeConfig (ppConfig *config, int argc, char **argv) {
+
+    // Parse the configurations (re-org a la ppImage)
+    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, "-glob",  0, "Load files based on a filename glob (eg, file0??.fits)", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-text",  0, "Load files from a text list", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-xml",   0, "Load files from an xml-format file", "");
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "-zero",  0, "Name of the mask image", 0);
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "-scale", 0, "Chip number to process (if positive)", 1);
+
+    if (! psArgumentParse(config->arguments, &argc, argv) || argc != 2) {
+        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, "-output", 0, "Name of the output image", argv[1]);
+
+    // define Database handle, if used
+    config->database = pmConfigDB(config->site);
+    return true;
+} 
+
+/*
+
+  possible invokations:
+  ppMerge -glob (glob) (output)
+  ppMerge -text (textfile) (output)
+  ppMerge -xml (xmlfile) (output)
+  options:
+    -zero zero
+    -scale scale
+  
+  - input is (in some form) a list of files to be merged
+  * each chip / cell may have a different bzero / bscale specified
+  * a simple glob list implies bzero / bscale are constant for all
+  * the textfile format consists of: (file) [zero scale]
+  * the xmlfile format includes zero,scale values for each cell
+  
+*/
+
Index: /trunk/ppMerge/src/ppMergeLoadPixels.c
===================================================================
--- /trunk/ppMerge/src/ppMergeLoadPixels.c	(revision 5860)
+++ /trunk/ppMerge/src/ppMergeLoadPixels.c	(revision 5860)
@@ -0,0 +1,58 @@
+# include "ppMerge.h"
+
+// this is identical to ppImageLoadPixels : merge and put in psModules
+bool ppMergeLoadPixels (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/ppMerge/src/ppMergeLoop.c
===================================================================
--- /trunk/ppMerge/src/ppMergeLoop.c	(revision 5860)
+++ /trunk/ppMerge/src/ppMergeLoop.c	(revision 5860)
@@ -0,0 +1,62 @@
+# include "ppMerge.h"
+
+bool ppMergeLoop (ppData *data, ppOptions *options, ppConfig *config) {
+
+    ppDetrend detrend;
+
+    ppFPA *sampleFPA = data->input->data[0];
+
+    psArray *cellList = psArrayAlloc (data->input->n);
+
+    // attempt to load at FPA level, if desired
+    if (options->imageLoadDepth == PP_LOAD_FPA) {
+	for (int n = 0; n < data->input->n; n++) {
+	    ppFPA *fpa = data->input->data[n];
+	    ppMergeLoadPixels (fpa, data->process, config->database, -1, -1);
+	}
+	ppMergeLoadPixels (data->mask, data->process, config->database, -1, -1);
+    }
+
+    // iterate over all chips, using sampleFPA as reference
+    for (int i = 0; i < sampleFPA->chips->n; i++) {
+        pmChip *sampleChip = sampleFPA->chips->data[i]; // Chip of interest in input image
+        pmChip *maskChip = data->mask->fpa->chips->data[i]; // Chip of interest in input image
+        if (! sampleChip->valid) { continue; }
+
+	// attempt to load at CHIP level, if desired
+	if (options->imageLoadDepth == PP_LOAD_CHIP) {
+	    for (int n = 0; n < data->input->n; n++) {
+		ppFPA *fpa = data->input->data[n];
+		ppMergeLoadPixels (fpa, data->process, config->database, i, -1);
+	    }
+	    ppMergeLoadPixels (data->mask, data->process, config->database, i, -1);
+	}
+
+	// iterate over all cells, using sampleChip as reference
+        for (int j = 0; j < sampleChip->cells->n; j++) {
+	    pmCell *sampleCell = sampleChip->cells->data[j];
+	    pmCell *maskCell = maskChip->cells->data[j];
+            if (! sampleCell->valid) { continue; }
+
+	    // attempt to load at CELL level, if desired
+	    if (options->imageLoadDepth == PP_LOAD_CELL) {
+		for (int n = 0; n < data->input->n; n++) {
+		    ppFPA *fpa = data->input->data[n];
+		    ppMergeLoadPixels (fpa, data->process, config->database, i, j);
+		}
+		ppMergeLoadPixels (data->mask, data->process, config->database, i, j);
+	    }
+
+	    // build an array of the cells of interest
+	    for (int k = 0; k < cellList->n; k++) {
+		pmFPA *thisFPA = data->input->data[k];
+		pmChip *thisChip = thisFPA->chips->data[i];
+		cellList->data[k] = thisChip->cells->data[j];
+	    }
+
+	    // run the merge function
+	    ppMergeCell (cellList, maskCell, options, config);
+	}
+    }
+    return true;
+}
Index: /trunk/ppMerge/src/ppMergeOptions.c
===================================================================
--- /trunk/ppMerge/src/ppMergeOptions.c	(revision 5860)
+++ /trunk/ppMerge/src/ppMergeOptions.c	(revision 5860)
@@ -0,0 +1,52 @@
+# include "ppImage.h"
+
+// XXX EAM : optionally choose the mask image based on the detrend database
+
+bool ppMergeOptions (ppData *data, ppOptions *options, ppConfig *config) {
+
+    bool status;
+
+    // at what depth is the image read?
+    options->imageLoadDepth = PP_LOAD_NONE;
+    char *depth = psMetadataLookupPtr(NULL, config->recipe, "LOAD.DEPTH");
+    if (depth == NULL) {
+	psAbort ("merge", "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 ("merge", "load depth not specified");
+    }
+
+    // how do we calculate the merge stack?
+    options->mergeStats = NULL;
+    psString stat = psMetadataLookupStr(NULL, config->recipe, "MERGE.STAT");
+    if (! strcasecmp(stat, "MEAN")) {
+      options->mergeStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    } else if (! strcasecmp(stat, "MEDIAN")) {
+      options->mergeStats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
+    } else {
+      psErrorStackPrint(stderr, "MERGE.STAT (%s) is not one of MEAN, MEDIAN: assuming MEAN\n", stat);
+      options->mergeStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    }
+
+    // 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("merge", PS_LOG_WARN, "Masking is desired, but no mask was supplied"
+		     " --- no masking will be performed.\n");
+        }
+    }
+
+    return true;
+}
Index: /trunk/ppMerge/src/ppMergeParseCamera.c
===================================================================
--- /trunk/ppMerge/src/ppMergeParseCamera.c	(revision 5860)
+++ /trunk/ppMerge/src/ppMergeParseCamera.c	(revision 5860)
@@ -0,0 +1,152 @@
+# include "ppMerge.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;
+    fpa->zero = 0;
+    fpa->scale = 1;
+
+    return fpa;
+}
+
+// psArray **data contains a list of the images to be merged (array of ppFPA)
+bool ppMergeParseCamera (ppData *data, ppConfig *config) {
+    
+    bool status;
+    glob_t globList;
+
+    psArray *input = psArrayAlloc (100);
+    input->n = 0;
+
+    // XXX EAM : push these into a ppMergeLoadInput function?
+    // load the list from the supplied file glob
+    char *globWord = psMetadataLookupPtr (NULL, config->arguments, "-glob");
+    if (*globWord) {
+	globList.gl_offs = 0;
+	glob (globWord, 0, NULL, &globList);
+	for (int i = 0; i < globList.gl_pathc; i++) {
+	    ppFPA *fpa = ppFPA_Alloc ();
+	    fpa->filename = psStringCopy (globList.gl_pathv[i]);
+	}
+	psArrayAdd (input, 100, fpa);
+    }
+
+    // load the list from the supplied text file
+    char *textWord = psMetadataLookupPtr (NULL, config->arguments, "-text");
+    if (*textWord) {
+	int nItems;
+	char line[1024];
+	char filename[1024];
+	double zero;
+	double scale;
+	
+	FILE *f = fopen (textWord, "r");
+	if (f == NULL) {
+	    psAbort ("merge", "unable to open specified text file");
+	}
+	while (fgets (line, 1024, f) != NULL) {
+	    nItems = fscanf (f, "%s %lf %lf", filename, &zero, &scale);
+	    fpa = ppFPA_Alloc ();
+	    switch (nItems) {
+	      case 0:
+		psFree (fpa);
+		break;
+	      case 3:
+		fpa->zero  = zero;
+		fpa->scale = scale;
+		// NOTE: continues to case 1 below
+	      case 1:
+		fpa->filename = psStringCopy (filename);
+		psArrayAdd (input, 100, fpa);
+		break;
+	      default:
+		// rigid format, no comments allowed?
+		psAbort ("merge", "error parsing input text file");
+		break;
+	    }
+	}
+    }		
+
+    // load the list from the supplied XML file
+    char *xmlWord = psMetadataLookupPtr (NULL, config->arguments, "-xml");
+    if (*xmlWord) {
+	psAbort ("merge", "-xml input file not yet implemented");
+    }
+
+    // Open the input images, test that they are the same camera
+    psLogMsg("phase2", PS_LOG_INFO, "Opening input images\n");
+    for (int i = 0; i < input->n; i++) {
+	
+	ppFPA *fpa = input->data[i];
+
+	fpa->fits = psFitsOpen(fpa->filename, "r"); // File handle for FITS file
+	if (! fpa->fits) {
+	    psErrorStackPrint(stderr, "Can't open input image: %s\n", fpa->filename);
+	    exit(EXIT_FAILURE);
+	}
+	fpa->header = psFitsReadHeader(NULL, fpa->fits); // primary FITS header
+
+	// Get camera configuration from header if not already defined
+	// else, validate that header matches camera
+	if (! config->camera) {
+	    config->camera = pmConfigCameraFromHeader(config->site, fpa->header);
+	    if (! config->camera) {
+		psErrorStackPrint(stderr, "Can't find camera configuration!\n");
+		exit(EXIT_FAILURE);
+	    }
+	} else {
+	    if (!pmConfigValidateCamera(config->camera, fpa->header)) {
+		psError(PS_ERR_IO, true, "%s does not seem to be from the camera.\n", fpa->filename);
+		exit(EXIT_FAILURE);
+	    }
+	}
+
+	// determine the correct recipe to use
+	if (! config->recipe) {
+	    config->recipe = pmConfigRecipeFromCamera(config->camera, RECIPE);
+	    if (!config->recipe) {
+		psErrorStackPrint(stderr, "Can't find recipe configuration!\n");
+		exit(EXIT_FAILURE);
+	    }
+	}
+
+	// Construct camera in preparation for reading
+	fpa->fpa   = pmFPAConstruct(config->camera);
+    }
+    data->input = input;
+
+    // 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
+    data->process = pmFPAConstruct(config->camera);
+
+    // see if we have selected a specific chip
+    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);
+    }
+
+    // data->mask carries a pixel mask for pixels to be ignored
+    // the name and contents of the mask are determined in ppMergeOptions
+    data->mask = pmFPAConstruct(config->camera);
+
+    return true;
+}
+
+// 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/ppMerge/src/ppMergeParseDetrend.c
===================================================================
--- /trunk/ppMerge/src/ppMergeParseDetrend.c	(revision 5860)
+++ /trunk/ppMerge/src/ppMergeParseDetrend.c	(revision 5860)
@@ -0,0 +1,30 @@
+# 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("merge", 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 ppMergeParseDetrend (ppData *data, ppOptions *options, ppConfig *config) {
+
+    ppFPAOpen (data->mask, config->camera, "mask", options->doMask);
+    return true;
+}
