Index: /trunk/psphot/src/psphot.c
===================================================================
--- /trunk/psphot/src/psphot.c	(revision 6861)
+++ /trunk/psphot/src/psphot.c	(revision 6862)
@@ -1,5 +1,4 @@
 # include "psphot.h"
 
-// XXX need a better structure for handling optional sequences
 int main (int argc, char **argv) {
 
Index: /trunk/psphot/src/psphot.h
===================================================================
--- /trunk/psphot/src/psphot.h	(revision 6861)
+++ /trunk/psphot/src/psphot.h	(revision 6862)
@@ -23,14 +23,15 @@
 // psphotReadout functions
 bool            psphotImageMedian (pmConfig *config, pmFPAview *view);
-psArray        *psphotFindPeaks (pmReadout *readout, psMetadata *config);
-psArray        *psphotSourceStats (pmReadout *readout, psMetadata *config, psArray *allpeaks);
-bool            psphotRoughClass (psArray *sources, psMetadata *config);
-bool            psphotBasicDeblend (psArray *sources, psMetadata *config);
-pmPSF          *psphotChoosePSF (psArray *sources, psMetadata *config);
-bool 	        psphotEnsemblePSF (pmReadout *readout, psArray *sources, psMetadata *config, pmPSF *psf, bool final);
-bool            psphotBlendFit (pmReadout *readout, psArray *sources, psMetadata *config, pmPSF *psf);
+psArray        *psphotFindPeaks (pmReadout *readout, psMetadata *recipe);
+psArray        *psphotSourceStats (pmReadout *readout, psMetadata *recipe, psArray *allpeaks);
+bool            psphotRoughClass (psArray *sources, psMetadata *recipe);
+bool            psphotBasicDeblend (psArray *sources, psMetadata *recipe);
+pmPSF          *psphotChoosePSF (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotPSFstats (pmReadout *readout, psMetadata *recipe, pmPSF *psf);
+bool 	        psphotEnsemblePSF (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, bool final);
+bool            psphotBlendFit (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
 bool            psphotReplaceUnfit (psArray *sources);
-bool            psphotApResid (pmReadout *readout, psArray *sources, psMetadata *config, pmPSF *psf);
-bool            psphotMagnitudes (psArray *sources, psMetadata *config, pmPSF *psf);
+bool            psphotApResid (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotMagnitudes (psArray *sources, psMetadata *recipe, pmPSF *psf);
 bool            psphotSkyReplace (pmConfig *config, pmFPAview *view);
 
@@ -42,17 +43,17 @@
 void            psphotTestArguments (int *argc, char **argv);
 bool            psphotMaskReadout (pmReadout *readout, psMetadata *recipe);
-bool            psphotBackgroundNames (psMetadata *arguments);
+// bool            psphotBackgroundNames (psMetadata *arguments);
 
 // functions to set the correct source pixels
-bool 		psphotInitRadiusPSF (psMetadata *config, pmModelType type);
+bool 		psphotInitRadiusPSF (psMetadata *recipe, pmModelType type);
 bool 		psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model);
 bool 		psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, float dR);
-bool 		psphotInitRadiusEXT (psMetadata *config, pmModelType type);
+bool 		psphotInitRadiusEXT (psMetadata *recipe, pmModelType type);
 bool 		psphotCheckRadiusEXT (pmReadout *readout, pmSource *source, pmModel *model);
 
 // output functions
-bool 		psphotDumpMoments (psMetadata *config, psArray *sources);
-psMetadata     *psphotDefineHeader (psMetadata *config);
-bool            psphotWeightBias (pmReadout *readout, psArray *sources, psMetadata *config, pmPSF *psf);
+bool 		psphotDumpMoments (psMetadata *recipe, psArray *sources);
+psMetadata     *psphotDefineHeader (psMetadata *recipe);
+bool            psphotWeightBias (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
 
 // PSF / DBL / EXT evaluation functions
@@ -62,6 +63,6 @@
 
 //  functions to support the source fitting process
-bool 		psphotInitLimitsPSF (psMetadata *config);
-bool		psphotInitLimitsEXT (psMetadata *config);
+bool 		psphotInitLimitsPSF (psMetadata *recipe);
+bool		psphotInitLimitsEXT (psMetadata *recipe);
 bool		psphotFitBlend (pmReadout *readout, pmSource *source, pmPSF *psf);
 bool		psphotFitBlob (pmReadout *readout, pmSource *source, psArray *sources, pmPSF *psf);
@@ -73,71 +74,2 @@
 bool 		psphotFitSet (pmSource *oneSrc, pmModel *oneModel, char *fitset, pmSourceFitMode mode);
 
-// XXX these can probably be dropped:
-
-# if (0)
-
-pmHDU          *pmHDUFromReadout (pmReadout *readout);
-bool            psRegionIsNaN (psRegion region);
-bool            pmReadoutSetWeights(pmReadout *readout);
-
-// bicubic interpolation
-psPolynomial2D *psImageBicubeFit (psImage *image, int x, int y);
-psPlane         psImageBicubeMin (psPolynomial2D *poly);
-
-bool            psImageJpegColormap (char *name);
-bool            psImageJpeg (psImage *image, char *filename, float zero, float scale);
-
-bool 		pmSourceFitSet (pmSource *source, psArray *modelSet, const bool PSF);
-psF32           pmModelFitSet (psVector *deriv, const psVector *params, const psVector *x);
-bool            pmModelFitSetInit (pmModelType type);
-void            pmModelFitSetClear (void);
-
-// optional mode for clip fit?
-psPolynomial4D *psVectorChiClipFitPolynomial4D(
-    psPolynomial4D *poly,
-    psStats *stats,
-    const psVector *mask,
-    psMaskType maskValue,
-    const psVector *f,
-    const psVector *fErr,
-    const psVector *x,
-    const psVector *y,
-    const psVector *z,
-    const psVector *t);
-
-# endif
-
-# if (0)
-bool            psphotSamplePSFs (pmPSF *psf, psImage *image, char *output);
-int             psphotSaveImage (psMetadata *header, psImage *image, char *filename);
-
-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 	     	pmPeaksWriteText (psArray *sources, char *filename);
-
-bool 	     	pmModelWritePSFs (psArray *sources, char *filename);
-bool 	     	pmModelWriteEXTs (psArray *sources, char *filename);
-bool 	     	pmModelWriteNULLs (psArray *sources, char *filename);
-bool 	     	pmMomentsWriteText (psArray *sources, char *filename);
-# endif
-
-// XXX deprecate
-
-// char           *psphotNameSubstitute (char *input, char *replace, char *key);
-// char           *psphotSplitName (psMetadata *header);
-// pmModel        *pmSourceMagnitudes (pmSource *source, pmPSF *psf, float apRadius);
-// float           pmSourceCrossProduct (pmSource *Mi, pmSource *Mj);
-// float           pmSourceCrossWeight (pmSource *Mi, pmSource *Mj);
-// psArray        *pmSourceContour_EAM (psImage *image, int x, int y, float threshold);
-// psStats        *psphotImageStats (pmReadout *readout, psMetadata *config);
-// psPolynomial2D *psphotImageBackground (pmReadout *readout, psMetadata *config, psStats *sky);
-// void            psphotOutput (pmReadout *readout, psMetadata *arguments);
-
-// void            psphotOutputPrep (ppFile *file, ppConfig *config);
-// void            psphotOutputCleanup (void);
-
-// bool            ppImageLoadPixels (ppFile *input, psDB *db, int chipNum, int cellNum);
-
Index: /trunk/psphot/src/psphotApResid.c
===================================================================
--- /trunk/psphot/src/psphotApResid.c	(revision 6861)
+++ /trunk/psphot/src/psphotApResid.c	(revision 6862)
@@ -3,5 +3,5 @@
 
 // measure the aperture residual statistics
-bool psphotApResid (pmReadout *readout, psArray *sources, psMetadata *config, pmPSF *psf) { 
+bool psphotApResid (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) { 
 
     int Npsf;
@@ -13,8 +13,8 @@
 
     // S/N limit to perform full non-linear fits
-    float FIT_SN_LIM = psMetadataLookupF32 (&status, config, "FULL_FIT_SN_LIM");
+    float FIT_SN_LIM = psMetadataLookupF32 (&status, recipe, "FULL_FIT_SN_LIM");
 
     // measure the aperture loss as a function of radius for PSF
-    float REF_RADIUS = psMetadataLookupF32 (&status, config, "PSF_REF_RADIUS");
+    float REF_RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_REF_RADIUS");
     psf->growth = pmGrowthCurveAlloc (3.0, 50.0, REF_RADIUS);
     psphotGrowthCurve (readout, psf);
@@ -76,5 +76,5 @@
 
     // APTREND options : NONE SKYBIAS XY_LIN XY_QUAD SKY_XY_LIN FULL 
-    char *ApTrendOption = psMetadataLookupPtr (&status, config, "APTREND");
+    char *ApTrendOption = psMetadataLookupPtr (&status, recipe, "APTREND");
     if (!status) ApTrendOption = DEFAULT_OPTION;
 
@@ -208,16 +208,4 @@
     psVector *fit   = psPolynomial4DEvalVector (psf->ApTrend, xPos, yPos, r2rflux, flux);
     psVector *resid = (psVector *) psBinaryOp (NULL, (void *) apResid, "-", (void *) fit);
-
-# if (1)
-    FILE *fout = fopen ("resid.dat", "w");
-    for (int i = 0; i < resid->n; i++) {
-	fprintf (fout, "%d %f %f %f %f  %f %f %f %f %d\n", 
-		 i, 
-		 (float) psVectorGet(xPos, i), (float) psVectorGet(yPos, i), (float) psVectorGet(r2rflux, i), (float) psVectorGet(flux, i), 
-		 (float) psVectorGet(apResid, i), (float) psVectorGet(fit, i), (float) psVectorGet(resid, i), (float) psVectorGet(dMag, i),
-		 mask->data.U8[i]);
-    }
-    fclose (fout);
-# endif
 
     // measure scatter for sources with dMag < 0.01 (S/N = 100)
@@ -240,10 +228,10 @@
 
     // save results for later output
-    psMetadataAdd (config, PS_LIST_TAIL, "SKYBIAS",  PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   psf->skyBias);
-    psMetadataAdd (config, PS_LIST_TAIL, "SKYSAT",   PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   psf->skySat);
-    psMetadataAdd (config, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   psf->ApResid);
-    psMetadataAdd (config, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->dApResid);
-    psMetadataAdd (config, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->growth->apLoss);
-    psMetadataAdd (config, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "ap residual scatter", psf->nApResid);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKYBIAS",  PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   psf->skyBias);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKYSAT",   PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   psf->skySat);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   psf->ApResid);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->dApResid);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->growth->apLoss);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "ap residual scatter", psf->nApResid);
 
     psLogMsg ("psphot.apresid", 3, "measure full-frame aperture residual: %f sec\n", psTimerMark ("psphot"));
Index: /trunk/psphot/src/psphotBasicDeblend.c
===================================================================
--- /trunk/psphot/src/psphotBasicDeblend.c	(revision 6861)
+++ /trunk/psphot/src/psphotBasicDeblend.c	(revision 6862)
@@ -2,5 +2,5 @@
 
 // 2006.02.07 : no leaks
-bool psphotBasicDeblend (psArray *sources, psMetadata *config) { 
+bool psphotBasicDeblend (psArray *sources, psMetadata *recipe) { 
 
     int N;
@@ -13,8 +13,8 @@
     psTimerStart ("psphot");
 
-    float FRACTION = psMetadataLookupF32 (&status, config, "DEBLEND_PEAK_FRACTION");
+    float FRACTION = psMetadataLookupF32 (&status, recipe, "DEBLEND_PEAK_FRACTION");
     if (!status) FRACTION = 0.25;
 
-    float NSIGMA   = psMetadataLookupF32 (&status, config, "DEBLEND_SKY_NSIGMA");
+    float NSIGMA   = psMetadataLookupF32 (&status, recipe, "DEBLEND_SKY_NSIGMA");
     if (!status) NSIGMA = 5.0;
 
@@ -129,5 +129,5 @@
     psLogMsg ("psphot.deblend", 3, "identified %d blended objects (%f sec)\n", Nblend, psTimerMark ("psphot"));
 
-    char *breakPt = psMetadataLookupStr (&status, config, "BREAK_POINT");
+    char *breakPt = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
     if (!strcasecmp (breakPt, "DEBLEND")) exit (0);
 
Index: /trunk/psphot/src/psphotBlendFit.c
===================================================================
--- /trunk/psphot/src/psphotBlendFit.c	(revision 6861)
+++ /trunk/psphot/src/psphotBlendFit.c	(revision 6862)
@@ -2,5 +2,5 @@
 
 // XXX I don't like this name
-bool psphotBlendFit (pmReadout *readout, psArray *sources, psMetadata *config, pmPSF *psf) { 
+bool psphotBlendFit (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) { 
 
     int Nfit = 0;
@@ -13,12 +13,12 @@
     
     // S/N limit to perform full non-linear fits
-    float FIT_SN_LIM = psMetadataLookupF32 (&status, config, "FULL_FIT_SN_LIM");
+    float FIT_SN_LIM = psMetadataLookupF32 (&status, recipe, "FULL_FIT_SN_LIM");
 
-    psphotInitLimitsPSF (config);
-    psphotInitLimitsEXT (config);
-    psphotInitRadiusPSF (config, psf->type);
+    psphotInitLimitsPSF (recipe);
+    psphotInitLimitsEXT (recipe);
+    psphotInitRadiusPSF (recipe, psf->type);
 
     // option to limit analysis to a specific region
-    char *region = psMetadataLookupStr (&status, config, "ANALYSIS_REGION");
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
     psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
     if (psRegionIsNaN (AnalysisRegion)) psAbort ("psphot", "analysis region mis-defined");
Index: /trunk/psphot/src/psphotChoosePSF.c
===================================================================
--- /trunk/psphot/src/psphotChoosePSF.c	(revision 6861)
+++ /trunk/psphot/src/psphotChoosePSF.c	(revision 6862)
@@ -1,11 +1,11 @@
 # include "psphot.h"
 
-// 2006.02.07 : no leaks!
 // try PSF models and select best option
-pmPSF *psphotChoosePSF (psArray *sources, psMetadata *config) { 
+pmPSF *psphotChoosePSF (pmReadout *readout, psArray *sources, psMetadata *recipe) { 
 
     bool       	    status;
     char       	   *modelName;
-    pmPSFtry 	   *try   = NULL;
+    pmPSF 	   *psf = NULL;
+    pmPSFtry 	   *try = NULL;
     psArray    	   *stars = NULL;
 
@@ -13,11 +13,6 @@
 
     // check if a PSF model is supplied by the user
-    char *psfFile = psMetadataLookupStr (&status, config, "PSF_INPUT_FILE");
-    if (status) {
-	int Nfail;
-	psMetadata *psfData = psMetadataConfigParse (NULL, &Nfail, psfFile, FALSE);
-	pmPSF *psf = pmPSFfromMD (psfData);
-	return psf;
-    }
+    psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    if (psf != NULL) return psf;
 
     // examine PSF sources in S/N order (brightest first)
@@ -25,9 +20,9 @@
 
     // array to store candidate PSF stars
-    int NSTARS = psMetadataLookupS32 (&status, config, "PSF_MAX_NSTARS");
+    int NSTARS = psMetadataLookupS32 (&status, recipe, "PSF_MAX_NSTARS");
     if (!status) NSTARS = PS_MIN (sources->n, 200);
 
     // use poissonian errors or local-sky errors
-    bool POISSON_ERRORS = psMetadataLookupBool (&status, config, "POISSON_ERRORS");
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
     if (!status) POISSON_ERRORS = true;
     pmSourceFitModelInit (15, 0.1, POISSON_ERRORS);
@@ -45,9 +40,9 @@
     // get the fixed PSF fit radius
     // XXX EAM : check that PSF_FIT_RADIUS < SKY_OUTER_RADIUS
-    float RADIUS = psMetadataLookupF32 (&status, config, "PSF_FIT_RADIUS");
+    float RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
 
     // get the list pointers for the PSF_MODEL entries
     psList *list = NULL;
-    psMetadataItem *mdi = psMetadataLookup (config, "PSF_MODEL");
+    psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
     if (mdi == NULL) psAbort ("psphotChoosePSF", "missing PSF_MODEL selection");
 
@@ -100,8 +95,8 @@
 
     // save only the best model;
-    pmPSF *psf = psMemCopy(try->psf);
+    psf = psMemCopy(try->psf);
     psFree (models);
 
-    psMetadataAdd (config, PS_LIST_TAIL, "NPSFSTAR", PS_DATA_S32 | PS_META_REPLACE, "number of stars used to make PSF", psf->nPSFstars);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "NPSFSTAR", PS_DATA_S32 | PS_META_REPLACE, "number of stars used to make PSF", psf->nPSFstars);
 
     modelName = pmModelGetType (psf->type);
@@ -109,7 +104,45 @@
     psLogMsg ("psphot.pspsf", 3, "selected psf model %s, ApResid: %f +/- %f\n", modelName, psf->ApResid, psf->dApResid);
 
-    char *breakPt = psMetadataLookupStr (&status, config, "BREAK_POINT");
+    char *breakPt = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
     if (!strcasecmp (breakPt, "PSFFIT")) exit (0);
 
     return (psf);
 }
+
+bool psphotPSFstats (pmReadout *readout, psMetadata *recipe, pmPSF *psf) {
+
+    psEllipseShape shape;
+    psEllipseAxes axes;
+
+    psImage *image = readout->image;
+
+    pmModel *modelEXT = pmModelAlloc (psf->type);
+
+    // make a model with unit central intensity at the image center
+    modelEXT->params->data.F32[0] = 0;
+    modelEXT->params->data.F32[1] = 1;
+    modelEXT->params->data.F32[2] = 0.5*image->numCols;
+    modelEXT->params->data.F32[3] = 0.5*image->numRows;
+
+    // construct a PSF model at this coordinate
+    pmModel *modelPSF = pmModelFromPSF (modelEXT, psf);
+
+    // get the correct model-radius function
+    pmModelRadius modelRadiusFunc = pmModelRadius_GetFunction (psf->type);
+
+    // get the model full-width at half-max
+    psF64 FWHM_X = 2*modelRadiusFunc (modelPSF->params, 0.5);
+    
+    shape.sx  = modelPSF->params->data.F32[4];
+    shape.sy  = modelPSF->params->data.F32[5];
+    shape.sxy = modelPSF->params->data.F32[6];
+    axes = psEllipseShapeToAxes (shape);
+
+    psF64 FWHM_Y = FWHM_X * (axes.minor / axes.major);
+
+    psMetadataAdd (recipe, PS_LIST_TAIL, "FWHM_X", PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM Major axis", FWHM_X);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "FWHM_Y", PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM Minor axis", FWHM_Y);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "ANGLE",  PS_DATA_F32 | PS_META_REPLACE, "PSF angle",           axes.theta);
+    
+    return true;
+}
Index: /trunk/psphot/src/psphotEnsemblePSF.c
===================================================================
--- /trunk/psphot/src/psphotEnsemblePSF.c	(revision 6861)
+++ /trunk/psphot/src/psphotEnsemblePSF.c	(revision 6862)
@@ -4,5 +4,5 @@
 // 2006.02.07 : no leaks!
 // fit all reasonable sources with the linear PSF model
-bool psphotEnsemblePSF (pmReadout *readout, psArray *sources, psMetadata *config, pmPSF *psf, bool final) { 
+bool psphotEnsemblePSF (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, bool final) { 
 
     bool  status;
@@ -21,8 +21,8 @@
     // radius for source moments if star is saturated
     // XXX we already do this in psphotSourceStats, is it still needed?
-    float INNER_RADIUS     = psMetadataLookupF32 (&status, config, "SKY_INNER_RADIUS");
+    float INNER_RADIUS     = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
 
     // setup the PSF fit details
-    psphotInitRadiusPSF (config, psf->type);
+    psphotInitRadiusPSF (recipe, psf->type);
 
     // pre-calculate all model pixels
@@ -33,5 +33,5 @@
 
     // option to limit analysis to a specific region
-    char *region = psMetadataLookupStr (&status, config, "ANALYSIS_REGION");
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
     psRegion AnalysisRegion = psRegionFromString (region);
     AnalysisRegion = psRegionForImage (readout->image, AnalysisRegion);
Index: /trunk/psphot/src/psphotEvalPSF.c
===================================================================
--- /trunk/psphot/src/psphotEvalPSF.c	(revision 6861)
+++ /trunk/psphot/src/psphotEvalPSF.c	(revision 6862)
@@ -31,13 +31,13 @@
 static float PSF_SHAPE_NSIGMA;
 
-bool psphotInitLimitsPSF (psMetadata *config) {
+bool psphotInitLimitsPSF (psMetadata *recipe) {
 
     bool status;
 
     // we may set this differently here from the value used to mark likely saturated stars
-    SATURATION       = psMetadataLookupF32 (&status, config, "SATURATION");
-    PSF_MIN_SN       = psMetadataLookupF32 (&status, config, "PSF_MIN_SN");
-    PSF_MAX_CHI      = psMetadataLookupF32 (&status, config, "PSF_MAX_CHI");
-    PSF_SHAPE_NSIGMA = psMetadataLookupF32 (&status, config, "PSF_SHAPE_NSIGMA");
+    SATURATION       = psMetadataLookupF32 (&status, recipe, "SATURATION");
+    PSF_MIN_SN       = psMetadataLookupF32 (&status, recipe, "PSF_MIN_SN");
+    PSF_MAX_CHI      = psMetadataLookupF32 (&status, recipe, "PSF_MAX_CHI");
+    PSF_SHAPE_NSIGMA = psMetadataLookupF32 (&status, recipe, "PSF_SHAPE_NSIGMA");
 
     return true;
Index: /trunk/psphot/src/psphotFindPeaks.c
===================================================================
--- /trunk/psphot/src/psphotFindPeaks.c	(revision 6861)
+++ /trunk/psphot/src/psphotFindPeaks.c	(revision 6862)
@@ -2,5 +2,5 @@
 
 // 2006.02.02 : no leaks
-psArray *psphotFindPeaks (pmReadout *readout, psMetadata *config) {
+psArray *psphotFindPeaks (pmReadout *readout, psMetadata *recipe) {
 
     bool  status = false;
@@ -12,6 +12,6 @@
     psTimerStart ("psphot");
 
-    SIGMA  = psMetadataLookupF32 (&status, config, "PEAKS_SMOOTH_SIGMA");
-    NSIGMA = psMetadataLookupF32 (&status, config, "PEAKS_SMOOTH_NSIGMA");
+    SIGMA  = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_SIGMA");
+    NSIGMA = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_NSIGMA");
 
     psImage *smooth_im = psImageCopy (NULL, readout->image, PS_TYPE_F32);
@@ -37,5 +37,5 @@
 
     // set peak threshold
-    NSIGMA = psMetadataLookupF32 (&status, config, "PEAKS_NSIGMA_LIMIT");
+    NSIGMA = psMetadataLookupF32 (&status, recipe, "PEAKS_NSIGMA_LIMIT");
     // threshold = NSIGMA*sky->sampleStdev + sky->sampleMean; 
     threshold = PS_SQR(NSIGMA);
@@ -49,5 +49,5 @@
 
     // optional dump of all peak data
-    char *output = psMetadataLookupStr (&status, config, "PEAKS_OUTPUT_FILE");
+    char *output = psMetadataLookupStr (&status, recipe, "PEAKS_OUTPUT_FILE");
     if (status && (output != NULL) && (output[0])) {
 	pmPeaksWriteText (peaks, output);
@@ -55,5 +55,5 @@
     psLogMsg ("psphot", 3, "%d peaks: %f sec\n", peaks->n, psTimerMark ("psphot"));
 
-    char *breakPt = psMetadataLookupStr (&status, config, "BREAK_POINT");
+    char *breakPt = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
     if (!strcasecmp (breakPt, "PEAKS")) exit (0);
 
Index: unk/psphot/src/psphotImageBackground.c
===================================================================
--- /trunk/psphot/src/psphotImageBackground.c	(revision 6861)
+++ 	(revision )
@@ -1,71 +1,0 @@
-# include "psphot.h"
-
-// 2006.02.02 : no leaks
-psPolynomial2D *psphotImageBackground (pmReadout *readout, psMetadata *config, psStats *sky) 
-{ 
-
-    // fit background to subset of image points within 3 sigma of sky
-    psTimerStart ("psphot");
-
-    psImage *image = readout->image;
-    psImage *mask  = readout->mask;
-
-    bool      status  = false;
-    int       Npixels = image->numRows*image->numCols;
-    int       Nsubset = PS_MIN (Npixels, psMetadataLookupF32 (&status, config, "IMSTATS_NPIX"));
-    psRandom *rnd     = psRandomAlloc (PS_RANDOM_TAUS, 0);
-
-    psVector *x = psVectorAlloc (Nsubset, PS_TYPE_F32);
-    psVector *y = psVectorAlloc (Nsubset, PS_TYPE_F32);
-    psVector *z = psVectorAlloc (Nsubset, PS_TYPE_F32);
-
-    float  Sky = sky->sampleMean;
-    float dSky = 3*sky->sampleStdev;
-
-    // choose Nsubset points between 0 and Nx*Ny, convert to coords
-    int Nout = 0;
-    Npixels = image->numRows*image->numCols;
-    for (int i = 0; (i < 3*Nsubset) && (Nout < Nsubset); i++) {
-	double frnd = psRandomUniform (rnd);
-	int pixel = Npixels * frnd;
-	int ix = pixel % image->numCols;
-	int iy = pixel / image->numCols;
-	float value = image->data.F32[iy][ix];
-	if (fabs(value - Sky) > dSky) continue;
-	z->data.F32[Nout] = value;
-	x->data.F32[Nout] = ix;
-	y->data.F32[Nout] = iy;
-	Nout ++;
-    }
-    x->n = y->n = z->n = Nout;
-
-    psPolynomial2D *skyModel = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, 1, 1);
-    skyModel->mask[1][1] = 1;
-
-    // psVector *mask = psVectorAlloc (Nout, PS_TYPE_U8);
-    // psVectorInit (mask, 0);
-    // psStats *fitstat = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
-    // skyModel = psVectorFitPolynomial2D (skyModel, mask, 1, z, NULL, x, y);
-
-    skyModel = psVectorFitPolynomial2D (skyModel, NULL, 0, z, NULL, x, y);
-	     
-    // this is a very inefficient way to evaluate the function..
-    // lame, temporary step to zero masked pixels
-    for (int j = 0; j < image->numRows; j++) {
-	for (int i = 0; i < image->numCols; i++) {
-	    if (!mask->data.U8[j][i]) {
-		Sky = psPolynomial2DEval (skyModel, i, j);
-		image->data.F32[j][i] -= Sky;
-	    }
-	}
-    }
-    sky->sampleMean = 0.0;
-
-    psFree (x);
-    psFree (y);
-    psFree (z);
-    psFree (rnd);  // XXX should this be made available at a higher level?
-
-    psLogMsg ("psphot", 3, "fit background model: %f sec\n", psTimerMark ("psphot"));
-    return (skyModel);
-}
Index: /trunk/psphot/src/psphotImageLoop.c
===================================================================
--- /trunk/psphot/src/psphotImageLoop.c	(revision 6861)
+++ /trunk/psphot/src/psphotImageLoop.c	(revision 6862)
@@ -2,5 +2,4 @@
 
 // XXX where do we load optional mask and weight input images?
-
 bool psphotImageLoop (pmConfig *config) {
 
@@ -23,12 +22,10 @@
     while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
         psLogMsg ("psphot", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
-        if (! chip->process) { continue; }
-        if (! chip->file_exists) { continue; }
+        if (! chip->process || ! chip->file_exists) { continue; }
 	pmFPAfileIOChecks (config->files, view, PM_FPA_BEFORE);
 
 	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
             psLogMsg ("psphot", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
-            if (! cell->process) { continue; }
-	    if (! cell->file_exists) { continue; }
+            if (! cell->process || ! cell->file_exists) { continue; }
 	    pmFPAfileIOChecks (config->files, view, PM_FPA_BEFORE);
 
@@ -63,29 +60,2 @@
 // PSPHOT.PSF     : sample PSF images
 
-
-/**
-
-filename | pmFPAfile  | pmFPA  | pmFPAview
-chip00.f | PSPHOT.IN  | input  | view
-chip01.f | PSPHOT.IN  | input  | view
-chip02.f | PSPHOT.IN  | input  | view
-chip03.f | PSPHOT.IN  | input  | view
-
-out00.f  | PSPHOT.OUT | input  | view
-out01.f  | PSPHOT.OUT | input  | view
-out02.f  | PSPHOT.OUT | input  | view
-out03.f  | PSPHOT.OUT | input  | view
-
-obj00.f  | PSPHOT.OBJ | input  | view
-obj01.f  | PSPHOT.OBJ | input  | view
-obj02.f  | PSPHOT.OBJ | input  | view
-obj03.f  | PSPHOT.OBJ | input  | view
-
-bin00.f  | PSPHOT.BIN | binned | view
-bin01.f  | PSPHOT.BIN | binned | view
-bin02.f  | PSPHOT.BIN | binned | view
-bin03.f  | PSPHOT.BIN | binned | view
-
-mosaic.f | PSPHOT.MOS | mosaic | view
-
-**/
Index: /trunk/psphot/src/psphotImageMedian.c
===================================================================
--- /trunk/psphot/src/psphotImageMedian.c	(revision 6861)
+++ /trunk/psphot/src/psphotImageMedian.c	(revision 6862)
@@ -33,8 +33,6 @@
 
     // scaling factor
-    int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND_XBIN");
-    if (!status) {DX = 64;}
-    int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND_YBIN");
-    if (!status) {DY = 64;}
+    int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+    int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
 
     // overhang : we will balance this evenly
@@ -69,6 +67,8 @@
 
 	    // XXX the value of the upper and lower cuts probably should be studied...
-	    modelData[iy][ix] = psImageClippedStats (subset, submask, 0xff, 0.25, 0.75) + SKY_BIAS;
+	    psStats *stats = psImageClippedStats (subset, submask, 0xff, 0.25, 0.75);
+	    modelData[iy][ix] = stats->robustMedian + SKY_BIAS;
 
+	    psFree (stats);
 	    psFree (subset);
 	    psFree (submask);
@@ -82,6 +82,14 @@
     psLogMsg ("psphot", 3, "build median image: %f sec\n", psTimerMark ("psphot"));
 
-    // XXX temporarily until we get the pmFPAfile output finished
-    psphotSaveImage (NULL, model->image, "model.fits");
+    // measure background stats and save for later output
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV | PS_STAT_MIN | PS_STAT_MAX);
+    stats = psImageStats (stats, model->image, NULL, 0);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_MEAN", PS_DATA_F32 | PS_META_REPLACE, "sky model mean",          stats->sampleMean);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_STDV", PS_DATA_F32 | PS_META_REPLACE, "sky model stdev",         stats->sampleStdev);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_MAX",  PS_DATA_F32 | PS_META_REPLACE, "sky model maximum value", stats->max);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_MIN",  PS_DATA_F32 | PS_META_REPLACE, "sky model minimum value", stats->min);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_NX",   PS_DATA_S32 | PS_META_REPLACE, "sky model size (x)",      nx);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_NY",   PS_DATA_S32 | PS_META_REPLACE, "sky model size (y)",      ny);
+    psFree (stats);
 
     // select background pixels, from output background file, or create
@@ -89,10 +97,5 @@
     if (background == NULL) {
 	background = pmFPAfileCreateInternal (config->files, "PSPHOT.BACKGND", Nx, Ny, PS_TYPE_F32);
-    } else {
-	// replace the supplied image data with an image of the desired size
-	// XXX should not have to do this: it should be allocated correctly
-	// psImageRecycle (background->image, Nx, Ny, PS_TYPE_F32);
-    }
-
+    } 
     psF32 **backData = background->image->data.F32;
 
@@ -100,7 +103,4 @@
     psImageUnbin (background->image, model->image, DX, DY, dx, dy);
     psLogMsg ("psphot", 3, "build resampled image: %f sec\n", psTimerMark ("psphot"));
-
-    // XXX temporarily until we get the pmFPAfile output finished...
-    psphotSaveImage (NULL, background->image, "back.fits");
 
     // back-sub image pixels, from output background file (don't create if not requested)
Index: unk/psphot/src/psphotImageStats.c
===================================================================
--- /trunk/psphot/src/psphotImageStats.c	(revision 6861)
+++ 	(revision )
@@ -1,56 +1,0 @@
-# include "psphot.h"
-
-// 2006.02.02 : no leaks
-psStats *psphotImageStats (pmReadout *readout, psMetadata *config) 
-{ 
-    psStats *stats = NULL;
-    psStats *sky   = NULL;
-
-    psImage *image = readout->image;
-
-    // get image stats on a subset of the image (random 1e5 pts)
-    psTimerStart ("psphot");
-
-    // this section fixes a bug in the psImageStats definition:
-    // it should limit the number of elements used for the initial 
-    // pass on the stats
-    { 
-	bool     status  = false;
-	int      Npixels = image->numRows*image->numCols;
-	int      Nsubset = PS_MIN (Npixels, psMetadataLookupF32 (&status, config, "IMSTATS_NPIX"));
-	psVector *subset = psVectorAlloc (Nsubset, PS_TYPE_F32);
-	psRandom *rnd    = psRandomAlloc (PS_RANDOM_TAUS, 0);
-
-	// choose Nsubset points between 0 and Nx*Ny, convert to coords
-	Npixels = image->numRows*image->numCols;
-	for (int i = 0; i < Nsubset; i++) {
-	  double frnd = psRandomUniform (rnd);
-	  int pixel = Npixels * frnd;
-	  int ix = pixel / image->numCols;
-	  int iy = pixel % image->numCols;
-	  subset->data.F32[i] = image->data.F32[iy][ix];
-	}
-
-	// this should use ROBUST not SAMPLE median
-	// robust median is broken in pslib (0.5)
-	stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN); 
-	stats = psVectorStats (stats, subset, NULL, NULL, 0); // use a mask?
-	psFree (subset);
-	psFree (rnd);
-    }
-
-    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));
-
-    psLogMsg ("psphot", 4, "background: %f +/- %f\n", sky->sampleMean, sky->sampleStdev);
-    psLogMsg ("psphot", 3, "image stats: %f sec\n", psTimerMark ("psphot"));
-
-    psFree (stats);
-    return (sky);
-}
Index: /trunk/psphot/src/psphotMagnitudes.c
===================================================================
--- /trunk/psphot/src/psphotMagnitudes.c	(revision 6861)
+++ /trunk/psphot/src/psphotMagnitudes.c	(revision 6862)
@@ -1,5 +1,5 @@
 # include "psphot.h"
 
-bool psphotMagnitudes (psArray *sources, psMetadata *config, pmPSF *psf) {
+bool psphotMagnitudes (psArray *sources, psMetadata *recipe, pmPSF *psf) {
 
     psTimerStart ("psphot");
@@ -13,2 +13,5 @@
     return true;
 }
+
+// XXX add in a measurement of the bright and faint completeness values
+// XXX push these into the recipe
Index: unk/psphot/src/psphotMaskCell.c
===================================================================
--- /trunk/psphot/src/psphotMaskCell.c	(revision 6861)
+++ 	(revision )
@@ -1,31 +1,0 @@
-# include "psphot.h"
-
-// 2006.02.04 : no leaks
-bool psphotMaskCell (pmCell *cell, psMetadata *recipe) {
-    
-    bool status;
-
-    // mask the excluded outer pixels
-    // these coordinates refer to the parent image
-    // these bounds will saturate on the subimage
-    // negative upper bounds will subtract from the *subimage*
-    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
-
-	// psRegionForImage creates a region for the subimage, not the parent
-	// XXX I propose that psRegionForImage treat the region as referring to the parent...
-	psRegion keep = psRegionForImage (readout->image, valid);
-
-	// psImageKeepRegion assumes the region refers to the parent coordinates
-	psImageKeepRegion (readout->mask, keep, "OR", PSPHOT_MASK_INVALID);
-    }
-    return true;
-}
Index: /trunk/psphot/src/psphotOutput.c
===================================================================
--- /trunk/psphot/src/psphotOutput.c	(revision 6861)
+++ /trunk/psphot/src/psphotOutput.c	(revision 6862)
@@ -1,3 +1,63 @@
 # include "psphot.h"
+
+int psphotSaveImage (psMetadata *header, psImage *image, char *filename) {
+
+    psFits *fits = psFitsOpen (filename, "w");
+    psFitsWriteImage (fits, NULL, image, 0, NULL);
+    psFitsClose (fits);
+    return (TRUE);
+}
+
+bool psphotDumpMoments (psMetadata *recipe, psArray *sources) {
+
+    bool status;
+
+    // optional dump of all rough source data
+    char *output = psMetadataLookupStr (&status, recipe, "MOMENTS_OUTPUT_FILE");
+    if (!status) return false;
+    if (output == NULL) return false;
+    if (output[0] == 0) return false;
+
+    pmMomentsWriteText (sources, output);
+    return true;
+}
+
+// these values are saved in an output header stub - they are added to either the
+// PHU header (CMP) or the MEF table header (CMF)
+psMetadata *psphotDefineHeader (psMetadata *recipe) {
+
+    psMetadata *header = psMetadataAlloc ();
+
+    // write necessary information to output header
+    psMetadataItemTransfer (header, recipe, "ZERO_PT");
+    psMetadataItemTransfer (header, recipe, "PHOTCODE");
+
+    psMetadataItemTransfer (header, recipe, "APMIFIT");
+    psMetadataItemTransfer (header, recipe, "DAPMIFIT");
+    psMetadataItemTransfer (header, recipe, "NAPMIFIT");
+    psMetadataItemTransfer (header, recipe, "SKYBIAS");
+    psMetadataItemTransfer (header, recipe, "SKYSAT");
+    
+    // PSF model parameters (shape values for image center)
+    psMetadataItemTransfer (header, recipe, "NPSFSTAR");
+    psMetadataItemTransfer (header, recipe, "APLOSS");
+    psMetadataItemTransfer (header, recipe, "FWHM_X");
+    psMetadataItemTransfer (header, recipe, "FWHM_Y");
+    psMetadataItemTransfer (header, recipe, "ANGLE");
+
+    // XXX these need to be defined from elsewhere
+    psMetadataAdd (header, PS_LIST_TAIL, "FSATUR",   PS_DATA_F32 | PS_META_REPLACE, "SATURATION MAG",      0.0);
+    psMetadataAdd (header, PS_LIST_TAIL, "FLIMIT",   PS_DATA_F32 | PS_META_REPLACE, "COMPLETENESS MAG",    0.0);
+
+    // sky background model statistics
+    psMetadataItemTransfer (header, recipe, "SKY_MEAN");
+    psMetadataItemTransfer (header, recipe, "SKY_STDV");
+    psMetadataItemTransfer (header, recipe, "SKY_MIN");
+    psMetadataItemTransfer (header, recipe, "SKY_MAX");
+    psMetadataItemTransfer (header, recipe, "SKY_NX");
+    psMetadataItemTransfer (header, recipe, "SKY_NY");
+
+    return header;
+}
 
 # if (0)
@@ -66,53 +126,2 @@
 }
 # endif
-
-int psphotSaveImage (psMetadata *header, psImage *image, char *filename) {
-
-    psFits *fits = psFitsOpen (filename, "w");
-    psFitsWriteImage (fits, NULL, image, 0, NULL);
-    psFitsClose (fits);
-    return (TRUE);
-}
-
-bool psphotDumpMoments (psMetadata *config, psArray *sources) {
-
-    bool status;
-
-    // optional dump of all rough source data
-    char *output = psMetadataLookupStr (&status, config, "MOMENTS_OUTPUT_FILE");
-    if (!status) return false;
-    if (output == NULL) return false;
-    if (output[0] == 0) return false;
-
-    pmMomentsWriteText (sources, output);
-    return true;
-}
-
-// these values are saved in an output header stub - they are added to either the
-// PHU header (CMP) or the MEF table header (CMF)
-psMetadata *psphotDefineHeader (psMetadata *recipe) {
-
-    psMetadata *header = psMetadataAlloc ();
-
-    // write necessary information to output header
-    psMetadataItemTransfer (header, recipe, "ZERO_PT");
-    psMetadataItemTransfer (header, recipe, "PHOTCODE");
-
-    psMetadataItemTransfer (header, recipe, "APMIFIT");
-    psMetadataItemTransfer (header, recipe, "DAPMIFIT");
-    psMetadataItemTransfer (header, recipe, "NAPMIFIT");
-    psMetadataItemTransfer (header, recipe, "SKYBIAS");
-    psMetadataItemTransfer (header, recipe, "SKYSAT");
-    
-    psMetadataItemTransfer (header, recipe, "NPSFSTAR");
-    psMetadataItemTransfer (header, recipe, "APLOSS");
-
-    // XXX these need to be defined from elsewhere
-    psMetadataAdd (header, PS_LIST_TAIL, "FWHM_X",   PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM X",          0.0);
-    psMetadataAdd (header, PS_LIST_TAIL, "FWHM_Y",   PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM Y",          0.0);
-    psMetadataAdd (header, PS_LIST_TAIL, "ANGLE",    PS_DATA_F32 | PS_META_REPLACE, "PSF ANGLE",           0.0);
-    psMetadataAdd (header, PS_LIST_TAIL, "FSATUR",   PS_DATA_F32 | PS_META_REPLACE, "SATURATION MAG",      0.0);
-    psMetadataAdd (header, PS_LIST_TAIL, "FLIMIT",   PS_DATA_F32 | PS_META_REPLACE, "COMPLETENESS MAG",    0.0);
-
-    return header;
-}
Index: /trunk/psphot/src/psphotParseCamera.c
===================================================================
--- /trunk/psphot/src/psphotParseCamera.c	(revision 6861)
+++ /trunk/psphot/src/psphotParseCamera.c	(revision 6862)
@@ -1,78 +1,78 @@
 # include "psphot.h"
 
+// define the needed / desired I/O files
 bool psphotParseCamera (pmConfig *config) {
 
-    bool status;
+    bool status = false;
 
-    status = false;
     pmFPAfile *input = pmFPAfileFromArgs (&status, config, "PSPHOT.INPUT", "INPUT");
-    if (!status) {
-	psAbort (__func__, "missing INPUT entry");
-    }
+    if (!status) { psAbort (__func__, "missing INPUT entry"); }
 
-    // recipe override values (command-line options):
+    // select recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes,   "PSPHOT");
     psMetadata *options = psMetadataLookupPtr (&status, config->arguments, "PSPHOT.OPTIONS");
-    psMetadata *recipe = psMetadataLookupPtr (&status, config->recipes, "PSPHOT");
-    psMetadataIterator *iter = psMetadataIteratorAlloc (options, PS_LIST_HEAD, NULL);
-    psMetadataItem *item = NULL;
-    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
-	psMetadataAddItem (recipe, item, PS_LIST_TAIL, PS_META_REPLACE);
-    }
-    psFree (iter);
+    psMetadataCopy (recipe, options);
+
+    // set default recipe values here
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "FITMODE",         PS_META_NO_REPLACE, "default fitting mode", "NONE");
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "PHOTCODE",        PS_META_NO_REPLACE, "default photcode",     "NONE");
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "BREAK_POINT",     PS_META_NO_REPLACE, "default break point",  "NONE");
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "ZERO_PT",         PS_META_NO_REPLACE, "default zero point",    25.00);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "BACKGROUND.XBIN", PS_META_NO_REPLACE, "default binning",          64);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "BACKGROUND.YBIN", PS_META_NO_REPLACE, "default binning",          64);
 
     // these calls bind the I/O handle to the specified fpa
     pmFPAfileDefine (config->files, config->camera, input->fpa, "PSPHOT.OUTPUT");
-    pmFPAfileDefine (config->files, config->camera, input->fpa, "PSPHOT.RESID");
 
-    // pmFPAfileDefine (config->files, format, input, "PSPHOT.PSF_INPUT");
-    // pmFPAfileDefine (config->files, format, input, "PSPHOT.PSF_OUTPUT");
+    // optionally save the residual image 
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.RESID")) {
+	pmFPAfileDefine (config->files, config->camera, input->fpa, "PSPHOT.RESID");
+    }
+
+    int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+    int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
 
     // these calls construct a new fpa for the I/O handle 
-    int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND_XBIN");
-    if (!status) {DX = 64;}
-    int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND_YBIN");
-    if (!status) {DY = 64;}
-    pmFPAfileFromFPA (config, input->fpa, DX, DY, "PSPHOT.BACKMDL");
-    pmFPAfileFromFPA (config, input->fpa,  1,  1, "PSPHOT.BACKGND");
-    pmFPAfileFromFPA (config, input->fpa,  1,  1, "PSPHOT.BACKSUB");
+
+    // optionally save the background model (small FITS image)
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKMDL")) {
+	pmFPAfileFromFPA (config, input->fpa, DX, DY, "PSPHOT.BACKMDL");
+    }
+    // optionally save the full background image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKGND")) {
+	pmFPAfileFromFPA (config, input->fpa,  1,  1, "PSPHOT.BACKGND");
+    }
+    // optionally save the background-subtracted image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKSUB")) {
+	pmFPAfileFromFPA (config, input->fpa,  1,  1, "PSPHOT.BACKSUB");
+    }
+    // optionally save the PSF Model
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.PSF")) {
+	pmFPAfileDefine (config->files, config->camera, input->fpa, "PSPHOT.PSF.SAVE");
+    }
+    // optionally load the PSF Model
+    if (psMetadataLookupBool(NULL, recipe, "LOAD.PSF")) {
+	pmFPAfileDefine (config->files, config->camera, input->fpa, "PSPHOT.PSF.LOAD");
+    }
 
     // pmFPAfileConstruct (config->files, format, config->camera, "PSPHOT.PSF_SAMPLE");
 
-    // psphot is supplied with the output name
-    // this needs to be better: supply to all output (WRITE) files?
+    // supply the output name (from cmd-line) to all output (WRITE) files
     char *output = psMetadataLookupPtr(&status, config->arguments, "OUTPUT");
     pmFPAfileAddFileNames (config->files, "OUTPUT", output, PM_FPA_MODE_WRITE);
 
-    // set default recipe values here
-    psMetadataAddStr (recipe, PS_LIST_TAIL, "FITMODE", PS_META_NO_REPLACE, "default fitting mode", "NONE");
-    psMetadataAddStr (recipe, PS_LIST_TAIL, "PHOTCODE", PS_META_NO_REPLACE, "default photcode", "NONE");
-    psMetadataAddStr (recipe, PS_LIST_TAIL, "BREAK_POINT", PS_META_NO_REPLACE, "default break point", "NONE");
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "ZERO_PT", PS_META_NO_REPLACE, "default zero point", 25.00);
-
-    // Chip selection: if we are using a single chip, select it for each FPA
-    // Chip numbers to work on: -chip 1,2,3,5,8,10
-    # if (0)
-    char *chips = psMetadataLookupStr(NULL, config->arguments, "CHIP_SELECTIONS"); 
-    if (chips != NULL) {
-	char *p = chips;
-	while (strlen(p)) {
-	    char *q = strchr (p, ',');
-	    if (q == NULL) { 
-		q = p + strlen(p);
-	    } else {
-		*q = 0;
-		q = q + 1;
-	    }
-	    int chipNum = atoi(p);
-	    // XXX EAM : extend this to allow an array of selected chips (by name)
-	    if (! pmFPASelectChip(input->fpa, chipNum)) {
+    // Chip selection: turn on only the chips specified
+    char *chipLine = psMetadataLookupStr(NULL, config->arguments, "CHIP_SELECTIONS"); 
+    psArray *chips = psStringSplitArray (chipLine, ",");
+    if (chips->n > 0) {
+	pmFPASelectChip (input->fpa, -1, true); // deselect all chips
+	for (int i = 0; i < chips->n; i++) {
+	    int chipNum = atoi(chips->data[i]);
+	    if (! pmFPASelectChip(input->fpa, chipNum, false)) {
 		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);
-	    p = q;
         }
     }
-    # endif
 
     psTrace(__func__, 1, "Done with psphotParseCamera...\n");
Index: /trunk/psphot/src/psphotRadiusChecks.c
===================================================================
--- /trunk/psphot/src/psphotRadiusChecks.c	(revision 6861)
+++ /trunk/psphot/src/psphotRadiusChecks.c	(revision 6862)
@@ -6,10 +6,10 @@
 static pmModelRadius modelRadiusPSF;
 
-bool psphotInitRadiusPSF (psMetadata *config, pmModelType type) {
+bool psphotInitRadiusPSF (psMetadata *recipe, pmModelType type) {
 
     bool status;
 
-    PSF_FIT_NSIGMA   = psMetadataLookupF32 (&status, config, "PSF_FIT_NSIGMA");
-    PSF_FIT_PADDING  = psMetadataLookupF32 (&status, config, "PSF_FIT_PADDING");
+    PSF_FIT_NSIGMA   = psMetadataLookupF32 (&status, recipe, "PSF_FIT_NSIGMA");
+    PSF_FIT_PADDING  = psMetadataLookupF32 (&status, recipe, "PSF_FIT_PADDING");
 
     // this function specifies the radius at this the model hits the given flux
@@ -56,10 +56,10 @@
 static pmModelRadius modelRadiusEXT;
 
-bool psphotInitRadiusEXT (psMetadata *config, pmModelType type) {
+bool psphotInitRadiusEXT (psMetadata *recipe, pmModelType type) {
 
     bool status;
 
-    EXT_FIT_NSIGMA   = psMetadataLookupF32 (&status, config, "EXT_FIT_NSIGMA");
-    EXT_FIT_PADDING  = psMetadataLookupF32 (&status, config, "EXT_FIT_PADDING");
+    EXT_FIT_NSIGMA   = psMetadataLookupF32 (&status, recipe, "EXT_FIT_NSIGMA");
+    EXT_FIT_PADDING  = psMetadataLookupF32 (&status, recipe, "EXT_FIT_PADDING");
 
     // this function specifies the radius at this the model hits the given flux
Index: /trunk/psphot/src/psphotReadout.c
===================================================================
--- /trunk/psphot/src/psphotReadout.c	(revision 6861)
+++ /trunk/psphot/src/psphotReadout.c	(revision 6862)
@@ -1,5 +1,4 @@
 # include "psphot.h"
 
-// XXX 2006.03.28 : no leaks!
 bool psphotReadout (pmConfig *config, pmFPAview *view) {
 
@@ -18,5 +17,4 @@
     // XXX move this input the psphotImageLoop level?
     pmReadoutSetWeights (readout);
-    psphotSaveImage (NULL, readout->weight, "weight.fits");
 
     // I have a valid mask, now mask in the analysis region of interest
@@ -44,5 +42,6 @@
 
     // use bright stellar objects to measure PSF
-    psf = psphotChoosePSF (sources, recipe);
+    psf = psphotChoosePSF (readout, sources, recipe);
+    psphotPSFstats (readout, recipe, psf);
 
     // linear PSF fit to peaks
@@ -52,4 +51,5 @@
     psphotBlendFit (readout, sources, recipe, psf);
 
+    // XXX this was an attempt to measure bias due to the weighting.
     // psphotWeightBias (readout, sources, recipe, psf);
 
@@ -91,13 +91,2 @@
     return true;
 }
-
-# if (0)
-fprintf (stderr, "making fake sources\n");
-sources = psphotFakeSources ();
-psMetadata *tmp = psMetadataAlloc ();
-status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY,    "psphot sources", sources);
-status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.HEADER",  PS_DATA_METADATA, "header stats", tmp);
-psFree (sources);
-psFree (tmp);
-return true;
-# endif
Index: /trunk/psphot/src/psphotRoughClass.c
===================================================================
--- /trunk/psphot/src/psphotRoughClass.c	(revision 6861)
+++ /trunk/psphot/src/psphotRoughClass.c	(revision 6862)
@@ -2,18 +2,18 @@
 
 // 2006.02.02 : no leaks
-bool psphotRoughClass (psArray *sources, psMetadata *config) {
+bool psphotRoughClass (psArray *sources, psMetadata *recipe) {
 
     bool status;
     pmPSFClump   psfClump;
 
-    psfClump = pmSourcePSFClump (sources, config);
+    psfClump = pmSourcePSFClump (sources, recipe);
 
     // group into STAR, COSMIC, EXTENDED, SATURATED, etc.
-    pmSourceRoughClass (sources, config, psfClump);
+    pmSourceRoughClass (sources, recipe, psfClump);
 
     // optional printout of source moments only
-    psphotDumpMoments (config, sources);
+    psphotDumpMoments (recipe, sources);
 
-    char *breakPt = psMetadataLookupStr (&status, config, "BREAK_POINT");
+    char *breakPt = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
     if (!strcasecmp (breakPt, "CLASS")) exit (0);
 
Index: /trunk/psphot/src/psphotSourceFits.c
===================================================================
--- /trunk/psphot/src/psphotSourceFits.c	(revision 6861)
+++ /trunk/psphot/src/psphotSourceFits.c	(revision 6862)
@@ -50,16 +50,16 @@
 static pmModelType modelTypeEXT;
 
-bool psphotInitLimitsEXT (psMetadata *config) {
+bool psphotInitLimitsEXT (psMetadata *recipe) {
 
     bool status;
 
     // extended source model parameters
-    EXT_MIN_SN       = psMetadataLookupF32 (&status, config, "EXT_MIN_SN");
-    EXT_MOMENTS_RAD  = psMetadataLookupF32 (&status, config, "EXT_MOMENTS_RADIUS");
+    EXT_MIN_SN       = psMetadataLookupF32 (&status, recipe, "EXT_MIN_SN");
+    EXT_MOMENTS_RAD  = psMetadataLookupF32 (&status, recipe, "EXT_MOMENTS_RADIUS");
 
     // extended source model descriptions
-    char *modelNameEXT = psMetadataLookupStr (&status, config, "EXT_MODEL");
+    char *modelNameEXT = psMetadataLookupStr (&status, recipe, "EXT_MODEL");
     modelTypeEXT = pmModelSetType (modelNameEXT);
-    psphotInitRadiusEXT (config, modelTypeEXT);
+    psphotInitRadiusEXT (recipe, modelTypeEXT);
 
     return true;
Index: /trunk/psphot/src/psphotSourceStats.c
===================================================================
--- /trunk/psphot/src/psphotSourceStats.c	(revision 6861)
+++ /trunk/psphot/src/psphotSourceStats.c	(revision 6862)
@@ -2,5 +2,5 @@
 
 // 2006.02.02 : no leaks
-psArray *psphotSourceStats (pmReadout *readout, psMetadata *config, psArray *peaks) 
+psArray *psphotSourceStats (pmReadout *readout, psMetadata *recipe, psArray *peaks) 
 {
     bool     status  = false;
@@ -11,7 +11,7 @@
 
     // determine properties (sky, moments) of initial sources
-    float INNER    = psMetadataLookupF32 (&status, config, "SKY_INNER_RADIUS");
-    float OUTER    = psMetadataLookupF32 (&status, config, "SKY_OUTER_RADIUS");
-    float RADIUS   = psMetadataLookupF32 (&status, config, "PSF_MOMENTS_RADIUS");
+    float INNER    = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+    float OUTER    = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    float RADIUS   = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
 
     sources = psArrayAlloc (peaks->n);
@@ -72,5 +72,5 @@
     psLogMsg ("psphot", 3, "%d moments: %f sec\n", sources->n, psTimerMark ("psphot"));
 
-    char *breakPt = psMetadataLookupStr (&status, config, "BREAK_POINT");
+    char *breakPt = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
     if (!strcasecmp (breakPt, "STATS")) exit (0);
 
Index: /trunk/psphot/src/psphotWeightBias.c
===================================================================
--- /trunk/psphot/src/psphotWeightBias.c	(revision 6861)
+++ /trunk/psphot/src/psphotWeightBias.c	(revision 6862)
@@ -5,5 +5,5 @@
 // only allow the normalization to vary 
 // XXX eventually, we should be able to do this with linear fitting...
-bool psphotWeightBias (pmReadout *readout, psArray *sources, psMetadata *config, pmPSF *psf) { 
+bool psphotWeightBias (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) { 
 
     int Nfit = 0;
@@ -21,5 +21,5 @@
     
     // option to limit analysis to a specific region
-    char *region = psMetadataLookupStr (&status, config, "ANALYSIS_REGION");
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
     psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
     if (psRegionIsNaN (AnalysisRegion)) psAbort ("psphot", "analysis region mis-defined");
