Index: trunk/psphot/src/pmCellSetMask.c
===================================================================
--- trunk/psphot/src/pmCellSetMask.c	(revision 6117)
+++ trunk/psphot/src/pmCellSetMask.c	(revision 6117)
@@ -0,0 +1,23 @@
+# include "psphot.h"
+
+bool pmCellSetMask (pmCell *cell, psMetadata *recipe) {
+    
+    bool status;
+
+    // mask the excluded outer pixels
+    float XMIN  = psMetadataLookupF32 (&status, recipe, "XMIN");
+    float XMAX  = psMetadataLookupF32 (&status, recipe, "XMAX");
+    float YMIN  = psMetadataLookupF32 (&status, recipe, "YMIN");
+    float YMAX  = psMetadataLookupF32 (&status, recipe, "YMAX");
+    psRegion valid = psRegionSet (XMIN, XMAX, YMIN, YMAX);
+
+    // Set the pixels
+    psArray *readouts = cell->readouts; // Array of readouts
+    for (int i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // The readout of interest
+
+	psRegion keep = psRegionForImage (readout->image, valid);
+	psImageKeepRegion (readout->mask, keep, "OR", PSPHOT_MASK_INVALID);
+    }
+    return true;
+}
Index: trunk/psphot/src/psImageJpeg.c
===================================================================
--- trunk/psphot/src/psImageJpeg.c	(revision 6117)
+++ trunk/psphot/src/psImageJpeg.c	(revision 6117)
@@ -0,0 +1,165 @@
+# include <stdio.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+# include "jpeglib.h"
+
+/* vectors to hold JPEG colormap */
+static psVector *colorMapR = NULL;
+static psVector *colorMapG = NULL;
+static psVector *colorMapB = NULL;
+
+# define RANGELIM(A)(PS_MAX(0,PS_MIN(255,(A))))
+
+# define SCALEVALUE(VALUE,ZERO,SCALE)(PS_MAX(0,PS_MIN(255,(SCALE*(VALUE-ZERO)))))
+
+bool psImageJpegColormap (char *name) {
+
+    colorMapR = psVectorRecycle (colorMapR, 256, PS_TYPE_U8);
+    colorMapG = psVectorRecycle (colorMapG, 256, PS_TYPE_U8);
+    colorMapB = psVectorRecycle (colorMapB, 256, PS_TYPE_U8);
+
+    /* grayscale */
+    if ((!strcasecmp (name, "grayscale")) || (!strcasecmp (name, "greyscale"))) {
+	for (int i = 0; i < colorMapR->n; i++) {  
+	    colorMapR->data.U8[i] = RANGELIM(i);
+	    colorMapG->data.U8[i] = RANGELIM(i);
+	    colorMapB->data.U8[i] = RANGELIM(i);
+	}
+	return true;
+    }    
+
+    /* -grayscale */
+    if ((!strcasecmp (name, "-grayscale")) || (!strcasecmp (name, "-greyscale"))) {
+	for (int i = 0; i < colorMapR->n; i++) {  
+	    colorMapR->data.U8[i] = RANGELIM(256 - i);
+	    colorMapG->data.U8[i] = RANGELIM(256 - i);
+	    colorMapB->data.U8[i] = RANGELIM(256 - i);
+	}
+	return true;
+    }    
+
+    /* rainbow */
+    if (!strcasecmp (name, "rainbow")) { 
+	int I1 = 0.25*colorMapR->n;
+	int I2 = 0.50*colorMapR->n;
+	int I3 = 0.75*colorMapR->n;
+	for (int i = 0; i < I1; i++) {  
+	    colorMapR->data.U8[i] = 0;
+	    colorMapG->data.U8[i] = 0;
+	    colorMapB->data.U8[i] = RANGELIM(4*i);
+	}
+	for (int i = I1; i < I2; i++) {  
+	    colorMapR->data.U8[i] = RANGELIM(4*(i - I1));
+	    colorMapG->data.U8[i] = 0;
+	    colorMapB->data.U8[i] = RANGELIM(4*(I2 - i));
+	}
+	for (int i = I2; i < I3; i++) {  
+	    colorMapR->data.U8[i] = 255;
+	    colorMapG->data.U8[i] = 4*(i - I2); 
+	    colorMapB->data.U8[i] = 0;
+	}
+	for (int i = I3; i < colorMapR->n; i++) {  
+	    colorMapR->data.U8[i] = 255;
+	    colorMapG->data.U8[i] = 255;
+	    colorMapB->data.U8[i] = RANGELIM(4*(i - I3));
+	}
+	return true;
+    }
+
+    /* heat */
+    if (!strcasecmp (name, "heat")) { 
+	int I1 = 0.25*colorMapR->n;
+	int I2 = 0.50*colorMapR->n;
+	int I3 = 0.75*colorMapR->n;
+	for (int i = 0; i < I1; i++) {  
+	    colorMapR->data.U8[i] = RANGELIM(2*i);
+	    colorMapG->data.U8[i] = 0;
+	    colorMapB->data.U8[i] = 0;
+	}
+	for (int i = I1; i < I2; i++) {  
+	    colorMapR->data.U8[i] = RANGELIM(2*i);
+	    colorMapG->data.U8[i] = RANGELIM(2*(i - I1));
+	    colorMapB->data.U8[i] = 0;
+	}
+	for (int i = I2; i < I3; i++) {  
+	    colorMapR->data.U8[i] = 255;
+	    colorMapG->data.U8[i] = RANGELIM(2*(i - I1)); 
+	    colorMapB->data.U8[i] = RANGELIM(2*(i - I2)); 
+	}
+	for (int i = I3; i < colorMapR->n; i++) {  
+	    colorMapR->data.U8[i] = 255;
+	    colorMapG->data.U8[i] = 255;
+	    colorMapB->data.U8[i] = RANGELIM(2*(i - I2));
+	}
+	return true;
+    }
+    return false;
+}
+
+bool psImageJpeg (psImage *image, char *filename, float min, float max) {
+
+    struct jpeg_compress_struct cinfo;
+    struct jpeg_error_mgr jerr;
+
+    int pixel;
+    JSAMPLE *jpegLine;			// Points to data for current line
+    JSAMPROW jpegLineList[1];		// pointer to JSAMPLE row[s]
+    JSAMPLE *outPix;
+
+    if (colorMapR == NULL) return false;
+    if (colorMapG == NULL) return false;
+    if (colorMapB == NULL) return false;
+
+    /* JPEG init calls */
+    cinfo.err = jpeg_std_error (&jerr);
+    jpeg_create_compress (&cinfo);
+
+    /* open file, prep for jpeg */
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+	fprintf (stderr, "failed to open %s for output\n", filename);
+	return (TRUE);
+    }
+    jpeg_stdio_dest(&cinfo, f);
+
+    /* set up color jpeg buffers */
+    int quality = 75;
+    cinfo.image_width = image->numCols; // image width and height, in pixels
+    cinfo.image_height = image->numRows;
+    cinfo.input_components = 3;		        
+    cinfo.in_color_space = JCS_RGB; 	
+    jpeg_set_defaults (&cinfo);
+    jpeg_set_quality (&cinfo, quality, true); // limit to baseline-JPEG values
+    jpeg_start_compress (&cinfo, true);
+
+    jpegLine = psAlloc (3*image->numCols*sizeof(JSAMPLE));
+    jpegLineList[0] = jpegLine;
+
+    psU8 *Rpix = colorMapR->data.U8;
+    psU8 *Gpix = colorMapG->data.U8;
+    psU8 *Bpix = colorMapB->data.U8;
+
+    float zero = min;
+    float scale = 256.0/(max - min);
+
+    for (int j = 0; j < image->numRows; j++) {
+	psF32 *row = image->data.F32[j];
+
+	outPix = jpegLine;
+	for (int i = 0; i < image->numCols; i++, outPix += 3) {
+	    pixel = SCALEVALUE(row[i],zero,scale);
+	    outPix[0] = Rpix[pixel];
+	    outPix[1] = Gpix[pixel];
+	    outPix[2] = Bpix[pixel];
+	}
+	jpeg_write_scanlines (&cinfo, jpegLineList, 1);
+    }
+
+    jpeg_finish_compress (&cinfo);
+    fclose (f);
+    jpeg_destroy_compress (&cinfo);
+
+    psFree (jpegLine);
+    return true;
+}
Index: trunk/psphot/src/psModulesUtils.c
===================================================================
--- trunk/psphot/src/psModulesUtils.c	(revision 6056)
+++ trunk/psphot/src/psModulesUtils.c	(revision 6117)
@@ -59,4 +59,18 @@
 }
 
+pmModel *pmModelSelect (pmSource *source) {
+    switch (source->type) {
+      case PM_SOURCE_STAR:
+	return source->modelPSF;
+	
+      case PM_SOURCE_EXTENDED:
+	return source->modelEXT;
+	
+      default:
+	return NULL;
+    }
+    return NULL;
+}
+
 bool pmSourcePhotometry (float *fitMag, float *obsMag, pmModel *model, psImage *image, psImage *mask) {
 
@@ -162,2 +176,65 @@
     return (flux);
 }
+
+static void ppConfigFree (ppConfig *config) {
+
+  if (config == NULL) return;
+  return;
+}
+
+// allocate a ppConfig structrue
+ppConfig *ppConfigAlloc (void) {
+
+  ppConfig *config = psAlloc (sizeof(ppConfig));
+  psMemSetDeallocator(config, (psFreeFunc) ppConfigFree);
+
+  config->site = NULL;
+  config->camera = NULL;
+  config->recipe = NULL;
+  config->arguments = NULL;
+  config->database = NULL;
+
+  return (config);
+}
+
+static void ppFileFree (ppFile *file) {
+
+  if (file == NULL) return;
+  return;
+}
+
+// allocate a ppFile structrue
+ppFile *ppFileAlloc (void) {
+
+  ppFile *file = psAlloc (sizeof(ppFile));
+  psMemSetDeallocator(file, (psFreeFunc) ppFileFree);
+
+  file->filename = NULL;
+  file->fits = NULL;
+  file->phu = NULL;
+  file->fpa = NULL;
+
+  return (file);
+}
+
+psMetadata *pmReadoutGetHeader (pmReadout *readout) {
+
+    p_pmHDU *hdu = NULL;
+
+    pmCell *cell = readout->parent;	// cell containing this readout;
+    hdu = cell->hdu;			// The data unit, containing the weight and mask originals
+    if (hdu) return hdu->header;
+    
+    pmChip *chip = cell->parent;	// The parent chip
+    hdu = chip->hdu;			// The data unit, containing the weight and mask originals
+    if (hdu) return hdu->header;
+
+    pmFPA *fpa = chip->parent;		// The parent FPA
+    hdu = fpa->hdu;
+    if (hdu) return hdu->header;
+
+    if (fpa->phu) return fpa->phu;
+    
+    psError(PS_ERR_UNKNOWN, true, "Unable to find the HDU in the hierarchy!\n");
+    return NULL;
+}
Index: trunk/psphot/src/psModulesUtils.h
===================================================================
--- trunk/psphot/src/psModulesUtils.h	(revision 6056)
+++ trunk/psphot/src/psModulesUtils.h	(revision 6117)
@@ -9,4 +9,29 @@
 # include "psLibUtils.h"
 
+// How much of the FPA to load at a time
+typedef enum {
+    PP_LOAD_NONE,                       // Don't load anything
+    PP_LOAD_FPA,                        // Load the entire FPA at once
+    PP_LOAD_CHIP,                       // Load by chip
+    PP_LOAD_CELL,                       // Load by cell
+} ppImageLoadDepth;
+
+// Configuration data
+typedef struct {
+    psMetadata *site;                   // The site configuration
+    psMetadata *camera;                 // The camera configuration
+    psMetadata *recipe;                 // The recipe (i.e., specific setups)
+    psMetadata *arguments;              // The command-line arguments
+    psDB       *database;               // Database handle
+} ppConfig;
+
+// A file to process
+typedef struct {
+    char *filename;                     // File name
+    psFits *fits;                       // The FITS file handle
+    psMetadata *phu;                    // The FITS header
+    pmFPA *fpa;                         // The FPA, with pixels and extensions
+} ppFile;
+
 // psModule extra utilities
 // bool	     pmSourceFitModel_EAM(pmSource *source, pmModel *model, const bool PSF);
@@ -18,4 +43,9 @@
 // unify with paul's image/header/metadata functions
 psF32        pmConfigLookupF32 (bool *status, psMetadata *config, psMetadata *header, char *name);
+pmModel *pmModelSelect (pmSource *source);
+
+ppConfig       *ppConfigAlloc (void);
+ppFile         *ppFileAlloc (void);
+psMetadata    *pmReadoutGetHeader (pmReadout *readout);
 
 # endif
Index: trunk/psphot/src/psphot.c
===================================================================
--- trunk/psphot/src/psphot.c	(revision 6056)
+++ trunk/psphot/src/psphot.c	(revision 6117)
@@ -1,83 +1,24 @@
 # include "psphot.h"
 
-// XXX need a better structure for handling optional sequence
+// XXX need a better structure for handling optional sequences
 int main (int argc, char **argv) {
-
-    psMetadata  *config  = NULL;
-    psMetadata  *header  = NULL;
-    pmReadout   *readout  = NULL;
-    psArray     *sources = NULL;
-    psArray     *peaks   = NULL;
-    pmPSF       *psf     = NULL;
-    psStats     *sky     = NULL;
-    bool         status;
 
     psTimerStart ("complete");
 
-    // load command-line arguments and options
-    config = psphotArguments (&argc, argv);
+    // load implementation-specific models
+    psphotModelGroupInit ();
+
+    // load command-line arguments, options, and system config data
+    ppConfig *config = psphotArguments (&argc, argv);
 
     // load input data (config and images (signal, noise, mask)
-    readout = psphotSetup (config, &header);
+    ppFile *input = psphotParseCamera (config);
 
-    // run a single-model test if desired
-    psphotModelTest (readout, config);
+    // run a single-model test if desired - XXX push in psphotImageLoop?
+    // psphotModelTest (input, options);
 
-    // measure image stats for initial guess 
-    sky = psphotImageStats (readout, config);
+    // call psphot for each readout
+    psphotImageLoop (input, config);
 
-    // generate a background model (currently, 2D polynomial)
-    // XXX this should be available to be re-added to the original image
-    psphotImageBackground (readout, config, sky);
-
-    // find the peaks in the image
-    peaks = psphotFindPeaks (readout, config, sky);
-
-    // construct sources and measure basic stats
-    sources = psphotSourceStats (readout, config, peaks);
-
-    // classify sources based on moments, brightness
-    psphotRoughClass (sources, config);
-
-    // mark blended peaks PS_SOURCE_BLEND
-    psphotBasicDeblend (sources, config, sky);
-
-    // use bright stellar objects to measure PSF
-    psf = psphotChoosePSF (config, sources, sky);
-
-    // XXX change FITMODE to a string
-    int FITMODE = psMetadataLookupS32 (&status, config, "FIT_MODE");
-    switch (FITMODE) {
-      case 0:
-	psphotEnsemblePSF (readout, config, sources, psf, sky);
-	break;
-
-      case 1:
-	psphotEnsemblePSF (readout, config, sources, psf, sky);
-	psphotFullFit (readout, config, sources, psf, sky);
-	psphotReplaceUnfit (sources);
-	psphotApResid (readout, sources, config, psf);
-	break;
-
-      case 2:
-	psphotEnsemblePSF (readout, config, sources, psf, sky);
-	psphotBlendFit (readout, config, sources, psf, sky);
-	psphotReplaceUnfit (sources);
-	psphotApResid (readout, sources, config, psf);
-	break;
-
-      case 3:
-	psphotApplyPSF (readout, config, sources, psf, sky);
-	break;
-
-      case 4:
-	psphotApplyPSF (readout, config, sources, psf, sky);
-	psphotFitExtended (readout, config, sources, sky);
-	break;
-    }
-
-    // write out data in appropriate format
-    psphotOutput (readout, header, config, sources, psf, sky);
-    psLogMsg ("psphot", 3, "wrote output: %f sec\n", psTimerMark ("psphot"));
     psLogMsg ("psphot", 3, "complete psphot run: %f sec\n", psTimerMark ("complete"));
     exit (0);
Index: trunk/psphot/src/psphot.h
===================================================================
--- trunk/psphot/src/psphot.h	(revision 6056)
+++ trunk/psphot/src/psphot.h	(revision 6117)
@@ -5,4 +5,6 @@
 # include <pmObjects.h>
 # include <pmGrowthCurve.h>
+# include <pmConfig.h>
+# include <pmFPARead.h>
 # include <pmPSF.h>
 # include <pmPSFtry.h>
@@ -11,11 +13,20 @@
 # include "psModulesUtils.h"
 # include "psSparse.h"
+#include "pmFPAConstruct.h"
+
+# define PSPHOT_RECIPE "PSPHOT"
 
 # define psMemCopy(A)(psMemIncrRefCounter((A)))
 
 // top-level psphot functions
-psMetadata     *psphotArguments (int *argc, char **argv);
-pmReadout      *psphotSetup (psMetadata *config, psMetadata **header);
+ppConfig       *psphotArguments (int *argc, char **argv);
+ppFile         *psphotParseCamera (ppConfig *config);
+bool            psphotImageLoop (ppFile *input, ppConfig *config);
+
 bool            psphotModelTest (pmReadout *readout, psMetadata *config);
+bool            psphotReadout (pmReadout *readout, psMetadata *config);
+bool            ppImageLoadPixels (ppFile *input, psDB *db, int chipNum, int cellNum);
+
+// psphotReadout functions
 psStats        *psphotImageStats (pmReadout *readout, psMetadata *config);
 psPolynomial2D *psphotImageBackground (pmReadout *readout, psMetadata *config, psStats *sky);
@@ -25,5 +36,5 @@
 bool            psphotBasicDeblend (psArray *sources, psMetadata *config, psStats *sky);
 pmPSF          *psphotChoosePSF (psMetadata *config, psArray *sources, psStats *sky);
-void            psphotOutput (pmReadout *readout, psMetadata *header, psMetadata *config, psArray *sources, pmPSF *psf, psStats *sky);
+void            psphotOutput (pmReadout *readout, psMetadata *config);
 
 // optional object analysis steps
@@ -46,4 +57,5 @@
 bool            psphotGrowthCurve (pmReadout *readout, pmPSF *psf);
 void            psphotTestArguments (int *argc, char **argv);
+bool            pmCellSetMask (pmCell *cell, psMetadata *recipe);
 
 // functions to set the correct source pixels
@@ -56,11 +68,11 @@
 
 // output functions
-bool 	     	pmSourcesWriteText (pmReadout *readout, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *sky);
-bool 	     	pmSourcesWriteOBJ  (pmReadout *readout, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *sky);
-bool 	     	pmSourcesWriteCMP  (pmReadout *readout, psMetadata *header, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *sky);
-bool 	     	pmSourcesWriteCMF  (pmReadout *readout, psMetadata *header, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *sky);
-bool 	     	pmSourcesWriteSX   (pmReadout *readout, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *sky);
+bool 	     	pmSourcesWriteSX   (psArray *sources, char *filename);
+bool 	     	pmSourcesWriteOBJ  (psArray *sources, char *filename);
+bool 	     	pmSourcesWriteCMP  (psArray *sources, char *filename, psMetadata *header);
+bool 	     	pmSourcesWriteCMF  (psArray *sources, char *filename, psMetadata *header);
+bool 	     	pmSourcesWriteText (psArray *sources, char *filename);
 
-bool 	     	pmModelWritePSFs (psArray *sources, psMetadata *config, char *filename, pmPSF *psf);
+bool 	     	pmModelWritePSFs (psArray *sources, char *filename);
 bool 	     	pmModelWriteEXTs (psArray *sources, char *filename);
 bool 	     	pmModelWriteNULLs (psArray *sources, char *filename);
@@ -74,4 +86,6 @@
 int  	     	pmSourcesDophotType (pmSource *source);
 bool            psMetadataItemTransfer (psMetadata *out, psMetadata *in, char *key);
+
+bool            psphotMagnitudes (psMetadata *config, psArray *sources, pmPSF *psf);
 
 // PSF / DBL / EXT evaluation functions
@@ -99,4 +113,7 @@
 psPlane         psImageBicubeMin (psPolynomial2D *poly);
 
+bool psImageJpegColormap (char *name);
+bool psImageJpeg (psImage *image, char *filename, float zero, float scale);
+
 // optional mode for clip fit?
 psPolynomial4D *psVectorChiClipFitPolynomial4D(
Index: trunk/psphot/src/psphotArguments.c
===================================================================
--- trunk/psphot/src/psphotArguments.c	(revision 6056)
+++ trunk/psphot/src/psphotArguments.c	(revision 6117)
@@ -1,66 +1,36 @@
 # include "psphot.h"
 
-static void usage (void) ;
+static void usage (psMetadata *arguments) ;
 
-psMetadata *psphotArguments (int *argc, char **argv) {
+ppConfig *psphotArguments (int *argc, char **argv) {
 
     int N;
-    unsigned int Nfail;
-
-    psMetadata *options = psMetadataAlloc ();
 
     // basic pslib options
     psLogSetFormat ("M");
-    psArgumentVerbosity (argc, argv);
 
-    // command-line options -- place on options MD
-    // mask image
-    if ((N = psArgumentGet (*argc, argv, "-mask"))) {
-	psArgumentRemove (N, argc, argv);
-	psMetadataAddStr (options, PS_LIST_TAIL, "MASK_IMAGE", 0, "", psStringCopy(argv[N]));
-	psArgumentRemove (N, argc, argv);
+    ppConfig *config = ppConfigAlloc ();
+
+    // load config data from default locations 
+    if (!pmConfigRead(&config->site, &config->camera, &config->recipe, argc, argv, PSPHOT_RECIPE)) {
+        psErrorStackPrint(stderr, "Can't find site configuration!\n");
+        exit (EXIT_FAILURE);
     }
 
-    // weight image
-    if ((N = psArgumentGet (*argc, argv, "-weight"))) {
-	psArgumentRemove (N, argc, argv);
-	psMetadataAddStr (options, PS_LIST_TAIL, "WEIGHT_IMAGE", 0, "", psStringCopy(argv[N]));
-	psArgumentRemove (N, argc, argv);
-    }
+    // Parse other command-line arguments
+    config->arguments = psMetadataAlloc ();
 
-    // output residual image
-    if ((N = psArgumentGet (*argc, argv, "-resid"))) {
-	psArgumentRemove (N, argc, argv);
-	psMetadataAddStr (options, PS_LIST_TAIL, "RESID_IMAGE", 0, "", psStringCopy(argv[N]));
-	psArgumentRemove (N, argc, argv);
-    }
-
-    // analysis region
-    if ((N = psArgumentGet (*argc, argv, "-region"))) {
-	psArgumentRemove (N, argc, argv);
-	psMetadataAddStr (options, PS_LIST_TAIL, "ANALYSIS_REGION", 0, "", psStringCopy(argv[N]));
-	psArgumentRemove (N, argc, argv);
-    }
-
-    // output residual image
-    psMetadataAddStr (options, PS_LIST_TAIL, "PHOTCODE", 0, "", psStringCopy("NONE"));
-    if ((N = psArgumentGet (*argc, argv, "-photcode"))) {
-	psArgumentRemove (N, argc, argv);
-	psMetadataAddStr (options, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "", psStringCopy(argv[N]));
-	psArgumentRemove (N, argc, argv);
-    }
-
-    // input PSF file
-    if ((N = psArgumentGet (*argc, argv, "-psf"))) {
-	psArgumentRemove (N, argc, argv);
-	psMetadataAddStr (options, PS_LIST_TAIL, "PSF_INPUT_FILE", 0, "", psStringCopy (argv[N]));
-	psArgumentRemove (N, argc, argv);
-    }
-
-    // run the 0ltest model (requires X,Y coordinate)
+    // arguments (must be supplied for each run, or not used)
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-psf", 0, "input psf file", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-mask", 0,   "Name of the mask image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-resid", 0,  "Name of the output residual image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-weight", 0, "Name of the weight image", "");
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "-chip", 0, "Chip number to process (if positive)", -1);
+    
+    // run the test model (requires X,Y coordinate)
     if ((N = psArgumentGet (*argc, argv, "-modeltest"))) {
-	psMetadataAddBool (options, PS_LIST_TAIL, "TEST_FIT",   0, "", true);
-	psMetadataAddF32  (options, PS_LIST_TAIL, "TEST_FIT_X", 0, "", atof(argv[N+1]));
-	psMetadataAddF32  (options, PS_LIST_TAIL, "TEST_FIT_Y", 0, "", atof(argv[N+2]));
+	psMetadataAddBool (config->arguments, PS_LIST_TAIL, "TEST_FIT",   0, "", true);
+	psMetadataAddF32  (config->arguments, PS_LIST_TAIL, "TEST_FIT_X", 0, "", atof(argv[N+1]));
+	psMetadataAddF32  (config->arguments, PS_LIST_TAIL, "TEST_FIT_Y", 0, "", atof(argv[N+2]));
 
 	psArgumentRemove (N, argc, argv);
@@ -71,5 +41,5 @@
 	if ((N = psArgumentGet (*argc, argv, "-model"))) {
 	    psArgumentRemove (N, argc, argv);
-	    psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_MODEL", 0, "", psStringCopy (argv[N]));
+	    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "TEST_FIT_MODEL", 0, "", psStringCopy (argv[N]));
 	    psArgumentRemove (N, argc, argv);
 	}
@@ -78,76 +48,90 @@
 	if ((N = psArgumentGet (*argc, argv, "-fitmode"))) {
 	    psArgumentRemove (N, argc, argv);
-	    psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_MODE", 0, "", psStringCopy (argv[N]));
+	    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "TEST_FIT_MODE", 0, "", psStringCopy (argv[N]));
 	    psArgumentRemove (N, argc, argv);
 	}
 	if ((N = psArgumentGet (*argc, argv, "-fitset"))) {
 	    psArgumentRemove (N, argc, argv);
-	    psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_SET", 0, "", psStringCopy (argv[N]));
+	    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "TEST_FIT_SET", 0, "", psStringCopy (argv[N]));
 	    psArgumentRemove (N, argc, argv);
 	}
     }
 
-    // other arbitrary options: -D key value (all added as string)
+    // Parse command-line overrides of recipe values
+    psMetadata *recipe = psMetadataAlloc ();
+
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-photcode", 0, "photometry code", "NONE");
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "-break", 0, "", "NONE");
+    psMetadataAddS32 (config->arguments, PS_LIST_TAIL, "-fitmode", 0, "", 2);
+
+    // analysis region : XXX override recipe 
+    if ((N = psArgumentGet (*argc, argv, "-photcode"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddStr (recipe, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "", psStringCopy(argv[N]));
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // analysis region : XXX override recipe 
+    if ((N = psArgumentGet (*argc, argv, "-break"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddStr (recipe, PS_LIST_TAIL, "BREAK_POINT", PS_META_REPLACE, "", psStringCopy(argv[N]));
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // analysis region : XXX override recipe 
+    if ((N = psArgumentGet (*argc, argv, "-fitmode"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddStr (recipe, PS_LIST_TAIL, "FITMODE", PS_META_REPLACE, "", psStringCopy(argv[N]));
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // analysis region : XXX override recipe 
+    if ((N = psArgumentGet (*argc, argv, "-region"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddStr (recipe, PS_LIST_TAIL, "ANALYSIS_REGION", 0, "", psStringCopy(argv[N]));
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // other arbitrary recipe values: -D key value (all added as string)
     while ((N = psArgumentGet (*argc, argv, "-D"))) {
 	psArgumentRemove (N, argc, argv);
-	psMetadataAddStr (options, PS_LIST_TAIL, argv[N], 0, "", argv[N+1]);
+	psMetadataAddStr (recipe, PS_LIST_TAIL, argv[N], 0, "", argv[N+1]);
 	psArgumentRemove (N, argc, argv);
 	psArgumentRemove (N, argc, argv);
     }
 
-    // other arbitrary options: -Df key value (all added as float)
+    // other arbitrary recipe values: -Df key value (all added as float)
     while ((N = psArgumentGet (*argc, argv, "-Df"))) {
 	psArgumentRemove (N, argc, argv);
-	psMetadataAddF32 (options, PS_LIST_TAIL, argv[N], 0, "", atof(argv[N+1]));
+	psMetadataAddF32 (recipe, PS_LIST_TAIL, argv[N], 0, "", atof(argv[N+1]));
 	psArgumentRemove (N, argc, argv);
 	psArgumentRemove (N, argc, argv);
     }
 
-    // other arbitrary options: -Di key value (all added as int)
+    // other arbitrary recipe values: -Di key value (all added as int)
     while ((N = psArgumentGet (*argc, argv, "-Di"))) {
 	psArgumentRemove (N, argc, argv);
-	psMetadataAddS32 (options, PS_LIST_TAIL, argv[N], 0, "", atoi(argv[N+1]));
+	psMetadataAddS32 (recipe, PS_LIST_TAIL, argv[N], 0, "", atoi(argv[N+1]));
 	psArgumentRemove (N, argc, argv);
 	psArgumentRemove (N, argc, argv);
     }
 
-    if (*argc != 4) usage ();
+    // place the recipe options on the arguments stack
+    psMetadataAddPtr (config->arguments, PS_LIST_TAIL, "RECIPE.OPTIONS", PS_DATA_UNKNOWN, "", recipe);
 
-    // load config information
-    psMetadata *config = psMetadataAlloc ();
+    if (!psArgumentParse(config->arguments, argc, argv)) usage (config->arguments);
+    if (*argc != 3) usage (config->arguments);
 
-    // add default values
-    psMetadataAddStr (config, PS_LIST_TAIL, "BREAK_POINT", 0, "", "NONE");
-    psMetadataAddS32 (config, PS_LIST_TAIL, "FIT_MODE", 0, "", 2);
-
-    psMetadataAdd (config, PS_LIST_TAIL, "PSF_MODEL", PS_DATA_METADATA_MULTI, "folder for psf model entries", NULL);
-
-    // config file values override defaults set here
-    config = psMetadataConfigParse (config, &Nfail, argv[3], TRUE);
-
-    psMetadataItem *item = NULL;
-    psMetadataIterator *iter = psMetadataIteratorAlloc (options, PS_LIST_HEAD, NULL);
-    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
-	psMetadataAddItem (config, item, PS_LIST_TAIL, PS_META_REPLACE);
-	psFree (item);
-    }
-    psFree (iter);
-
-    // identify input image & optional weight & mask images
-    // command-line entries override config-file entries
-    psMetadataAddStr (config, PS_LIST_TAIL, "IMAGE",       PS_META_REPLACE, "", argv[1]);
-    psMetadataAddStr (config, PS_LIST_TAIL, "OUTPUT_FILE", PS_META_REPLACE, "", argv[2]);
+    // input and output positions are fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "-input",  PS_META_REPLACE, "", argv[1]);
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "-output", PS_META_REPLACE, "", argv[2]);
 
     return (config);
 }
 
-static void usage (void) {
+static void usage (psMetadata *arguments) {
 
-    fprintf (stderr, "USAGE: psphot (image) (output) (config)\n");
-    fprintf (stderr, "options: \n");
-    fprintf (stderr, "  -mask  (filename)\n");
-    fprintf (stderr, "  -weight (filename)\n");
-    fprintf (stderr, "  -resid (filename)\n");
-    fprintf (stderr, "  -photcode (photcode)\n");
+    fprintf (stderr, "USAGE: psphot (image) (output)\n");
+    psArgumentHelp (arguments);
     exit (2);
 }
Index: trunk/psphot/src/psphotChoosePSF.c
===================================================================
--- trunk/psphot/src/psphotChoosePSF.c	(revision 6056)
+++ trunk/psphot/src/psphotChoosePSF.c	(revision 6117)
@@ -34,9 +34,14 @@
 
     // get the list pointers for the PSF_MODEL entries
+    psList *list = NULL;
     psMetadataItem *mdi = psMetadataLookup (config, "PSF_MODEL");
     if (mdi == NULL) psAbort ("psphotChoosePSF", "missing PSF_MODEL selection");
-
-    psList *list = (psList *) mdi->data.list;
-    psListIterator *iter = psListIteratorAlloc (list, PS_LIST_HEAD, FALSE); 
+    if (mdi->type == PS_DATA_STRING) {
+	list = psListAlloc(NULL);
+	psListAdd (list, PS_LIST_HEAD, mdi);
+    } else {
+	if (mdi->type != PS_DATA_METADATA_MULTI) psAbort ("psphotChoosePSF", "missing PSF_MODEL selection");
+	list = psMemIncrRefCounter(mdi->data.list);
+    }
 
     // set up an array to store the results
@@ -44,4 +49,5 @@
 
     // try each model option listed in config
+    psListIterator *iter = psListIteratorAlloc (list, PS_LIST_HEAD, FALSE); 
     for (int i = 0; i < models->n; i++) { 
 
@@ -54,5 +60,5 @@
     }
     psFree (iter);
-    // psFree (list); XXX is list freed with iter?
+    psFree (list);
     psFree (stars);
 
Index: trunk/psphot/src/psphotImageLoop.c
===================================================================
--- trunk/psphot/src/psphotImageLoop.c	(revision 6117)
+++ trunk/psphot/src/psphotImageLoop.c	(revision 6117)
@@ -0,0 +1,83 @@
+# include "psphot.h"
+
+bool psphotImageLoop (ppFile *file, ppConfig *config) {
+
+    bool status;
+    ppImageLoadDepth imageLoadDepth;
+    char filename[1024];
+
+    // determine the load depth
+    const char *depth = psMetadataLookupStr(&status, config->recipe, "LOAD.DEPTH");
+    if (! status || ! depth || strlen(depth) == 0) {
+        psLogMsg("psphot", PS_LOG_ERROR, "LOAD.DEPTH not specified in recipe.");
+        exit(EXIT_FAILURE);
+    }
+    imageLoadDepth = PP_LOAD_NONE;
+    if (!strcasecmp(depth, "FPA")) imageLoadDepth = PP_LOAD_FPA;
+    if (!strcasecmp(depth, "CHIP")) imageLoadDepth = PP_LOAD_CHIP;
+    if (!strcasecmp(depth, "CELL")) imageLoadDepth = PP_LOAD_CELL;
+    if (imageLoadDepth == PP_LOAD_NONE) {
+        psLogMsg(__func__, PS_LOG_ERROR, "LOAD.DEPTH in recipe is not FPA, CHIP or CELL.");
+        exit(EXIT_FAILURE);
+    }
+
+    if (imageLoadDepth == PP_LOAD_FPA) {
+        psTrace(__func__, 1, "Loading pixels for FPA...\n");
+        ppImageLoadPixels(file, config->database, -1, -1);
+    }
+
+    char *outputRoot = psMetadataLookupPtr (&status, config->arguments, "-output");
+    if (!status) psAbort ("psphot", "output file not specified");
+
+    int Nout = 0;
+    for (int i = 0; i < file->fpa->chips->n; i++) {
+        pmChip *chip = file->fpa->chips->data[i]; // Chip of interest in input image
+
+        psLogMsg ("psphot", 4, "Chip %d: %x %x\n", i, chip->exists, chip->process);
+        if (! chip->process) { continue; }
+
+        if (imageLoadDepth == PP_LOAD_CHIP) {
+            psTrace(__func__, 1, "Loading pixels for chip %d...\n", i);
+            ppImageLoadPixels(file, config->database, i, -1);
+        }
+
+        for (int j = 0; j < chip->cells->n; j++) {
+            pmCell *cell = chip->cells->data[j]; // Cell of interest in input image
+
+            psLogMsg ("psphot", 4, "Cell %d: %x %x\n", j, cell->exists, cell->process);
+            if (! cell->process) { continue; }
+
+            if (imageLoadDepth == PP_LOAD_CELL) {
+                psTrace(__func__, 1, "Loading pixels for chip %d, cell %d...\n", i, j);
+                ppImageLoadPixels(file, config->database, i, j);
+            }
+
+	    pmCellSetWeights(cell);
+
+	    // I have a valid mask, now mask in the analysis region of interest
+	    pmCellSetMask (cell, config->recipe); 
+
+
+	    // process each of the readouts
+	    for (int k = 0; k < cell->readouts->n; k++) {
+		pmReadout *readout = cell->readouts->data[k]; // Readout of interest in input image
+
+		// psphotSaveImage (NULL, readout->image, "image.fits");
+		// psphotSaveImage (NULL, readout->weight, "weight.fits");
+		// psphotSaveImage (NULL, readout->mask, "mask.fits");
+
+		psphotReadout (readout, config->recipe);
+
+		// XXX EAM : temporarily write out only CMF with fixed extension
+		sprintf (filename, "%s.%02d.cmf", outputRoot, Nout);
+		psMetadata *header = pmReadoutGetHeader (readout);
+
+		psArray *sources = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.SOURCES");
+
+		pmSourcesWriteCMF (sources, filename, header);
+		Nout++;
+	    }
+        }
+    }
+    return true;
+}
Index: trunk/psphot/src/psphotImageStats.c
===================================================================
--- trunk/psphot/src/psphotImageStats.c	(revision 6056)
+++ trunk/psphot/src/psphotImageStats.c	(revision 6117)
@@ -39,13 +39,12 @@
     }
 
-    // we store these values in mean,stdev 
-    bool status = false;
-    float NOISE = psMetadataLookupF32 (&status, config, "RDNOISE");
-    float GAIN  = psMetadataLookupF32 (&status, config, "GAIN");
+    pmCell *cell = readout->parent;	// cell containing this readout;
+    float gain = psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN"); // Cell gain
+    float noise = psMetadataLookupF32(NULL, cell->concepts, "CELL.READNOISE"); // Cell read noise
 
     // convert instrumental background to poisson stats
     sky = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
     sky->sampleMean   = stats->sampleMedian;
-    sky->sampleStdev  = sqrt(sky->sampleMean/GAIN + PS_SQR(NOISE));
+    sky->sampleStdev  = sqrt(sky->sampleMean/gain + PS_SQR(noise));
 
     psLogMsg ("psphot", 4, "background: %f +/- %f\n", sky->sampleMean, sky->sampleStdev);
Index: trunk/psphot/src/psphotLoadPixels.c
===================================================================
--- trunk/psphot/src/psphotLoadPixels.c	(revision 6117)
+++ trunk/psphot/src/psphotLoadPixels.c	(revision 6117)
@@ -0,0 +1,37 @@
+# include "psphot.h"
+
+bool ppImageLoadPixels (ppFile *input, psDB *db, int chipNum, int cellNum)
+{
+    // an input chip is valid for processing if:
+    // (((chipNum == i) || (chipNum == -1)) && chip.process)
+
+    // If we have not opened the file, skip it
+    if (input->fits == NULL) {
+        return false;
+    }
+
+    psTrace(__func__, 1, "Loading %d,%d for %s\n", chipNum, cellNum, input->filename);
+
+    // set input:valid flags according to process and chipNum/cellNum
+    for (int i = 0; i < input->fpa->chips->n; i++) {
+        pmChip *chip = input->fpa->chips->data[i]; // Chip in input image
+        chip->process = (! chip->exists && ((chipNum == i) || (chipNum == -1)));
+
+        for (int j = 0; j < chip->cells->n; j++) {
+            pmCell *cell = chip->cells->data[j]; // Cell in input image
+            cell->process &= chip->process;
+            cell->process = (! cell->exists && ((cellNum == i) || (cellNum == -1)));
+        }
+    }
+
+    // Read in the input pixels
+    if (! pmFPARead(input->fpa, input->fits, input->phu, db)) {
+        psErrorStackPrint(stderr, "Unable to populate camera from input FITS file\n");
+        exit(EXIT_FAILURE);
+    }
+    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/psphot/src/psphotMagnitudes.c
===================================================================
--- trunk/psphot/src/psphotMagnitudes.c	(revision 6056)
+++ trunk/psphot/src/psphotMagnitudes.c	(revision 6117)
@@ -71,2 +71,15 @@
 
 */
+
+bool psphotMagnitudes (psMetadata *config, psArray *sources, pmPSF *psf) {
+
+    bool status;
+
+    float RADIUS = psMetadataLookupF32 (&status, config, "AP_RADIUS");
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+	pmSourceMagnitudes (source, psf, RADIUS);
+    }	
+    return true;
+}
+
Index: trunk/psphot/src/psphotOutput.c
===================================================================
--- trunk/psphot/src/psphotOutput.c	(revision 6056)
+++ trunk/psphot/src/psphotOutput.c	(revision 6117)
@@ -2,9 +2,14 @@
 
 // output functions: we have several fixed modes (sx, obj, cmp)
-void psphotOutput (pmReadout *readout, psMetadata *header, psMetadata *config, psArray *sources, pmPSF *psf, psStats *sky) {
+void psphotOutput (pmReadout *readout, psMetadata *config) {
 
     bool status;
 
     psTimerStart ("psphot");
+
+    psMetadata *header = pmReadoutGetHeader (readout);
+
+    psArray *sources = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.SOURCES");
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
 
     char *outputMode = psMetadataLookupPtr (&status, config, "OUTPUT_MODE");
@@ -29,24 +34,24 @@
 	return;
     }
+    if (!strcasecmp (outputMode, "SX")) {
+	pmSourcesWriteSX (sources, outputFile);
+	return;
+    }
     if (!strcasecmp (outputMode, "OBJ")) {
-	pmSourcesWriteOBJ (readout, config, outputFile, sources, psf, sky);
+	pmSourcesWriteOBJ (sources, outputFile);
 	return;
     }
-    if (!strcasecmp (outputMode, "SX")) {
-	pmSourcesWriteSX (readout, config, outputFile, sources, psf, sky);
+    if (!strcasecmp (outputMode, "CMP")) {
+	pmSourcesWriteCMP (sources, outputFile, header);
 	return;
     }
-    if (!strcasecmp (outputMode, "CMP")) {
-	pmSourcesWriteCMP (readout, header, config, outputFile, sources, psf, sky);
+    if (!strcasecmp (outputMode, "CMF")) {
+	pmSourcesWriteCMF (sources, outputFile, header);
 	return;
     }
-    if (!strcasecmp (outputMode, "CMF")) {
-	pmSourcesWriteCMF (readout, header, config, outputFile, sources, psf, sky);
+    if (!strcasecmp (outputMode, "TEXT")) {
+	pmSourcesWriteText (sources, outputFile);
 	return;
     }
-    if (!strcasecmp (outputMode, "TEXT")) {
-	pmSourcesWriteText (readout, config, outputFile, sources, psf, sky);
-	return;
-    }
 
     psAbort ("psphot", "unknown output mode %s", outputMode);
@@ -54,11 +59,9 @@
 
 // dophot-style output list with fixed line width
-bool pmSourcesWriteOBJ (pmReadout *readout, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *skyStats) {
+bool pmSourcesWriteOBJ (psArray *sources, char *filename) {
 
     int type;
-    pmModel *model;
     psF32 *PAR, *dPAR;
     float dmag, apResid;
-    bool status;
 
     psLine *line = psLineAlloc (104);  // 104 is dophot-defined line length
@@ -69,12 +72,9 @@
 	return false;
     }
-
-    float RADIUS = psMetadataLookupF32 (&status, config, "AP_RADIUS");
 
     // write sources with models 
     for (int i = 0; i < sources->n; i++) {
 	pmSource *source = (pmSource *) sources->data[i];
-
-	model = pmSourceMagnitudes (source, psf, RADIUS);
+	pmModel *model = pmModelSelect (source);
 	if (model == NULL) continue;
 
@@ -106,10 +106,8 @@
 
 // elixir-mode / sextractor-style output list with fixed line width
-bool pmSourcesWriteSX (pmReadout *readout, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *skyStats) {
-
-    pmModel *model;
+bool pmSourcesWriteSX (psArray *sources, char *filename) {
+
     psF32 *PAR, *dPAR;
     float dmag;
-    bool status;
 
     psLine *line = psLineAlloc (110);  // 110 is sextractor line length
@@ -120,12 +118,9 @@
 	return false;
     }
-
-    float RADIUS = psMetadataLookupF32 (&status, config, "AP_RADIUS");
 
     // write sources with models 
     for (int i = 0; i < sources->n; i++) {
 	pmSource *source = (pmSource *) sources->data[i];
-
-	model = pmSourceMagnitudes (source, psf, RADIUS);
+	pmModel *model = pmModelSelect (source);
 	if (model == NULL) continue;
 
@@ -157,8 +152,7 @@
 
 // elixir-style pseudo FITS table (header + ascii list)
-bool pmSourcesWriteCMP (pmReadout *readout, psMetadata *header, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *skyStats) {
+bool pmSourcesWriteCMP (psArray *sources, char *filename, psMetadata *header) {
 
     int i, type;
-    pmModel *model;
     psMetadataItem *mdi;
     psF32 *PAR, *dPAR;
@@ -167,10 +161,8 @@
 
     // find config information for output header
-    float RADIUS = psMetadataLookupF32 (&status, config, "AP_RADIUS");
-    float ZERO_POINT = psMetadataLookupF32 (&status, config, "ZERO_POINT");
+    float ZERO_POINT = psMetadataLookupF32 (&status, header, "ZERO_PT");
     if (!status) ZERO_POINT = 25.0;
 
     // write necessary information to output header
-    psphotUpdateHeader (header, config);
     psMetadataAdd (header, PS_LIST_TAIL, "NSTARS",   PS_DATA_S32 | PS_META_REPLACE, "NUMBER OF STARS", sources->n);
 
@@ -200,6 +192,5 @@
     for (i = 0; i < sources->n; i++) {
 	pmSource *source = (pmSource *) sources->data[i];
-
-	model = pmSourceMagnitudes (source, psf, RADIUS);
+	pmModel *model = pmModelSelect (source);
 	if (model == NULL) continue;
 
@@ -232,5 +223,5 @@
 // this format consists of a header derived from the image header
 // followed by a zero-size matrix, followed by the table data
-bool pmSourcesWriteCMF (pmReadout *readout, psMetadata *header, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *skyStats) {
+bool pmSourcesWriteCMF (psArray *sources, char *filename, psMetadata *header) {
 
     psArray *table;
@@ -239,5 +230,4 @@
     psMetadata *theader;
     int i, type;
-    pmModel *model;
     psF32 *PAR, *dPAR;
     float dmag, lsky;
@@ -245,9 +235,5 @@
 
     // find config information for output header
-    float RADIUS = psMetadataLookupF32 (&status, config, "AP_RADIUS");
-    float ZERO_POINT = psMetadataLookupF32 (&status, config, "ZERO_POINT");
-
-    // write necessary information to output header
-    psphotUpdateHeader (header, config);
+    float ZERO_POINT = psMetadataLookupF32 (&status, header, "ZERO_PT");
 
     table = psArrayAlloc (sources->n);
@@ -256,6 +242,5 @@
     for (i = 0; i < sources->n; i++) {
 	pmSource *source = (pmSource *) sources->data[i];
-
-	model = pmSourceMagnitudes (source, psf, RADIUS);
+	pmModel *model = pmModelSelect (source);
 	if (model == NULL) continue;
 
@@ -309,10 +294,10 @@
 /***** Text Output Methods *****/
 
-bool pmSourcesWriteText (pmReadout *readout, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *sky) {
+bool pmSourcesWriteText (psArray *sources, char *filename) {
 
     char *name = (char *) psAlloc (strlen(filename) + 10);
 
     sprintf (name, "%s.psf.dat", filename);
-    pmModelWritePSFs (sources, config, name, psf);
+    pmModelWritePSFs (sources, name);
 
     sprintf (name, "%s.ext.dat", filename);
@@ -330,5 +315,5 @@
 
 // write the PSF sources to an output file
-bool pmModelWritePSFs (psArray *sources, psMetadata *config, char *filename, pmPSF *psf) {
+bool pmModelWritePSFs (psArray *sources, char *filename) {
 
     double dPos, dMag;
@@ -337,7 +322,4 @@
     psF32 *PAR, *dPAR;
     pmModel  *model;
-    bool status;
-
-    float RADIUS = psMetadataLookupF32 (&status, config, "AP_RADIUS");
 
     f = fopen (filename, "w");
@@ -350,7 +332,6 @@
     for (i = 0; i < sources->n; i++) {
 	pmSource *source = (pmSource *) sources->data[i];
-
 	if (source->type != PM_SOURCE_STAR) continue;
-	model = pmSourceMagnitudes (source, psf, RADIUS);
+	model = source->modelPSF;
 	if (model == NULL) continue;
 
Index: trunk/psphot/src/psphotParseCamera.c
===================================================================
--- trunk/psphot/src/psphotParseCamera.c	(revision 6117)
+++ trunk/psphot/src/psphotParseCamera.c	(revision 6117)
@@ -0,0 +1,75 @@
+# include "psphot.h"
+
+ppFile *psphotParseCamera (ppConfig *config) {
+
+    bool status;
+
+    ppFile *input = ppFileAlloc ();
+
+    input->filename = psMemIncrRefCounter(psMetadataLookupStr(NULL, config->arguments, "-input"));
+
+    // Open the input image
+    psLogMsg("psphot", PS_LOG_INFO, "Opening input image: %s\n", input->filename);
+    input->fits = psFitsOpen (input->filename, "r"); // File handle for FITS file
+    if (! input->fits) {
+        psErrorStackPrint(stderr, "Can't open input image: %s\n", input->filename);
+        exit(EXIT_FAILURE);
+    }
+    input->phu = psFitsReadHeader(NULL, input->fits); // FITS header
+
+    // Get camera configuration from header if not already defined
+    if (! config->camera) {
+        config->camera = pmConfigCameraFromHeader(config->site, input->phu);
+        if (! config->camera) {
+             // There's no point in continuing if we can't recognize what we've got
+            psErrorStackPrint(stderr, "Can't find camera configuration!\n");
+            exit(EXIT_FAILURE);
+        }
+   } else if (! pmConfigValidateCamera(config->camera, input->phu)) {
+       // There's no point in continuing if what we've got doesn't match what we've been told
+        psError(PS_ERR_IO, true, "%s does not seem to be from the specified camera.\n",
+                input->filename);
+        exit(EXIT_FAILURE);
+    }
+
+    // Determine the correct recipe to use
+    // if the user specifies a recipe, no default values are supplied
+    if (!config->recipe) {
+
+	config->recipe = pmConfigRecipeFromCamera(config->camera, PSPHOT_RECIPE);
+	if (config->recipe == NULL) {
+	    psErrorStackPrint(stderr, "Can't find recipe configuration!\n");
+	    exit(EXIT_FAILURE);
+	}
+    }
+    
+    // recipe override values:
+    psMetadata *recipe = psMetadataLookupPtr (&status, config->arguments, "RECIPE.OPTIONS");
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc (recipe, PS_LIST_HEAD, NULL);
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+	psMetadataAddItem (config->recipe, item, PS_LIST_TAIL, PS_META_REPLACE);
+	psFree (item);
+    }
+    psFree (iter);
+
+    psMetadataAddStr (config->recipe, PS_LIST_TAIL, "FITMODE", 0, "", "NONE");
+    psMetadataAddStr (config->recipe, PS_LIST_TAIL, "PHOTCODE", 0, "", "NONE");
+    psMetadataAddStr (config->recipe, PS_LIST_TAIL, "BREAK_POINT", 0, "", "NONE");
+
+    // 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
+    int chipNum = psMetadataLookupS32(NULL, config->arguments, "-chip"); // Chip number to work on
+    if (chipNum >= 0) {
+        if (! pmFPASelectChip(input->fpa, chipNum)) {
+            psErrorStackPrint(stderr, "Chip number %d doesn't exist in camera.\n", chipNum);
+            exit(EXIT_FAILURE);
+        }
+        psLogMsg("psphot", PS_LOG_INFO, "Operating only on chip %d\n", chipNum);
+    }
+
+    // Construct cameras in preparation for reading
+    input->fpa = pmFPAConstruct(config->camera);
+
+    return input;
+}
Index: trunk/psphot/src/psphotReadout.c
===================================================================
--- trunk/psphot/src/psphotReadout.c	(revision 6117)
+++ trunk/psphot/src/psphotReadout.c	(revision 6117)
@@ -0,0 +1,69 @@
+# include "psphot.h"
+
+bool psphotReadout (pmReadout *readout, psMetadata *config) {
+
+    psArray     *sources = NULL;
+    psArray     *peaks   = NULL;
+    pmPSF       *psf     = NULL;
+    psStats     *sky     = NULL;
+    bool         status;
+
+    // measure image stats for initial guess 
+    sky = psphotImageStats (readout, config);
+
+    // generate a background model (currently, 2D polynomial)
+    // XXX this should be available to be re-added to the original image
+    psphotImageBackground (readout, config, sky);
+
+    // find the peaks in the image
+    peaks = psphotFindPeaks (readout, config, sky);
+
+    // construct sources and measure basic stats
+    sources = psphotSourceStats (readout, config, peaks);
+
+    // classify sources based on moments, brightness
+    psphotRoughClass (sources, config);
+
+    // mark blended peaks PS_SOURCE_BLEND
+    psphotBasicDeblend (sources, config, sky);
+
+    // use bright stellar objects to measure PSF
+    psf = psphotChoosePSF (config, sources, sky);
+
+    // XXX change FITMODE to a string
+    char *FITMODE = psMetadataLookupStr (&status, config, "FITMODE");
+    if (!strcasecmp(FITMODE, "ENSEMBLE")) {
+	psphotEnsemblePSF (readout, config, sources, psf, sky);
+    }
+
+    if (!strcasecmp(FITMODE, "FULL")) {
+	psphotEnsemblePSF (readout, config, sources, psf, sky);
+	psphotFullFit (readout, config, sources, psf, sky);
+	psphotReplaceUnfit (sources);
+	psphotApResid (readout, sources, config, psf);
+    }
+
+    if (!strcasecmp(FITMODE, "BLEND")) {
+	psphotEnsemblePSF (readout, config, sources, psf, sky);
+	psphotBlendFit (readout, config, sources, psf, sky);
+	psphotReplaceUnfit (sources);
+	psphotApResid (readout, sources, config, psf);
+    }
+
+    if (!strcasecmp(FITMODE, "BASIC")) {
+	psphotApplyPSF (readout, config, sources, psf, sky);
+	psphotFitExtended (readout, config, sources, sky);
+    }
+
+    psphotMagnitudes (config, sources, psf);
+
+    psMetadata *header = pmReadoutGetHeader (readout);
+    psphotUpdateHeader (header, config);
+
+    // need to do something with the sources, psf, and sky
+    status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES",   PS_DATA_ARRAY,   "psphot sources", sources);
+    status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.PSF",       PS_DATA_UNKNOWN, "psphot psf", psf);
+    status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SKY.MEAN",  PS_DATA_F32,     "psphot sky mean", sky->sampleMean);
+    status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SKY.SIGMA", PS_DATA_F32,     "psphot sky stdev", sky->sampleStdev);
+    return true;
+}
Index: trunk/psphot/src/psphotTest.c
===================================================================
--- trunk/psphot/src/psphotTest.c	(revision 6056)
+++ trunk/psphot/src/psphotTest.c	(revision 6117)
@@ -15,10 +15,5 @@
 int main (int argc, char **argv) {
 
-    psMetadata *row;
-    psArray *table;
-
-    psMetadataItem *mdi;
     psRegion region = {0,0,0,0};	// a region representing the entire array
-
     psphotTestArguments (&argc, argv);
 
@@ -27,4 +22,31 @@
     psImage *image = psFitsReadImage (NULL, file, region, 0);
     psFitsClose (file);
+
+    psImageJpegColormap (argv[5]);
+
+    // psImage *fimage = psImageCopy (NULL, image, PS_TYPE_F32);
+
+    int binning = atof(argv[6]);
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN);
+    psImage *fimage = psImageRebin (NULL, image, NULL, 0, binning, stats);
+
+    float min = atof(argv[3]);
+    float max = atof(argv[4]);
+
+    psImageJpeg (fimage, argv[2], min, max);
+
+    psFree (header);
+    psFree (image);
+    exit (0);
+}
+
+
+# if (0)
+
+    psMetadata *row;
+    psArray *table;
+
+    psMetadataItem *mdi;
 
     psMetadataConfigWrite (header, argv[2]);
@@ -59,8 +81,4 @@
     psFitsWriteHeader (header, fits);
     psFitsWriteTable (fits, theader, table);
-    psFitsClose (fits);
 
-    psFree (header);
-    psFree (image);
-    exit (0);
-}
+# endif
Index: trunk/psphot/src/psphotTestArguments.c
===================================================================
--- trunk/psphot/src/psphotTestArguments.c	(revision 6056)
+++ trunk/psphot/src/psphotTestArguments.c	(revision 6117)
@@ -8,5 +8,5 @@
   psArgumentVerbosity (argc, argv);
 
-  if (*argc != 4) usage ();
+  if (*argc != 7) usage ();
 
   return;
@@ -15,5 +15,5 @@
 static int usage () {
 
-    fprintf (stderr, "USAGE: psphotTest (input.fits) (output.hdr) (out.fits)\n");
+    fprintf (stderr, "USAGE: psphotTest (input.fits) (output.jpg) (zero) (scale) (colormap) (rebin)\n");
     exit (2);
 }
