Index: trunk/psphot/src/Makefile.am
===================================================================
--- trunk/psphot/src/Makefile.am	(revision 26618)
+++ trunk/psphot/src/Makefile.am	(revision 26894)
@@ -25,7 +25,6 @@
 libpsphot_la_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
 
-bin_PROGRAMS = psphot psphotForced psphotMakePSF psphotTest psphotMomentsStudy 
-# bin_PROGRAMS = psphotPetrosianStudy 
-# bin_PROGRAMS = psphot
+bin_PROGRAMS = psphot psphotForced psphotMakePSF
+# bin_PROGRAMS = psphotPetrosianStudy psphotTest psphotMomentsStudy 
 
 psphot_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
@@ -41,15 +40,15 @@
 psphotMakePSF_LDADD = libpsphot.la
 
-psphotTest_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
-psphotTest_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
-psphotTest_LDADD = libpsphot.la
-
-psphotMomentsStudy_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
-psphotMomentsStudy_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
-psphotMomentsStudy_LDADD = libpsphot.la
+# psphotMomentsStudy_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+# psphotMomentsStudy_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+# psphotMomentsStudy_LDADD = libpsphot.la
 
 # psphotPetrosianStudy_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
 # psphotPetrosianStudy_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
 # psphotPetrosianStudy_LDADD = libpsphot.la
+
+# psphotTest_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+# psphotTest_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+# psphotTest_LDADD = libpsphot.la
 
 # standard psphot for generic photometry
@@ -82,21 +81,20 @@
 	psphotCleanup.c
 
-
-# psphot analysis of the detectability of specified positions
-psphotDetect_SOURCES =            \
-        psphotDetect.c            \
-	psphotDetectArguments.c	  \
-	psphotDetectParseCamera.c \
-	psphotDetectImageLoop.c	  \
-	psphotDetectReadout.c	  \
-	psphotMosaicChip.c	  \
-	psphotCleanup.c
-
-psphotTest_SOURCES = \
-        psphotTest.c
-
-psphotMomentsStudy_SOURCES = \
-        psphotMomentsStudy.c
-
+# # psphot analysis of the detectability of specified positions
+# psphotDetect_SOURCES =            \
+#         psphotDetect.c            \
+# 	psphotDetectArguments.c	  \
+# 	psphotDetectParseCamera.c \
+# 	psphotDetectImageLoop.c	  \
+# 	psphotDetectReadout.c	  \
+# 	psphotMosaicChip.c	  \
+# 	psphotCleanup.c
+
+# psphotTest_SOURCES = \
+#         psphotTest.c
+# 
+# psphotMomentsStudy_SOURCES = \
+#         psphotMomentsStudy.c
+# 
 # psphotPetrosianStudy_SOURCES = \
 #         psphotPetrosianStudy.c
@@ -146,5 +144,4 @@
 	psphotKernelFromPSF.c	       \
 	psphotPSFConvModel.c	       \
-	psphotModelTest.c	       \
 	psphotFitSet.c		       \
 	psphotSourceFreePixels.c       \
@@ -175,4 +172,7 @@
 	psphotEfficiency.c
 
+# XXX need to fix this for the new apis
+#	psphotModelTest.c	       
+
 # re-instate these
 #	psphotIsophotal.c	       \
Index: trunk/psphot/src/psphot.h
===================================================================
--- trunk/psphot/src/psphot.h	(revision 26618)
+++ trunk/psphot/src/psphot.h	(revision 26894)
@@ -12,4 +12,12 @@
 
 #define PSPHOT_RECIPE_PSF_FAKE_ALLOW "PSF.FAKE.ALLOW" // Name for recipe component permitting fake PSFs
+
+// pmPCMData : PSF Convolved Model data storage structure
+typedef struct {
+    psImage *model;
+    psArray *dmodels;
+    psImage *modelConv;
+    psArray *dmodelsConv;
+} pmPCMData;
 
 // top-level psphot functions
@@ -29,5 +37,7 @@
 bool            psphotReadoutMinimal(pmConfig *config, const pmFPAview *view);
 
-bool            psphotReadoutCleanup (pmConfig *config, pmReadout *readout, psMetadata *recipe, pmDetections *detections, pmPSF *psf, psArray *sources);
+bool            psphotReadoutCleanup (pmConfig *config, const pmFPAview *view);
+bool            psphotReadoutCleanupReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
 bool            psphotDefineFiles (pmConfig *config, pmFPAfile *input);
 void            psphotFilesActivate(pmConfig *config, bool state);
@@ -38,46 +48,122 @@
 // XXX test functions
 psArray        *psphotFakeSources (void);
+bool            psphotMaskCosmicRayFootprintCheck (psArray *sources);
 
 // psphotReadout functions
-bool            psphotModelBackground (pmConfig *config, const pmFPAview *view, const char *filename);
-bool            psphotSubtractBackground (pmConfig *config, const pmFPAview *view, const char *filename) ;
-
-psImageBinning *psphotBackgroundBinning(const psImage *, const pmConfig *);
-
-pmDetections   *psphotFindDetections (pmDetections *detections, pmReadout *readout, psMetadata *recipe);
-
-bool            psphotRoughClass (pmReadout *readout, psArray *sources, psMetadata *recipe, const bool findPsfClump);
-bool            psphotBasicDeblend (psArray *sources, psMetadata *recipe);
-pmPSF          *psphotChoosePSF (pmReadout *readout, psArray *sources, psMetadata *recipe);
-bool            psphotPSFstats (pmReadout *readout, psMetadata *recipe, pmPSF *psf);
-bool            psphotMomentsStats (pmReadout *readout, psMetadata *recipe, psArray *sources);
-bool            psphotFitSourcesLinear (pmReadout *readout, psArray *sources, const psMetadata *recipe, const pmPSF *psf, bool final);
-bool            psphotReplaceUnfitSources (psArray *sources, psImageMaskType maskVal);
-
-bool            psphotReplaceAllSources (psArray *sources, psMetadata *recipe);
-bool            psphotRemoveAllSources (const psArray *sources, const psMetadata *recipe);
-
-bool            psphotBlendFit (pmConfig *config, pmReadout *readout, psArray *sources, pmPSF *psf);
+bool            psphotAddPhotcode (pmConfig *config, const pmFPAview *view);
+bool            psphotAddPhotcodeReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index);
+
+bool            psphotSetMaskAndVariance (pmConfig *config, const pmFPAview *view);
+bool            psphotSetMaskAndVarianceReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotModelBackground (pmConfig *config, const pmFPAview *view);
+bool            psphotModelBackgroundReadoutFileIndex (pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+bool            psphotSubtractBackground (pmConfig *config, const pmFPAview *view);
+bool            psphotSubtractBackgroundReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotFindDetections (pmConfig *config, const pmFPAview *view, bool firstPass);
+bool            psphotFindDetectionsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool firstPass);
+
+bool            psphotSourceStats (pmConfig *config, const pmFPAview *view, bool setWindow);
+bool            psphotSourceStatsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool setWindow);
+
+bool            psphotDeblendSatstars (pmConfig *config, const pmFPAview *view);
+bool            psphotDeblendSatstarsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+bool            psphotBasicDeblend (pmConfig *config, const pmFPAview *view);
+bool            psphotBasicDeblendReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+bool            psphotRoughClass (pmConfig *config, const pmFPAview *view);
+bool            psphotRoughClassReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+bool            psphotRoughClassRegion (int nRegion, psRegion *region, psArray *sources, psMetadata *analysis, psMetadata *recipe, const bool havePSF);
+
+bool            psphotImageQuality (pmConfig *config, const pmFPAview *view);
+bool            psphotImageQualityReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotChoosePSF (pmConfig *config, const pmFPAview *view);
+bool            psphotChoosePSFReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotGuessModels (pmConfig *config, const pmFPAview *view);
+bool            psphotGuessModelsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+bool            psphotMergeSources (pmConfig *config, const pmFPAview *view);
+bool            psphotMergeSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+bool            psphotFitSourcesLinear (pmConfig *config, const pmFPAview *view, bool final);
+bool            psphotFitSourcesLinearReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, bool final);
+
+bool            psphotSourceSize (pmConfig *config, const pmFPAview *view, bool getPSFsize);
+bool            psphotSourceSizeReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool getPSFsize);
+
+bool            psphotBlendFit (pmConfig *config, const pmFPAview *view);
+bool            psphotBlendFitReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
 bool            psphotBlendFit_Threaded (psThreadJob *job);
 
-bool            psphotSourceStatsUpdate (psArray *sources, pmConfig *config, pmReadout *readout);
-psArray        *psphotSourceStats (pmConfig *config, pmReadout *readout, pmDetections *detections, bool setWindow);
-bool            psphotSourceStats_Threaded (psThreadJob *job);
-
-bool            psphotGuessModels (pmConfig *config, pmReadout *readout, psArray *sources, pmPSF *psf);
-bool            psphotGuessModel_Threaded (psThreadJob *job);
-
-bool            psphotMagnitudes (pmConfig *config, pmReadout *readout, const pmFPAview *view, psArray *sources, const pmPSF *psf);
+bool            psphotReplaceAllSources (pmConfig *config, const pmFPAview *view);
+bool            psphotReplaceAllSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotAddNoise (pmConfig *config, const pmFPAview *view);
+bool            psphotSubNoise (pmConfig *config, const pmFPAview *view);
+bool            psphotAddOrSubNoise (pmConfig *config, const pmFPAview *view, bool add);
+bool            psphotAddOrSubNoiseReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool add);
+
+bool            psphotExtendedSourceAnalysis (pmConfig *config, const pmFPAview *view);
+bool            psphotExtendedSourceAnalysisReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotExtendedSourceFits (pmConfig *config, const pmFPAview *view);
+bool            psphotExtendedSourceFitsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotApResid (pmConfig *config, const pmFPAview *view);
+bool            psphotApResidReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotMagnitudes (pmConfig *config, const pmFPAview *view);
+bool            psphotMagnitudesReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
 bool            psphotMagnitudes_Threaded (psThreadJob *job);
+
+bool            psphotEfficiency (pmConfig *config, const pmFPAview *view);
+bool            psphotEfficiencyReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
 
 bool            psphotPSFWeights(pmConfig *config, pmReadout *readout, const pmFPAview *view, psArray *sources);
 bool            psphotPSFWeights_Threaded (psThreadJob *job);
 
-bool            psphotApResid (pmConfig *config, pmReadout *readout, psArray *sources, pmPSF *psf);
-
 bool            psphotSkyReplace (pmConfig *config, const pmFPAview *view);
-bool            psphotExtendedSourceAnalysis (pmReadout *readout, psArray *sources, psMetadata *recipe);
-bool            psphotExtendedSourceFits (pmReadout *readout, psArray *sources, psMetadata *recipe);
-bool            psphotEfficiency(pmConfig *config, pmReadout *readout, const pmFPAview *view, const pmPSF *psf, psMetadata *recipe, const psArray *realSources);
+bool            psphotSkyReplaceReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+bool            psphotSourceFreePixels (pmConfig *config, const pmFPAview *view);
+bool            psphotSourceFreePixelsReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+// in psphotSourceStats.c:
+bool            psphotSourceStats_Threaded (psThreadJob *job);
+bool            psphotSourceStatsUpdate (psArray *sources, pmConfig *config, pmReadout *readout);
+bool            psphotSetMomentsWindow (psMetadata *recipe, psMetadata *analysis, psArray *sources);
+
+// in psphotChoosePSF.c:
+bool            psphotPSFstats (pmReadout *readout, pmPSF *psf);
+bool            psphotMomentsStats (pmReadout *readout, psArray *sources);
+
+// in psphotGuessModel.c
+bool            psphotGuessModel_Threaded (psThreadJob *job);
+
+// in psphotMergeSources.c:
+bool            psphotLoadExtSources (pmConfig *config, const pmFPAview *view);
+psArray        *psphotLoadPSFSources (pmConfig *config, const pmFPAview *view);
+bool            psphotRepairLoadedSources (pmConfig *config, const pmFPAview *view);
+bool            psphotCheckExtSources (pmConfig *config, const pmFPAview *view);
+
+// generate the detection structure for the supplied array of sources
+bool            psphotDetectionsFromSources (pmConfig *config, const pmFPAview *view, psArray *sources);
+
+// generate the detection structure for the supplied array of sources
+bool            psphotSetSourceParams (pmConfig *config, psArray *sources, pmPSF *psf);
+
+// in psphotModelBackground.c:
+// Create a background model for a readout, without saving the result as a pmFPAfile on config->files.  Otherwise identical to psphotModelBackgroundFileIndex.
+psImage        *psphotModelBackgroundReadoutNoFile(pmReadout *readout, const pmConfig *config);
+psImageBinning *psphotBackgroundBinning(const psImage *image, const pmConfig *config);
+
+// in psphotReplaceUnfit.c:
+bool            psphotRemoveAllSources (const psArray *sources, const psMetadata *recipe);
+bool            psphotReplaceUnfitSources (psArray *sources, psImageMaskType maskVal);
 
 // thread-related:
@@ -93,8 +179,5 @@
 psErrorCode     psphotCullPeaks(const psImage *img, const psImage *weight, const psMetadata *recipe, psArray *footprints);
 
-// generate the detection structure for the supplied array of sources
-pmDetections   *psphotDetectionsFromSources (pmConfig *config, psArray *sources);
-
-// used by ApResid
+// in psphotApResid.c:
 pmTrend2D      *psphotApResidTrend (float *apResidSysErr, pmReadout *readout, int Nx, int Ny, psVector *xPos, psVector *yPos, psVector *apResid, psVector *dMag);
 bool            psphotApResidMags_Threaded (psThreadJob *job);
@@ -103,9 +186,8 @@
 void            psphotModelClassInit (void);
 bool            psphotGrowthCurve (pmReadout *readout, pmPSF *psf, bool ignore, psImageMaskType maskVal);
-bool            psphotSetMaskAndVariance (pmConfig *config, pmReadout *readout, psMetadata *recipe);
-void            psphotSourceFreePixels (psArray *sources);
 
 // functions to set the correct source pixels
-bool            psphotInitRadiusPSF (const psMetadata *recipe, const pmModelType type);
+bool            psphotInitRadiusPSF(const psMetadata *recipe, const psMetadata *analysis, const pmModelType type);
+
 bool            psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model, psImageMaskType markVal);
 bool            psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, psImageMaskType markVal, float dR);
@@ -114,6 +196,4 @@
 float           psphotSetRadiusEXT (pmReadout *readout, pmSource *source, psImageMaskType markVal);
 
-// output functions
-bool            psphotAddPhotcode (psMetadata *recipe, pmConfig *config, const pmFPAview *view, const char *filerule);
 bool            psphotDumpMoments (psMetadata *recipe, psArray *sources);
 psMetadata     *psphotDefineHeader (psMetadata *recipe);
@@ -146,23 +226,12 @@
 bool            psphotFitSummary (void);
 
-bool            psphotMergeSources (psArray *oldSources, psArray *newSources);
-bool            psphotLoadExtSources (pmConfig *config, const pmFPAview *view, psArray *sources);
-psArray        *psphotLoadPSFSources (pmConfig *config, const pmFPAview *view);
-
-pmPSF          *psphotLoadPSF (pmConfig *config, const pmFPAview *view, psMetadata *recipe);
+bool            psphotLoadPSF (pmConfig *config, const pmFPAview *view);
+bool            psphotLoadPSFReadout (pmConfig *config, const pmFPAview *view, const char *outFilename, const char *inFilename, int index);
+
 bool            psphotSetHeaderNstars (psMetadata *recipe, psArray *sources);
-bool            psphotAddNoise (pmReadout *readout, const psArray *sources, const psMetadata *recipe);
-bool            psphotSubNoise (pmReadout *readout, const psArray *sources, const psMetadata *recipe);
-bool            psphotAddOrSubNoise (pmReadout *readout, const psArray *sources, const psMetadata *recipe, bool add);
 bool            psphotRadialPlot (int *kapa, const char *filename, pmSource *source);
 bool            psphotSourcePlots (pmReadout *readout, psArray *sources, psMetadata *recipe);
 bool            psphotMosaicSubimage (psImage *outImage, pmSource *source, int Xo, int Yo, int DX, int DY, bool normalize);
 
-bool            psphotAddWithTest (pmSource *source, bool useState, psImageMaskType maskVal);
-bool            psphotSubWithTest (pmSource *source, bool useState, psImageMaskType maskVal);
-bool            psphotSetState (pmSource *source, bool curState, psImageMaskType maskVal);
-bool            psphotDeblendSatstars (pmReadout *readout, psArray *sources, psMetadata *recipe);
-bool            psphotSourceSize (pmConfig *config, pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, long first);
-
 bool            psphotMakeResiduals (psArray *sources, psMetadata *recipe, pmPSF *psf, psImageMaskType maskVal);
 
@@ -172,35 +241,33 @@
 
 // functions related to extended source analysis
-bool  psphotRadialProfile (pmSource *source, psMetadata *recipe, float skynoise, psImageMaskType maskVal);
-bool  psphotRadialProfilesByAngles (pmSource *source, int Nsec, float Rmax);
-float psphotRadiusFromProfile (pmSource *source, psVector *radius, psVector *flux, float fluxMin, float fluxMax);
-bool  psphotRadiiFromProfiles (pmSource *source, float fluxMin, float fluxMax);
-bool  psphotEllipticalProfile (pmSource *source);
-bool  psphotEllipticalContour (pmSource *source);
+bool            psphotRadialProfile (pmSource *source, psMetadata *recipe, float skynoise, psImageMaskType maskVal);
+bool            psphotRadialProfilesByAngles (pmSource *source, int Nsec, float Rmax);
+float           psphotRadiusFromProfile (pmSource *source, psVector *radius, psVector *flux, float fluxMin, float fluxMax);
+bool            psphotRadiiFromProfiles (pmSource *source, float fluxMin, float fluxMax);
+bool            psphotEllipticalProfile (pmSource *source);
+bool            psphotEllipticalContour (pmSource *source);
 
 // psphotVisual functions
-bool psphotVisualShowImage (pmReadout *readout);
-bool psphotVisualShowBackground (pmConfig *config, const pmFPAview *view, pmReadout *readout);
-bool psphotVisualShowSignificance (psImage *image);
-bool psphotVisualShowPeaks (pmDetections *detections);
-bool psphotVisualShowFootprints (pmDetections *detections);
-bool psphotVisualShowMoments (psArray *sources);
-bool psphotVisualPlotMoments (psMetadata *recipe, psMetadata *analysis, psArray *sources);
-bool psphotVisualShowRoughClass (psArray *sources);
-bool psphotVisualShowPSFStars (psMetadata *recipe, pmPSF *psf, psArray *sources);
-bool psphotVisualShowSatStars (psMetadata *recipe, pmPSF *psf, psArray *sources);
-bool psphotVisualShowPSFModel (pmReadout *readout, pmPSF *psf);
-bool psphotVisualPlotRadialProfile (int myKapa, pmSource *source, psImageMaskType maskVal);
-bool psphotVisualPlotRadialProfiles (psMetadata *recipe, psArray *sources);
-bool psphotVisualShowFlags (psArray *sources);
-bool psphotVisualShowSourceSize (pmReadout *readout, psArray *sources);
-bool psphotVisualShowResidualImage (pmReadout *readout);
-bool psphotVisualPlotApResid (psArray *sources, float mean, float error);
-bool psphotVisualPlotChisq (psArray *sources);
-bool psphotVisualPlotSourceSize (psMetadata *recipe, psMetadata *analysis, psArray *sources);
-bool psphotVisualShowPetrosians (psArray *sources);
-
-// bool psphotPetrosianAnalysis (pmReadout *readout, psArray *sources, psMetadata *recipe);
-// bool psphotPetrosianProfile (pmReadout *readout, pmSource *source, float skynoise);
+bool            psphotVisualShowImage (pmReadout *readout);
+bool            psphotVisualShowBackground (pmConfig *config, const pmFPAview *view, pmReadout *readout);
+bool            psphotVisualShowSignificance (psImage *image, float min, float max);
+bool            psphotVisualShowPeaks (pmDetections *detections);
+bool            psphotVisualShowFootprints (pmDetections *detections);
+bool            psphotVisualShowMoments (psArray *sources);
+bool            psphotVisualPlotMoments (psMetadata *recipe, psMetadata *analysis, psArray *sources);
+bool            psphotVisualShowRoughClass (psArray *sources);
+bool            psphotVisualShowPSFStars (psMetadata *recipe, pmPSF *psf, psArray *sources);
+bool            psphotVisualShowSatStars (psMetadata *recipe, pmPSF *psf, psArray *sources);
+bool            psphotVisualShowPSFModel (pmReadout *readout, pmPSF *psf);
+bool            psphotVisualPlotRadialProfile (int myKapa, pmSource *source, psImageMaskType maskVal);
+bool            psphotVisualPlotRadialProfiles (psMetadata *recipe, psArray *sources);
+bool            psphotVisualShowFlags (psArray *sources);
+bool            psphotVisualShowSourceSize (pmReadout *readout, psArray *sources);
+bool            psphotVisualShowResidualImage (pmReadout *readout);
+bool            psphotVisualPlotApResid (psArray *sources, float mean, float error);
+bool            psphotVisualPlotChisq (psArray *sources);
+bool            psphotVisualPlotSourceSize (psMetadata *recipe, psMetadata *analysis, psArray *sources);
+bool            psphotVisualShowPetrosians (psArray *sources);
+bool            psphotVisualEraseOverlays (int channel, char *overlay);
 
 bool psphotPetrosian (pmSource *source, psMetadata *recipe, float skynoise, psImageMaskType maskVal);
@@ -208,5 +275,5 @@
 bool psphotPetrosianStats (pmSource *source);
 
-// XXX old versions, currently disabled
+// currently disabled:
 // bool            psphotIsophotal (pmSource *source, psMetadata *recipe, psImageMaskType maskVal);
 // bool            psphotAnnuli (pmSource *source, psMetadata *recipe, psImageMaskType maskVal);
@@ -223,16 +290,5 @@
 //                               float petFlux, float radiusForFlux);
 
-bool psphotImageQuality (psMetadata *recipe, psArray *sources);
-
 // structures & functions to support psf-convolved model fitting
-
-// pmPCMData : PSF Convolved Model data storage structure
-typedef struct {
-    psImage *model;
-    psArray *dmodels;
-    psImage *modelConv;
-    psArray *dmodelsConv;
-} pmPCMData;
-
 
 // psf-convolved model fitting
@@ -277,11 +333,4 @@
 int psphotSupplementStars (psArray *stars, psArray *sources, psImageBinning *binning, int ix, int iy);
 
-
-// Create a background model for a readout
-// Essentially identical to psphotModelBackground, but with a more convenient API for outsiders.
-psImage *psphotBackgroundModel(pmReadout *ro, // Readout for which to generate background model
-                               const pmConfig *config // Configuration
-    );
-
 pmConfig *psphotForcedArguments(int argc, char **argv);
 bool psphotForcedImageLoop (pmConfig *config);
Index: trunk/psphot/src/psphotAddNoise.c
===================================================================
--- trunk/psphot/src/psphotAddNoise.c	(revision 26618)
+++ trunk/psphot/src/psphotAddNoise.c	(revision 26894)
@@ -1,13 +1,35 @@
 # include "psphotInternal.h"
 
-bool psphotAddNoise (pmReadout *readout, const psArray *sources, const psMetadata *recipe) {
-  return psphotAddOrSubNoise (readout, sources, recipe, true);
+bool psphotAddNoise (pmConfig *config, const pmFPAview *view) {
+    return psphotAddOrSubNoise (config, view, true);
 }
 
-bool psphotSubNoise (pmReadout *readout, const psArray *sources, const psMetadata *recipe) {
-  return psphotAddOrSubNoise (readout, sources, recipe, false);
+bool psphotSubNoise (pmConfig *config, const pmFPAview *view) {
+    return psphotAddOrSubNoise (config, view, false);
 }
 
-bool psphotAddOrSubNoise (pmReadout *readout, const psArray *sources, const psMetadata *recipe, bool add) {
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotAddOrSubNoise (pmConfig *config, const pmFPAview *view, bool add)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotAddOrSubNoiseReadout (config, view, "PSPHOT.INPUT", i, recipe, add)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on to modify noise for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotAddOrSubNoiseReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool add) {
 
     bool status = false;
@@ -16,7 +38,18 @@
     psEllipseAxes axes;
 
-    PS_ASSERT (readout, false);
-    PS_ASSERT (readout->parent, false);
-    PS_ASSERT (readout->parent->concepts, false);
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+    psAssert (readout->parent, "missing cell?");
+    psAssert (readout->parent->concepts, "missing concepts?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
 
     psTimerStart ("psphot.noise");
@@ -24,5 +57,5 @@
     // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
     psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
-    assert (maskVal);
+    psAssert (maskVal, "missing mask value?");
 
     // increase variance by factor*(object noise):
Index: trunk/psphot/src/psphotApResid.c
===================================================================
--- trunk/psphot/src/psphotApResid.c	(revision 26618)
+++ trunk/psphot/src/psphotApResid.c	(revision 26894)
@@ -4,5 +4,27 @@
 // measure the aperture residual statistics and 2D variations
 
-bool psphotApResid (pmConfig *config, pmReadout *readout, psArray *sources, pmPSF *psf)
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotApResid (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotApResidReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to measure aperture residual for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotApResidReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe)
 {
     int Nfail = 0;
@@ -13,14 +35,26 @@
     pmSource *source;
 
-    PS_ASSERT_PTR_NON_NULL(config, false);
-    PS_ASSERT_PTR_NON_NULL(readout, false);
-    PS_ASSERT_PTR_NON_NULL(sources, false);
-    PS_ASSERT_PTR_NON_NULL(psf, false);
-
     psTimerStart ("psphot.apresid");
 
-    // select the appropriate recipe information
-    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
-    assert (recipe);
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping ap resid");
+	return true;
+    }
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (psf, "missing psf?");
 
     // determine the number of allowed threads
@@ -33,8 +67,8 @@
     if (!measureAptrend) {
         // save nan values since these were not calculated
-        psMetadataAdd (recipe, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   NAN);
-        psMetadataAdd (recipe, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", NAN);
-        psMetadataAdd (recipe, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "number of apresid stars", 0);
-        psMetadataAdd (recipe, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "aperture loss (mag)", NAN);
+        psMetadataAdd (readout->analysis, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   NAN);
+        psMetadataAdd (readout->analysis, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", NAN);
+        psMetadataAdd (readout->analysis, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "number of apresid stars", 0);
+        psMetadataAdd (readout->analysis, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "aperture loss (mag)", NAN);
         return true;
     }
@@ -288,8 +322,8 @@
 
     // save results for later output
-    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, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "number of apresid stars", psf->nApResid);
-    psMetadataAdd (recipe, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "aperture loss (mag)", psf->growth->apLoss);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   psf->ApResid);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->dApResid);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "number of apresid stars", psf->nApResid);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "aperture loss (mag)", psf->growth->apLoss);
 
     psLogMsg ("psphot.apresid", PS_LOG_DETAIL, "aperture residual: %f +/- %f\n", psf->ApResid, psf->dApResid);
@@ -308,8 +342,8 @@
 escape:
     // save nan values since these were not calculated
-    psMetadataAdd (recipe, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   NAN);
-    psMetadataAdd (recipe, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", NAN);
-    psMetadataAdd (recipe, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "number of apresid stars", 0);
-    psMetadataAdd (recipe, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "aperture loss (mag)", NAN);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   NAN);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", NAN);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "number of apresid stars", 0);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "aperture loss (mag)", NAN);
 
     psFree (xPos);
@@ -318,5 +352,6 @@
     psFree (mag);
     psFree (dMag);
-    return false;
+    return true;
+    // this is a quality error, not a programming error
 }
 
Index: trunk/psphot/src/psphotBasicDeblend.c
===================================================================
--- trunk/psphot/src/psphotBasicDeblend.c	(revision 26618)
+++ trunk/psphot/src/psphotBasicDeblend.c	(revision 26894)
@@ -1,5 +1,23 @@
 # include "psphotInternal.h"
 
-bool psphotBasicDeblend (psArray *sources, psMetadata *recipe) {
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotBasicDeblend (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotBasicDeblendReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on basic deblend analysis for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotBasicDeblendReadout (pmConfig *config, const pmFPAview *view, const char *filename, int fileIndex) {
 
     int N;
@@ -8,7 +26,29 @@
     pmSource *source, *testSource;
 
+    psTimerStart ("psphot.deblend.basic");
+
     int Nblend = 0;
 
-    psTimerStart ("psphot.deblend.basic");
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, fileIndex); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping basic deblend");
+	return true;
+    }
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
 
     float FRACTION = psMetadataLookupF32 (&status, recipe, "DEBLEND_PEAK_FRACTION");
Index: trunk/psphot/src/psphotBlendFit.c
===================================================================
--- trunk/psphot/src/psphotBlendFit.c	(revision 26618)
+++ trunk/psphot/src/psphotBlendFit.c	(revision 26894)
@@ -1,6 +1,28 @@
 # include "psphotInternal.h"
 
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotBlendFit (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotBlendFitReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to fit sources (non-linear) for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
 // XXX I don't like this name
-bool psphotBlendFit (pmConfig *config, pmReadout *readout, psArray *sources, pmPSF *psf) {
+bool psphotBlendFitReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
 
     int Nfit = 0;
@@ -12,7 +34,24 @@
     psTimerStart ("psphot.fit.nonlinear");
 
-    // select the appropriate recipe information
-    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
-    assert (recipe);
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping blend fit");
+	return true;
+    }
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (psf, "missing psf?");
 
     // determine the number of allowed threads
@@ -40,5 +79,5 @@
     psphotInitLimitsPSF (recipe, readout);
     psphotInitLimitsEXT (recipe);
-    psphotInitRadiusPSF (recipe, psf->type);
+    psphotInitRadiusPSF (recipe, readout->analysis, psf->type);
 
     // starts the timer, sets up the array of fitSets
@@ -47,4 +86,8 @@
     // source analysis is done in S/N order (brightest first)
     sources = psArraySort (sources, pmSourceSortBySN);
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping blend");
+	return true;
+    }
 
     // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
@@ -272,109 +315,2 @@
 }
 
-# if (0)
-bool psphotBlendFit_Unthreaded (int *nfit, int *npsf, int *next, int *nfail, pmReadout *readout, psMetadata *recipe, psArray *sources, pmPSF *psf, psArray *newSources) {
-
-    bool status = false;
-    int Nfit = 0;
-    int Npsf = 0;
-    int Next = 0;
-    int Nfail = 0;
-
-    // bit-masks to test for good/bad pixels
-    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
-    assert (maskVal);
-
-    // bit-mask to mark pixels not used in analysis
-    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
-    assert (markVal);
-
-    // maskVal is used to test for rejected pixels, and must include markVal
-    maskVal |= markVal;
-
-    // S/N limit to perform full non-linear fits
-    float FIT_SN_LIM = psMetadataLookupF32 (&status, recipe, "FULL_FIT_SN_LIM");
-
-    // option to limit analysis to a specific region
-    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
-    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
-    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
-
-    for (int i = 0; i < sources->n; i++) {
-        pmSource *source = sources->data[i];
-
-        // skip non-astronomical objects (very likely defects)
-        if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
-        if (source->mode &  PM_SOURCE_MODE_CR_LIMIT) continue;
-        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
-        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
-
-        // skip DBL second sources (ie, added by psphotFitBlob)
-        if (source->mode &  PM_SOURCE_MODE_PAIR) continue;
-
-        // limit selection to some SN limit
-        if (source->peak->SN < FIT_SN_LIM) continue;
-
-        // exclude sources outside optional analysis region
-        if (source->peak->xf < AnalysisRegion.x0) continue;
-        if (source->peak->yf < AnalysisRegion.y0) continue;
-        if (source->peak->xf > AnalysisRegion.x1) continue;
-        if (source->peak->yf > AnalysisRegion.y1) continue;
-
-        // if model is NULL, we don't have a starting guess
-        if (source->modelPSF == NULL) continue;
-
-        // skip sources which are insignificant flux?
-        // XXX this is somewhat ad-hoc
-        if (source->modelPSF->params->data.F32[1] < 0.1) {
-            psTrace ("psphot", 5, "skipping near-zero source: %f, %f : %f\n",
-                     source->modelPSF->params->data.F32[1],
-                     source->modelPSF->params->data.F32[2],
-                     source->modelPSF->params->data.F32[3]);
-            continue;
-        }
-
-        // replace object in image
-        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
-            pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
-        }
-        Nfit ++;
-
-        // try fitting PSFs or extended sources depending on source->mode
-        // these functions subtract the resulting fitted source
-        if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
-            if (psphotFitBlob (readout, source, newSources, psf, maskVal, markVal)) {
-                source->type = PM_SOURCE_TYPE_EXTENDED;
-                psTrace ("psphot", 5, "source at %7.1f, %7.1f is ext", source->peak->xf, source->peak->yf);
-                Next ++;
-                continue;
-            }
-        } else {
-            if (psphotFitBlend (readout, source, psf, maskVal, markVal)) {
-                source->type = PM_SOURCE_TYPE_STAR;
-                psTrace ("psphot", 5, "source at %7.1f, %7.1f is psf", source->peak->xf, source->peak->yf);
-                Npsf ++;
-                continue;
-            }
-        }
-
-        psTrace ("psphot", 5, "source at %7.1f, %7.1f failed", source->peak->xf, source->peak->yf);
-        Nfail ++;
-
-        // re-subtract the object, leave local sky
-        pmSourceCacheModel (source, maskVal);
-        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
-    }
-
-    // change the value of a scalar on the array (wrap this and put it in psArray.h)
-    *nfit  = Nfit;
-    *npsf  = Npsf;
-    *next  = Next;
-    *nfail = Nfail;
-
-    // moments are modified by the fit; re-display
-    psphotVisualPlotMoments (recipe, sources);
-    psphotVisualShowResidualImage (readout);
-
-    return true;
-}
-# endif
Index: trunk/psphot/src/psphotChoosePSF.c
===================================================================
--- trunk/psphot/src/psphotChoosePSF.c	(revision 26618)
+++ trunk/psphot/src/psphotChoosePSF.c	(revision 26894)
@@ -1,18 +1,64 @@
 # include "psphotInternal.h"
 
+// generate a PSF model for inputs without PSF models already loaded
+bool psphotChoosePSF (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotChoosePSFReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to choose a psf model for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
 // try PSF models and select best option
-pmPSF *psphotChoosePSF (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+bool psphotChoosePSFReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
 
     bool status;
 
     psTimerStart ("psphot.choose.psf");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // do not generate a PSF if we already were supplied one
+    if (psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF")) {
+	psLogMsg ("psphot", PS_LOG_DETAIL, "psf model supplied for input file %d", index);
+        return true;
+    }
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping PSF model");
+	return true;
+    }
 
     // bit-masks to test for good/bad pixels
     psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
-    assert (maskVal);
+    psAssert (maskVal, "missing mask value?");
 
     // bit-mask to mark pixels not used in analysis
     psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
-    assert (markVal);
+    psAssert (markVal, "missing mark value?");
 
     // maskVal is used to test for rejected pixels, and must include markVal
@@ -61,7 +107,10 @@
     // assert (status);
 
-    // We have calculated a Gaussian window function, use that for both the PSF fit radius and
-    // the aperture radius (scaling SIGMA)
-    float gaussSigma = psMetadataLookupF32(&status, recipe, "MOMENTS_GAUSS_SIGMA");
+    // values on readout->analysis if we have calculated a Gaussian window function, use that
+    // for both the PSF fit radius and the aperture radius (scaling SIGMA), otherwise base the value on the recipe value for MOMENTS_GAUSS_SIGMA
+    float gaussSigma = psMetadataLookupF32 (&status, readout->analysis, "MOMENTS_GAUSS_SIGMA");
+    if (!status) {
+	gaussSigma = psMetadataLookupF32 (&status, recipe, "MOMENTS_GAUSS_SIGMA");
+    }
     float fitScale = psMetadataLookupF32(&status, recipe, "PSF_FIT_RADIUS_SCALE");
     float apScale = psMetadataLookupF32(&status, recipe, "PSF_APERTURE_SCALE");
@@ -70,6 +119,6 @@
 
     // XXX use the same radii for standard analysis as for the PSF creation
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "PSF_FIT_RADIUS", PS_META_REPLACE, "fit radius", options->fitRadius);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "PSF_APERTURE", PS_META_REPLACE, "psf aperture", options->apRadius);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "PSF_FIT_RADIUS", PS_META_REPLACE, "fit radius", options->fitRadius);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "PSF_APERTURE", PS_META_REPLACE, "psf aperture", options->apRadius);
 
     // dimensions of the field for which the PSF is defined
@@ -158,5 +207,5 @@
         bool status = true;
         status &= psphotMakeFluxScale (readout->image, recipe, psf);
-        status &= psphotPSFstats (readout, recipe, psf);
+        status &= psphotPSFstats (readout, psf);
         if (!status) {
             psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
@@ -233,5 +282,5 @@
         bool status = true;
         status &= psphotMakeFluxScale (readout->image, recipe, psf);
-        status &= psphotPSFstats (readout, recipe, psf);
+        status &= psphotPSFstats (readout, psf);
         if (!status) {
             psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
@@ -290,5 +339,5 @@
     psFree (models);
 
-    if (!psphotPSFstats (readout, recipe, psf)) {
+    if (!psphotPSFstats (readout, psf)) {
         psError(PSPHOT_ERR_PSF, false, "cannot measure PSF shape terms");
         psFree(options);
@@ -302,9 +351,20 @@
 
     psFree (options);
-    return (psf);
+
+    // save PSF on readout->analysis
+    if (!psMetadataAddPtr (readout->analysis, PS_LIST_TAIL, "PSPHOT.PSF", PS_META_REPLACE | PS_DATA_UNKNOWN, "psphot psf model", psf)) {
+	psError (PSPHOT_ERR_UNKNOWN, false, "problem saving sources on readout");
+        return false;
+    }
+    psFree (psf); // XXX double-check
+
+    // move into psphotChoosePSF 
+    psphotVisualShowPSFModel (readout, psf);
+
+    return true;
 }
 
 // measure average parameters of the PSF model
-bool psphotPSFstats (pmReadout *readout, psMetadata *recipe, pmPSF *psf) {
+bool psphotPSFstats (pmReadout *readout, pmPSF *psf) {
 
     psEllipseShape shape;
@@ -312,5 +372,4 @@
 
     PS_ASSERT_PTR_NON_NULL(readout, false);
-    PS_ASSERT_PTR_NON_NULL(recipe, false);
     PS_ASSERT_PTR_NON_NULL(psf, false);
 
@@ -362,8 +421,8 @@
     }
 
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_MAJ",   PS_META_REPLACE, "PSF FWHM Major axis (mean)", stats->sampleMean);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FW_MJ_SG",   PS_META_REPLACE, "PSF FWHM Major axis (sigma)", stats->sampleStdev);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FW_MJ_LQ",   PS_META_REPLACE, "PSF FWHM Major axis (lower quartile)", stats->sampleLQ);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FW_MJ_UQ",   PS_META_REPLACE, "PSF FWHM Major axis (upper quartile)", stats->sampleUQ);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FWHM_MAJ",   PS_META_REPLACE, "PSF FWHM Major axis (mean)", stats->sampleMean);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MJ_SG",   PS_META_REPLACE, "PSF FWHM Major axis (sigma)", stats->sampleStdev);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MJ_LQ",   PS_META_REPLACE, "PSF FWHM Major axis (lower quartile)", stats->sampleLQ);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MJ_UQ",   PS_META_REPLACE, "PSF FWHM Major axis (upper quartile)", stats->sampleUQ);
 
     if (!psVectorStats (stats, fwhmMinor, NULL, NULL, 0)) {
@@ -371,12 +430,12 @@
         return false;
     }
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_MIN",   PS_META_REPLACE, "PSF FWHM Minor axis (mean)", stats->sampleMean);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FW_MN_SG",   PS_META_REPLACE, "PSF FWHM Minor axis (sigma)", stats->sampleStdev);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FW_MN_LQ",   PS_META_REPLACE, "PSF FWHM Minor axis (lower quartile)", stats->sampleLQ);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FW_MN_UQ",   PS_META_REPLACE, "PSF FWHM Minor axis (upper quartile)", stats->sampleUQ);
-
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "ANGLE",    PS_META_REPLACE, "PSF angle",           axes.theta);
-    psMetadataAddS32 (recipe, PS_LIST_TAIL, "NPSFSTAR", PS_META_REPLACE, "Number of stars used to make PSF", psf->nPSFstars);
-    psMetadataAddBool(recipe, PS_LIST_TAIL, "PSFMODEL", PS_META_REPLACE, "Valid PSF Model?", true);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FWHM_MIN",   PS_META_REPLACE, "PSF FWHM Minor axis (mean)", stats->sampleMean);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MN_SG",   PS_META_REPLACE, "PSF FWHM Minor axis (sigma)", stats->sampleStdev);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MN_LQ",   PS_META_REPLACE, "PSF FWHM Minor axis (lower quartile)", stats->sampleLQ);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MN_UQ",   PS_META_REPLACE, "PSF FWHM Minor axis (upper quartile)", stats->sampleUQ);
+
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "ANGLE",    PS_META_REPLACE, "PSF angle",           axes.theta);
+    psMetadataAddS32 (readout->analysis, PS_LIST_TAIL, "NPSFSTAR", PS_META_REPLACE, "Number of stars used to make PSF", psf->nPSFstars);
+    psMetadataAddBool(readout->analysis, PS_LIST_TAIL, "PSFMODEL", PS_META_REPLACE, "Valid PSF Model?", true);
 
     psFree (fwhmMajor);
@@ -387,5 +446,5 @@
 
 // determine approximate PSF shape parameters based on the moments
-bool psphotMomentsStats (pmReadout *readout, psMetadata *recipe, psArray *sources) {
+bool psphotMomentsStats (pmReadout *readout, psArray *sources) {
 
     // without the PSF model, we can only rely on the source->moments
@@ -401,5 +460,4 @@
 
     PS_ASSERT_PTR_NON_NULL(readout, false);
-    PS_ASSERT_PTR_NON_NULL(recipe, false);
     PS_ASSERT_PTR_NON_NULL(sources, false);
 
@@ -429,17 +487,15 @@
     FWHM_T /= FWHM_N;
 
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_MAJ",   PS_META_REPLACE, "PSF FWHM Major axis (mean)", FWHM_X);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FW_MJ_SG",   PS_META_REPLACE, "PSF FWHM Major axis (sigma)", 0);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FW_MJ_LQ",   PS_META_REPLACE, "PSF FWHM Major axis (lower quartile)", 0);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FW_MJ_UQ",   PS_META_REPLACE, "PSF FWHM Major axis (upper quartile)", 0);
-
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_MIN",   PS_META_REPLACE, "PSF FWHM Minor axis (mean)", FWHM_Y);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FW_MN_SG",   PS_META_REPLACE, "PSF FWHM Minor axis (sigma)", 0);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FW_MN_LQ",   PS_META_REPLACE, "PSF FWHM Minor axis (lower quartile)", 0);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FW_MN_UQ",   PS_META_REPLACE, "PSF FWHM Minor axis (upper quartile)", 0);
-
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "ANGLE",    PS_META_REPLACE, "PSF angle",           FWHM_T);
-    psMetadataAddS32 (recipe, PS_LIST_TAIL, "NPSFSTAR", PS_META_REPLACE, "Number of stars used to make PSF", 0);
-    psMetadataAddBool(recipe, PS_LIST_TAIL, "PSFMODEL", PS_META_REPLACE, "Valid PSF Model?", false);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FWHM_MAJ",   PS_META_REPLACE, "PSF FWHM Major axis (mean)", FWHM_X);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MJ_SG",   PS_META_REPLACE, "PSF FWHM Major axis (sigma)", 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MJ_LQ",   PS_META_REPLACE, "PSF FWHM Major axis (lower quartile)", 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MJ_UQ",   PS_META_REPLACE, "PSF FWHM Major axis (upper quartile)", 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FWHM_MIN",   PS_META_REPLACE, "PSF FWHM Minor axis (mean)", FWHM_Y);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MN_SG",   PS_META_REPLACE, "PSF FWHM Minor axis (sigma)", 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MN_LQ",   PS_META_REPLACE, "PSF FWHM Minor axis (lower quartile)", 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MN_UQ",   PS_META_REPLACE, "PSF FWHM Minor axis (upper quartile)", 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "ANGLE",    PS_META_REPLACE, "PSF angle",           FWHM_T);
+    psMetadataAddS32 (readout->analysis, PS_LIST_TAIL, "NPSFSTAR", PS_META_REPLACE, "Number of stars used to make PSF", 0);
+    psMetadataAddBool(readout->analysis, PS_LIST_TAIL, "PSFMODEL", PS_META_REPLACE, "Valid PSF Model?", false);
 
     return true;
Index: trunk/psphot/src/psphotDeblendSatstars.c
===================================================================
--- trunk/psphot/src/psphotDeblendSatstars.c	(revision 26618)
+++ trunk/psphot/src/psphotDeblendSatstars.c	(revision 26894)
@@ -1,8 +1,27 @@
 # include "psphotInternal.h"
 
-bool psphotDeblendSatstars (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotDeblendSatstars (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotDeblendSatstarsReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on saturated star deblend analysis for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotDeblendSatstarsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int fileIndex) {
 
     int N;
     pmSource *source;
+    bool status;
 
     psTimerStart ("psphot.deblend.sat");
@@ -11,5 +30,26 @@
     float SAT_MIN_RADIUS = 5.0;
 
-    bool status;
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, fileIndex); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping satstar blend");
+	return true;
+    }
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
     pmCell *cell = readout->parent;
     float SATURATION = 0.75*psMetadataLookupF32 (&status, cell->concepts, "CELL.SATURATION");
Index: trunk/psphot/src/psphotEfficiency.c
===================================================================
--- trunk/psphot/src/psphotEfficiency.c	(revision 26618)
+++ trunk/psphot/src/psphotEfficiency.c	(revision 26894)
@@ -6,4 +6,6 @@
 //#define TESTING
 
+
+# if 0
 
 // Calculate the limiting magnitude for an image
@@ -33,8 +35,6 @@
 
     psKernel *kernel = psImageSmoothKernel(smoothSigma, smoothNsigma); // Kernel used for smoothing
-    psKernel *newCovar = psImageCovarianceCalculate(kernel, ro->covariance); // Covariance matrix
+    *covarFactor = psImageCovarianceCalculateFactor(kernel, ro->covariance);
     psFree(kernel);
-    *covarFactor = psImageCovarianceFactor(newCovar); // Variance factor
-    psFree(newCovar);
 
     psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN); // Statistics for variance
@@ -152,16 +152,56 @@
 }
 
+# endif
+
+bool psphotEfficiency (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotEfficiencyReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to measure detection efficiency for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
 
 // Determine detection efficiency
-bool psphotEfficiency(pmConfig *config, pmReadout *readout, const pmFPAview *view, const pmPSF *psf,
-                psMetadata *recipe, const psArray *realSources)
+bool psphotEfficiencyReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe)
 {
+# if 0
+    bool status = true;
+
+    psTimerStart("psphot.fake");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *realSources = detections->allSources;
+    psAssert (realSources, "missing sources?");
+
+    // XXX do we need to skip this step if we do not have a psf?
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (psf, "missing psf?");
+
     PM_ASSERT_READOUT_NON_NULL(readout, false);
     PM_ASSERT_READOUT_IMAGE(readout, false);
     PS_ASSERT_PTR_NON_NULL(psf, false);
     PS_ASSERT_METADATA_NON_NULL(recipe, false);
-    PS_ASSERT_ARRAY_NON_NULL(realSources, false);
-
-    psTimerStart("psphot.fake");
 
     // Collect recipe information
@@ -206,5 +246,5 @@
     // remove all sources, adding noise for subtracted sources
     psphotRemoveAllSources(realSources, recipe);
-    //    psphotAddNoise(readout, realSources, recipe);
+    // psphotAddNoise(readout, realSources, recipe);
 
 
@@ -480,4 +520,5 @@
     psLogMsg("psphot", PS_LOG_INFO, "Detection efficiency: %lf sec\n", psTimerClear("psphot.fake"));
 
+# endif
 
     return true;
Index: trunk/psphot/src/psphotExtendedSourceAnalysis.c
===================================================================
--- trunk/psphot/src/psphotExtendedSourceAnalysis.c	(revision 26618)
+++ trunk/psphot/src/psphotExtendedSourceAnalysis.c	(revision 26894)
@@ -1,6 +1,34 @@
 # include "psphotInternal.h"
 
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotExtendedSourceAnalysis (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    // perform full non-linear fits / extended source analysis?
+    if (!psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANALYSIS")) {
+	psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source measurements\n");
+	return true;
+    }
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotExtendedSourceAnalysisReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on measure extended source aperture-like parameters for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
 // aperture-like measurements for extended sources
-bool psphotExtendedSourceAnalysis (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+bool psphotExtendedSourceAnalysisReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
 
     bool status;
@@ -11,13 +39,25 @@
     int Nkron = 0;
 
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping source size");
+	return true;
+    }
+
     // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
     psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
     assert (maskVal);
-
-    // perform full non-linear fits / extended source analysis?
-    if (!psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANALYSIS")) {
-	psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source measurements\n");
-	return true;
-    }
 
     // XXX require petrosian analysis for non-linear fits? 
Index: trunk/psphot/src/psphotExtendedSourceFits.c
===================================================================
--- trunk/psphot/src/psphotExtendedSourceFits.c	(revision 26618)
+++ trunk/psphot/src/psphotExtendedSourceFits.c	(revision 26894)
@@ -1,6 +1,34 @@
 # include "psphotInternal.h"
 
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotExtendedSourceFits (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    // perform full extended source non-linear fits?
+    if (!psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_FITS")) {
+        psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source measurements\n");
+        return true;
+    }
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotExtendedSourceFitsReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on to fit extended sources for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
 // non-linear model fitting for extended sources
-bool psphotExtendedSourceFits (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+bool psphotExtendedSourceFitsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
 
     bool status;
@@ -12,4 +40,22 @@
     bool savePics = false;
 
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping source size");
+	return true;
+    }
+
     // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
     psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
@@ -21,10 +67,4 @@
     // maskVal is used to test for rejected pixels, and must include markVal
     maskVal |= markVal;
-
-    // perform full extended source non-linear fits?
-    if (!psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_FITS")) {
-        psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source measurements\n");
-        return true;
-    }
 
     // select the collection of desired models
Index: trunk/psphot/src/psphotFake.c
===================================================================
--- trunk/psphot/src/psphotFake.c	(revision 26618)
+++ trunk/psphot/src/psphotFake.c	(revision 26894)
@@ -28,8 +28,6 @@
     float smoothSigma  = 0.5*(FWHM_X + FWHM_Y) / (2.0*sqrtf(2.0*log(2.0)));
     psKernel *kernel = psImageSmoothKernel(smoothSigma, smoothNsigma); // Kernel used for smoothing
-    psKernel *newCovar = psImageCovarianceCalculate(kernel, readout->covariance); // Covariance matrix
+    float factor = psImageCovarianceCalculateFactor(kernel, readout->covariance); // Covariance matrix
     psFree(kernel);
-    float factor = psImageCovarianceFactor(newCovar); // Variance factor
-    psFree(newCovar);
 
     psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN); // Statistics for variance
@@ -213,10 +211,7 @@
 
     // Putting results on recipe because that appears to be the psphot standard, but it's not a good idea
-    psMetadataAddVector(recipe, PS_LIST_TAIL, "FAKE.EFF", PS_META_REPLACE,
-                        "Efficiency fractions", frac);
-    psMetadataAddVector(recipe, PS_LIST_TAIL, "FAKE.MAG", PS_META_REPLACE,
-                        "Efficiency magnitudes", magOffsets);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "FAKE.REF", PS_META_REPLACE,
-                     "Efficiency reference magnitude", magLim);
+    psMetadataAddVector(readout->analysis, PS_LIST_TAIL, "FAKE.EFF", PS_META_REPLACE, "Efficiency fractions", frac);
+    psMetadataAddVector(readout->analysis, PS_LIST_TAIL, "FAKE.MAG", PS_META_REPLACE, "Efficiency magnitudes", magOffsets);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "FAKE.REF", PS_META_REPLACE, "Efficiency reference magnitude", magLim);
 
     psFree(frac);
Index: trunk/psphot/src/psphotFindDetections.c
===================================================================
--- trunk/psphot/src/psphotFindDetections.c	(revision 26618)
+++ trunk/psphot/src/psphotFindDetections.c	(revision 26894)
@@ -1,6 +1,30 @@
 # include "psphotInternal.h"
 
+// we store the detections on the readout->analysis for each readout this function finds new
+// peaks and new footprints.  any old peaks are saved on oldPeaks.  the resulting footprint set
+// contains all footprints (old and new)
+bool psphotFindDetections (pmConfig *config, const pmFPAview *view, bool firstPass)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotFindDetectionsReadout (config, view, "PSPHOT.INPUT", i, recipe, firstPass)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to find initial detections for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
 // smooth the image, search for peaks, optionally define footprints based on the peaks
-pmDetections *psphotFindDetections (pmDetections *detections, pmReadout *readout, psMetadata *recipe) {
+bool psphotFindDetectionsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool firstPass) {
 
     bool status;
@@ -9,14 +33,34 @@
     int NMAX = 0;
 
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
     // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
     psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
-    assert (maskVal);
+    psAssert (maskVal, "missing mask value?");
 
     // Use the new pmFootprints approach?
     const bool useFootprints = psMetadataLookupBool(NULL, recipe, "USE_FOOTPRINTS");
 
-    // on first pass, detections have not yet been allocated
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    // on the initial pass, detections have not yet been allocated or saved on readout->analysis
     if (!detections) {
+	// create the container
         detections = pmDetectionsAlloc();
+	
+	// save detections on the readout->analysis
+	if (!psMetadataAddPtr (readout->analysis, PS_LIST_TAIL, "PSPHOT.DETECTIONS", PS_META_REPLACE | PS_DATA_UNKNOWN, "psphot detections", detections)) {
+	    psError (PSPHOT_ERR_CONFIG, false, "problem saving detections on readout");
+	    return false;
+	}
+    } else {
+	psMemIncrRefCounter(detections); // so we can free the detections below
+    }
+
+    if (firstPass) {
         pass = 1;
         NSIGMA_PEAK = psMetadataLookupF32 (&status, recipe, "PEAKS_NSIGMA_LIMIT"); PS_ASSERT (status, NULL);
@@ -30,6 +74,10 @@
     float threshold = PS_SQR(NSIGMA_PEAK);
 
-    // move the old peaks array (if it exists) to oldPeaks
-    // XXX generically, we should be able to call this function an arbitrary number of times
+    // move the old peaks array (if it exists) to oldPeaks XXX generically, we should be able
+    // to call this function an arbitrary number of times the old peaks are saved so they can
+    // be freed later -- the have to be freed after psphotFindFootprints is called below, since
+    // they are also owned by the oldFootprints, which are in turn merged into the new
+    // footprints.  (what about the source->peak entry?)
+    
     assert (detections->oldPeaks == NULL);
     detections->oldPeaks = detections->peaks;
@@ -39,18 +87,28 @@
     psImage *significance = psphotSignificanceImage (readout, recipe, pass, maskVal);
 
-    // display the backsub and backgnd images
-    psphotVisualShowSignificance (significance);
+    // display the significance image
+    psphotVisualShowSignificance (significance, -1.0, PS_SQR(3.0*NSIGMA_PEAK));
+
+    // XXX getting some strange results from significance image
+    if (0) {
+	psImage *lsig = (psImage *) psUnaryOp (NULL, significance, "log");
+	psphotVisualShowSignificance (lsig, 0.0, 4.0);
+	psFree (lsig);
+    }	
 
     // detect the peaks in the significance image
     detections->peaks = psphotFindPeaks (significance, readout, recipe, threshold, NMAX);
-    psMetadataAddF32  (recipe, PS_LIST_TAIL, "PEAK_THRESHOLD", PS_META_REPLACE, "Peak Detection Threshold", threshold);
+    psMetadataAddF32  (readout->analysis, PS_LIST_TAIL, "PEAK_THRESHOLD", PS_META_REPLACE, "Peak Detection Threshold", threshold);
     if (!detections->peaks) {
-        // No peaks found
-        return NULL;
+	// we only get a NULL peaks array due to a programming or config error. 
+	// this will result in a failure.
+	psFree (detections);
+	psError (PSPHOT_ERR_CONFIG, false, "failed on peak search");
+        return false;
     }
 
     // optionally merge peaks into footprints
     if (useFootprints) {
-        psphotFindFootprints (detections, significance, readout, recipe, threshold, pass, maskVal);
+	psphotFindFootprints (detections, significance, readout, recipe, threshold, pass, maskVal);
     }
 
@@ -61,5 +119,7 @@
     psphotVisualShowFootprints (detections);
 
-    return detections;
+    psFree (detections);
+
+    return true;
 }
 
Index: trunk/psphot/src/psphotFindFootprints.c
===================================================================
--- trunk/psphot/src/psphotFindFootprints.c	(revision 26618)
+++ trunk/psphot/src/psphotFindFootprints.c	(revision 26894)
@@ -41,7 +41,5 @@
     if (pass == 2) {
         // merge in old peaks;
-        const int includePeaks = 0x0 | 0x2; // i.e. just from newFootprints
-
-        // XXX EAM : still not sure I understand this: are we double-couning or undercounting peaks
+        const int includePeaks = 0x1 | 0x2; // i.e. both new and old footprints
 
         psArray *mergedFootprints = pmFootprintArraysMerge(detections->footprints, footprints, includePeaks);
@@ -50,4 +48,6 @@
         psFree(footprints);
         psFree(detections->footprints);
+
+	// replace the merged footprints on the detection structure
         detections->footprints = mergedFootprints;
     } else {
@@ -61,2 +61,17 @@
     return detections;
 }
+
+bool psphotCheckFootprints (pmDetections *detections) {
+
+    // check for messed up footprints in the old peaks
+    for (int i = 0; i < detections->oldPeaks->n; i++) {
+	pmPeak *peak = detections->oldPeaks->data[i];
+	pmFootprint *footprint = peak->footprint;
+	if (!footprint) continue;
+	for (int j = 0; j < footprint->spans->n; j++) {
+	    pmSpan *sp = footprint->spans->data[j];
+	    psAssert (sp, "missing span");
+	}
+    }
+    return true;
+}
Index: trunk/psphot/src/psphotFindPeaks.c
===================================================================
--- trunk/psphot/src/psphotFindPeaks.c	(revision 26618)
+++ trunk/psphot/src/psphotFindPeaks.c	(revision 26894)
@@ -13,10 +13,15 @@
     psArray *peaks = pmPeaksInImage (significance, threshold);
     if (peaks == NULL) {
-	// XXX should we be sending back an empty array instead of NULL?
-        // XXX this may also be due to a programming or config error
+	// we only get a NULL peaks array due to a programming or config error. 
+	// this will result in a failure.
+        psError(PSPHOT_ERR_DATA, false, "no peaks found in this image");
+        return NULL;
+    }
+
+    if (peaks->n == 0) {
         // XXX do we need to set something in the readout->analysis to indicate that
         // we tried and failed to find peaks (something in the header data)
-        psError(PSPHOT_ERR_DATA, false, "no peaks found in this image");
-        return false;
+	psLogMsg ("psphot", PS_LOG_INFO, "no peaks found in this image");
+	return peaks;
     }
 
Index: trunk/psphot/src/psphotFitSourcesLinear.c
===================================================================
--- trunk/psphot/src/psphotFitSourcesLinear.c	(revision 26618)
+++ trunk/psphot/src/psphotFitSourcesLinear.c	(revision 26894)
@@ -12,5 +12,23 @@
 static bool SetBorderMatrixElements (psSparseBorder *border, pmReadout *readout, psArray *sources, bool constant_weights, int SKY_FIT_ORDER, psImageMaskType markVal);
 
-bool psphotFitSourcesLinear (pmReadout *readout, psArray *sources, const psMetadata *recipe, const pmPSF *psf, bool final) {
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotFitSourcesLinear (pmConfig *config, const pmFPAview *view, bool final)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotFitSourcesLinearReadout (config, view, "PSPHOT.INPUT", i, final)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to fit sources (linear) for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotFitSourcesLinearReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, bool final) {
 
     bool status;
@@ -19,4 +37,29 @@
     float f;
     // float r;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping linear fit");
+	return true;
+    }
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (sources, "missing psf?");
 
     psTimerStart ("psphot.linear");
@@ -248,4 +291,8 @@
     psphotVisualPlotChisq (sources);
     // psphotVisualShowFlags (sources);
+
+    // We have to place this visualization here because the models are not realized until
+    // psphotGuessModels or fitted until psphotFitSourcesLinear.
+    psphotVisualShowPSFStars (recipe, psf, sources);
 
     return true;
Index: trunk/psphot/src/psphotForcedReadout.c
===================================================================
--- trunk/psphot/src/psphotForcedReadout.c	(revision 26618)
+++ trunk/psphot/src/psphotForcedReadout.c	(revision 26894)
@@ -20,12 +20,8 @@
 
     // set the photcode for this image
-    if (!psphotAddPhotcode (recipe, config, view, "PSPHOT.INPUT")) {
+    if (!psphotAddPhotcode (config, view)) {
         psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
         return false;
     }
-
-    // find the currently selected readout
-    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
-    PS_ASSERT_PTR_NON_NULL (readout, false);
 
     // optional break-point for processing
@@ -34,42 +30,34 @@
 
     // Generate the mask and weight images, including the user-defined analysis region of interest
-    psphotSetMaskAndVariance (config, readout, recipe);
+    psphotSetMaskAndVariance (config, view);
     if (!strcasecmp (breakPt, "NOTHING")) {
-        return psphotReadoutCleanup(config, readout, recipe, NULL, NULL, NULL);
+        return psphotReadoutCleanup(config, view);
     }
 
-    // display the image, weight, mask (ch 1,2,3)
-    psphotVisualShowImage (readout);
-
     // generate a background model (median, smoothed image)
-    if (!psphotModelBackground (config, view, "PSPHOT.INPUT")) {
-        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    if (!psphotModelBackground (config, view)) {
+        return psphotReadoutCleanup (config, view);
     }
-    if (!psphotSubtractBackground (config, view, "PSPHOT.INPUT")) {
-        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    if (!psphotSubtractBackground (config, view)) {
+        return psphotReadoutCleanup (config, view);
     }
     if (!strcasecmp (breakPt, "BACKMDL")) {
-        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+        return psphotReadoutCleanup (config, view);
     }
 
-    // display the backsub and backgnd images
-    psphotVisualShowBackground (config, view, readout);
-
-    // load the psf model, if suppled.  FWHM_X,FWHM_Y,etc are saved in the recipe
-    pmPSF *psf = psphotLoadPSF (config, view, recipe);
-    if (!psf) {
-        psError(PSPHOT_ERR_CONFIG, false, "unable to load psf model");
-        return false;
+    if (!psphotLoadPSF (config, view)) {
+    	// this only happens if we had a programming error in psphotLoadPSF
+        psError (PSPHOT_ERR_UNKNOWN, false, "error loading psf model");
+        return psphotReadoutCleanup (config, view);
     }
 
     // include externally-supplied sources
-    psArray *sources = psArrayAllocEmpty(100);
-    psphotLoadExtSources (config, view, sources);
+    psphotLoadExtSources (config, view);
 
     // construct an initial model for each object, set the radius to fitRadius, set circular fit mask
-    psphotGuessModels (config, readout, sources, psf);
+    psphotGuessModels (config, view);
 
     // linear PSF fit to source peaks, subtract the models from the image (in PSF mask)
-    psphotFitSourcesLinear (readout, sources, recipe, psf, FALSE);
+    psphotFitSourcesLinear (config, view, false);
 
     // identify CRs and extended sources
@@ -79,5 +67,5 @@
 
     // calculate source magnitudes
-    psphotMagnitudes(config, readout, view, sources, psf);
+    psphotMagnitudes(config, view);
 
     // XXX do I want to do this?
@@ -91,7 +79,7 @@
 
     // drop the references to the image pixels held by each source
-    psphotSourceFreePixels (sources);
+    psphotSourceFreePixels (config, view);
 
     // create the exported-metadata and free local data
-    return psphotReadoutCleanup(config, readout, recipe, NULL, psf, sources);
+    return psphotReadoutCleanup(config, view);
 }
Index: trunk/psphot/src/psphotGuessModels.c
===================================================================
--- trunk/psphot/src/psphotGuessModels.c	(revision 26618)
+++ trunk/psphot/src/psphotGuessModels.c	(revision 26894)
@@ -3,14 +3,53 @@
 // XXX : the threading here is not great.  this may be due to blocks between elements, but
 // the selection of the objects in a cell is not optimal.  To fix:
-// 1) define the boundaries of the cells up front 
+// 1) define the boundaries of the cells up front
 // 2) loop over the sources once and associate them with their cell
 // 3) define the threaded function to work with sources for a given cell
 
-// construct an initial PSF model for each object
-bool psphotGuessModels (pmConfig *config, pmReadout *readout, psArray *sources, pmPSF *psf) {
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotGuessModels (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (!psphotGuessModelsReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on to guess models for PSPHOT.INPUT entry %d", i);
+            return false;
+        }
+    }
+    return true;
+}
+
+// construct an initial PSF model for each object (new sources only)
+bool psphotGuessModelsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index) {
 
     bool status;
 
     psTimerStart ("psphot.models");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping model guess");
+        return true;
+    }
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (psf, "missing psf?");
 
     // select the appropriate recipe information
@@ -21,5 +60,5 @@
     int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
     if (!status) {
-	nThreads = 0;
+        nThreads = 0;
     }
 
@@ -36,5 +75,5 @@
 
     // setup the PSF fit radius details
-    psphotInitRadiusPSF (recipe, psf->type);
+    psphotInitRadiusPSF (recipe, readout->analysis, psf->type);
 
     // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
@@ -46,43 +85,43 @@
     for (int i = 0; i < cellGroups->n; i++) {
 
-	psArray *cells = cellGroups->data[i];
-
-	for (int j = 0; j < cells->n; j++) {
-
-	    // allocate a job -- if threads are not defined, this just runs the job
-	    psThreadJob *job = psThreadJobAlloc ("PSPHOT_GUESS_MODEL");
-	    psArrayAdd(job->args, 1, readout);
-	    psArrayAdd(job->args, 1, cells->data[j]); // sources
-	    psArrayAdd(job->args, 1, psf);
-
-	    // XXX change these to use abstract mask type info
-	    PS_ARRAY_ADD_SCALAR(job->args, maskVal,  PS_TYPE_IMAGE_MASK);
-	    PS_ARRAY_ADD_SCALAR(job->args, markVal,  PS_TYPE_IMAGE_MASK);
-
-	    if (!psThreadJobAddPending(job)) {
-		psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
-		psFree (job);
-		return false;
-	    }
-	    psFree(job);
-	}
-
-	// wait for the threads to finish and manage results
-	// wait here for the threaded jobs to finish
-	// fprintf (stderr, "wait for threads (%d, %d)\n", jx, jy);
-	if (!psThreadPoolWait (false)) {
-	    psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
-	    return false;
-	}
-
-	// we have only supplied one type of job, so we can assume the types here
-	psThreadJob *job = NULL;
-	while ((job = psThreadJobGetDone()) != NULL) {
-	    // we have no returned data from this operation
-	    if (job->args->n < 1) {
-		fprintf (stderr, "error with job\n");
-	    }
-	    psFree(job);
-	}
+        psArray *cells = cellGroups->data[i];
+
+        for (int j = 0; j < cells->n; j++) {
+
+            // allocate a job -- if threads are not defined, this just runs the job
+            psThreadJob *job = psThreadJobAlloc ("PSPHOT_GUESS_MODEL");
+            psArrayAdd(job->args, 1, readout);
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+            psArrayAdd(job->args, 1, psf);
+
+            // XXX change these to use abstract mask type info
+            PS_ARRAY_ADD_SCALAR(job->args, maskVal,  PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, markVal,  PS_TYPE_IMAGE_MASK);
+
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+                psFree (job);
+                return false;
+            }
+            psFree(job);
+        }
+
+        // wait for the threads to finish and manage results
+        // wait here for the threaded jobs to finish
+        // fprintf (stderr, "wait for threads (%d, %d)\n", jx, jy);
+        if (!psThreadPoolWait (false)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+            return false;
+        }
+
+        // we have only supplied one type of job, so we can assume the types here
+        psThreadJob *job = NULL;
+        while ((job = psThreadJobGetDone()) != NULL) {
+            // we have no returned data from this operation
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            }
+            psFree(job);
+        }
     }
 
@@ -90,9 +129,9 @@
     int nMiss = 0;
     for (int i = 0; i < sources->n; i++) {
-	pmSource *source = sources->data[i];
-	if (source->tmpFlags & PM_SOURCE_TMPF_MODEL_GUESS) {
-	    continue;
-	}
-	nMiss ++;
+        pmSource *source = sources->data[i];
+        if (source->tmpFlags & PM_SOURCE_TMPF_MODEL_GUESS) {
+            continue;
+        }
+        nMiss ++;
     }
     psAssert (nMiss == 0, "failed to attempt to build models for %d objects\n", nMiss);
@@ -110,5 +149,5 @@
     psArray *sources   = job->args->data[1];
     pmPSF *psf         = job->args->data[2];
-    
+
     psImageMaskType maskVal = PS_SCALAR_VALUE(job->args->data[3],PS_TYPE_IMAGE_MASK_DATA);
     psImageMaskType markVal = PS_SCALAR_VALUE(job->args->data[4],PS_TYPE_IMAGE_MASK_DATA);
@@ -117,69 +156,69 @@
 
     for (int i = 0; i < sources->n; i++) {
-	pmSource *source = sources->data[i];
-
-	// this is used to mark sources for which the model is measured. We check later that
-	// all are used.
-	source->tmpFlags |= PM_SOURCE_TMPF_MODEL_GUESS;
-
-	// skip non-astronomical objects (very likely defects)
-	if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
-	if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
-	if (!source->peak) continue;
-
-	nSrc ++;
-	
-	// the guess central intensity comes from the peak:
-	float Io = source->peak->flux;
-
-	// We have two options to get a guess for the object position: the position from the
-	// peak and the position from the moments.  Use the peak position if (a) there are no
-	// moments and (b) the sources is not saturated
-
-	bool useMoments = false;
-	useMoments = (source->mode & PM_SOURCE_MODE_SATSTAR);  // we only want to try if SATSTAR is set, but..
-	useMoments = (useMoments && source->moments);	       // can't if there are no moments
-	useMoments = (useMoments && source->moments->nPixels); // can't if the moments were not measured
-	useMoments = (useMoments && !(source->mode && PM_SOURCE_MODE_MOMENTS_FAILURE)); // can't if the moments failed...
-
-	float Xo, Yo;
-	if (useMoments) {
-	    Xo = source->moments->Mx;
-	    Yo = source->moments->My;
-	} else {
-	    Xo = source->peak->xf;
-	    Yo = source->peak->yf;
-	}
-
-	// set PSF parameters for this model (apply 2D shape model to coordinates Xo, Yo)
-	pmModel *modelPSF = pmModelFromPSFforXY(psf, Xo, Yo, Io);
-
-	if (modelPSF == NULL) {
-	    psWarning ("Failed to determine PSF model at (%f,%f); trying image center", Xo, Yo);
-
-	    float Xc = 0.5*readout->image->numCols;
-	    float Yc = 0.5*readout->image->numRows;
-	    pmModel *modelPSF = pmModelFromPSFforXY(psf, Xc, Yc, Io);
-	    if (modelPSF == NULL) {
-		psError(PSPHOT_ERR_PSF, false, "Failed to determine PSF model at center of image");
-		return false;
-	    }
-
-	    // Now set the object position at the expected location:
-	    modelPSF->params->data.F32[PM_PAR_XPOS] = Xo;
-	    modelPSF->params->data.F32[PM_PAR_YPOS] = Yo;
-	    source->mode |= PM_SOURCE_MODE_BADPSF;
-	}
-
-	// set the fit radius based on the object flux limit and the model
-	// this function affects the mask pixels
-	psphotCheckRadiusPSF (readout, source, modelPSF, markVal);
-
-	// set the source PSF model
-	psAssert (source->modelPSF == NULL, "failed to free one of the models?");
-	source->modelPSF = modelPSF;
-	source->modelPSF->residuals = psf->residuals;
-
-	pmSourceCacheModel (source, maskVal);  // ALLOC x14 (!)
+        pmSource *source = sources->data[i];
+
+        // this is used to mark sources for which the model is measured. We check later that
+        // all are used.
+        source->tmpFlags |= PM_SOURCE_TMPF_MODEL_GUESS;
+
+        // skip non-astronomical objects (very likely defects)
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+        if (!source->peak) continue;
+
+        nSrc ++;
+
+        // the guess central intensity comes from the peak:
+        float Io = source->peak->flux;
+
+        // We have two options to get a guess for the object position: the position from the
+        // peak and the position from the moments.  Use the peak position if (a) there are no
+        // moments and (b) the sources is not saturated
+
+        bool useMoments = false;
+        useMoments = (source->mode & PM_SOURCE_MODE_SATSTAR);  // we only want to try if SATSTAR is set, but..
+        useMoments = (useMoments && source->moments);          // can't if there are no moments
+        useMoments = (useMoments && source->moments->nPixels); // can't if the moments were not measured
+        useMoments = (useMoments && !(source->mode && PM_SOURCE_MODE_MOMENTS_FAILURE)); // can't if the moments failed...
+
+        float Xo, Yo;
+        if (useMoments) {
+            Xo = source->moments->Mx;
+            Yo = source->moments->My;
+        } else {
+            Xo = source->peak->xf;
+            Yo = source->peak->yf;
+        }
+
+        // set PSF parameters for this model (apply 2D shape model to coordinates Xo, Yo)
+        pmModel *modelPSF = pmModelFromPSFforXY(psf, Xo, Yo, Io);
+
+        if (modelPSF == NULL) {
+            psWarning ("Failed to determine PSF model at (%f,%f); trying image center", Xo, Yo);
+
+            float Xc = 0.5*readout->image->numCols;
+            float Yc = 0.5*readout->image->numRows;
+            pmModel *modelPSF = pmModelFromPSFforXY(psf, Xc, Yc, Io);
+            if (modelPSF == NULL) {
+                psError(PSPHOT_ERR_PSF, false, "Failed to determine PSF model at center of image");
+                return false;
+            }
+
+            // Now set the object position at the expected location:
+            modelPSF->params->data.F32[PM_PAR_XPOS] = Xo;
+            modelPSF->params->data.F32[PM_PAR_YPOS] = Yo;
+            source->mode |= PM_SOURCE_MODE_BADPSF;
+        }
+
+        // set the fit radius based on the object flux limit and the model
+        // this function affects the mask pixels
+        psphotCheckRadiusPSF (readout, source, modelPSF, markVal);
+
+        // set the source PSF model
+        psAssert (source->modelPSF == NULL, "failed to free one of the models?");
+        source->modelPSF = modelPSF;
+        source->modelPSF->residuals = psf->residuals;
+
+        pmSourceCacheModel (source, maskVal);  // ALLOC x14 (!)
     }
 
Index: trunk/psphot/src/psphotImageQuality.c
===================================================================
--- trunk/psphot/src/psphotImageQuality.c	(revision 26618)
+++ trunk/psphot/src/psphotImageQuality.c	(revision 26894)
@@ -4,7 +4,54 @@
 // XXX Lots of code duplication here
 
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotImageQuality (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotImageQualityReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on to measure image quality for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
 // selecting the 'good' stars (likely to be psf stars), measure the M_cn, M_sn terms for n = 2,3,4
-bool psphotImageQuality(psMetadata *recipe, psArray *sources)
+bool psphotImageQualityReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe)
 {
+    bool status = true;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // if we have a PSF, skip this analysis
+    if (psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF")) {
+	return true;
+    }
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping image quality");
+	return true;
+    }
+
     psVector *FWHM_MAJOR = psVectorAllocEmpty(100, PS_TYPE_F32);
     psVector *FWHM_MINOR = psVectorAllocEmpty(100, PS_TYPE_F32);
@@ -81,10 +128,9 @@
 
     if (num == 0) {
-      psWarning("Unable to find sources from which to measure image quality");
-      return false;
-    }
-
-    psMetadataAddS32(recipe, PS_LIST_TAIL, "IQ_NSTAR", PS_META_REPLACE,
-                     "Number of stars used for IQ measurements", M2->n);
+	psLogMsg ("psphot", PS_LOG_INFO, "no valid sources for image quality, skipping");
+	return true;
+    }
+
+    psMetadataAddS32(readout->analysis, PS_LIST_TAIL, "IQ_NSTAR", PS_META_REPLACE, "Number of stars used for IQ measurements", M2->n);
 
 // XXX make this a recipe option
@@ -98,8 +144,6 @@
 
     float fwhm_major = stats->sampleMean;
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_FW1",   PS_META_REPLACE,
-                     "FWHM of Major Axis from moments", stats->sampleMean);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_FW1_E", PS_META_REPLACE,
-                     "FWHM scatter (Major) from moments", stats->sampleStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW1",   PS_META_REPLACE, "FWHM of Major Axis from moments", stats->sampleMean);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW1_E", PS_META_REPLACE, "FWHM scatter (Major) from moments", stats->sampleStdev);
 
     if (!psVectorStats(stats, FWHM_MINOR, NULL, NULL, 0)) {
@@ -108,8 +152,6 @@
     }
     float fwhm_minor = stats->sampleMean;
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_FW2",   PS_META_REPLACE,
-                     "FWHM of Minor Axis from moments", stats->sampleMean);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_FW2_E", PS_META_REPLACE,
-                     "FWHM scatter (Minor) from moments", stats->sampleStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW2",   PS_META_REPLACE, "FWHM of Minor Axis from moments", stats->sampleMean);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW2_E", PS_META_REPLACE, "FWHM scatter (Minor) from moments", stats->sampleStdev);
 
     if (!psVectorStats(stats, M2, NULL, NULL, 0)) {
@@ -119,12 +161,8 @@
     float vM2 = stats->sampleMean;
     float dM2 = stats->sampleStdev;
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2",    PS_META_REPLACE,
-                     "M_2 = sqrt (M_c2^2 + M_s2^2)", vM2);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2_ER", PS_META_REPLACE,
-                     "Stdev of M_2", dM2);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2_LQ", PS_META_REPLACE,
-                     "Lower Quartile of M_2", stats->sampleLQ);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2_UQ", PS_META_REPLACE,
-                     "Upper Quartile of M_2", stats->sampleUQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2",    PS_META_REPLACE, "M_2 = sqrt (M_c2^2 + M_s2^2)", vM2);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2_ER", PS_META_REPLACE, "Stdev of M_2", dM2);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2_LQ", PS_META_REPLACE, "Lower Quartile of M_2", stats->sampleLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2_UQ", PS_META_REPLACE, "Upper Quartile of M_2", stats->sampleUQ);
 
     if (!psVectorStats(stats, M2c, NULL, NULL, 0)) {
@@ -132,12 +170,8 @@
         goto FAIL;
     }
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2C",   PS_META_REPLACE,
-                     "M_2c = sum f r^2 cos(2phi) / sum f", stats->sampleMean);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2C_E", PS_META_REPLACE,
-                     "Stdev of M_2c", stats->sampleStdev);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2C_L", PS_META_REPLACE,
-                     "Lower Quartile of M_2c", stats->sampleLQ);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2C_U", PS_META_REPLACE,
-                     "Upper Quartile of M_2c", stats->sampleUQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C",   PS_META_REPLACE, "M_2c = sum f r^2 cos(2phi) / sum f", stats->sampleMean);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C_E", PS_META_REPLACE, "Stdev of M_2c", stats->sampleStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C_L", PS_META_REPLACE, "Lower Quartile of M_2c", stats->sampleLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C_U", PS_META_REPLACE, "Upper Quartile of M_2c", stats->sampleUQ);
 
     if (!psVectorStats(stats, M2s, NULL, NULL, 0)) {
@@ -145,12 +179,8 @@
         goto FAIL;
     }
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2S",   PS_META_REPLACE,
-                     "M_2s = sum f r^2 cos(2phi) / sum f", stats->sampleMean);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2S_E", PS_META_REPLACE,
-                     "Stdev of M_2s", stats->sampleStdev);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2S_L", PS_META_REPLACE,
-                     "Lower Quartile of M_2s", stats->sampleLQ);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2S_U", PS_META_REPLACE,
-                     "Upper Quartile of M_2s", stats->sampleUQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S",   PS_META_REPLACE, "M_2s = sum f r^2 cos(2phi) / sum f", stats->sampleMean);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S_E", PS_META_REPLACE, "Stdev of M_2s", stats->sampleStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S_L", PS_META_REPLACE, "Lower Quartile of M_2s", stats->sampleLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S_U", PS_META_REPLACE, "Upper Quartile of M_2s", stats->sampleUQ);
 
     if (!psVectorStats(stats, M3, NULL, NULL, 0)) {
@@ -160,12 +190,8 @@
     float vM3 = stats->sampleMean;
     float dM3 = stats->sampleStdev;
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M3",   PS_META_REPLACE,
-                     "M_3 = sqrt (M_c3^2 + M_s3^2)", vM3);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M3_ER", PS_META_REPLACE,
-                     "Stdev of M_3", dM3);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M3_LQ", PS_META_REPLACE,
-                     "Lower Quartile of M_3", stats->sampleLQ);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M3_UQ", PS_META_REPLACE,
-                     "Upper Quartile of M_3", stats->sampleUQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3",   PS_META_REPLACE, "M_3 = sqrt (M_c3^2 + M_s3^2)", vM3);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3_ER", PS_META_REPLACE, "Stdev of M_3", dM3);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3_LQ", PS_META_REPLACE, "Lower Quartile of M_3", stats->sampleLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3_UQ", PS_META_REPLACE, "Upper Quartile of M_3", stats->sampleUQ);
 
     if (!psVectorStats(stats, M4, NULL, NULL, 0)) {
@@ -175,12 +201,8 @@
     float vM4 = stats->sampleMean;
     float dM4 = stats->sampleStdev;
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M4",   PS_META_REPLACE,
-                     "M_4 = sqrt (M_c4^2 + M_s4^2)", vM4);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M4_ER", PS_META_REPLACE,
-                     "Stdev of M_4", dM4);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M4_LQ", PS_META_REPLACE,
-                     "Lower Quartile of M_4", stats->sampleLQ);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M4_UQ", PS_META_REPLACE,
-                     "Upper Quartile of M_4", stats->sampleUQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4",   PS_META_REPLACE, "M_4 = sqrt (M_c4^2 + M_s4^2)", vM4);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4_ER", PS_META_REPLACE, "Stdev of M_4", dM4);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4_LQ", PS_META_REPLACE, "Lower Quartile of M_4", stats->sampleLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4_UQ", PS_META_REPLACE, "Upper Quartile of M_4", stats->sampleUQ);
 
 #else
@@ -192,8 +214,6 @@
     }
     float fwhm_major = stats->robustMedian;
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_FW1",   PS_META_REPLACE,
-                     "FWHM of Major Axis from moments", stats->robustMedian);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_FW1_E", PS_META_REPLACE,
-                     "FWHM scatter (Major) from moments", stats->robustStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW1",   PS_META_REPLACE, "FWHM of Major Axis from moments", stats->robustMedian);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW1_E", PS_META_REPLACE, "FWHM scatter (Major) from moments", stats->robustStdev);
 
     if (!psVectorStats(stats, FWHM_MINOR, NULL, NULL, 0)) {
@@ -202,8 +222,6 @@
     }
     float fwhm_minor = stats->robustMedian;
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_FW2",   PS_META_REPLACE,
-                     "FWHM of Minor Axis from moments", stats->robustMedian);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_FW2_E", PS_META_REPLACE,
-                     "FWHM scatter (Minor) from moments", stats->robustStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW2",   PS_META_REPLACE, "FWHM of Minor Axis from moments", stats->robustMedian);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW2_E", PS_META_REPLACE, "FWHM scatter (Minor) from moments", stats->robustStdev);
 
     if (!psVectorStats(stats, M2, NULL, NULL, 0)) {
@@ -213,12 +231,8 @@
     float vM2 = stats->robustMedian;
     float dM2 = stats->robustStdev;
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2",    PS_META_REPLACE,
-                     "M_2 = sqrt (M_c2^2 + M_s2^2)", vM2);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2_ER", PS_META_REPLACE,
-                     "Stdev of M_2", dM2);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2_LQ", PS_META_REPLACE,
-                     "Lower Quartile of M_2", stats->robustLQ);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2_UQ", PS_META_REPLACE,
-                     "Upper Quartile of M_2", stats->robustUQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2",    PS_META_REPLACE, "M_2 = sqrt (M_c2^2 + M_s2^2)", vM2);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2_ER", PS_META_REPLACE, "Stdev of M_2", dM2);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2_LQ", PS_META_REPLACE, "Lower Quartile of M_2", stats->robustLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2_UQ", PS_META_REPLACE, "Upper Quartile of M_2", stats->robustUQ);
 
     if (!psVectorStats(stats, M2c, NULL, NULL, 0)) {
@@ -226,12 +240,8 @@
         goto FAIL;
     }
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2C",   PS_META_REPLACE,
-                     "M_2c = sum f r^2 cos(2phi) / sum f", stats->robustMedian);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2C_E", PS_META_REPLACE,
-                     "Stdev of M_2c", stats->robustStdev);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2C_L", PS_META_REPLACE,
-                     "Lower Quartile of M_2c", stats->robustLQ);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2C_U", PS_META_REPLACE,
-                     "Upper Quartile of M_2c", stats->robustUQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C",   PS_META_REPLACE, "M_2c = sum f r^2 cos(2phi) / sum f", stats->robustMedian);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C_E", PS_META_REPLACE, "Stdev of M_2c", stats->robustStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C_L", PS_META_REPLACE, "Lower Quartile of M_2c", stats->robustLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C_U", PS_META_REPLACE, "Upper Quartile of M_2c", stats->robustUQ);
 
     if (!psVectorStats(stats, M2s, NULL, NULL, 0)) {
@@ -239,12 +249,8 @@
         goto FAIL;
     }
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2S",   PS_META_REPLACE,
-                     "M_2s = sum f r^2 cos(2phi) / sum f", stats->robustMedian);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2S_E", PS_META_REPLACE,
-                     "Stdev of M_2s", stats->robustStdev);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2S_L", PS_META_REPLACE,
-                     "Lower Quartile of M_2s", stats->robustLQ);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M2S_U", PS_META_REPLACE,
-                     "Upper Quartile of M_2s", stats->robustUQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S",   PS_META_REPLACE, "M_2s = sum f r^2 cos(2phi) / sum f", stats->robustMedian);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S_E", PS_META_REPLACE, "Stdev of M_2s", stats->robustStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S_L", PS_META_REPLACE, "Lower Quartile of M_2s", stats->robustLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S_U", PS_META_REPLACE, "Upper Quartile of M_2s", stats->robustUQ);
 
     if (!psVectorStats(stats, M3, NULL, NULL, 0)) {
@@ -254,12 +260,8 @@
     float vM3 = stats->robustMedian;
     float dM3 = stats->robustStdev;
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M3",   PS_META_REPLACE,
-                     "M_3 = sqrt (M_c3^2 + M_s3^2)", vM3);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M3_ER", PS_META_REPLACE,
-                     "Stdev of M_3", dM3);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M3_LQ", PS_META_REPLACE,
-                     "Lower Quartile of M_3", stats->robustLQ);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M3_UQ", PS_META_REPLACE,
-                     "Upper Quartile of M_3", stats->robustUQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3",   PS_META_REPLACE, "M_3 = sqrt (M_c3^2 + M_s3^2)", vM3);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3_ER", PS_META_REPLACE, "Stdev of M_3", dM3);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3_LQ", PS_META_REPLACE, "Lower Quartile of M_3", stats->robustLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3_UQ", PS_META_REPLACE, "Upper Quartile of M_3", stats->robustUQ);
 
     if (!psVectorStats(stats, M4, NULL, NULL, 0)) {
@@ -269,12 +271,8 @@
     float vM4 = stats->robustMedian;
     float dM4 = stats->robustStdev;
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M4",   PS_META_REPLACE,
-                     "M_4 = sqrt (M_c4^2 + M_s4^2)", vM4);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M4_ER", PS_META_REPLACE,
-                     "Stdev of M_4", dM4);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M4_LQ", PS_META_REPLACE,
-                     "Lower Quartile of M_4", stats->robustLQ);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "IQ_M4_UQ", PS_META_REPLACE,
-                     "Upper Quartile of M_4", stats->robustUQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4",   PS_META_REPLACE, "M_4 = sqrt (M_c4^2 + M_s4^2)", vM4);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4_ER", PS_META_REPLACE, "Stdev of M_4", dM4);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4_LQ", PS_META_REPLACE, "Lower Quartile of M_4", stats->robustLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4_UQ", PS_META_REPLACE, "Upper Quartile of M_4", stats->robustUQ);
 #endif
 
Index: trunk/psphot/src/psphotLoadPSF.c
===================================================================
--- trunk/psphot/src/psphotLoadPSF.c	(revision 26618)
+++ trunk/psphot/src/psphotLoadPSF.c	(revision 26894)
@@ -1,28 +1,79 @@
 # include "psphotInternal.h"
 
+// NOTE : pmPSF_IO.c functions must load the psf model onto the chip->analysis metadata because
+// the I/O operation likely occurs before the readout exists.  This implementation assumes that
+// a single psf model is valid for the entire set of readouts (not valid for a time series of readouts)
+
+// XXX for now (2010.01.27), the supporting programs do not define multiple PSPHOT.PSF.LOAD
+// files to go with multiple PSPHOT.INPUT files.  as a result, the implementation below is
+// currently going to work for the case of a single input file, but will fail if we try with a
+// stack of images.
+
 // load an externally supplied psf model
-pmPSF *psphotLoadPSF (pmConfig *config, const pmFPAview *view, psMetadata *recipe) {
+bool psphotLoadPSFReadout (pmConfig *config, const pmFPAview *view, const char *outFilename, const char *inFilename, int index) {
+
+    bool status;
+
+    // find the currently selected readout
+    pmFPAfile *inFile = pmFPAfileSelectSingle(config->files, inFilename, index); // File of interest
+    if (inFile == NULL) {
+        psLogMsg ("psphot", 3, "no psf supplied for this chip");
+        return true;
+    }
+
+    // find the currently selected readout
+    pmFPAfile *outFile = pmFPAfileSelectSingle(config->files, outFilename, index); // File of interest
+    if (outFile == NULL) {
+        psLogMsg ("psphot", 3, "no psf supplied for this chip");
+        return true;
+    }
 
     // find the currently selected chip
-    pmChip *chip = pmFPAfileThisChip (config->files, view, "PSPHOT.PSF.LOAD");
-    if (!chip) return NULL;
+    pmChip *chip = pmFPAviewThisChip (view, inFile->fpa);
+    if (!chip) return false;
 
     // find the currently selected readout
-    pmReadout *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.PSF.LOAD");
-    if (!readout) return NULL;
+    pmReadout *readout = pmFPAviewThisReadout (view, outFile->fpa);
+    if (!readout) return false;
 
     // check if a PSF model is supplied by the user
-    pmPSF *psf = psMetadataLookupPtr (NULL, chip->analysis, "PSPHOT.PSF");
+    pmPSF *psf = psMetadataLookupPtr (&status, chip->analysis, "PSPHOT.PSF");
     if (psf == NULL) {
         psLogMsg ("psphot", 3, "no psf supplied for this chip");
-        return NULL;
+        return true;
     }
 
-    if (!psphotPSFstats (readout, recipe, psf)) psAbort("cannot measure PSF shape terms");
+    if (!psphotPSFstats (readout, psf)) {
+        psAbort("cannot measure PSF shape terms");
+    }
 
     psLogMsg ("psphot", 3, "using externally supplied PSF model for this readout");
 
-    // we return a psf which can be free (and which may be removed from the metadata)
-    psMemIncrRefCounter (psf);
-    return psf;
+    // save PSF on readout->analysis
+    if (!psMetadataAddPtr (readout->analysis, PS_LIST_TAIL, "PSPHOT.PSF", PS_META_REPLACE | PS_DATA_UNKNOWN, "psphot psf model", psf)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "problem saving sources on readout");
+        return false;
+    }
+
+    return true;
 }
+
+bool psphotLoadPSF (pmConfig *config, const pmFPAview *view) {
+
+    bool status = false;
+
+    // XXX PSPHOT.PSF.LOAD vs PSPHOT.INPUT -- see note at top
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+
+        // Generate the mask and weight images, including the user-defined analysis region of interest
+        if (!psphotLoadPSFReadout (config, view, "PSPHOT.INPUT", "PSPHOT.PSF.LOAD", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to load PSF model for PSPHOT.PSF.LOAD entry %d", i);
+            return false;
+        }
+    }
+    return true;
+}
Index: trunk/psphot/src/psphotMagnitudes.c
===================================================================
--- trunk/psphot/src/psphotMagnitudes.c	(revision 26618)
+++ trunk/psphot/src/psphotMagnitudes.c	(revision 26894)
@@ -1,5 +1,26 @@
 # include "psphotInternal.h"
 
-bool psphotMagnitudes(pmConfig *config, pmReadout *readout, const pmFPAview *view, psArray *sources, const pmPSF *psf) {
+bool psphotMagnitudes (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotMagnitudesReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to measure magnitudes for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotMagnitudesReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
 
     bool status = false;
@@ -8,7 +29,24 @@
     psTimerStart ("psphot.mags");
 
-    // select the appropriate recipe information
-    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
-    assert (recipe);
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping source size");
+	return true;
+    }
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (psf, "missing psf?");
 
     // determine the number of allowed threads
@@ -64,5 +102,5 @@
 
             psArrayAdd(job->args, 1, cells->data[j]); // sources
-            psArrayAdd(job->args, 1, (pmPSF*)psf);    // Casting away const
+            psArrayAdd(job->args, 1, psf);
             psArrayAdd(job->args, 1, binning);
             psArrayAdd(job->args, 1, backModel);
@@ -179,43 +217,4 @@
 }
 
-# if (0)
-bool psphotMagnitudes_Unthreaded (int *nap, psArray *sources, pmPSF *psf, psImageBinning *binning, pmReadout *backModel, pmReadout *backStdev, pmSourcePhotometryMode photMode, psImageMaskType maskVal) {
-
-    bool status;
-    int Nap = 0;
-
-    for (int i = 0; i < sources->n; i++) {
-        pmSource *source = (pmSource *) sources->data[i];
-        status = pmSourceMagnitudes (source, psf, photMode, maskVal);
-        if (status && isfinite(source->apMag)) Nap ++;
-
-        if (backModel) {
-            psAssert (binning, "if backModel is defined, so should binning be");
-            source->sky = psImageUnbinPixel(source->peak->x, source->peak->y, backModel->image, binning);
-            if (isnan(source->sky) && false) {
-                psLogMsg ("psphot.magnitudes", PS_LOG_DETAIL, "error setting pmSource.sky");
-            }
-        } else {
-            source->sky = NAN;
-        }
-
-        if (backStdev) {
-            psAssert (binning, "if backStdev is defined, so should binning be");
-            source->skyErr = psImageUnbinPixel(source->peak->x, source->peak->y, backStdev->image, binning);
-            if (isnan(source->skyErr) && false) {
-                psLogMsg ("psphot.magnitudes", PS_LOG_DETAIL, "error setting pmSource.skyErr");
-            }
-        } else {
-            source->skyErr = NAN;
-        }
-    }
-
-    // change the value of a scalar on the array (wrap this and put it in psArray.h)
-    *nap = Nap;
-
-    return true;
-}
-# endif
-
 bool psphotPSFWeights(pmConfig *config, pmReadout *readout, const pmFPAview *view, psArray *sources) {
 
Index: trunk/psphot/src/psphotMakePSFReadout.c
===================================================================
--- trunk/psphot/src/psphotMakePSFReadout.c	(revision 26618)
+++ trunk/psphot/src/psphotMakePSFReadout.c	(revision 26894)
@@ -19,12 +19,8 @@
 
     // set the photcode for this image
-    if (!psphotAddPhotcode (recipe, config, view, "PSPHOT.INPUT")) {
+    if (!psphotAddPhotcode (config, view)) {
         psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
         return false;
     }
-
-    // find the currently selected readout
-    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
-    PS_ASSERT_PTR_NON_NULL (readout, false);
 
     // optional break-point for processing
@@ -33,96 +29,48 @@
 
     // Generate the mask and weight images, including the user-defined analysis region of interest
-    psphotSetMaskAndVariance (config, readout, recipe);
+    psphotSetMaskAndVariance (config, view);
     if (!strcasecmp (breakPt, "NOTHING")) {
-        return psphotReadoutCleanup(config, readout, recipe, NULL, NULL, NULL);
+        return psphotReadoutCleanup(config, view);
     }
 
-    // display the image, weight, mask (ch 1,2,3)
-    psphotVisualShowImage (readout);
-
     // generate a background model (median, smoothed image)
-    if (!psphotModelBackground (config, view, "PSPHOT.INPUT")) {
-        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    if (!psphotModelBackground (config, view)) {
+        return psphotReadoutCleanup (config, view);
     }
-    if (!psphotSubtractBackground (config, view, "PSPHOT.INPUT")) {
-        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    if (!psphotSubtractBackground (config, view)) {
+        return psphotReadoutCleanup (config, view);
     }
     if (!strcasecmp (breakPt, "BACKMDL")) {
-        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+        return psphotReadoutCleanup (config, view);
     }
 
-    // display the backsub and backgnd images
-    psphotVisualShowBackground (config, view, readout);
+    psphotLoadExtSources (config, view);
 
-    pmDetections *detections = NULL;
-
-    // If sources have been supplied, then these should be used to measure the PSF
-    // include externally-supplied sources
-
-    // XXX sources loaded from a text file have no valid SN values, but psphotChoosePSF
-    // selected the top PSF_MAX_NSTARS to generate the PSF, excluding an arbitrary subset.
-    psArray *sources = psArrayAllocEmpty(100);
-    psphotLoadExtSources (config, view, sources);
-    if (sources->n) {
-	// the user wants to make the psf from these stars; define them as psf stars:
-	for (int i = 0; i < sources->n; i++) {
-	    pmSource *source = sources->data[i];
-	    source->mode |= PM_SOURCE_MODE_PSFSTAR;
-	}
-	// force psphotChoosePSF to use all loaded sources
-	psMetadataAddS32 (recipe, PS_LIST_TAIL, "PSF_MAX_NSTARS", PS_META_REPLACE, "fit radius", sources->n);
-
-	// measure stats of externally specified sources
-	if (!psphotSourceStatsUpdate (sources, config, readout)) {
-	    psError(PSPHOT_ERR_CONFIG, false, "failure to measure stats of existing sources");
-	    return false;
-	}
-
-    } else {
-	// find the detections (by peak and/or footprint) in the image.
-	detections = psphotFindDetections (NULL, readout, recipe);
-	if (!detections) {
-	    psLogMsg ("psphot", 3, "unable to find detections in this image");
-	    return psphotReadoutCleanup (config, readout, recipe, detections, NULL, sources);
-	}
-
-	// construct sources and measure basic stats
-	psFree (sources);
-	sources = psphotSourceStats (config, readout, detections, true);
-	if (!sources) return false;
-
-	// find blended neighbors of very saturated stars
-	// XXX merge this with Basic Deblend?
-	psphotDeblendSatstars (readout, sources, recipe);
-
-	// mark blended peaks PS_SOURCE_BLEND
-	if (!psphotBasicDeblend (sources, recipe)) {
-	    psLogMsg ("psphot", 3, "failed on deblend analysis");
-	    return psphotReadoutCleanup (config, readout, recipe, detections, NULL, sources);
-	}
-
-	// classify sources based on moments, brightness
-	if (!psphotRoughClass (readout, sources, recipe, false)) {
-	    psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
-	    return psphotReadoutCleanup (config, readout, recipe, detections, NULL, sources);
-	}
+    // If sources have been supplied, then these should be used to measure the PSF include
+    // externally-supplied sources; if not, we need to generate a set of possible PSF sources.
+    // This function updates the SN entries for the loaded sources or generates a set of
+    // detections from the image, if no external ones have been supplied.  Sources loaded from
+    // a text file have no valid SN values, but psphotChoosePSF needs to select the top
+    // PSF_MAX_NSTARS to generate the PSF.
+    if (!psphotCheckExtSources (config, view)) {
+	psLogMsg ("psphot", 3, "failure to select possible PSF sources (external or internal)");
+	return psphotReadoutCleanup (config, view);
     }
 
-    // use bright stellar objects to measure PSF
-    // XXX if we do not have enough stars to generate the PSF, build one
-    // from the SEEING guess and model class
-    pmPSF *psf = psphotChoosePSF (readout, sources, recipe);
-    if (psf == NULL) {
+    // Use bright stellar objects to measure PSF. If we do not have enough stars to generate
+    // the PSF, build one from the SEEING guess and model class
+    if (!psphotChoosePSF (config, view)) {
 	psLogMsg ("psphot", 3, "failure to construct a psf model");
-	return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+	return psphotReadoutCleanup (config, view);
     }
-    psphotVisualShowPSFModel (readout, psf);
-    return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
-}
 
     // measure aperture photometry corrections
-    // XXX isn't this part of the PSF??
-    // if (!psphotApResid (config, readout, sources, psf)) {
-    //     psLogMsg ("psphot", 3, "failed on psphotApResid");
-    //     return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
-    // }
+# if 0
+    if (!psphotApResid (config, view)) {
+        psLogMsg ("psphot", 3, "failed on psphotApResid");
+        return psphotReadoutCleanup (config, view);
+    }
+# endif
+
+    return psphotReadoutCleanup (config, view);
+}
Index: trunk/psphot/src/psphotMaskReadout.c
===================================================================
--- trunk/psphot/src/psphotMaskReadout.c	(revision 26618)
+++ trunk/psphot/src/psphotMaskReadout.c	(revision 26894)
@@ -1,12 +1,40 @@
 # include "psphotInternal.h"
 
+bool psphotSetMaskAndVariance (pmConfig *config, const pmFPAview *view) {
+
+    bool status = false;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+
+	// Generate the mask and weight images, including the user-defined analysis region of interest
+	if (!psphotSetMaskAndVarianceReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to generate mask for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
 // generate mask and variance if not defined, additional mask for restricted subregion
-bool psphotSetMaskAndVariance (pmConfig *config, pmReadout *readout, psMetadata *recipe) {
+bool psphotSetMaskAndVarianceReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
 
     bool status;
 
-    // ** Interpret the mask values:
-    // XXX drop the write to recipe and move config into psphotRoughClass?
-    // XXX alternatively, define a function to set the psphot recipe masks
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PSPHOT.INPUT", index); // File of interest
+    psAssert (file, "missing file?");
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAviewThisReadout (view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // save maskSat and maskBad on the psphot recipe (mostly for psphotRoughClass)
     psImageMaskType maskSat  = pmConfigMaskGet("SAT", config); // Mask value for saturated pixels
     psMetadataAddImageMask (recipe, PS_LIST_TAIL, "MASK.SAT", PS_META_REPLACE, "user-defined mask", maskSat);
@@ -14,5 +42,5 @@
     psImageMaskType maskBad  = pmConfigMaskGet("LOW", config); // Mask value for low pixels
     if (!maskBad) {
-        // XXX: for backward compatability look up old name
+        // for backward compatability look up old name
         maskBad  = pmConfigMaskGet("BAD", config);
     }
@@ -73,4 +101,7 @@
     }
 
+    // display the image, weight, mask (ch 1,2,3)
+    psphotVisualShowImage (readout);
+
     return true;
 }
Index: trunk/psphot/src/psphotMergeSources.c
===================================================================
--- trunk/psphot/src/psphotMergeSources.c	(revision 26618)
+++ trunk/psphot/src/psphotMergeSources.c	(revision 26894)
@@ -5,32 +5,112 @@
                          PM_SOURCE_MODE_CR_LIMIT | PM_SOURCE_MODE_EXT_LIMIT) // Mask to apply for PSF sources
 
-// merge the externally supplied sources with the existing sources.  mark them as having
-// mode PM_SOURCE_MODE_EXTERNAL
-bool psphotLoadExtSources (pmConfig *config, const pmFPAview *view, psArray *sources) {
-
-    psArray *extSourcesCMF = NULL;
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotMergeSources (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (!psphotMergeSourcesReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to merge sources for PSPHOT.INPUT entry %d", i);
+            return false;
+        }
+    }
+    return true;
+}
+
+// add newly selected sources to the existing list of sources
+bool psphotMergeSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index) {
+
+    bool status;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *newSources = detections->newSources;
+    psAssert (newSources, "missing sources?");
+
+    // XXX TEST:
+    if (detections->allSources) {
+        psphotMaskCosmicRayFootprintCheck(detections->allSources);
+    }
+    if (detections->newSources) {
+        psphotMaskCosmicRayFootprintCheck(detections->newSources);
+    }
+
+    if (!detections->allSources) {
+        detections->allSources = psArrayAllocEmpty(newSources->n);
+    }
+    psArray *allSources = detections->allSources;
+
+    for (int i = 0; i < newSources->n; i++) {
+        pmSource *source = newSources->data[i];
+        psArrayAdd (allSources, 100, source);
+    }
+
+    psFree (detections->newSources);
+    detections->newSources = NULL;
+
+    return true;
+}
+
+// Merge the externally supplied sources with the existing sources.  Mark them as having mode
+// PM_SOURCE_MODE_EXTERNAL.
+
+// XXX This function needs to be updated to loop over set of input files.  At the moment, we
+// only expect a single entry for PSPHOT.INPUT.CMF and PSPHOT.SOURCES.TEXT, so we can only
+// associate input sources with a single entry for PSPHOT.INPUT
+bool psphotLoadExtSources (pmConfig *config, const pmFPAview *view) {
+
+    bool status;
+    pmDetections *extCMF = NULL;
     psArray *extSourcesTXT = NULL;
 
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PSPHOT.INPUT", 0); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    // XXX allSources or newSources?
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
     // load data from input CMF file:
-    { 
-	pmReadout *readoutCMF = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT.CMF");
-	if (!readoutCMF) goto loadTXT;
-
-	extSourcesCMF = psMetadataLookupPtr (NULL, readoutCMF->analysis, "PSPHOT.SOURCES");
-	if (extSourcesCMF) {
-	    for (int i = 0; i < extSourcesCMF->n; i++) {
-		pmSource *source = extSourcesCMF->data[i];
-		source->mode |= PM_SOURCE_MODE_EXTERNAL;
-
-		// the supplied peak flux needs to be re-normalized
-		source->peak->flux = 1.0;
-		source->peak->value = 1.0;
-
-		// drop the loaded source modelPSF
-		psFree (source->modelPSF);
-		source->modelPSF = NULL;
-	    }
-	    psphotMergeSources (sources, extSourcesCMF);
-	}
+    {
+        pmReadout *readoutCMF = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT.CMF");
+        if (!readoutCMF) goto loadTXT;
+
+        extCMF = psMetadataLookupPtr (NULL, readoutCMF->analysis, "PSPHOT.DETECTIONS");
+        if (extCMF) {
+            for (int i = 0; i < extCMF->allSources->n; i++) {
+                pmSource *source = extCMF->allSources->data[i];
+                source->mode |= PM_SOURCE_MODE_EXTERNAL;
+
+                // the supplied peak flux needs to be re-normalized
+                source->peak->flux = 1.0;
+                source->peak->value = 1.0;
+
+                // drop the loaded source modelPSF
+                psFree (source->modelPSF);
+                source->modelPSF = NULL;
+
+                psArrayAdd (detections->allSources, 100, source);
+            }
+        }
     }
 
@@ -38,49 +118,43 @@
 
     // load data from input TXT file:
-    { 
-	pmChip *chipTXT = pmFPAfileThisChip (config->files, view, "PSPHOT.INPUT");
-	if (!chipTXT) goto finish;
-
-	extSourcesTXT = psMetadataLookupPtr (NULL, chipTXT->analysis, "PSPHOT.SOURCES.TEXT");
-	if (extSourcesTXT) {
-	    for (int i = 0; i < extSourcesTXT->n; i++) {
-		pmSource *source = extSourcesTXT->data[i];
-		source->mode |= PM_SOURCE_MODE_EXTERNAL;
-
-		// drop the loaded source modelPSF
-		psFree (source->modelPSF);
-		source->modelPSF = NULL;
-	    }
-	    psphotMergeSources (sources, extSourcesTXT);
-	}
+    {
+        pmChip *chipTXT = pmFPAfileThisChip (config->files, view, "PSPHOT.INPUT");
+        if (!chipTXT) goto finish;
+
+        extSourcesTXT = psMetadataLookupPtr (NULL, chipTXT->analysis, "PSPHOT.SOURCES.TEXT");
+        if (extSourcesTXT) {
+            for (int i = 0; i < extSourcesTXT->n; i++) {
+                pmSource *source = extSourcesTXT->data[i];
+                source->mode |= PM_SOURCE_MODE_EXTERNAL;
+
+                // drop the loaded source modelPSF
+                psFree (source->modelPSF);
+                source->modelPSF = NULL;
+
+                psArrayAdd (detections->allSources, 100, source);
+            }
+        }
     }
 
 finish:
 
-    if (!extSourcesTXT && !extSourcesTXT) {
+    if (!extCMF && !extSourcesTXT) {
         psLogMsg ("psphot", 3, "no external sources for this readout");
         return true;
     }
 
-    int nCMF = extSourcesCMF ? extSourcesCMF->n : 0;
+    int nCMF = extCMF        ? extCMF->allSources->n        : 0;
     int nTXT = extSourcesTXT ? extSourcesTXT->n : 0;
 
-    psLogMsg ("psphot", 3, "%d external sources (%d cmf, %d text) merged to yield %ld total sources", 
-	      nCMF + nTXT, nCMF, nTXT, sources->n);
-    return true;
-}
-
-// add newly selected sources to the existing list of sources
-bool psphotMergeSources (psArray *oldSources, psArray *newSources) {
-
-    for (int i = 0; i < newSources->n; i++) {
-        pmSource *source = newSources->data[i];
-        psArrayAdd (oldSources, 100, source);
-    }
+    psLogMsg ("psphot", 3, "%d external sources (%d cmf, %d text) merged to yield %ld total sources",
+              nCMF + nTXT, nCMF, nTXT, sources->n);
     return true;
 }
 
 // extract the input sources corresponding to this readout
+// XXX this function needs to be updated to work with the new context of pshot inputs
 psArray *psphotLoadPSFSources (pmConfig *config, const pmFPAview *view) {
+
+    bool status;
 
     // find the currently selected readout
@@ -91,7 +165,14 @@
     }
 
-    psArray *sources = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.SOURCES");
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    if (!detections) {
+        psLogMsg ("psphot", 3, "no psf sources for this readout");
+        return NULL;
+    }
+
+    psArray *sources = detections->allSources;
     if (!sources) {
         psLogMsg ("psphot", 3, "no psf sources for this readout");
+        return NULL;
     }
 
@@ -99,6 +180,45 @@
 }
 
+// this function is used to fix sources which were loaded externally, but have passed from
+// psphotDetectionsFromSources to psphotSourceStats and are now stored on
+// detections->newSources.
+bool psphotRepairLoadedSources (pmConfig *config, const pmFPAview *view) {
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PSPHOT.INPUT", 0); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.DETECTIONS");
+    if (!detections) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing detections");
+        return false;
+    }
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    // peak flux is wrong : set based on previous image
+    // use the peak measured in the moments analysis:
+    for (int i = 0; i < sources->n; i++) {
+      pmSource *source = sources->data[i];
+      source->peak->flux = source->moments->Peak;
+    }
+
+    return true;
+}
+
 // generate the detection structure for the supplied array of sources
-pmDetections *psphotDetectionsFromSources (pmConfig *config, psArray *sources) {
+// XXX this currently assumes there is a single input file
+bool psphotDetectionsFromSources (pmConfig *config, const pmFPAview *view, psArray *sources) {
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PSPHOT.INPUT", 0); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
 
     pmDetections *detections = pmDetectionsAlloc();
@@ -113,5 +233,5 @@
     float snMin = psMetadataLookupF32(NULL, recipe, "MOMENTS_SN_MIN");
     if (!isfinite(snMin)) {
-        return NULL;
+        return false;
     }
 
@@ -121,4 +241,11 @@
 
         if (source->mode & PSF_SOURCE_MASK || !isfinite(source->psfMag)) {
+            continue;
+        }
+
+        // use the existing peak information, otherwise generate a new peak
+        if (source->peak) {
+            source->peak->assigned = false; // So the moments will be measured
+            psArrayAdd (detections->peaks, 100, source->peak);
             continue;
         }
@@ -133,9 +260,9 @@
         peak->flux = flux; // this are being set wrong, but does it matter?
 
-	if (isfinite (source->errMag) && (source->errMag > 0.0)) {
-	  peak->SN = 1.0 / source->errMag; 
-	} else {
-	  peak->SN = 0.0;
-	}
+        if (isfinite (source->errMag) && (source->errMag > 0.0)) {
+          peak->SN = 1.0 / source->errMag;
+        } else {
+          peak->SN = 0.0;
+        }
 
         psArrayAdd (detections->peaks, 100, peak);
@@ -145,5 +272,11 @@
     psLogMsg ("psphot", 3, "%ld PSF sources loaded", detections->peaks->n);
 
-    return detections;
+    // save detections on the readout->analysis
+    if (!psMetadataAddPtr (readout->analysis, PS_LIST_TAIL, "PSPHOT.DETECTIONS", PS_META_REPLACE | PS_DATA_UNKNOWN, "psphot detectinos", detections)) {
+        psError (PSPHOT_ERR_CONFIG, false, "problem saving detections on readout");
+        return false;
+    }
+    psFree (detections);
+    return true;
 }
 
@@ -174,9 +307,9 @@
         peak->flux = flux; // this are being set wrong, but does it matter?
 
-	if (isfinite (source->errMag) && (source->errMag > 0.0)) {
-	  peak->SN = 1.0 / source->errMag; 
-	} else {
-	  peak->SN = 0.0;
-	}
+        if (isfinite (source->errMag) && (source->errMag > 0.0)) {
+          peak->SN = 1.0 / source->errMag;
+        } else {
+          peak->SN = 0.0;
+        }
 
         source->peak = peak;
@@ -187,2 +320,67 @@
     return true;
 }
+
+bool psphotCheckExtSources (pmConfig *config, const pmFPAview *view) {
+
+    bool status;
+
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PSPHOT.INPUT", 0); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    // XXX allSources of newSources?
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (sources->n) {
+        // the user wants to make the psf from these stars; define them as psf stars:
+        for (int i = 0; i < sources->n; i++) {
+            pmSource *source = sources->data[i];
+            source->mode |= PM_SOURCE_MODE_PSFSTAR;
+        }
+        // force psphotChoosePSF to use all loaded sources
+        psMetadataAddS32 (recipe, PS_LIST_TAIL, "PSF_MAX_NSTARS", PS_META_REPLACE, "max number of sources for PSF model", sources->n);
+
+        // measure stats of externally specified sources
+        if (!psphotSourceStatsUpdate (sources, config, readout)) {
+            psError(PSPHOT_ERR_CONFIG, false, "failure to measure stats of existing sources");
+            return false;
+        }
+    } else {
+
+        // find the detections (by peak and/or footprint) in the image.
+        if (!psphotFindDetections (config, view, true)) {
+            psError(PSPHOT_ERR_CONFIG, false, "unable to find detections in this image");
+            return psphotReadoutCleanup (config, view);
+        }
+
+        // construct sources and measure basic stats
+        psphotSourceStats (config, view, true);
+
+        // find blended neighbors of very saturated stars
+        psphotDeblendSatstars (config, view);
+
+        // mark blended peaks PS_SOURCE_BLEND
+        if (!psphotBasicDeblend (config, view)) {
+            psLogMsg ("psphot", 3, "failed on deblend analysis");
+            return psphotReadoutCleanup (config, view);
+        }
+
+        // classify sources based on moments, brightness
+        if (!psphotRoughClass (config, view)) {
+            psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+            return psphotReadoutCleanup (config, view);
+        }
+    }
+
+    return true;
+}
Index: trunk/psphot/src/psphotModelBackground.c
===================================================================
--- trunk/psphot/src/psphotModelBackground.c	(revision 26618)
+++ trunk/psphot/src/psphotModelBackground.c	(revision 26894)
@@ -30,11 +30,18 @@
 
 // Generate the background model
-static bool backgroundModel(psImage *model,  // Model image
-                            psImage *modelStdev, // Model stdev image
-                            psMetadata *analysis, // Analysis metadata for outputs
-                            pmReadout *readout, // Readout for which to generate a background model
-                            psImageBinning *binning, // Binning parameters
-                            const pmConfig *config // Configuration
-                            )
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+//
+// NOTE that the 'analysis' metedata pass in here is used to store the binning information.
+// This may be the analysis for this readout, but it may be the analysis for the pmFPAfile
+// corresponding to the model.  Other information about the background model is saved on the
+// readout->analysis
+static bool psphotModelBackgroundReadout(psImage *model,  // Model image
+                                  psImage *modelStdev, // Model stdev image
+                                  psMetadata *analysis, // Analysis metadata for outputs
+                                  pmReadout *readout, // Readout for which to generate a background model
+                                  psImageBinning *binning, // Binning parameters
+                                  const pmConfig *config // Configuration
+    )
 {
     psTimerStart ("psphot.background");
@@ -138,6 +145,5 @@
 
     // we save the binning structure for use in psphotMagnitudes
-    psMetadataAddPtr(analysis, PS_LIST_TAIL, "PSPHOT.BACKGROUND.BINNING",
-                     PS_DATA_UNKNOWN | PS_META_REPLACE, "Background binning", binning);
+    psMetadataAddPtr(analysis, PS_LIST_TAIL, "PSPHOT.BACKGROUND.BINNING", PS_DATA_UNKNOWN | PS_META_REPLACE, "Background binning", binning);
 
     psF32 **modelData = model->data.F32;
@@ -304,6 +310,6 @@
     psLogMsg ("psphot", PS_LOG_INFO, "built background image: %f sec\n", psTimerMark ("psphot.background"));
 
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "SKY_MEAN", PS_META_REPLACE, "sky mean", Value);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "SKY_STDEV", PS_META_REPLACE, "sky stdev", ValueStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "SKY_MEAN", PS_META_REPLACE, "sky mean", Value);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "SKY_STDEV", PS_META_REPLACE, "sky stdev", ValueStdev);
     psLogMsg ("psphot", PS_LOG_INFO, "image sky : mean %f stdev %f", Value, ValueStdev);
 
@@ -314,16 +320,10 @@
                                       PS_STAT_MAX);
     psImageStats (statsBck, model, NULL, 0);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MN",
-                      PS_META_REPLACE, "sky model mean",          statsBck->sampleMean);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_SIG",
-                      PS_META_REPLACE, "sky model stdev",         statsBck->sampleStdev);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MAX",
-                      PS_META_REPLACE, "sky model maximum value", statsBck->max);
-    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MIN",
-                      PS_META_REPLACE, "sky model minimum value", statsBck->min);
-    psMetadataAddS32 (recipe, PS_LIST_TAIL, "MSKY_NX",
-                      PS_META_REPLACE, "sky model size (x)",      model->numCols);
-    psMetadataAddS32 (recipe, PS_LIST_TAIL, "MSKY_NY",
-                      PS_META_REPLACE, "sky model size (y)",      model->numRows);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "MSKY_MN", PS_META_REPLACE, "sky model mean",          statsBck->sampleMean);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "MSKY_SIG", PS_META_REPLACE, "sky model stdev",         statsBck->sampleStdev);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "MSKY_MAX", PS_META_REPLACE, "sky model maximum value", statsBck->max);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "MSKY_MIN", PS_META_REPLACE, "sky model minimum value", statsBck->min);
+    psMetadataAddS32 (readout->analysis, PS_LIST_TAIL, "MSKY_NX", PS_META_REPLACE, "sky model size (x)",      model->numCols);
+    psMetadataAddS32 (readout->analysis, PS_LIST_TAIL, "MSKY_NY", PS_META_REPLACE, "sky model size (y)",      model->numRows);
     psLogMsg ("psphot", PS_LOG_INFO, "background sky : min %f mean %f max %f stdev %f",
               statsBck->min, statsBck->sampleMean, statsBck->max, statsBck->sampleStdev);
@@ -338,17 +338,16 @@
 }
 
-
-
-psImage *psphotBackgroundModel(pmReadout *ro, const pmConfig *config)
+// generate a background model for a single readout. do not save an associated pmFPAfile for possible output
+psImage *psphotModelBackgroundReadoutNoFile(pmReadout *readout, const pmConfig *config)
 {
-    PM_ASSERT_READOUT_NON_NULL(ro, NULL);
-    PM_ASSERT_READOUT_IMAGE(ro, NULL);
+    PM_ASSERT_READOUT_NON_NULL(readout, NULL);
+    PM_ASSERT_READOUT_IMAGE(readout, NULL);
     PS_ASSERT_PTR_NON_NULL(config, NULL);
 
-    psImageBinning *binning = psphotBackgroundBinning(ro->image, config); // Image binning parameters
+    psImageBinning *binning = psphotBackgroundBinning(readout->image, config); // Image binning parameters
     psImage *model = psImageAlloc(binning->nXruff, binning->nYruff, PS_TYPE_F32); // Background model
     psImage *modelStdev = psImageAlloc(binning->nXruff, binning->nYruff, PS_TYPE_F32); // Standard deviation
 
-    if (!backgroundModel(model, modelStdev, ro->analysis, ro, binning, config)) {
+    if (!psphotModelBackgroundReadout(model, modelStdev, readout->analysis, readout, binning, config)) {
         psFree(model);
         psFree(modelStdev);
@@ -356,21 +355,18 @@
         return NULL;
     }
-
     psFree(modelStdev);
-
     return model;
 }
 
-
-// generate the median in NxN boxes, clipping heavily
-// linear interpolation to generate full-scale model
-bool psphotModelBackground (pmConfig *config, const pmFPAview *view, const char *filename)
+// generate a background model for readout number index; save an associated pmFPAfile for possible output
+bool psphotModelBackgroundReadoutFileIndex (pmConfig *config, const pmFPAview *view, const char *filename, int index)
 {
-    bool status = true;
-
     // find the currently selected readout
-    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, filename);
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
     pmFPA *inFPA = file->fpa;
     pmReadout *readout = pmFPAviewThisReadout(view, inFPA);
+    psAssert (readout, "missing readout?");
 
     psImageBinning *binning = psphotBackgroundBinning(readout->image, config); // Image binning parameters
@@ -378,5 +374,5 @@
     pmReadout *modelStdev = pmFPAGenerateReadout(config, view, "PSPHOT.BACKMDL.STDEV", inFPA, binning);
 
-    if (!backgroundModel(model->image, modelStdev->image, model->analysis, readout, binning, config)) {
+    if (!psphotModelBackgroundReadout(model->image, modelStdev->image, model->analysis, readout, binning, config)) {
         psError(PS_ERR_UNKNOWN, false, "Unable to generate background model");
         return false;
@@ -386,2 +382,20 @@
     return true;
 }
+
+// XXX supply filename or keep PSPHOT.INPUT fixed?
+bool psphotModelBackground (pmConfig *config, const pmFPAview *view)
+{
+    bool status = false;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (!psphotModelBackgroundReadoutFileIndex(config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to model background for PSPHOT.INPUT entry %d", i);
+            return false;
+        }
+    }
+    return true;
+}
Index: trunk/psphot/src/psphotModelTest.c
===================================================================
--- trunk/psphot/src/psphotModelTest.c	(revision 26618)
+++ trunk/psphot/src/psphotModelTest.c	(revision 26894)
@@ -139,5 +139,5 @@
 
     // get the source moments
-    status = pmSourceMoments (source, mRADIUS, 0.0, 1.0);
+    status = pmSourceMoments (source, mRADIUS, 0.0, 1.0, maskVal);
     if (!status) psAbort("psSourceMoments error");
     source->peak->value = source->moments->Peak;
Index: trunk/psphot/src/psphotMomentsStudy.c
===================================================================
--- trunk/psphot/src/psphotMomentsStudy.c	(revision 26618)
+++ trunk/psphot/src/psphotMomentsStudy.c	(revision 26894)
@@ -148,5 +148,5 @@
       pmSourceAdd(source, PM_MODEL_OP_FUNC, 0); 
 
-      pmSourceMoments (source, 32.0, sigWindow, 0.0);
+      pmSourceMoments (source, 32.0, sigWindow, 0.0, 0xffff);
       // fprintf (stderr, "sigOut : %f\n", sqrt(source->moments->Mxx));
 
Index: trunk/psphot/src/psphotOutput.c
===================================================================
--- trunk/psphot/src/psphotOutput.c	(revision 26618)
+++ trunk/psphot/src/psphotOutput.c	(revision 26894)
@@ -126,16 +126,33 @@
 }
 
-bool psphotAddPhotcode (psMetadata *recipe, pmConfig *config, const pmFPAview *view, const char *filerule) {
-
-    bool status;
-
-    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, filerule);
-    PS_ASSERT (status, false);
-
-    // determine PHOTCODE from fpa & view, overwrite in recipe
-    char *photcode = pmConceptsPhotcodeForView (input, view);
+bool psphotAddPhotcode (pmConfig *config, const pmFPAview *view) {
+
+    bool status = false;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotAddPhotcodeReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to add photcode to PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotAddPhotcodeReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index) {
+
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, index); // File of interest
+    PS_ASSERT (file, false);
+
+    pmReadout  *readout = pmFPAviewThisReadout (view, file->fpa);
+
+    // determine PHOTCODE from fpa & view, overwrite in readout->analysis
+    char *photcode = pmConceptsPhotcodeForView (file, view);
     PS_ASSERT (photcode, false);
 
-    psMetadataAddStr (recipe, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "photcode from FPA concepts", photcode);
+    psMetadataAddStr (readout->analysis, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "photcode from FPA concepts", photcode);
     psLogMsg ("psphot", 3, "PHOTCODE is %s", photcode);
 
@@ -144,5 +161,5 @@
 }
 
-bool psphotSetHeaderNstars (psMetadata *recipe, psArray *sources) {
+bool psphotSetHeaderNstars (psMetadata *header, psArray *sources) {
 
     int nSrc = 0;
@@ -173,93 +190,93 @@
     }
 
-    psMetadataAddS32 (recipe, PS_LIST_TAIL, "NSTARS",   PS_META_REPLACE, "Number of sources", nSrc);
-    psMetadataAddS32 (recipe, PS_LIST_TAIL, "NDET_EXT", PS_META_REPLACE, "Number of extended sources", nEXT);
-    psMetadataAddS32 (recipe, PS_LIST_TAIL, "NDET_CR",  PS_META_REPLACE, "Number of cosmic rays", nCR);
-    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) {
+    psMetadataAddS32 (header, PS_LIST_TAIL, "NSTARS",   PS_META_REPLACE, "Number of sources", nSrc);
+    psMetadataAddS32 (header, PS_LIST_TAIL, "NDET_EXT", PS_META_REPLACE, "Number of extended sources", nEXT);
+    psMetadataAddS32 (header, PS_LIST_TAIL, "NDET_CR",  PS_META_REPLACE, "Number of cosmic rays", nCR);
+    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).  before the header is created, each readout has these
+// values stored on readout->analysis
+psMetadata *psphotDefineHeader (psMetadata *analysis) {
+
+    bool status = true;
 
     psMetadata *header = psMetadataAlloc ();
 
     // write necessary information to output header
-    psMetadataItemSupplement (header, recipe, "ZERO_PT");
-    psMetadataItemSupplement (header, recipe, "PHOTCODE");
-
-    psMetadataItemSupplement (header, recipe, "APMIFIT");
-    psMetadataItemSupplement (header, recipe, "DAPMIFIT");
-    psMetadataItemSupplement (header, recipe, "NAPMIFIT");
+    psMetadataItemSupplement (&status, header, analysis, "ZERO_PT");
+    psMetadataItemSupplement (&status, header, analysis, "PHOTCODE");
+
+    psMetadataItemSupplement (&status, header, analysis, "APMIFIT");
+    psMetadataItemSupplement (&status, header, analysis, "DAPMIFIT");
+    psMetadataItemSupplement (&status, header, analysis, "NAPMIFIT");
 
     // PSF model parameters (shape values for image center)
-    psMetadataItemSupplement (header, recipe, "NPSFSTAR");
-    psMetadataItemSupplement (header, recipe, "APLOSS");
-
-    psMetadataItemSupplement (header, recipe, "FWHM_MAJ");
-    psMetadataItemSupplement (header, recipe, "FW_MJ_SG");
-    psMetadataItemSupplement (header, recipe, "FW_MJ_LQ");
-    psMetadataItemSupplement (header, recipe, "FW_MJ_UQ");
-
-    psMetadataItemSupplement (header, recipe, "FWHM_MIN");
-    psMetadataItemSupplement (header, recipe, "FW_MN_SG");
-    psMetadataItemSupplement (header, recipe, "FW_MN_LQ");
-    psMetadataItemSupplement (header, recipe, "FW_MN_UQ");
-
-    psMetadataItemSupplement (header, recipe, "ANGLE");
+    psMetadataItemSupplement (&status, header, analysis, "NPSFSTAR");
+    psMetadataItemSupplement (&status, header, analysis, "APLOSS");
+
+    psMetadataItemSupplement (&status, header, analysis, "FWHM_MAJ");
+    psMetadataItemSupplement (&status, header, analysis, "FW_MJ_SG");
+    psMetadataItemSupplement (&status, header, analysis, "FW_MJ_LQ");
+    psMetadataItemSupplement (&status, header, analysis, "FW_MJ_UQ");
+
+    psMetadataItemSupplement (&status, header, analysis, "FWHM_MIN");
+    psMetadataItemSupplement (&status, header, analysis, "FW_MN_SG");
+    psMetadataItemSupplement (&status, header, analysis, "FW_MN_LQ");
+    psMetadataItemSupplement (&status, header, analysis, "FW_MN_UQ");
+
+    psMetadataItemSupplement (&status, header, analysis, "ANGLE");
 
     // Image Quality measurements
-    psMetadataItemSupplement (header, recipe, "IQ_NSTAR");
-
-    psMetadataItemSupplement (header, recipe, "IQ_FW1");
-    psMetadataItemSupplement (header, recipe, "IQ_FW1_E");
-    psMetadataItemSupplement (header, recipe, "IQ_FW2");
-    psMetadataItemSupplement (header, recipe, "IQ_FW2_E");
-
-    psMetadataItemSupplement (header, recipe, "IQ_M2");
-    psMetadataItemSupplement (header, recipe, "IQ_M2_ER");
-    psMetadataItemSupplement (header, recipe, "IQ_M2_LQ");
-    psMetadataItemSupplement (header, recipe, "IQ_M2_UQ");
-
-    psMetadataItemSupplement (header, recipe, "IQ_M2C");
-    psMetadataItemSupplement (header, recipe, "IQ_M2C_E");
-    psMetadataItemSupplement (header, recipe, "IQ_M2C_L");
-    psMetadataItemSupplement (header, recipe, "IQ_M2C_U");
-
-    psMetadataItemSupplement (header, recipe, "IQ_M2S");
-    psMetadataItemSupplement (header, recipe, "IQ_M2S_E");
-    psMetadataItemSupplement (header, recipe, "IQ_M2S_L");
-    psMetadataItemSupplement (header, recipe, "IQ_M2S_U");
-
-    psMetadataItemSupplement (header, recipe, "IQ_M3");
-    psMetadataItemSupplement (header, recipe, "IQ_M3_ER");
-    psMetadataItemSupplement (header, recipe, "IQ_M3_LQ");
-    psMetadataItemSupplement (header, recipe, "IQ_M3_UQ");
-
-    psMetadataItemSupplement (header, recipe, "IQ_M4");
-    psMetadataItemSupplement (header, recipe, "IQ_M4_ER");
-    psMetadataItemSupplement (header, recipe, "IQ_M4_LQ");
-    psMetadataItemSupplement (header, recipe, "IQ_M4_UQ");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_NSTAR");
+
+    psMetadataItemSupplement (&status, header, analysis, "IQ_FW1");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_FW1_E");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_FW2");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_FW2_E");
+
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2_ER");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2_LQ");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2_UQ");
+
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2C");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2C_E");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2C_L");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2C_U");
+
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2S");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2S_E");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2S_L");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2S_U");
+
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M3");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M3_ER");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M3_LQ");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M3_UQ");
+
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M4");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M4_ER");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M4_LQ");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M4_UQ");
 
     // 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);
-    psMetadataItemSupplement (header, recipe, "NSTARS");
-
-    psMetadataItemSupplement (header, recipe, "NDET_EXT");
-    psMetadataItemSupplement (header, recipe, "NDET_CR");
+    psMetadataItemSupplement (&status, header, analysis, "NSTARS");
+
+    psMetadataItemSupplement (&status, header, analysis, "NDET_EXT");
+    psMetadataItemSupplement (&status, header, analysis, "NDET_CR");
 
     // sky background model statistics
-    psMetadataItemSupplement (header, recipe, "MSKY_MN");
-    psMetadataItemSupplement (header, recipe, "MSKY_SIG");
-    psMetadataItemSupplement (header, recipe, "MSKY_MIN");
-    psMetadataItemSupplement (header, recipe, "MSKY_MAX");
-    psMetadataItemSupplement (header, recipe, "MSKY_NX");
-    psMetadataItemSupplement (header, recipe, "MSKY_NY");
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_MN");
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_SIG");
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_MIN");
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_MAX");
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_NX");
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_NY");
 
     psMetadataAddF32 (header, PS_LIST_TAIL, "DT_PHOT", PS_META_REPLACE, "elapsed psphot time", psTimerMark ("psphotReadout"));
-
-    // XXX : don't require any of these about values to exist
-    psErrorClear ();
 
     return header;
Index: trunk/psphot/src/psphotPSFConvModel.c
===================================================================
--- trunk/psphot/src/psphotPSFConvModel.c	(revision 26618)
+++ trunk/psphot/src/psphotPSFConvModel.c	(revision 26894)
@@ -39,5 +39,5 @@
     // adjust the pixels based on the footprint
     float radius = psphotSetRadiusEXT (readout, source, markVal);
-    if (!pmSourceMoments (source, radius, 0.0, 0.0)) return false;
+    if (!pmSourceMoments (source, radius, 0.0, 0.0, maskVal)) return false;
 
     // XXX test : modify the Io, SXX, SYY terms based on the psf SXX, SYY terms:
Index: trunk/psphot/src/psphotParseCamera.c
===================================================================
--- trunk/psphot/src/psphotParseCamera.c	(revision 26618)
+++ trunk/psphot/src/psphotParseCamera.c	(revision 26894)
@@ -39,4 +39,6 @@
         return NULL;
     }
+    // specify the number of psphot input images
+    psMetadataAddS32 (config->arguments, PS_LIST_TAIL, "PSPHOT.INPUT.NUM", PS_META_REPLACE, "number of inputs", 1);
 
     // define the additional input/output files associated with psphot
Index: trunk/psphot/src/psphotRadiusChecks.c
===================================================================
--- trunk/psphot/src/psphotRadiusChecks.c	(revision 26618)
+++ trunk/psphot/src/psphotRadiusChecks.c	(revision 26894)
@@ -4,18 +4,24 @@
 static float PSF_FIT_NSIGMA;
 static float PSF_FIT_PADDING;
+static float PSF_APERTURE = 0;	// radius to use in PSF aperture mags
 static float PSF_FIT_RADIUS = 0;	// radius to use in fitting (ignored if <= 0,
 					// and a per-object radius is calculated)
 
-static float PSF_APERTURE = 0;	// radius to use in PSF aperture mags
-
-
-bool psphotInitRadiusPSF(const psMetadata *recipe, const pmModelType type) {
+bool psphotInitRadiusPSF(const psMetadata *recipe, const psMetadata *analysis, const pmModelType type) {
 
     bool status = true;
 
-    PSF_FIT_NSIGMA  = psMetadataLookupF32(&status, recipe, "PSF_FIT_NSIGMA");
+    PSF_FIT_NSIGMA = psMetadataLookupF32(&status, recipe, "PSF_FIT_NSIGMA");
     PSF_FIT_PADDING = psMetadataLookupF32(&status, recipe, "PSF_FIT_PADDING");
-    PSF_FIT_RADIUS  =  psMetadataLookupF32(&status, recipe, "PSF_FIT_RADIUS");
-    PSF_APERTURE    =  psMetadataLookupF32(&status, recipe, "PSF_APERTURE");
+
+    PSF_FIT_RADIUS =  psMetadataLookupF32(&status, analysis, "PSF_FIT_RADIUS");
+    if (!status) {
+	PSF_FIT_RADIUS = psMetadataLookupF32(&status, recipe, "PSF_FIT_RADIUS");
+    }
+
+    PSF_APERTURE =  psMetadataLookupF32(&status, analysis, "PSF_APERTURE");
+    if (!status) {
+	PSF_APERTURE =  psMetadataLookupF32(&status, recipe, "PSF_APERTURE");
+    }
 
     return true;
Index: trunk/psphot/src/psphotReadout.c
===================================================================
--- trunk/psphot/src/psphotReadout.c	(revision 26618)
+++ trunk/psphot/src/psphotReadout.c	(revision 26894)
@@ -3,5 +3,4 @@
 // this should be called by every program that links against libpsphot
 bool psphotInit (void) {
-
     psphotErrorRegister();              // register our error codes/messages
     psphotModelClassInit ();            // load implementation-specific models
@@ -12,8 +11,6 @@
 bool psphotReadout(pmConfig *config, const pmFPAview *view) {
 
-    // measure the total elapsed time in psphotReadout.  XXX the current threading plan
-    // for psphot envisions threading within psphotReadout, not multiple threads calling
-    // the same psphotReadout.  In the current plan, this dtime is the elapsed time used
-    // jointly by the multiple threads, not the total time used by all threads.
+    // measure the total elapsed time in psphotReadout.  dtime is the elapsed time used jointly
+    // by the multiple threads, not the total time used by all threads.
     psTimerStart ("psphotReadout");
 
@@ -26,181 +23,175 @@
         return false;
     }
+    // optional break-point for processing
+    char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
+    psAssert (breakPt, "configuration error: set BREAK_POINT");
 
     // set the photcode for this image
-    if (!psphotAddPhotcode (recipe, config, view, "PSPHOT.INPUT")) {
+    if (!psphotAddPhotcode (config, view)) {
         psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
         return false;
     }
 
-    // find the currently selected readout
-    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
-    PS_ASSERT_PTR_NON_NULL (readout, false);
-
-    // optional break-point for processing
-    char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
-    PS_ASSERT_PTR_NON_NULL (breakPt, false);
-
     // Generate the mask and weight images, including the user-defined analysis region of interest
-    psphotSetMaskAndVariance (config, readout, recipe);
+    psphotSetMaskAndVariance (config, view);
     if (!strcasecmp (breakPt, "NOTHING")) {
-        return psphotReadoutCleanup(config, readout, recipe, NULL, NULL, NULL);
-    }
-
-    // display the image, weight, mask (ch 1,2,3)
-    psphotVisualShowImage (readout);
+        return psphotReadoutCleanup(config, view);
+    }
 
     // generate a background model (median, smoothed image)
-    if (!psphotModelBackground (config, view, "PSPHOT.INPUT")) {
-        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
-    }
-    if (!psphotSubtractBackground (config, view, "PSPHOT.INPUT")) {
-        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    if (!psphotModelBackground (config, view)) {
+        return psphotReadoutCleanup (config, view);
+    }
+    if (!psphotSubtractBackground (config, view)) {
+        return psphotReadoutCleanup (config, view);
     }
     if (!strcasecmp (breakPt, "BACKMDL")) {
-        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
-    }
-
-    // display the backsub and backgnd images
-    psphotVisualShowBackground (config, view, readout);
-
-    // run a single-model test if desired (exits from here if test is run)
-    psphotModelTest (config, view, recipe);
-
-    // load the psf model, if suppled.  FWHM_X,FWHM_Y,etc are saved in the recipe
-    pmPSF *psf = psphotLoadPSF (config, view, recipe);
-
-    // several functions below behave differently if we have a PSF model already
-    bool havePSF = (psf != NULL);
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // load the psf model, if suppled.  FWHM_X,FWHM_Y,etc are determined and saved on
+    // readout->analysis XXX this function currently only works with a single PSPHOT.INPUT
+    if (!psphotLoadPSF (config, view)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "error loading psf model");
+        return psphotReadoutCleanup (config, view);
+    }
 
     // find the detections (by peak and/or footprint) in the image.
-    pmDetections *detections = psphotFindDetections (NULL, readout, recipe);
-    if (!detections) {
-        psLogMsg ("psphot", 3, "unable to find detections in this image");
-        return psphotReadoutCleanup (config, readout, recipe, detections, psf, NULL);
-    }
-
-    // construct sources and measure basic stats
-    psArray *sources = psphotSourceStats (config, readout, detections, true);
-    if (!sources) return false;
+    if (!psphotFindDetections (config, view, true)) { // pass 1
+        // this only happens if we had an error in psphotFindDetections
+        psError (PSPHOT_ERR_UNKNOWN, false, "failure in peak analysis");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // construct sources and measure basic stats (saved on detections->newSources)
+    if (!psphotSourceStats (config, view, true)) { // pass 1
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate sources");
+        return psphotReadoutCleanup (config, view);
+    }
     if (!strcasecmp (breakPt, "PEAKS")) {
-        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
-    }
-
-    // find blended neighbors of very saturated stars
-    // XXX merge this with Basic Deblend?
-    psphotDeblendSatstars (readout, sources, recipe);
-
-    // mark blended peaks PS_SOURCE_BLEND
-    if (!psphotBasicDeblend (sources, recipe)) {
-        psLogMsg ("psphot", 3, "failed on deblend analysis");
-        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
-    }
-
-    // classify sources based on moments, brightness
-    if (!psphotRoughClass (readout, sources, recipe, havePSF)) {
-        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
-        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+        return psphotReadoutCleanup(config, view);
+    }
+
+    // find blended neighbors of very saturated stars (detections->newSources)
+    if (!psphotDeblendSatstars (config, view)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed on satstar deblend analysis");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // mark blended peaks PS_SOURCE_BLEND (detections->newSources)
+    if (!psphotBasicDeblend (config, view)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed on deblend analysis");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // classify sources based on moments, brightness.  if a PSF model has been loaded, the PSF
+    // clump defined for it is used not measured (detections->newSources)
+    if (!psphotRoughClass (config, view)) { // pass 1
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed to determine rough classifications");
+        return psphotReadoutCleanup (config, view);
     }
     if (!strcasecmp (breakPt, "MOMENTS")) {
-        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
-    }
-
-    if (!havePSF && !psphotImageQuality (recipe, sources)) {
-        psLogMsg("psphot", 3, "failed to measure image quality");
-        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
-    }
-
-    // if we were not supplied a PSF, choose one here
-    if (psf == NULL) {
-        // use bright stellar objects to measure PSF
-        // XXX if we do not have enough stars to generate the PSF, build one
-        // from the SEEING guess and model class
-        psf = psphotChoosePSF (readout, sources, recipe);
-        if (psf == NULL) {
-            psLogMsg ("psphot", 3, "failure to construct a psf model");
-            return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
-        }
-        havePSF = true;
+        return psphotReadoutCleanup(config, view);
+    }
+
+    // if we were not supplied a PSF model, determine the IQ stats here (detections->newSources)
+    if (!psphotImageQuality (config, view)) { // pass 1
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed to measure image quality");
+        return psphotReadoutCleanup(config, view);
+    }
+
+    // use bright stellar objects to measure PSF if we were supplied a PSF for any input file,
+    // this step is skipped
+    if (!psphotChoosePSF (config, view)) { // pass 1
+        psLogMsg ("psphot", 3, "failure to construct a psf model");
+        return psphotReadoutCleanup (config, view);
     }
     if (!strcasecmp (breakPt, "PSFMODEL")) {
-        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
-    }
-    psphotVisualShowPSFModel (readout, psf);
+        return psphotReadoutCleanup (config, view);
+    }
 
     // include externally-supplied sources
-    psphotLoadExtSources (config, view, sources);
-
-    // construct an initial model for each object, set the radius to fitRadius, set circular fit mask
-    psphotGuessModels (config, readout, sources, psf);
+    // XXX fix this in the new multi-input context
+    // psphotLoadExtSources (config, view); // pass 1
+
+    // construct an initial model for each object, set the radius to fitRadius, set circular
+    // fit mask (detections->newSources)
+    psphotGuessModels (config, view); // pass 1
+
+    // merge the newly selected sources into the existing list
+    // NOTE: merge OLD and NEW
+    psphotMergeSources (config, view);
 
     // linear PSF fit to source peaks, subtract the models from the image (in PSF mask)
-    psphotFitSourcesLinear (readout, sources, recipe, psf, FALSE);
-
-    // We have to place these visualizations here because the models are not realized until
-    // psphotGuessModels or fitted until psphotFitSourcesLinear.
-    psphotVisualShowPSFStars (recipe, psf, sources);
-
-    // identify CRs and extended sources
-    psphotSourceSize (config, readout, sources, recipe, psf, 0);
+    psphotFitSourcesLinear (config, view, false); // pass 1 (detections->allSources)
+
+    // identify CRs and extended sources (only unmeasured sources are measured)
+    psphotSourceSize (config, view, true); // pass 1 (detections->allSources)
     if (!strcasecmp (breakPt, "ENSEMBLE")) {
         goto finish;
     }
-    psphotVisualShowSatStars (recipe, psf, sources);
 
     // non-linear PSF and EXT fit to brighter sources
     // replace model flux, adjust mask as needed, fit, subtract the models (full stamp)
-    psphotBlendFit (config, readout, sources, psf);
+    psphotBlendFit (config, view); // pass 1 (detections->allSources)
 
     // replace all sources
-    psphotReplaceAllSources (sources, recipe);
+    psphotReplaceAllSources (config, view); // pass 1 (detections->allSources)
 
     // linear fit to include all sources (subtract again)
-    psphotFitSourcesLinear (readout, sources, recipe, psf, TRUE);
+    // NOTE : apply to ALL sources (extended + psf)
+    psphotFitSourcesLinear (config, view, true); // pass 2 (detections->allSources)
 
     // if we only do one pass, skip to extended source analysis
-    if (!strcasecmp (breakPt, "PASS1")) {
-        goto pass1finish;
-    }
-    // NOTE: possibly re-measure background model here with objects subtracted
+    if (!strcasecmp (breakPt, "PASS1")) goto pass1finish;
+
+    // NOTE: possibly re-measure background model here with objects subtracted / or masked
 
     // add noise for subtracted objects
-    psphotAddNoise (readout, sources, recipe);
-
-    // find fainter sources (pass 2)
-    detections = psphotFindDetections (detections, readout, recipe);
+    psphotAddNoise (config, view); // pass 1 (detections->allSources)
+
+    // find fainter sources
+    // NOTE: finds new peaks and new footprints, OLD and FULL set are saved on detections
+    psphotFindDetections (config, view, false); // pass 2 (detections->peaks, detections->footprints)
 
     // remove noise for subtracted objects (ie, return to normal noise level)
-    psphotSubNoise (readout, sources, recipe);
+    // NOTE: this needs to operate only on the OLD sources
+    psphotSubNoise (config, view); // pass 1 (detections->allSources)
 
     // define new sources based on only the new peaks
-    psArray *newSources = psphotSourceStats (config, readout, detections, false);
+    // NOTE: new sources are saved on detections->newSources
+    psphotSourceStats (config, view, false); // pass 2 (detections->newSources)
 
     // set source type
-    if (!psphotRoughClass (readout, newSources, recipe, havePSF)) {
+    // NOTE: apply only to detections->newSources
+    if (!psphotRoughClass (config, view)) { // pass 2 (detections->newSources)
         psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
-        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+        return psphotReadoutCleanup (config, view);
     }
 
     // create full input models, set the radius to fitRadius, set circular fit mask
-    psphotGuessModels (config, readout, newSources, psf);
+    // NOTE: apply only to detections->newSources
+    psphotGuessModels (config, view); // pass 2 (detections->newSources)
 
     // replace all sources so fit below applies to all at once
-    psphotReplaceAllSources (sources, recipe);
+    // NOTE: apply only to OLD sources (which have been subtracted)
+    psphotReplaceAllSources (config, view); // pass 2
 
     // merge the newly selected sources into the existing list
-    psphotMergeSources (sources, newSources);
-    psFree (newSources);
-
-    // linear fit to all sources
-    psphotFitSourcesLinear (readout, sources, recipe, psf, TRUE);
+    // NOTE: merge OLD and NEW
+    // XXX check on free of sources...
+    psphotMergeSources (config, view); // (detections->newSources + detections->allSources -> detections->allSources)
+
+    // NOTE: apply to ALL sources
+    psphotFitSourcesLinear (config, view, true); // pass 3 (detections->allSources)
 
 pass1finish:
 
     // measure source size for the remaining sources
-    psphotSourceSize (config, readout, sources, recipe, psf, 0);
-
-    psphotExtendedSourceAnalysis (readout, sources, recipe);
-
-    psphotExtendedSourceFits (readout, sources, recipe);
+    // NOTE: applies only to NEW (unmeasured) sources
+    psphotSourceSize (config, view, false); // pass 2 (detections->allSources)
+
+    psphotExtendedSourceAnalysis (config, view); // pass 1 (detections->allSources)
+    psphotExtendedSourceFits (config, view); // pass 1 (detections->allSources)
 
 finish:
@@ -210,13 +201,13 @@
 
     // measure aperture photometry corrections
-    if (!psphotApResid (config, readout, sources, psf)) {
+    if (!psphotApResid (config, view)) { // pass 1 (detections->allSources)
         psLogMsg ("psphot", 3, "failed on psphotApResid");
-        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+        return psphotReadoutCleanup (config, view);
     }
 
     // calculate source magnitudes
-    psphotMagnitudes(config, readout, view, sources, psf);
-
-    if (!psphotEfficiency(config, readout, view, psf, recipe, sources)) {
+    psphotMagnitudes(config, view); // pass 1 (detections->allSources)
+
+    if (!psphotEfficiency(config, view)) { // pass 1
         psErrorStackPrint(stderr, "Unable to determine detection efficiencies from fake sources");
         psErrorClear();
@@ -227,11 +218,10 @@
 
     // replace background in residual image
-    psphotSkyReplace (config, view);
+    psphotSkyReplace (config, view); // pass 1
 
     // drop the references to the image pixels held by each source
-    psphotSourceFreePixels (sources);
+    psphotSourceFreePixels (config, view); // pass 1
 
     // create the exported-metadata and free local data
-    return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    return psphotReadoutCleanup(config, view);
 }
-
Index: trunk/psphot/src/psphotReadoutCleanup.c
===================================================================
--- trunk/psphot/src/psphotReadoutCleanup.c	(revision 26618)
+++ trunk/psphot/src/psphotReadoutCleanup.c	(revision 26894)
@@ -1,8 +1,8 @@
 # include "psphotInternal.h"
 
-// psphotReadoutCleanup is called on exit from psphotReadout.  If the last raised error is
-// not a DATA error, then there was a serious problem.  Only in this case, or if the fail
-// on the stats measurement, do we return false
-bool psphotReadoutCleanup (pmConfig *config, pmReadout *readout, psMetadata *recipe, pmDetections *detections, pmPSF *psf, psArray *sources) {
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotReadoutCleanup (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
 
     // remove internal pmFPAfiles, if created
@@ -12,17 +12,52 @@
     }
     if (psErrorCodeLast() != PS_ERR_NONE) {
-        psFree (psf);
-        psFree (sources);
-        psFree (detections);
         return false;
     }
 
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotReadoutCleanupReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on psphotReadoutCleanup for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+
+    // XXX move this to top of loop?
+    pmKapaClose ();
+
+    return true;
+}
+
+// psphotReadoutCleanup is called on exit from psphotReadout.  If the last raised error is
+// not a DATA error, then there was a serious problem.  Only in this case, or if the fail
+// on the stats measurement, do we return false
+bool psphotReadoutCleanupReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
+
+    bool status = true;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // when psphotReadoutCleanup is called, these are not necessarily defined
+    pmPSF        *psf        = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psArray      *sources    = detections->allSources;
+    // XXX where do we free these, in here (psMetadataRemove?)
+
     // use the psf-model to measure FWHM stats
     if (psf) {
-        if (!psphotPSFstats (readout, recipe, psf)) {
+        if (!psphotPSFstats (readout, psf)) {
             psError(PSPHOT_ERR_PROG, false, "Failed to measure PSF shape parameters");
-            psFree (psf);
-            psFree (sources);
-            psFree (detections);
             return false;
         }
@@ -30,9 +65,6 @@
     // otherwise, use the source moments to measure FWHM stats
     if (!psf && sources) {
-        if (!psphotMomentsStats (readout, recipe, sources)) {
+        if (!psphotMomentsStats (readout, sources)) {
             psError(PSPHOT_ERR_PROG, false, "Failed to measure Moment shape parameters");
-            psFree (psf);
-            psFree (sources);
-            psFree (detections);
             return false;
         }
@@ -40,4 +72,5 @@
 
     // Check to see if the image quality was measured
+    // XXX not sure we want / need this test
     if (!psf) {
         bool mdok;                      // Status of MD lookup
@@ -45,24 +78,21 @@
         if (!mdok || nIQ <= 0) {
             psError(PSPHOT_ERR_DATA, false, "Unable to measure image quality");
-            psFree (psf);
-            psFree (sources);
-            psFree (detections);
             return false;
         }
     }
 
+    // create an output header with stats results currently saved on readout->analysis
+    psMetadata *header = psphotDefineHeader (readout->analysis);
 
     // write NSTARS to the image header
-    psphotSetHeaderNstars (recipe, sources);
-
-    // create an output header with stats results
-    psMetadata *header = psphotDefineHeader (recipe);
+    psphotSetHeaderNstars (header, sources);
 
     // save the results of the analysis
+    // this should happen way up stream (when needed?)
     psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.HEADER",  PS_DATA_METADATA, "header stats", header);
-    if (sources) {
-        psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY, "psphot sources", sources);
-    }
+
     if (psf) {
+	// XXX this seems a little silly : we saved the psf on readout->analysis above, but now
+	// we are moving it to chip->analysis.
         // save the psf for possible output.  if there was already an entry, it was loaded from external sources
         // the new one may have been updated or modified, so replace the existing entry.  We
@@ -79,12 +109,5 @@
     }
 
-    // XXX move this to top of loop?
-    pmKapaClose ();
-
-    psFree (detections);
-    psFree (psf);
     psFree (header);
-    psFree (sources);
-
     return true;
 }
Index: trunk/psphot/src/psphotReadoutFindPSF.c
===================================================================
--- trunk/psphot/src/psphotReadoutFindPSF.c	(revision 26618)
+++ trunk/psphot/src/psphotReadoutFindPSF.c	(revision 26894)
@@ -7,26 +7,12 @@
     psTimerStart ("psphotReadout");
 
-    // select the current recipe
-    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
-    if (!recipe) {
-        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
-        return false;
-    }
-
-    // set the photcode for this image
-    if (!psphotAddPhotcode(recipe, config, view, "PSPHOT.INPUT")) {
+    // set the photcode for the PSPHOT.INPUT
+    if (!psphotAddPhotcode(config, view)) {
         psError(PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
         return false;
     }
 
-    // find the currently selected readout
-    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
-    PS_ASSERT_PTR_NON_NULL (readout, false);
-
     // Generate the mask and variance images, including the user-defined analysis region of interest
-    psphotSetMaskAndVariance (config, readout, recipe);
-
-    // display the image, weight, mask (ch 1,2,3)
-    psphotVisualShowImage (readout);
+    psphotSetMaskAndVariance (config, view);
 
     // Note that in this implementation, we do NOT model the background and we do not
@@ -34,40 +20,62 @@
 
     // include externally-supplied sources (supplied as PSPHOT.INPUT.CMF)
-    pmDetections *detections = psphotDetectionsFromSources (config, inSources);
-    if (!detections || !detections->peaks) {
+    // XXX we assume a single set of input sources is supplied
+    if (!psphotDetectionsFromSources (config, view, inSources)) {
         psError(PSPHOT_ERR_ARGUMENTS, true, "Can't find PSF stars");
-        return psphotReadoutCleanup(config, readout, recipe, detections, NULL, NULL);
+        return psphotReadoutCleanup(config, view);
     }
 
-    // construct sources and measure basic stats (moments, local sky)
-    psArray *sources = psphotSourceStats(config, readout, detections, true);
-    if (!sources) return false;
+    // construct detections->newSources and measure basic stats (moments, local sky)
+    if (!psphotSourceStats(config, view, true)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate sources");
+	return false;
+    }
 
-    // peak flux is wrong : set based on previous image
-    // use the peak measured in the moments analysis:
-    for (int i = 0; i < sources->n; i++) {
-      pmSource *source = sources->data[i];
-      source->peak->flux = source->moments->Peak;
+    // peak flux is wrong : use the peak measured in the moments analysis:
+    if (!psphotRepairLoadedSources(config, view)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to repair sources");
+	return false;
     }
 
     // classify sources based on moments, brightness (psf is not known)
-    if (!psphotRoughClass (readout, sources, recipe, false)) {
-        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
-        return psphotReadoutCleanup (config, readout, recipe, detections, NULL, sources);
+    if (!psphotRoughClass (config, view)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed to determine rough source class");
+        return psphotReadoutCleanup (config, view);
     }
 
-    pmPSF *psf = psphotChoosePSF(readout, sources, recipe);
-    if (!psf) {
+    if (!psphotImageQuality (config, view)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed to measure image quality");
+        return psphotReadoutCleanup(config, view);
+    }
+
+    if (!psphotChoosePSF(config, view)) {
         psError(PSPHOT_ERR_PSF, false, "Failed to construct a psf model");
-        psFree(sources);
-        return psphotReadoutCleanup(config, readout, recipe, detections, NULL, NULL);
+        return psphotReadoutCleanup(config, view);
     }
-    psphotVisualShowPSFModel(readout, psf);
+
+# if 0
+    // XXX if we want to determine the aperture residual correction here, we either
+    // need to carry it out of the PSF determination analysis above, or save the model
+    // fits from that analysis, or run the linear PSF fit for all objects currently in hand
+    // construct an initial model for each object, set the radius to fitRadius, set circular fit mask
+    psphotGuessModels (config, view);
+# endif
+
+    // merge the newly selected sources into the existing list
+    // NOTE: merge OLD and NEW
+    psphotMergeSources (config, view); 
+
+# if 0
+    // measure aperture photometry corrections
+    if (!psphotApResid (config, view)) {
+        psLogMsg ("psphot", 3, "failed on psphotApResid");
+        return psphotReadoutCleanup (config, view);
+    }
+# endif
 
     // drop the references to the image pixels held by each source
-    psphotSourceFreePixels(sources);
-    psFree(sources);
+    psphotSourceFreePixels(config, view);
 
     // create the exported-metadata and free local data
-    return psphotReadoutCleanup(config, readout, recipe, detections, psf, NULL);
+    return psphotReadoutCleanup(config, view);
 }
Index: trunk/psphot/src/psphotReadoutKnownSources.c
===================================================================
--- trunk/psphot/src/psphotReadoutKnownSources.c	(revision 26618)
+++ trunk/psphot/src/psphotReadoutKnownSources.c	(revision 26894)
@@ -1,32 +1,18 @@
 # include "psphotInternal.h"
 
-// in this psphotReadout-variant, we are only measuring the photometry for known sources,
-// using a PSF generated from this observation those sources
+// in this psphotReadout-variant, we are only measuring the photometry for known sources, using
+// a PSF generated for this observation from those sources
 bool psphotReadoutKnownSources(pmConfig *config, const pmFPAview *view, psArray *inSources) {
 
     psTimerStart ("psphotReadout");
 
-    // select the current recipe
-    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
-    if (!recipe) {
-        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
-        return false;
-    }
-
     // set the photcode for this image
-    if (!psphotAddPhotcode(recipe, config, view, "PSPHOT.INPUT")) {
+    if (!psphotAddPhotcode(config, view)) {
         psError(PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
         return false;
     }
 
-    // find the currently selected readout
-    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
-    PS_ASSERT_PTR_NON_NULL (readout, false);
-
     // Generate the mask and weight images, including the user-defined analysis region of interest
-    psphotSetMaskAndVariance (config, readout, recipe);
-
-    // display the image, weight, mask (ch 1,2,3)
-    psphotVisualShowImage (readout);
+    psphotSetMaskAndVariance (config, view);
 
     // Note that in this implementation, we do NOT model the background and we do not
@@ -34,58 +20,55 @@
 
     // include externally-supplied sources (supplied as PSPHOT.INPUT.CMF)
-    pmDetections *detections = psphotDetectionsFromSources (config, inSources);
-    if (!detections || !detections->peaks) {
+    if (!psphotDetectionsFromSources (config, view, inSources)) {
         psError(PSPHOT_ERR_ARGUMENTS, true, "Can't find PSF stars");
-        return psphotReadoutCleanup(config, readout, recipe, detections, NULL, NULL);
+        return psphotReadoutCleanup(config, view);
     }
 
     // construct sources and measure basic stats
-    psArray *sources = psphotSourceStats (config, readout, detections, true);
-    if (!sources) return false;
+    if (!psphotSourceStats (config, view, true)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate sources");
+	return false;
+    }
 
-    // peak flux is wrong : set based on previous image
-    // use the peak measured in the moments analysis:
-    for (int i = 0; i < sources->n; i++) {
-        pmSource *source = sources->data[i];
-        source->peak->flux = source->moments->Peak;
+    // peak flux is wrong : use the peak measured in the moments analysis:
+    if (!psphotRepairLoadedSources(config, view)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to repair sources");
+	return false;
     }
 
     // classify sources based on moments, brightness (psf is not known)
-    if (!psphotRoughClass (readout, sources, recipe, false)) {
-        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
-        return psphotReadoutCleanup (config, readout, recipe, detections, NULL, sources);
+    if (!psphotRoughClass (config, view)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed to determine rough source class");
+        return psphotReadoutCleanup(config, view);
     }
 
-    pmPSF *psf = psphotChoosePSF (readout, sources, recipe);
-    if (!psf) {
-        psLogMsg ("psphot", 3, "failure to construct a psf model");
-        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    if (!psphotChoosePSF (config, view)) {
+        psError(PSPHOT_ERR_PSF, false, "Failed to construct a psf model");
+        return psphotReadoutCleanup(config, view);
     }
-    psphotVisualShowPSFModel (readout, psf);
 
     // construct an initial model for each object
-    psphotGuessModels (config, readout, sources, psf);
+    psphotGuessModels (config, view);
+
+    // merge the newly selected sources into the existing list
+    // NOTE: merge OLD and NEW
+    psphotMergeSources (config, view); 
 
     // linear PSF fit to source peaks
-    psphotFitSourcesLinear (readout, sources, recipe, psf, FALSE);
-
-    // We have to place these visualizations here because the models are not realized until
-    // psphotGuessModels or fitted until psphotFitSourcesLinear.
-    psphotVisualShowPSFStars (recipe, psf, sources);
-    psphotVisualShowSatStars (recipe, psf, sources);
+    psphotFitSourcesLinear (config, view, false);
 
     // measure aperture photometry corrections
-    if (!psphotApResid (config, readout, sources, psf)) {
+    if (!psphotApResid (config, view)) {
         psLogMsg ("psphot", 3, "failed on psphotApResid");
-        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+        return psphotReadoutCleanup(config, view);
     }
 
     // calculate source magnitudes
-    psphotMagnitudes(config, readout, view, sources, psf);
+    psphotMagnitudes(config, view);
 
     // drop the references to the image pixels held by each source
-    psphotSourceFreePixels (sources);
+    psphotSourceFreePixels (config, view);
 
     // create the exported-metadata and free local data
-    return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    return psphotReadoutCleanup(config, view);
 }
Index: trunk/psphot/src/psphotReadoutMinimal.c
===================================================================
--- trunk/psphot/src/psphotReadoutMinimal.c	(revision 26618)
+++ trunk/psphot/src/psphotReadoutMinimal.c	(revision 26894)
@@ -1,7 +1,7 @@
 # include "psphotInternal.h"
 
-// this badly-named function performs photometry assuming (a) a supplied PSF, (b)
-// background subtraction, (c) linear psf-model fits only.  it is currently only being
-// used by ppSub.
+// this badly-named function performs photometry assuming (a) a supplied PSF, (b) background
+// subtraction, (c) linear psf-model fits only, (d) a prior analysis has supplied the moments
+// window parameters.  It is currently only being used by ppSub.
 
 // NOTE: ppSub needs to perform extended source analysis for comets and trails.
@@ -17,87 +17,70 @@
     pmModelClassSetLimits(PM_MODEL_LIMITS_LAX);
 
-    // select the current recipe
-    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
-    if (!recipe) {
-        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
-        return false;
-    }
-
     // set the photcode for this image
-    if (!psphotAddPhotcode(recipe, config, view, "PSPHOT.INPUT")) {
+    if (!psphotAddPhotcode(config, view)) {
         psError(PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
         return false;
     }
 
-    // find the currently selected readout
-    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
-    PS_ASSERT_PTR_NON_NULL (readout, false);
+    // Generate the mask and weight images, including the user-defined analysis region of interest
+    psphotSetMaskAndVariance (config, view);
 
-    // Generate the mask and weight images, including the user-defined analysis region of interest
-    psphotSetMaskAndVariance (config, readout, recipe);
-
-    // display the image, weight, mask (ch 1,2,3)
-    psphotVisualShowImage (readout);
-
-    // load the psf model, if suppled.  FWHM_X,FWHM_Y,etc are saved in the recipe
-    pmPSF *psf = psphotLoadPSF (config, view, recipe);
-    if (!psf) {
+    // load the psf model, if suppled.  FWHM_X,FWHM_Y,etc are saved on readout->analysis
+    if (!psphotLoadPSF (config, view)) {
       psError (PSPHOT_ERR_CONFIG, false, "missing psf model");
-      return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+      return psphotReadoutCleanup (config, view);
     }
 
-    // find the detections (by peak and/or footprint) in the image.
-    pmDetections *detections = pmDetectionsAlloc(); // New detections; allocated to ensure pass=2
-    detections = psphotFindDetections(detections, readout, recipe);
-    if (!detections) {
-        psLogMsg ("psphot", 3, "unable to find detections in this image");
-        return psphotReadoutCleanup (config, readout, recipe, detections, psf, NULL);
+    // find the detections (by peak and/or footprint) in the image. (final pass)
+    if (!psphotFindDetections(config, view, false)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "failure in peak analysis");
+        return psphotReadoutCleanup (config, view);
     }
 
-    // construct sources and measure basic stats
-    psArray *sources = psphotSourceStats (config, readout, detections, true);
-    if (!sources) return false;
+    // construct sources and measure basic stats (saved on detections->newSources)
+    if (!psphotSourceStats (config, view, false)) { // pass 1
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate sources");
+        return psphotReadoutCleanup (config, view);
+    }
 
     // find blended neighbors of very saturated stars
-    // XXX merge this with Basic Deblend?
-    psphotDeblendSatstars (readout, sources, recipe);
+    psphotDeblendSatstars (config, view);
 
     // mark blended peaks PS_SOURCE_BLEND
-    if (!psphotBasicDeblend (sources, recipe)) {
+    if (!psphotBasicDeblend (config, view)) {
         psLogMsg ("psphot", 3, "failed on deblend analysis");
-        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+        return psphotReadoutCleanup (config, view);
     }
 
     // classify sources based on moments, brightness (use supplied psf shape parameters)
-    if (!psphotRoughClass (readout, sources, recipe, true)) {
+    if (!psphotRoughClass (config, view)) {
         psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
-        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+        return psphotReadoutCleanup (config, view);
     }
 
     // construct an initial model for each object
-    psphotGuessModels (config, readout, sources, psf);
+    psphotGuessModels (config, view);
+
+    // merge the newly selected sources into the existing list
+    psphotMergeSources (config, view);
 
     // linear PSF fit to source peaks
-    psphotFitSourcesLinear (readout, sources, recipe, psf, FALSE);
-
-    // We have to place these visualizations here because the models are not realized until
-    // psphotGuessModels or fitted until psphotFitSourcesLinear.
-    psphotVisualShowPSFStars (recipe, psf, sources);
-    psphotVisualShowSatStars (recipe, psf, sources);
+    psphotFitSourcesLinear (config, view, false);
 
 // XXX eventually, add the extended source fits here
 # if (0)
     // measure source size for the remaining sources
-    psphotSourceSize (config, readout, sources, recipe, 0);
+    psphotSourceSize (config, view);
 
-    psphotExtendedSourceAnalysis (readout, sources, recipe);
+    psphotExtendedSourceAnalysis (config, view);
 
-    psphotExtendedSourceFits (readout, sources, recipe);
+    psphotExtendedSourceFits (config, view);
 # endif
 
     // calculate source magnitudes
-    psphotMagnitudes(config, readout, view, sources, psf);
+    psphotMagnitudes(config, view);
 
-    if (!psphotEfficiency(config, readout, view, psf, recipe, sources)) {
+    // XXX ensure this is measured if the analysis succeeds (even if quality is low)
+    if (!psphotEfficiency(config, view)) {
         psErrorStackPrint(stderr, "Unable to determine detection efficiencies from fake sources");
         psErrorClear();
@@ -105,7 +88,7 @@
 
     // drop the references to the image pixels held by each source
-    psphotSourceFreePixels (sources);
+    psphotSourceFreePixels (config, view);
 
     // create the exported-metadata and free local data
-    return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    return psphotReadoutCleanup(config, view);
 }
Index: trunk/psphot/src/psphotReplaceUnfit.c
===================================================================
--- trunk/psphot/src/psphotReplaceUnfit.c	(revision 26618)
+++ trunk/psphot/src/psphotReplaceUnfit.c	(revision 26894)
@@ -22,5 +22,27 @@
 }
 
-bool psphotReplaceAllSources (psArray *sources, psMetadata *recipe) {
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotReplaceAllSources (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotReplaceAllSourcesReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to replace all sources for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotReplaceAllSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
 
     bool status;
@@ -29,7 +51,20 @@
     psTimerStart ("psphot.replace");
 
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
     // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
     psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
-    assert (maskVal);
+    psAssert (maskVal, "missing mask value?");
 
     for (int i = 0; i < sources->n; i++) {
@@ -67,42 +102,2 @@
     return true;
 }
-
-# if (0)
-// add source, if the source has been subtracted; do not modify state
-bool psphotAddWithTest (pmSource *source, bool useState, psImageMaskType maskVal) {
-
-    // what is current state? (true : add; false : sub)
-    bool state = !(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
-    if (state && useState) return true;
-
-    pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
-    return true;
-}
-
-// sub source, if the source has been added; do not modify state
-bool psphotSubWithTest (pmSource *source, bool useState, psImageMaskType maskVal) {
-
-    // what is current state? (true : sub; false : add)
-    bool state = (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
-    if (state && useState) return true;
-
-    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
-    return true;
-}
-
-// add or sub source to match recorded state: supply current state as true (add) or false (sub)
-bool psphotSetState (pmSource *source, bool curState, psImageMaskType maskVal) {
-
-    // what is desired state? (true : add; false : sub)
-    bool newState = !(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
-    if (curState == newState) return true;
-
-    if (curState && !newState) {
-        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
-    }
-    if (newState && !curState) {
-        pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
-    }
-    return true;
-}
-# endif
Index: trunk/psphot/src/psphotRoughClass.c
===================================================================
--- trunk/psphot/src/psphotRoughClass.c	(revision 26618)
+++ trunk/psphot/src/psphotRoughClass.c	(revision 26894)
@@ -7,12 +7,55 @@
         } }
 
-bool psphotRoughClassRegion (int nRegion, psRegion *region, psArray *sources, psMetadata *target, psMetadata *recipe, const bool havePSF);
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotRoughClass (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
 
-// 2006.02.02 : no leaks
-bool psphotRoughClass (pmReadout *readout, psArray *sources, psMetadata *recipe, const bool havePSF) {
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotRoughClassReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on rough classification for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotRoughClassReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
 
     bool status;
 
     psTimerStart ("psphot.rough");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // if we have a PSF, use the existing PSF clump region below
+    bool havePSF = false;
+    if (psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF")) {
+	havePSF = true;
+    }
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping rough classification");
+	return true;
+    }
 
     // we make this measurement on a NxM grid of regions across the readout
@@ -75,5 +118,26 @@
         // determine the PSF parameters from the source moment values
         // XXX why not save the psfClump as a PTR?
-        psfClump = pmSourcePSFClump (region, sources, recipe);
+
+	float PSF_SN_LIM = psMetadataLookupF32(&status, recipe, "PSF_SN_LIM"); psAssert (status, "missing PSF_SN_LIM");
+	float MOMENTS_AR_MAX = psMetadataLookupF32(&status, recipe, "MOMENTS_AR_MAX"); psAssert (status, "missing MOMENTS_AR_MAX");
+
+	float PSF_CLUMP_GRID_SCALE = psMetadataLookupF32(&status, analysis, "PSF_CLUMP_GRID_SCALE");
+	if (!status) {
+	    PSF_CLUMP_GRID_SCALE = psMetadataLookupF32(&status, recipe, "PSF_CLUMP_GRID_SCALE");
+	    psAssert (status, "missing PSF_CLUMP_GRID_SCALE");
+	}
+	float MOMENTS_SX_MAX = psMetadataLookupF32(&status, analysis, "MOMENTS_SX_MAX");
+	if (!status) {
+	    MOMENTS_SX_MAX = psMetadataLookupF32(&status, recipe, "MOMENTS_SX_MAX");
+	    psAssert (status, "missing MOMENTS_SX_MAX");
+	}
+	float MOMENTS_SY_MAX = psMetadataLookupF32(&status, analysis, "MOMENTS_SY_MAX");
+	if (!status) {
+	    MOMENTS_SY_MAX = psMetadataLookupF32(&status, recipe, "MOMENTS_SY_MAX");
+	    psAssert (status, "missing MOMENTS_SY_MAX");
+	}
+
+        psfClump = pmSourcePSFClump (NULL, region, sources, PSF_SN_LIM, PSF_CLUMP_GRID_SCALE, MOMENTS_SX_MAX, MOMENTS_SY_MAX, MOMENTS_AR_MAX);
+
         psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.X",  PS_META_REPLACE, "psf clump center", psfClump.X);
         psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.Y",  PS_META_REPLACE, "psf clump center", psfClump.Y);
@@ -103,6 +167,10 @@
     psLogMsg ("psphot", 3, "psf clump DX, DY: %f, %f\n", psfClump.dX, psfClump.dY);
 
+    // get basic parameters, or set defaults
+    float PSF_SN_LIM = psMetadataLookupF32 (&status, recipe, "PSF_SN_LIM"); psAssert (status, "missing PSF_SN_LIM");
+    float PSF_CLUMP_NSIGMA = psMetadataLookupF32 (&status, recipe, "PSF_CLUMP_NSIGMA"); psAssert (status, "missing PSF_CLUMP_NSIGMA");
+
     // group into STAR, COSMIC, EXTENDED, SATURATED, etc.
-    if (!pmSourceRoughClass (region, sources, recipe, psfClump, maskSat)) {
+    if (!pmSourceRoughClass (region, sources, PSF_SN_LIM, PSF_CLUMP_NSIGMA, psfClump, maskSat)) {
         psError(PSPHOT_ERR_PROG, false, "programming error calling pmSourceRoughClass");
         return false;
Index: trunk/psphot/src/psphotSetThreads.c
===================================================================
--- trunk/psphot/src/psphotSetThreads.c	(revision 26618)
+++ trunk/psphot/src/psphotSetThreads.c	(revision 26894)
@@ -25,5 +25,5 @@
     psFree(task);
 
-    task = psThreadTaskAlloc("PSPHOT_SOURCE_STATS", 5);
+    task = psThreadTaskAlloc("PSPHOT_SOURCE_STATS", 10);
     task->function = &psphotSourceStats_Threaded;
     psThreadTaskAdd(task);
Index: trunk/psphot/src/psphotSignificanceImage.c
===================================================================
--- trunk/psphot/src/psphotSignificanceImage.c	(revision 26618)
+++ trunk/psphot/src/psphotSignificanceImage.c	(revision 26894)
@@ -22,7 +22,8 @@
     }
 
+    // if we have already determined the PSF model, then we have a better idea how to smooth this image
     bool statusMajor, statusMinor;
-    float fwhmMajor = psMetadataLookupF32(&statusMajor, recipe, "FWHM_MAJ");
-    float fwhmMinor = psMetadataLookupF32(&statusMinor, recipe, "FWHM_MIN");
+    float fwhmMajor = psMetadataLookupF32(&statusMajor, readout->analysis, "FWHM_MAJ");
+    float fwhmMinor = psMetadataLookupF32(&statusMinor, readout->analysis, "FWHM_MIN");
     if (statusMajor && statusMinor) {
         // if we know the FHWM, use that to set the smoothing kernel (XXX allow an optional override?)
@@ -43,6 +44,5 @@
     }
     // record the actual smoothing sigma
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "SIGMA_SMOOTH", PS_META_REPLACE, "Smoothing sigma for detections",
-                     SIGMA_SMTH);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "SIGMA_SMOOTH", PS_META_REPLACE, "Smoothing sigma for detections", SIGMA_SMTH);
 
     // smooth the image, applying the mask as we go
@@ -59,6 +59,5 @@
     // effective per-pixel variance is maintained.
     psImage *smooth_wt = psImageCopy(NULL, readout->variance, PS_TYPE_F32);
-    psImageSmoothMask_Threaded(smooth_wt, smooth_wt, readout->mask, maskVal, SIGMA_SMTH * M_SQRT1_2,
-                      NSIGMA_SMTH, minGauss);
+    psImageSmoothMask_Threaded(smooth_wt, smooth_wt, readout->mask, maskVal, SIGMA_SMTH * M_SQRT1_2, NSIGMA_SMTH, minGauss);
     psLogMsg("psphot", PS_LOG_MINUTIA, "smooth variance: %f sec\n", psTimerMark("psphot.smooth"));
 
@@ -77,14 +76,11 @@
     // Calculate correction factor for the covariance produced by the (potentially multiple) smoothing
     psKernel *kernel = psImageSmoothKernel(SIGMA_SMTH, NSIGMA_SMTH); // Kernel used for smoothing
-    psKernel *covar = psImageCovarianceCalculate(kernel, readout->covariance); // Covariance matrix
+    float factor = 1.0 / psImageCovarianceCalculateFactor(kernel, readout->covariance);
     psFree(kernel);
-    float factor = 1.0 / psImageCovarianceFactor(covar);
-    psFree(covar);
 
     // record the effective area and significance scaling factor
     float effArea = 8.0 * M_PI * PS_SQR(SIGMA_SMTH);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "EFFECTIVE_AREA", PS_META_REPLACE, "Effective Area", effArea);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "SIGNIFICANCE_SCALE_FACTOR", PS_META_REPLACE,
-                     "Signicance scale factor", factor);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "EFFECTIVE_AREA", PS_META_REPLACE, "Effective Area", effArea);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "SIGNIFICANCE_SCALE_FACTOR", PS_META_REPLACE, "Signicance scale factor", factor);
 
     // XXX multithread this if needed
Index: trunk/psphot/src/psphotSkyReplace.c
===================================================================
--- trunk/psphot/src/psphotSkyReplace.c	(revision 26618)
+++ trunk/psphot/src/psphotSkyReplace.c	(revision 26894)
@@ -1,13 +1,33 @@
 # include "psphotInternal.h"
+
+bool psphotSkyReplace (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotSkyReplaceReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to replace sky for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
 
 // XXX make this an option?
 // in order to  successfully replace the sky, we must define a corresponding file...
-bool psphotSkyReplace (pmConfig *config, const pmFPAview *view) {
+bool psphotSkyReplaceReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index) {
 
     psTimerStart ("psphot.skyreplace");
 
     // find the currently selected readout
-    pmReadout *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
-    if (readout == NULL) psAbort("input not defined");
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
 
     // select background pixels, from output background file, or create
Index: trunk/psphot/src/psphotSourceFits.c
===================================================================
--- trunk/psphot/src/psphotSourceFits.c	(revision 26618)
+++ trunk/psphot/src/psphotSourceFits.c	(revision 26894)
@@ -219,4 +219,7 @@
     double chiTrend;
     pmModel *ONE = NULL;
+    pmSource *tmpSrc = NULL;
+    pmModel *EXT = NULL;
+    psArray *DBL = NULL;
 
     // skip the source if we don't think it is extended
@@ -232,5 +235,5 @@
     // at this stage, skip Gaussian windowing, and do not clip pixels by S/N
     // this uses the footprint to judge both radius and aperture?
-    if (!pmSourceMoments (source, radius, 0.0, 0.0)) return false;
+    if (!pmSourceMoments (source, radius, 0.0, 0.0, maskVal)) return false;
 
     psTrace ("psphot", 5, "trying blob...\n");
@@ -240,9 +243,10 @@
 
     // this temporary source is used as a place-holder by the psphotEval functions below
-    pmSource *tmpSrc = pmSourceAlloc ();
+    tmpSrc = pmSourceAlloc ();
 
     // XXX need to handle failures better here
-    pmModel *EXT = psphotFitEXT (readout, source, modelTypeEXT, maskVal, markVal);
-    if (!isfinite(EXT->params->data.F32[PM_PAR_I0])) psAbort("nan in fit");
+    EXT = psphotFitEXT (readout, source, modelTypeEXT, maskVal, markVal);
+    if (!EXT) goto escape;
+    if (!isfinite(EXT->params->data.F32[PM_PAR_I0])) goto escape;
 
     okEXT = psphotEvalEXT (tmpSrc, EXT);
@@ -250,5 +254,8 @@
 
     // DBL will always be defined, but DBL->data[n] might not
-    psArray *DBL = psphotFitDBL (readout, source, maskVal, markVal);
+    DBL = psphotFitDBL (readout, source, maskVal, markVal);
+    if (!DBL) goto escape;
+    if (!DBL->n) goto escape;
+
     okDBL  = psphotEvalDBL (tmpSrc, DBL->data[0]);
     okDBL &= psphotEvalDBL (tmpSrc, DBL->data[1]);
@@ -362,4 +369,11 @@
     psFree (DBL);
     return true;
+
+escape:
+    psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); 
+    psFree (tmpSrc);
+    psFree (EXT);
+    psFree (DBL);
+    return false;
 }
 
@@ -425,5 +439,8 @@
     // use the source moments, etc to guess basic model parameters
     pmModel *EXT = pmSourceModelGuess (source, modelType);
-    PS_ASSERT (EXT, NULL);
+    if (!EXT) {
+	psTrace ("psphot", 5, "failed to generate a model for source: moments: %f %f\n", source->moments->Mxx, source->moments->Myy);
+	return NULL;
+    }
 
     if ((source->moments->Mxx < 1e-3) || (source->moments->Myy < 1e-3)) {
Index: trunk/psphot/src/psphotSourceFreePixels.c
===================================================================
--- trunk/psphot/src/psphotSourceFreePixels.c	(revision 26618)
+++ trunk/psphot/src/psphotSourceFreePixels.c	(revision 26894)
@@ -1,7 +1,37 @@
 # include "psphotInternal.h"
 
-void psphotSourceFreePixels (psArray *sources) {
+bool psphotSourceFreePixels (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
 
-    if (!sources) return;
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotSourceFreePixelsReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to free source pixels for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotSourceFreePixelsReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index) {
+
+    bool status;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
 
     for (int i = 0; i < sources->n; i++) {
@@ -9,4 +39,4 @@
 	pmSourceFreePixels (source);
     }
-    return;
+    return true;
 }
Index: trunk/psphot/src/psphotSourceSize.c
===================================================================
--- trunk/psphot/src/psphotSourceSize.c	(revision 26618)
+++ trunk/psphot/src/psphotSourceSize.c	(revision 26894)
@@ -15,10 +15,11 @@
 } psphotSourceSizeOptions;
 
+// local functions:
 bool psphotSourceSizePSF (psphotSourceSizeOptions *options, psArray *sources, pmPSF *psf);
 bool psphotSourceClass (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, psphotSourceSizeOptions *options);
 bool psphotSourceClassRegion (psRegion *region, pmPSFClump *psfClump, psArray *sources, psMetadata *recipe, pmPSF *psf, psphotSourceSizeOptions *options);
 bool psphotSourceSizeCR (pmReadout *readout, psArray *sources, psphotSourceSizeOptions *options);
-bool psphotMaskCosmicRay (psImage *mask, pmSource *source, psImageMaskType maskVal, psImageMaskType crMask);
-bool psphotMaskCosmicRayIsophot (pmSource *source, psImageMaskType maskVal, psImageMaskType crMask);
+bool psphotMaskCosmicRay (pmReadout *readout, pmSource *source, psImageMaskType maskVal);
+bool psphotMaskCosmicRayFootprintCheck (psArray *sources);
 
 // we need to call this function after sources have been fitted to the PSF model and
@@ -28,6 +29,28 @@
 // deviation from the psf model at the r = FWHM/2 position
 
-// XXX use an internal flag to mark sources which have already been measured
-bool psphotSourceSize(pmConfig *config, pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, long first)
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotSourceSize (pmConfig *config, const pmFPAview *view, bool getPSFsize)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotSourceSizeReadout (config, view, "PSPHOT.INPUT", i, recipe, getPSFsize)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on source size analysis for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+// this function use an internal flag to mark sources which have already been measured
+bool psphotSourceSizeReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool getPSFsize)
 {
     bool status;
@@ -35,4 +58,25 @@
 
     psTimerStart ("psphot.size");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping source size");
+	return true;
+    }
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (psf, "missing psf?");
 
     // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
@@ -70,25 +114,530 @@
 
     // We are using the value psfMag - 2.5*log10(moment.Sum) as a measure of the extendedness
-    // of and object.  We need to model this distribution for the PSF stars before we can test
+    // of an object.  We need to model this distribution for the PSF stars before we can test
     // the significance for a specific object
     // XXX move this to the code that generates the PSF?
     // XXX store the results on pmPSF?
-    psphotSourceSizePSF (&options, sources, psf);
+
+    // XXX this should only be done on the first pass (ie, if we have newSources or allSources?)
+    if (getPSFsize) {
+	psphotSourceSizePSF (&options, sources, psf);
+    }
 
     // classify the sources based on ApResid and Moments (extended sources)
+    // NOTE: only sources not already measured !(source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED)
     psphotSourceClass(readout, sources, recipe, psf, &options);
 
+    // NOTE: only sources not already measured !(source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED)
     psphotSourceSizeCR (readout, sources, &options);
 
-    psLogMsg ("psphot.size", PS_LOG_INFO, "measure source sizes for %ld sources: %f sec\n", sources->n - first, psTimerMark ("psphot.size"));
+    // XXX fix this (was source->n  - first)
+    psLogMsg ("psphot.size", PS_LOG_INFO, "measure source sizes for %ld sources: %f sec\n", sources->n, psTimerMark ("psphot.size"));
 
     psphotVisualPlotSourceSize (recipe, readout->analysis, sources);
     psphotVisualShowSourceSize (readout, sources);
     psphotVisualPlotApResid (sources, options.ApResid, options.ApSysErr);
-
-    return true;
-}
-
-bool psphotMaskCosmicRay (psImage *mask, pmSource *source, psImageMaskType maskVal, psImageMaskType crMask) {
+    psphotVisualShowSatStars (recipe, psf, sources);
+
+    return true;
+}
+
+// model the apmifit distribution for the psf stars:
+bool psphotSourceSizePSF (psphotSourceSizeOptions *options, psArray *sources, pmPSF *psf) {
+
+    // select stats from the psf stars
+    psVector *Ap = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *ApErr = psVectorAllocEmpty (100, PS_TYPE_F32);
+
+    psImageMaskType maskVal = options->maskVal | options->markVal;
+
+    // XXX  why PHOT_WEIGHT??
+    pmSourcePhotometryMode photMode = PM_SOURCE_PHOT_WEIGHT;
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+        // replace object in image
+        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, options->maskVal);
+        }
+
+        // clear the mask bit and set the circular mask pixels
+        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(options->markVal));
+        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, source->apRadius, "OR", options->markVal);
+
+        // XXX can we test if psfMag is set and calculate only if needed?
+        pmSourceMagnitudes (source, psf, photMode, maskVal); // maskVal includes markVal
+
+        // clear the mask bit
+        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(options->markVal));
+
+        // re-subtract the object, leave local sky
+        pmSourceSub (source, PM_MODEL_OP_FULL, options->maskVal);
+
+        float apMag = -2.5*log10(source->moments->Sum);
+        float dMag = source->psfMag - apMag;
+
+        psVectorAppend (Ap, dMag);
+        psVectorAppend (ApErr, source->errMag);
+    }
+
+    // model the distribution as a mean or median value and a systematic error from that value:
+    psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
+    psVectorStats (stats, Ap, NULL, NULL, 0);
+
+    psVector *dAp = psVectorAlloc (Ap->n, PS_TYPE_F32);
+    for (int i = 0; i < Ap->n; i++) {
+        dAp->data.F32[i] = Ap->data.F32[i] - stats->robustMedian;
+    }
+
+    options->ApResid = stats->robustMedian;
+    options->ApSysErr = psVectorSystematicError(dAp, ApErr, 0.05);
+    // XXX this is quite arbitrary...
+    if (!isfinite(options->ApSysErr)) options->ApSysErr = 0.01;
+    psLogMsg ("psphot", PS_LOG_DETAIL, "psf - Sum: %f +/- %f\n", options->ApResid, options->ApSysErr);
+
+    psFree (Ap);
+    psFree (ApErr);
+    psFree (stats);
+    psFree (dAp);
+
+    return true;
+}
+
+// classify sources based on the combination of psf-mag, Mxx, Myy
+bool psphotSourceClass (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, psphotSourceSizeOptions *options) {
+
+    bool status;
+    pmPSFClump psfClump;
+    char regionName[64];
+
+    psLogMsg("psModules.objects", PS_LOG_INFO, "Source Size classifications: %4s %4s %4s %4s %4s %4s", "Npsf", "Next", "Nsat", "Ncr", "Nmiss", "Nskip");
+
+    int nRegions = psMetadataLookupS32 (&status, readout->analysis, "PSF.CLUMP.NREGIONS");
+    for (int i = 0; i < nRegions; i ++) {
+        snprintf (regionName, 64, "PSF.CLUMP.REGION.%03d", i);
+        psMetadata *regionMD = psMetadataLookupPtr (&status, readout->analysis, regionName);
+        psAssert (regionMD, "regions must be defined by earlier call to psphotRoughClassRegion");
+
+        psRegion *region = psMetadataLookupPtr (&status, regionMD, "REGION");
+        psAssert (region, "regions must be defined by earlier call to psphotRoughClassRegion");
+
+        // pull FWHM_X,Y from the recipe, use to define psfClump.X,Y
+        psfClump.X  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.X");   psAssert (status, "missing PSF.CLUMP.X");
+        psfClump.Y  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.Y");   psAssert (status, "missing PSF.CLUMP.Y");
+        psfClump.dX = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DX");  psAssert (status, "missing PSF.CLUMP.DX");
+        psfClump.dY = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DY");  psAssert (status, "missing PSF.CLUMP.DY");
+
+        if ((psfClump.X < 0) || (psfClump.Y < 0) || !psfClump.X || !psfClump.Y || isnan(psfClump.X) || isnan(psfClump.Y)) {
+            psLogMsg ("psphot", 4, "Failed to find a valid PSF clump for region %f,%f - %f,%f\n", region->x0, region->y0, region->x1, region->y1);
+            continue;
+        }
+
+        if (!psphotSourceClassRegion (region, &psfClump, sources, recipe, psf, options)) {
+            psLogMsg ("psphot", 4, "Failed to determine source classification for region %f,%f - %f,%f\n", region->x0, region->y0, region->x1, region->y1);
+            continue;
+        }
+    }
+
+    return true;
+}
+
+bool psphotSourceClassRegion (psRegion *region, pmPSFClump *psfClump, psArray *sources, psMetadata *recipe, pmPSF *psf, psphotSourceSizeOptions *options) {
+
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+
+    int Nsat  = 0;
+    int Next  = 0;
+    int Npsf  = 0;
+    int Ncr   = 0;
+    int Nmiss = 0;
+    int Nskip = 0;
+
+    pmSourceMode noMoments = PM_SOURCE_MODE_MOMENTS_FAILURE | PM_SOURCE_MODE_SKYVAR_FAILURE | PM_SOURCE_MODE_SKY_FAILURE | PM_SOURCE_MODE_BELOW_MOMENTS_SN;
+    pmSourcePhotometryMode photMode = PM_SOURCE_PHOT_WEIGHT;
+
+    psImageMaskType maskVal = options->maskVal | options->markVal;
+
+    for (psS32 i = 0 ; i < sources->n ; i++) {
+
+        pmSource *source = (pmSource *) sources->data[i];
+
+        // psfClumps are found for image subregions:
+        // skip sources not in this region
+        if (source->peak->x <  region->x0) continue;
+        if (source->peak->x >= region->x1) continue;
+        if (source->peak->y <  region->y0) continue;
+        if (source->peak->y >= region->y1) continue;
+
+        // skip source if it was already measured
+        if (source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED) {
+            psTrace("psphot", 7, "Not calculating source size since it has already been measured\n");
+            continue;
+        }
+
+        // source must have been subtracted
+        if (!(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED)) {
+            source->mode |= PM_SOURCE_MODE_SIZE_SKIPPED;
+            psTrace("psphot", 7, "Not calculating source size since source is not subtracted\n");
+            continue;
+        }
+
+        // we are basically classifying by moments; use the default if not found
+        psAssert (source->moments, "why is this source missing moments?");
+        if (source->mode & noMoments) {
+            Nskip ++;
+            continue;
+        }
+
+        psF32 Mxx = source->moments->Mxx;
+        psF32 Myy = source->moments->Myy;
+
+        // replace object in image
+        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, options->maskVal);
+        }
+
+        // clear the mask bit and set the circular mask pixels
+        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(options->markVal));
+        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, source->apRadius, "OR", options->markVal);
+
+        // XXX can we test if psfMag is set and calculate only if needed?
+        pmSourceMagnitudes (source, psf, photMode, maskVal); // maskVal includes markVal
+
+        // clear the mask bit
+        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(options->markVal));
+
+        // re-subtract the object, leave local sky
+        pmSourceSub (source, PM_MODEL_OP_FULL, options->maskVal);
+
+        float apMag = -2.5*log10(source->moments->Sum);
+        float dMag = source->psfMag - apMag;
+        float nSigma = (dMag - options->ApResid) / hypot(source->errMag, options->ApSysErr);
+
+        source->extNsigma = nSigma;
+        source->tmpFlags |= PM_SOURCE_TMPF_SIZE_MEASURED;
+
+        // Anything within this region is a probably PSF-like object. Saturated stars may land
+        // in this region, but are detected elsewhere on the basis of their peak value.
+        bool isPSF = ((fabs(nSigma) < options->nSigmaApResid) &&
+		      (fabs(Mxx - psfClump->X) < options->nSigmaMoments*psfClump->dX) &&
+		      (fabs(Myy - psfClump->Y) < options->nSigmaMoments*psfClump->dY));
+        if (isPSF) {
+	  psTrace("psphot.czw",4,"CLASS: %g %g\t%g %g  %g %g  %g %g\t%g %g\t%g PSF\t%g %g\n",
+		  source->peak->xf,source->peak->yf,Mxx,Myy,psfClump->X,psfClump->Y,psfClump->dX,psfClump->dY,apMag,dMag,nSigma,
+		  options->nSigmaApResid,options->nSigmaMoments);
+	  Npsf ++;
+	  continue;
+        }
+
+        // Defects may not always match CRs from peak curvature analysis
+        // Defects may also be marked as SATSTAR -- XXX deactivate this flag?
+        // XXX this rule is not great
+        if ((Mxx < psfClump->X) || (Myy < psfClump->Y)) {
+
+	  psTrace("psphot.czw",4,"CLASS: %g %g\t%g %g  %g %g  %g %g\t%g %g\t%g CR\t%g %g\n",
+		  source->peak->xf,source->peak->yf,Mxx,Myy,psfClump->X,psfClump->Y,psfClump->dX,psfClump->dY,apMag,dMag,nSigma,
+		  options->nSigmaApResid,options->nSigmaMoments);
+            source->mode |= PM_SOURCE_MODE_DEFECT;
+            Ncr ++;
+            continue;
+        }
+
+        // saturated star (determined in PSF fit).  These may also be saturated galaxies, or
+        // just large saturated regions.
+        if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	  psTrace("psphot.czw",4,"CLASS: %g %g\t%g %g  %g %g  %g %g\t%g %g\t%g SAT\t%g %g\n",
+		  source->peak->xf,source->peak->yf,Mxx,Myy,psfClump->X,psfClump->Y,psfClump->dX,psfClump->dY,apMag,dMag,nSigma,
+		  options->nSigmaApResid,options->nSigmaMoments);
+	  
+            Nsat ++;
+            continue;
+        }
+
+        // XXX allow the Mxx, Myy to be less than psfClump->X,Y (by some nSigma)?
+        bool isEXT = (nSigma > options->nSigmaApResid) || ((Mxx > psfClump->X) && (Myy > psfClump->Y));
+        if (isEXT) {
+	  psTrace("psphot.czw",4,"CLASS: %g %g\t%g %g  %g %g  %g %g\t%g %g\t%g Ext\t%g %g\n",
+		  source->peak->xf,source->peak->yf,Mxx,Myy,psfClump->X,psfClump->Y,psfClump->dX,psfClump->dY,apMag,dMag,nSigma,
+		  options->nSigmaApResid,options->nSigmaMoments);
+	  
+            source->mode |= PM_SOURCE_MODE_EXT_LIMIT;
+            Next ++;
+            continue;
+        }
+	psTrace("psphot.czw",4,"CLASS: %g %g\t%g %g  %g %g  %g %g\t%g %g\t%g Unk\t%g %g\n",
+		source->peak->xf,source->peak->yf,Mxx,Myy,psfClump->X,psfClump->Y,psfClump->dX,psfClump->dY,apMag,dMag,nSigma,
+		options->nSigmaApResid,options->nSigmaMoments);
+	
+        psWarning ("sourse size was missed for %f,%f : %f %f -- %f\n", source->peak->xf, source->peak->yf, Mxx, Myy, nSigma);
+        Nmiss ++;
+    }
+
+    psLogMsg("psModules.objects", PS_LOG_INFO, "Source Size classifications: %4d %4d %4d %4d %4d %4d", Npsf, Next, Nsat, Ncr, Nmiss, Nskip);
+
+    return true;
+}
+
+// given an object suspected to be a defect, generate a pixel mask using the Lapacian transform
+// if enough of the object is detected as 'sharp', consider the object a cosmic ray
+bool psphotSourceSizeCR (pmReadout *readout, psArray *sources, psphotSourceSizeOptions *options) {
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip source if it was already measured
+        if (source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED) {
+            psTrace("psphot", 7, "Not calculating source size since it has already been measured\n");
+            continue;
+        }
+
+        // Integer position of peak
+        int xPeak = source->peak->xf - source->pixels->col0 + 0.5;
+        int yPeak = source->peak->yf - source->pixels->row0 + 0.5;
+
+        // Skip sources which are too close to a boundary.  These are mostly caught as DEFECT
+        if (xPeak < 1 || xPeak > source->pixels->numCols - 2 ||
+            yPeak < 1 || yPeak > source->pixels->numRows - 2) {
+            psTrace("psphot", 7, "Not calculating crNsigma due to edge\n");
+	    //	    psTrace("psphot.czw", 2, "Not calculating crNsigma due to edge\n");
+            continue;
+        }
+	
+        // this source is thought to be a cosmic ray.  flag the detection and mask the pixels
+	if (source->mode & PM_SOURCE_MODE_DEFECT) {
+	    // XXX this is running slowly and is too agressive, but it more-or-less works
+	    // psphotMaskCosmicRayCZW(readout, source, options->crMask);
+	    
+	    // XXX these are the old versions which we are not using
+	    // psphotMaskCosmicRay (readout->mask, source, maskVal, crMask);
+	    // psphotMaskCosmicRay_Old (source, maskVal, crMask);
+        }
+    }
+    
+    // now that we have masked pixels associated with CRs, we can grow the mask
+    if (options->grow > 0) {
+        bool oldThreads = psImageConvolveSetThreads(true); // Old value of threading for psImageConvolveMask
+        psImage *newMask = psImageConvolveMask(NULL, readout->mask, options->crMask, options->crMask, -options->grow, options->grow, -options->grow, options->grow);
+        psImageConvolveSetThreads(oldThreads);
+        if (!newMask) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to grow CR mask");
+            return false;
+        }
+        psFree(readout->mask);
+        readout->mask = newMask;
+    }
+
+    // XXX test : save the mask image
+    if (0) {
+	psphotSaveImage (NULL, readout->mask,   "mask.fits");
+    }
+    return true;
+}
+
+// Comments by CZW 20091209 : Mechanics of how to identify CR pixels taken from "Cosmic-Ray
+// Rejection by Laplacian Edge Detection" by Pieter van Dokkum, arXiv:astro-ph/0108003.  This
+// does no repair or recovery of the CR pixels, it only masks them out.  My test code can be
+// found at /data/ipp031.0/watersc1/psphot.20091209/algo_check.c
+bool psphotMaskCosmicRay (pmReadout *readout, pmSource *source, psImageMaskType maskVal) {
+
+    // Get the actual images and information about the peak.
+    psImage *mask = readout->mask;
+    pmPeak *peak = source->peak;
+    pmFootprint *footprint = peak->footprint;
+
+    int xm = footprint->bbox.x0;
+    int xM = footprint->bbox.x1;
+    int ym = footprint->bbox.y0;
+    int yM = footprint->bbox.y1;
+
+    if (xm < 0) { xm = 0; }
+    if (ym < 0) { ym = 0; }
+    if (xM > mask->numCols) { xM = mask->numCols; }
+    if (yM > mask->numRows) { yM = mask->numRows; }
+    int dx = xM - xm;
+    int dy = yM - ym;
+
+    // Bounding boxes are inclusive of final pixel
+    // XXX ensure dx,dy do not become too large here
+    dx++;
+    dy++;
+
+    psImage *image= readout->image;
+    psImage *variance = readout->variance;
+      
+    int binning = 1;
+    float sigma_thresh = 2.0;
+    int iteration = 0;
+    int max_iter = 5;
+    float noise_factor = 5.0 / 4.0;  // Intrinsic to the Laplacian making noise spikes spikier.
+
+    // Temporary images.
+    psImage *mypix  = psImageAlloc(dx,dy,image->type.type);
+    psImage *myvar  = psImageAlloc(dx,dy,image->type.type);
+    psImage *binned = psImageAlloc(dx * binning,dy * binning,image->type.type);
+    psImage *conved = psImageAlloc(dx * binning,dy * binning,image->type.type);
+    psImage *edges  = psImageAlloc(dx,dy,image->type.type);
+    psImage *mymask = psImageAlloc(dx,dy,PS_TYPE_IMAGE_MASK);
+      
+    int x,y;
+    // Load my copy of things.
+    for (x = 0; x < dx; x++) {
+	for (y = 0; y < dy; y++) {
+	    psImageSet(mypix,x,y,psImageGet(image,x+xm,y+ym));
+	    psImageSet(myvar,x,y,psImageGet(variance,x+xm,y+ym));
+	    mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] = 0x00;
+	}
+    }
+    // Mask so I can see on the output image where the footprint is.
+    for (int i = 0; i < footprint->spans->n; i++) {
+	pmSpan *sp = footprint->spans->data[i];
+	for (int j = sp->x0; j <= sp->x1; j++) {
+	    y = sp->y - ym;
+	    x = j - xm;
+	    mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= 0x01;
+	}
+    }
+
+    int CRpix_count = 0;      
+    do {
+	CRpix_count = 0;
+	// Zero out my temp images.
+	for (x = 0; x < binning * dx; x++) {
+	    for (y = 0; y < binning * dy; y++) {
+		psImageSet(binned,x,y,0.0);
+		psImageSet(conved,x,y,0.0);
+		psImageSet(edges,x/binning,y/ binning,0.0);
+	    }
+	}      
+	// Make subsampled image. Maybe this should be called "unbinned" or something
+	for (x = 0; x < binning * dx; x++) {
+	    for (y = 0; y < binning * dy; y++) {
+		psImageSet(binned,x,y,psImageGet(mypix,x / binning,y / binning));
+	    }
+	}
+	// Apply Laplace transform (kernel = [[0 -0.25 0][-0.25 1 -0.25][0 -0.25 0]]), clipping at zero
+	for (x = 1; x < dx - 1; x++) {
+	    for (y = 1; y < dy - 1; y++) {
+		psImageSet(conved,x,y,psImageGet(binned,x,y) - 0.25 *
+			   (psImageGet(binned,x-1,y) + psImageGet(binned,x+1,y) +
+			    psImageGet(binned,x,y-1) + psImageGet(binned,x,y+1)));
+		if (psImageGet(conved,x,y) < 0.0) {
+		    psImageSet(conved,x,y,0.0);
+		}
+	    }
+	}
+	// Create an edge map by rebinning
+	for (x = 0; x < binning * dx; x++) {
+	    for (y = 0; y < binning * dy; y++) {
+		psImageSet(edges,x / binning, y / binning,
+			   psImageGet(edges, x / binning, y / binning) +
+			   psImageGet(conved,x,y));
+	    }
+	}
+	// Modify my mask if we're above the significance threshold
+	for (x = 0; x < dx; x++) {
+	    for (y = 0; y < dy; y++) {
+		if ( psImageGet(edges,x,y) / (binning * sqrt(noise_factor * psImageGet(myvar,x,y))) > sigma_thresh ) {
+		    if (mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & 0x01) {
+			mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= 0x40;
+			CRpix_count++;
+		    }
+		}
+	    }
+	}
+
+	// "Repair" Masked pixels for the next round.
+	for (x = 1; x < dx - 1; x++) {
+	    for (y = 1; y < dy - 1; y++) {
+		if (mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & 0x40) {
+		    psImageSet(mypix,x,y,0.25 *
+			       (psImageGet(mypix,x-1,y) + psImageGet(mypix,x+1,y) +
+				psImageGet(mypix,x,y-1) + psImageGet(mypix,x,y+1)));
+		}
+	    }
+	}
+	
+# if 0
+ 	if ((psTraceGetLevel("psphot.czw") >= 2)&&(abs(xm - 2770) < 5)&&(abs(ym - 2581) < 5)&&(iteration == 0)) {
+ 	  psTrace("psphot.czw",2,"TRACEMOTRON %d %d %d %d %d\n",xm,ym,dx,dy,iteration);
+ 	  psphotSaveImage (NULL, mypix,   "czw.pix.fits");
+ 	  psphotSaveImage (NULL, myvar,   "czw.var.fits");
+ 	  psphotSaveImage (NULL, binned,  "czw.binn.fits");
+ 	  psphotSaveImage (NULL, conved,  "czw.conv.fits");
+ 	  psphotSaveImage (NULL, edges,   "czw.edge.fits");
+ 	  psphotSaveImage (NULL, mymask,  "czw.mask.fits");
+ 	}
+# endif
+
+	psTrace("psphot.czw",2,"Iter: %d Count: %d",iteration,CRpix_count);
+	iteration++;
+    } while ((iteration < max_iter)&&(CRpix_count > 0));
+
+    // A solitary masked pixel is likely a lie. Remove those.
+    for (x = 0; x < dx; x++) {
+	for (y = 0; y < dy; y++) {
+	    if (mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & 0x40) {
+		if ((x-1 >= 0)&&(mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x-1] & 0x40)) {
+		    continue;
+		}
+		if ((y-1 >= 0)&&(mymask->data.PS_TYPE_IMAGE_MASK_DATA[y-1][x] & 0x40)) {
+		    continue;
+		}
+		if ((x+1 < dx)&&(mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x+1] & 0x40)) {
+		    continue;
+		}
+		if ((y+1 < dy)&&(mymask->data.PS_TYPE_IMAGE_MASK_DATA[y+1][x] & 0x40)) {
+		    continue;
+		}
+		mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] ^= 0x40;
+	    }
+	}
+    }
+
+    // transfer temporary mask to real mask
+    for (x = 0; x < dx; x++) {
+	for (y = 0; y < dy; y++) {
+	    if (mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & 0x40) {
+		mask->data.PS_TYPE_IMAGE_MASK_DATA[y+ym+mask->row0][x+xm+mask->col0] |= maskVal;
+	    }
+	}
+    }
+      
+    // XXX if we decide this REALLY is a cosmic ray, set the CR_LIMIT bit
+    source->mode |= PM_SOURCE_MODE_CR_LIMIT;
+
+    psFree(mypix);
+    psFree(myvar);
+    psFree(binned);
+    psFree(conved);
+    psFree(edges);
+    psFree(mymask);
+      
+    return true;
+}
+
+bool psphotMaskCosmicRayFootprintCheck (psArray *sources) {
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+	pmPeak *peak = source->peak;
+	pmFootprint *footprint = peak->footprint;
+	if (!footprint) continue;
+	for (int j = 0; j < footprint->spans->n; j++) {
+	    pmSpan *sp = footprint->spans->data[j];
+	    psAssert (sp, "missing span");
+	}
+    }
+    return true;
+}
+
+/**** ------ old versions of cosmic ray masking ----- ****/
+
+bool psphotMaskCosmicRayIsophot (pmSource *source, psImageMaskType maskVal, psImageMaskType crMask);
+
+// This attempt to mask the cosmic rays used the isophotal boundary
+bool psphotMaskCosmicRay_V1 (psImage *mask, pmSource *source, psImageMaskType maskVal, psImageMaskType crMask) {
 
     // replace the source flux
@@ -103,4 +652,6 @@
     pmFootprint *footprint = peak->footprint;
     if (!footprint) {
+      psTrace("psphot.czw",2,"Using isophot CR mask code.");
+      
         // if we have not footprint, use the old code to mask by isophot
         psphotMaskCosmicRayIsophot (source, maskVal, crMask);
@@ -109,21 +660,23 @@
 
     if (!footprint->spans) {
+      psTrace("psphot.czw",2,"Using isophot CR mask code.");
+      
         // if we have no footprint, use the old code to mask by isophot
         psphotMaskCosmicRayIsophot (source, maskVal, crMask);
         return true;
     }
-
+    psphotMaskCosmicRayIsophot (source, maskVal, crMask);
     // mask all of the pixels covered by the spans of the footprint
-    for (int j = 1; j < footprint->spans->n; j++) {
-        pmSpan *span1 = footprint->spans->data[j];
-
-        int iy = span1->y;
-        int xs = span1->x0;
-        int xe = span1->x1;
-
-        for (int ix = xs; ix < xe; ix++) {
-            mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= crMask;
-        }
-    }
+    for (int j = 1; j < footprint->spans->n; j++) { 
+	pmSpan *span1 = footprint->spans->data[j]; 
+
+	int iy = span1->y; 
+	int xs = span1->x0; 
+	int xe = span1->x1; 
+
+	for (int ix = xs; ix < xe; ix++) { 
+	    mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= crMask; 
+	} 
+    } 
     return true;
 }
@@ -192,223 +745,4 @@
         }
     }
-    return true;
-}
-
-// model the apmifit distribution for the psf stars:
-bool psphotSourceSizePSF (psphotSourceSizeOptions *options, psArray *sources, pmPSF *psf) {
-
-    // select stats from the psf stars
-    psVector *Ap = psVectorAllocEmpty (100, PS_TYPE_F32);
-    psVector *ApErr = psVectorAllocEmpty (100, PS_TYPE_F32);
-
-    psImageMaskType maskVal = options->maskVal | options->markVal;
-
-    // XXX  why PHOT_WEIGHT??
-    pmSourcePhotometryMode photMode = PM_SOURCE_PHOT_WEIGHT;
-
-    for (int i = 0; i < sources->n; i++) {
-        pmSource *source = sources->data[i];
-        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
-
-        // replace object in image
-        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
-            pmSourceAdd (source, PM_MODEL_OP_FULL, options->maskVal);
-        }
-
-        // clear the mask bit and set the circular mask pixels
-        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(options->markVal));
-        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, source->apRadius, "OR", options->markVal);
-
-        // XXX can we test if psfMag is set and calculate only if needed?
-        pmSourceMagnitudes (source, psf, photMode, maskVal); // maskVal includes markVal
-
-        // clear the mask bit
-        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(options->markVal));
-
-        // re-subtract the object, leave local sky
-        pmSourceSub (source, PM_MODEL_OP_FULL, options->maskVal);
-
-        float apMag = -2.5*log10(source->moments->Sum);
-        float dMag = source->psfMag - apMag;
-
-        psVectorAppend (Ap, dMag);
-        psVectorAppend (ApErr, source->errMag);
-    }
-
-    // model the distribution as a mean or median value and a systematic error from that value:
-    psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
-    psVectorStats (stats, Ap, NULL, NULL, 0);
-
-    psVector *dAp = psVectorAlloc (Ap->n, PS_TYPE_F32);
-    for (int i = 0; i < Ap->n; i++) {
-        dAp->data.F32[i] = Ap->data.F32[i] - stats->robustMedian;
-    }
-
-    options->ApResid = stats->robustMedian;
-    options->ApSysErr = psVectorSystematicError(dAp, ApErr, 0.05);
-    // XXX this is quite arbitrary...
-    if (!isfinite(options->ApSysErr)) options->ApSysErr = 0.01;
-    psLogMsg ("psphot", PS_LOG_DETAIL, "psf - Sum: %f +/- %f\n", options->ApResid, options->ApSysErr);
-
-    psFree (Ap);
-    psFree (ApErr);
-    psFree (stats);
-    psFree (dAp);
-
-    return true;
-}
-
-// classify sources based on the combination of psf-mag, Mxx, Myy
-bool psphotSourceClass (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, psphotSourceSizeOptions *options) {
-
-    bool status;
-    pmPSFClump psfClump;
-    char regionName[64];
-
-    psLogMsg("psModules.objects", PS_LOG_INFO, "Source Size classifications: %4s %4s %4s %4s %4s %4s", "Npsf", "Next", "Nsat", "Ncr", "Nmiss", "Nskip");
-
-    int nRegions = psMetadataLookupS32 (&status, readout->analysis, "PSF.CLUMP.NREGIONS");
-    for (int i = 0; i < nRegions; i ++) {
-        snprintf (regionName, 64, "PSF.CLUMP.REGION.%03d", i);
-        psMetadata *regionMD = psMetadataLookupPtr (&status, readout->analysis, regionName);
-        psAssert (regionMD, "regions must be defined by earlier call to psphotRoughClassRegion");
-
-        psRegion *region = psMetadataLookupPtr (&status, regionMD, "REGION");
-        psAssert (region, "regions must be defined by earlier call to psphotRoughClassRegion");
-
-        // pull FWHM_X,Y from the recipe, use to define psfClump.X,Y
-        psfClump.X  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.X");   psAssert (status, "missing PSF.CLUMP.X");
-        psfClump.Y  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.Y");   psAssert (status, "missing PSF.CLUMP.Y");
-        psfClump.dX = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DX");  psAssert (status, "missing PSF.CLUMP.DX");
-        psfClump.dY = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DY");  psAssert (status, "missing PSF.CLUMP.DY");
-
-        if ((psfClump.X < 0) || (psfClump.Y < 0) || !psfClump.X || !psfClump.Y || isnan(psfClump.X) || isnan(psfClump.Y)) {
-            psLogMsg ("psphot", 4, "Failed to find a valid PSF clump for region %f,%f - %f,%f\n", region->x0, region->y0, region->x1, region->y1);
-            continue;
-        }
-
-        if (!psphotSourceClassRegion (region, &psfClump, sources, recipe, psf, options)) {
-            psLogMsg ("psphot", 4, "Failed to determine source classification for region %f,%f - %f,%f\n", region->x0, region->y0, region->x1, region->y1);
-            continue;
-        }
-    }
-
-    return true;
-}
-
-bool psphotSourceClassRegion (psRegion *region, pmPSFClump *psfClump, psArray *sources, psMetadata *recipe, pmPSF *psf, psphotSourceSizeOptions *options) {
-
-    PS_ASSERT_PTR_NON_NULL(sources, false);
-    PS_ASSERT_PTR_NON_NULL(recipe, false);
-
-    int Nsat  = 0;
-    int Next  = 0;
-    int Npsf  = 0;
-    int Ncr   = 0;
-    int Nmiss = 0;
-    int Nskip = 0;
-
-    pmSourceMode noMoments = PM_SOURCE_MODE_MOMENTS_FAILURE | PM_SOURCE_MODE_SKYVAR_FAILURE | PM_SOURCE_MODE_SKY_FAILURE | PM_SOURCE_MODE_BELOW_MOMENTS_SN;
-    pmSourcePhotometryMode photMode = PM_SOURCE_PHOT_WEIGHT;
-
-    psImageMaskType maskVal = options->maskVal | options->markVal;
-
-    for (psS32 i = 0 ; i < sources->n ; i++) {
-
-        pmSource *source = (pmSource *) sources->data[i];
-
-        // psfClumps are found for image subregions:
-        // skip sources not in this region
-        if (source->peak->x <  region->x0) continue;
-        if (source->peak->x >= region->x1) continue;
-        if (source->peak->y <  region->y0) continue;
-        if (source->peak->y >= region->y1) continue;
-
-        // skip source if it was already measured
-        if (source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED) {
-            psTrace("psphot", 7, "Not calculating source size since it has already been measured\n");
-            continue;
-        }
-
-        // source must have been subtracted
-        if (!(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED)) {
-            source->mode |= PM_SOURCE_MODE_SIZE_SKIPPED;
-            psTrace("psphot", 7, "Not calculating source size since source is not subtracted\n");
-            continue;
-        }
-
-        // we are basically classifying by moments; use the default if not found
-        psAssert (source->moments, "why is this source missing moments?");
-        if (source->mode & noMoments) {
-            Nskip ++;
-            continue;
-        }
-
-        psF32 Mxx = source->moments->Mxx;
-        psF32 Myy = source->moments->Myy;
-
-        // replace object in image
-        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
-            pmSourceAdd (source, PM_MODEL_OP_FULL, options->maskVal);
-        }
-
-        // clear the mask bit and set the circular mask pixels
-        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(options->markVal));
-        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, source->apRadius, "OR", options->markVal);
-
-        // XXX can we test if psfMag is set and calculate only if needed?
-        pmSourceMagnitudes (source, psf, photMode, maskVal); // maskVal includes markVal
-
-        // clear the mask bit
-        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(options->markVal));
-
-        // re-subtract the object, leave local sky
-        pmSourceSub (source, PM_MODEL_OP_FULL, options->maskVal);
-
-        float apMag = -2.5*log10(source->moments->Sum);
-        float dMag = source->psfMag - apMag;
-        float nSigma = (dMag - options->ApResid) / hypot(source->errMag, options->ApSysErr);
-
-        source->extNsigma = nSigma;
-        source->tmpFlags |= PM_SOURCE_TMPF_SIZE_MEASURED;
-
-        // Anything within this region is a probably PSF-like object. Saturated stars may land
-        // in this region, but are detected elsewhere on the basis of their peak value.
-        bool isPSF = (fabs(nSigma) < options->nSigmaApResid) && (fabs(Mxx - psfClump->X) < options->nSigmaMoments*psfClump->dX) && (fabs(Myy - psfClump->Y) < options->nSigmaMoments*psfClump->dY);
-        if (isPSF) {
-            Npsf ++;
-            continue;
-        }
-
-        // Defects may not always match CRs from peak curvature analysis
-        // Defects may also be marked as SATSTAR -- XXX deactivate this flag?
-        // XXX this rule is not great
-        if ((Mxx < psfClump->X) || (Myy < psfClump->Y)) {
-            source->mode |= PM_SOURCE_MODE_DEFECT;
-            Ncr ++;
-            continue;
-        }
-
-        // saturated star (determined in PSF fit).  These may also be saturated galaxies, or
-        // just large saturated regions.
-        if (source->mode & PM_SOURCE_MODE_SATSTAR) {
-            Nsat ++;
-            continue;
-        }
-
-        // XXX allow the Mxx, Myy to be less than psfClump->X,Y (by some nSigma)?
-        bool isEXT = (nSigma > options->nSigmaApResid) || ((Mxx > psfClump->X) && (Myy > psfClump->Y));
-        if (isEXT) {
-            source->mode |= PM_SOURCE_MODE_EXT_LIMIT;
-            Next ++;
-            continue;
-        }
-
-        psWarning ("sourse size was missed for %f,%f : %f %f -- %f\n", source->peak->xf, source->peak->yf, Mxx, Myy, nSigma);
-        Nmiss ++;
-    }
-
-    psLogMsg("psModules.objects", PS_LOG_INFO, "Source Size classifications: %4d %4d %4d %4d %4d %4d", Npsf, Next, Nsat, Ncr, Nmiss, Nskip);
-
     return true;
 }
@@ -488,5 +822,6 @@
 }
 
-bool psphotSourceSizeCR (pmReadout *readout, psArray *sources, psphotSourceSizeOptions *options) {
+// this was an old attempt to identify cosmic rays based on the peak curvature
+bool psphotSourcePeakCurvature (pmReadout *readout, psArray *sources, psphotSourceSizeOptions *options) {
 
     // classify the sources based on the CR test (place this in a function?)
@@ -620,2 +955,3 @@
     return true;
 }
+
Index: trunk/psphot/src/psphotSourceStats.c
===================================================================
--- trunk/psphot/src/psphotSourceStats.c	(revision 26618)
+++ trunk/psphot/src/psphotSourceStats.c	(revision 26894)
@@ -1,7 +1,28 @@
 # include "psphotInternal.h"
 
-bool psphotSetMomentsWindow (psMetadata *recipe, psMetadata *analysis, psArray *sources);
-
-psArray *psphotSourceStats (pmConfig *config, pmReadout *readout, pmDetections *detections, bool setWindow) {
+// convert detections to sources and measure their basic properties (moments, local sky, sky
+// variance) Note: this function only generates sources for the new peaks (peak->assigned)
+bool psphotSourceStats (pmConfig *config, const pmFPAview *view, bool setWindow)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotSourceStatsReadout (config, view, "PSPHOT.INPUT", i, recipe, setWindow)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to find initial detections for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotSourceStatsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool setWindow) {
 
     bool status = false;
@@ -10,7 +31,22 @@
     psTimerStart ("psphot.stats");
 
-    // select the appropriate recipe information
-    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
-    assert (recipe);
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+    psAssert (!detections->newSources, "new sources already defined?");
+
+    // XXX TEST:
+    if (detections->allSources) {
+	psphotMaskCosmicRayFootprintCheck(detections->allSources);
+    }
+    if (detections->newSources) {
+	psphotMaskCosmicRayFootprintCheck(detections->newSources);
+    }
 
     // determine the number of allowed threads
@@ -21,20 +57,39 @@
 
     // determine properties (sky, moments) of initial sources
-    float OUTER    = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
-    if (!status) return NULL;
-
+    float OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    psAssert (status, "missing SKY_OUTER_RADIUS in recipe?");
+
+    // XXX this seems like an arbitrary number...
     OUTER = PS_MAX(OUTER, 20.0); // XXX Guarantee that we can encompass the max moments radius
 
     char *breakPt  = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
-    if (!status) return NULL;
+    psAssert (status, "missing BREAK_POINT?");
+
+    float INNER        = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS"); psAssert (status, "missing SKY_INNER_RADIUS");
+    float MIN_SN       = psMetadataLookupF32 (&status, recipe, "MOMENTS_SN_MIN"); psAssert (status, "missing MOMENTS_SN_MIN");
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    psAssert (maskVal, "missing MASK.PSPHOT");
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    psAssert (markVal, "missing MARK.PSPHOT");
 
     psArray *peaks = detections->peaks;
     if (!peaks) {
         psError(PS_ERR_UNEXPECTED_NULL, false, "No peaks found!");
-        return NULL;
+        return false;
     }
 
     // generate the array of sources, define the associated pixel
     sources = psArrayAllocEmpty (peaks->n);
+
+    // if there are no peaks, we save the empty source array and return
+    if (!peaks->n) {
+	// save the new sources on the detection structure:
+	detections->newSources = sources;
+	return true;
+    }
 
     for (int i = 0; i < peaks->n; i++) {
@@ -58,4 +113,159 @@
         psArrayAdd (sources, 100, source);
         psFree (source);
+    }
+
+    if (!strcasecmp (breakPt, "PEAKS")) {
+        psLogMsg ("psphot", PS_LOG_INFO, "%ld sources : %f sec\n", sources->n, psTimerMark ("psphot.stats"));
+        psLogMsg ("psphot", PS_LOG_INFO, "break point PEAKS, skipping MOMENTS\n");
+        psphotVisualShowMoments (sources);
+	detections->newSources = sources;
+        return true;
+    }
+
+    if (setWindow) {
+        if (!psphotSetMomentsWindow(recipe, readout->analysis, sources)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Failed to determine Moments Window!");
+	    psFree(sources);
+            return false;
+        }
+    }
+
+    // if we have measured the window, we will be saving the modified version of these recipe values on readout->analysis
+    float SIGMA = psMetadataLookupF32 (&status, readout->analysis, "MOMENTS_GAUSS_SIGMA");
+    if (!status) {
+	SIGMA = psMetadataLookupF32 (&status, recipe, "MOMENTS_GAUSS_SIGMA");
+    }
+    float RADIUS = psMetadataLookupF32 (&status, readout->analysis, "PSF_MOMENTS_RADIUS");
+    if (!status) {
+	RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    }
+
+    // threaded measurement of the source magnitudes
+    int Nfail = 0;
+    int Nmoments = 0;
+    int Nfaint = 0;
+
+    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
+    int Cx = 1, Cy = 1;
+    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
+
+    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
+
+    for (int i = 0; i < cellGroups->n; i++) {
+
+        psArray *cells = cellGroups->data[i];
+
+        for (int j = 0; j < cells->n; j++) {
+
+            // allocate a job -- if threads are not defined, this just runs the job
+            psThreadJob *job = psThreadJobAlloc ("PSPHOT_SOURCE_STATS");
+
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+
+            PS_ARRAY_ADD_SCALAR(job->args, INNER,   PS_TYPE_F32);
+            PS_ARRAY_ADD_SCALAR(job->args, MIN_SN,  PS_TYPE_F32);
+            PS_ARRAY_ADD_SCALAR(job->args, RADIUS,  PS_TYPE_F32);
+            PS_ARRAY_ADD_SCALAR(job->args, SIGMA,   PS_TYPE_F32);
+
+            PS_ARRAY_ADD_SCALAR(job->args, maskVal, PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, markVal, PS_TYPE_IMAGE_MASK);
+
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nmoments
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nfail
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nfaint
+
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to launch thread job PSPHOT_SOURCE_STATS");
+                psFree (job);
+		psFree(sources);
+                return false;
+            }
+            psFree(job);
+        }
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false)) {
+            psError(PS_ERR_UNKNOWN, false, "Failure in thread job PSPHOT_SOURCE_STATS");
+	    psFree(sources);
+            return false;
+        }
+
+        // we have only supplied one type of job, so we can assume the types here
+        psThreadJob *job = NULL;
+        while ((job = psThreadJobGetDone()) != NULL) {
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            } else {
+                psScalar *scalar = NULL;
+                scalar = job->args->data[7];
+                Nmoments += scalar->data.S32;
+                scalar = job->args->data[8];
+                Nfail += scalar->data.S32;
+                scalar = job->args->data[9];
+                Nfaint += scalar->data.S32;
+            }
+            psFree(job);
+        }
+    }
+
+    psFree (cellGroups);
+
+    psLogMsg ("psphot", PS_LOG_INFO, "%ld sources, %d moments, %d faint, %d failed: %f sec\n", sources->n, Nmoments, Nfaint, Nfail, psTimerMark ("psphot.stats"));
+
+    psphotVisualShowMoments (sources);
+
+    // save the new sources on the detection structure:
+    detections->newSources = sources;
+
+
+    if (detections->allSources) {
+	psphotMaskCosmicRayFootprintCheck(detections->allSources);
+    }
+    if (detections->newSources) {
+	psphotMaskCosmicRayFootprintCheck(detections->newSources);
+    }
+
+    return true;
+}
+
+// this function is currently only called by psphotCheckExtSources 
+bool psphotSourceStatsUpdate (psArray *sources, pmConfig *config, pmReadout *readout) {
+
+    bool status = false;
+
+    psTimerStart ("psphot.stats");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    // determine properties (sky, moments) of initial sources
+    float OUTER    = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    if (!status) return false;
+
+    OUTER = PS_MAX(OUTER, 20.0); // XXX Guarantee that we can encompass the max moments radius
+
+    char *breakPt  = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
+    if (!status) return false;
+
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+        if (!source->peak) continue; // XXX how can we have a peak-less source?
+
+        // allocate space for moments
+	if (!source->moments) {
+	    source->moments = pmMomentsAlloc();
+	}
+
+        // allocate image, weight, mask arrays for each peak (square of radius OUTER)
+        pmSourceDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
+        source->peak->assigned = true;
     }
 
@@ -67,9 +277,8 @@
     }
 
-    if (setWindow) {
-        if (!psphotSetMomentsWindow(recipe, readout->analysis, sources)) {
-            psError(PS_ERR_UNEXPECTED_NULL, false, "Failed to determine Moments Window!");
-            return NULL;
-        }
+    // XXX how else could we get the window size in?
+    if (!psphotSetMomentsWindow(recipe, readout->analysis, sources)) {
+	psError(PS_ERR_UNEXPECTED_NULL, false, "Failed to determine Moments Window!");
+	return NULL;
     }
 
@@ -94,4 +303,5 @@
             psThreadJob *job = psThreadJobAlloc ("PSPHOT_SOURCE_STATS");
 
+	    // XXX: this must match the above
             psArrayAdd(job->args, 1, cells->data[j]); // sources
             psArrayAdd(job->args, 1, recipe);
@@ -141,124 +351,4 @@
 }
 
-bool psphotSourceStatsUpdate (psArray *sources, pmConfig *config, pmReadout *readout) {
-
-    bool status = false;
-
-    psTimerStart ("psphot.stats");
-
-    // select the appropriate recipe information
-    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
-    assert (recipe);
-
-    // determine the number of allowed threads
-    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
-    if (!status) {
-        nThreads = 0;
-    }
-
-    // determine properties (sky, moments) of initial sources
-    float OUTER    = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
-    if (!status) return NULL;
-
-    OUTER = PS_MAX(OUTER, 20.0); // XXX Guarantee that we can encompass the max moments radius
-
-    char *breakPt  = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
-    if (!status) return NULL;
-
-    for (int i = 0; i < sources->n; i++) {
-
-        pmSource *source = sources->data[i];
-        if (!source->peak) continue; // XXX how can we have a peak-less source?
-
-        // allocate space for moments
-	if (!source->moments) {
-	    source->moments = pmMomentsAlloc();
-	}
-
-        // allocate image, weight, mask arrays for each peak (square of radius OUTER)
-        pmSourceDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
-        source->peak->assigned = true;
-    }
-
-    if (!strcasecmp (breakPt, "PEAKS")) {
-        psLogMsg ("psphot", PS_LOG_INFO, "%ld sources : %f sec\n", sources->n, psTimerMark ("psphot.stats"));
-        psLogMsg ("psphot", PS_LOG_INFO, "break point PEAKS, skipping MOMENTS\n");
-        psphotVisualShowMoments (sources);
-        return sources;
-    }
-
-    // XXX how else could we get the window size in?
-    if (!psphotSetMomentsWindow(recipe, readout->analysis, sources)) {
-	psError(PS_ERR_UNEXPECTED_NULL, false, "Failed to determine Moments Window!");
-	return NULL;
-    }
-
-    // threaded measurement of the source magnitudes
-    int Nfail = 0;
-    int Nmoments = 0;
-    int Nfaint = 0;
-
-    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
-    int Cx = 1, Cy = 1;
-    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
-
-    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
-
-    for (int i = 0; i < cellGroups->n; i++) {
-
-        psArray *cells = cellGroups->data[i];
-
-        for (int j = 0; j < cells->n; j++) {
-
-            // allocate a job -- if threads are not defined, this just runs the job
-            psThreadJob *job = psThreadJobAlloc ("PSPHOT_SOURCE_STATS");
-
-            psArrayAdd(job->args, 1, cells->data[j]); // sources
-            psArrayAdd(job->args, 1, recipe);
-            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nmoments
-            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nfail
-            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nfaint
-
-            if (!psThreadJobAddPending(job)) {
-                psError(PS_ERR_UNKNOWN, false, "Unable to launch thread job PSPHOT_SOURCE_STATS");
-                psFree (job);
-                return NULL;
-            }
-            psFree(job);
-        }
-
-        // wait for the threads to finish and manage results
-        if (!psThreadPoolWait (false)) {
-            psError(PS_ERR_UNKNOWN, false, "Failure in thread job PSPHOT_SOURCE_STATS");
-            return NULL;
-        }
-
-        // we have only supplied one type of job, so we can assume the types here
-        psThreadJob *job = NULL;
-        while ((job = psThreadJobGetDone()) != NULL) {
-            if (job->args->n < 1) {
-                fprintf (stderr, "error with job\n");
-            } else {
-                psScalar *scalar = NULL;
-                scalar = job->args->data[2];
-                Nmoments += scalar->data.S32;
-                scalar = job->args->data[3];
-                Nfail += scalar->data.S32;
-                scalar = job->args->data[4];
-                Nfaint += scalar->data.S32;
-            }
-            psFree(job);
-        }
-    }
-
-    psFree (cellGroups);
-
-    psLogMsg ("psphot", PS_LOG_INFO, "%ld sources, %d moments, %d faint, %d failed: %f sec\n", sources->n, Nmoments, Nfaint, Nfail, psTimerMark ("psphot.stats"));
-
-    psphotVisualShowMoments (sources);
-
-    return (sources);
-}
-
 bool psphotSourceStats_Threaded (psThreadJob *job) {
 
@@ -268,22 +358,12 @@
 
     psArray *sources                = job->args->data[0];
-    psMetadata *recipe              = job->args->data[1];
-
-    float INNER        = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
-    if (!status) return false;
-    float MIN_SN       = psMetadataLookupF32 (&status, recipe, "MOMENTS_SN_MIN");
-    if (!status) return false;
-    float RADIUS       = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
-    if (!status) return false;
-    float SIGMA        = psMetadataLookupF32 (&status, recipe, "MOMENTS_GAUSS_SIGMA");
-    if (!status) return false;
-
-    // bit-masks to test for good/bad pixels
-    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
-    assert (maskVal);
-
-    // bit-mask to mark pixels not used in analysis
-    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
-    assert (markVal);
+
+    float INNER                     = PS_SCALAR_VALUE(job->args->data[1],F32);
+    float MIN_SN                    = PS_SCALAR_VALUE(job->args->data[2],F32);
+    float RADIUS                    = PS_SCALAR_VALUE(job->args->data[3],F32);
+    float SIGMA                     = PS_SCALAR_VALUE(job->args->data[4],F32);
+
+    psImageMaskType maskVal         = PS_SCALAR_VALUE(job->args->data[5],PS_TYPE_IMAGE_MASK_DATA);
+    psImageMaskType markVal         = PS_SCALAR_VALUE(job->args->data[6],PS_TYPE_IMAGE_MASK_DATA);
 
     // maskVal is used to test for rejected pixels, and must include markVal
@@ -325,5 +405,5 @@
 
         // measure basic source moments (no S/N clipping on input pixels)
-        status = pmSourceMoments (source, RADIUS, SIGMA, 0.0);
+        status = pmSourceMoments (source, RADIUS, SIGMA, 0.0, maskVal);
         if (status) {
             Nmoments ++;
@@ -335,5 +415,5 @@
         BIG_RADIUS = PS_MIN (INNER, 3*RADIUS);
         psTrace ("psphot", 4, "retrying moments for %d, %d\n", source->peak->x, source->peak->y);
-        status = pmSourceMoments (source, BIG_RADIUS, 3.0*SIGMA, 0.0);
+        status = pmSourceMoments (source, BIG_RADIUS, 3.0*SIGMA, 0.0, maskVal);
         if (status) {
             source->mode |= PM_SOURCE_MODE_BIG_RADIUS;
@@ -349,11 +429,11 @@
 
     // change the value of a scalar on the array (wrap this and put it in psArray.h)
-    scalar = job->args->data[2];
+    scalar = job->args->data[7];
     scalar->data.S32 = Nmoments;
 
-    scalar = job->args->data[3];
+    scalar = job->args->data[8];
     scalar->data.S32 = Nfail;
 
-    scalar = job->args->data[4];
+    scalar = job->args->data[9];
     scalar->data.S32 = Nfaint;
 
@@ -367,6 +447,7 @@
     bool status;
 
-    float MIN_SN = psMetadataLookupF32 (&status, recipe, "MOMENTS_SN_MIN");
-    if (!status) return false;
+    float MIN_SN = psMetadataLookupF32 (&status, recipe, "MOMENTS_SN_MIN"); psAssert (status, "missing MOMENTS_SN_MIN");
+    float PSF_SN_LIM = psMetadataLookupF32(&status, recipe, "PSF_SN_LIM"); psAssert (status, "missing PSF_SN_LIM");
+    psF32 MOMENTS_AR_MAX = psMetadataLookupF32(&status, recipe, "MOMENTS_AR_MAX"); psAssert (status, "missing MOMENTS_AR_MAX");
 
     // XXX move this to a config file?
@@ -394,14 +475,14 @@
 
             // measure basic source moments (no S/N clipping on input pixels)
-            status = pmSourceMoments (source, 4*sigma[i], sigma[i], 0.0);
+            status = pmSourceMoments (source, 4*sigma[i], sigma[i], 0.0, 0xffff);
         }
 
         // choose a grid scale that is a fixed fraction of the psf sigma^2
-        psMetadataAddF32(recipe, PS_LIST_TAIL, "PSF_CLUMP_GRID_SCALE", PS_META_REPLACE, "clump grid", 0.1*PS_SQR(sigma[i]));
-        psMetadataAddF32(recipe, PS_LIST_TAIL, "MOMENTS_SX_MAX", PS_META_REPLACE, "moments limit", 2.0*PS_SQR(sigma[i]));
-        psMetadataAddF32(recipe, PS_LIST_TAIL, "MOMENTS_SY_MAX", PS_META_REPLACE, "moments limit", 2.0*PS_SQR(sigma[i]));
+        float PSF_CLUMP_GRID_SCALE = 0.1*PS_SQR(sigma[i]);
+        float MOMENTS_SX_MAX = 2.0*PS_SQR(sigma[i]);
+        float MOMENTS_SY_MAX = 2.0*PS_SQR(sigma[i]);
 
         // determine the PSF parameters from the source moment values
-        pmPSFClump psfClump = pmSourcePSFClump (NULL, sources, recipe);
+        pmPSFClump psfClump = pmSourcePSFClump (NULL, NULL, sources, PSF_SN_LIM, PSF_CLUMP_GRID_SCALE, MOMENTS_SX_MAX, MOMENTS_SY_MAX, MOMENTS_AR_MAX);
         psLogMsg ("psphot", 3, "radius %.1f, nStars: %d, nSigma: %5.2f, X,  Y: %f, %f (%f, %f)\n", sigma[i], psfClump.nStars, psfClump.nSigma, psfClump.X, psfClump.Y, sqrt(psfClump.X) / sigma[i], sqrt(psfClump.Y) / sigma[i]);
 
@@ -418,5 +499,5 @@
         psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DY", PS_META_REPLACE, "psf clump center", psfClump.dY);
 
-        // psphotVisualPlotMoments (recipe, sources);
+        psphotVisualPlotMoments (recipe, analysis, sources);
 
         Sout[i] = sqrt(0.5*(psfClump.X + psfClump.Y)) / sigma[i];
@@ -442,11 +523,15 @@
 
     // choose a grid scale that is a fixed fraction of the psf sigma^2
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "PSF_CLUMP_GRID_SCALE", PS_META_REPLACE, "clump grid", 0.1*PS_SQR(Sigma));
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "MOMENTS_SX_MAX", PS_META_REPLACE, "moments limit", 2.0*PS_SQR(Sigma));
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "MOMENTS_SY_MAX", PS_META_REPLACE, "moments limit", 2.0*PS_SQR(Sigma));
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "MOMENTS_GAUSS_SIGMA", PS_META_REPLACE, "moments limit", Sigma);
-    psMetadataAddF32(recipe, PS_LIST_TAIL, "PSF_MOMENTS_RADIUS", PS_META_REPLACE, "moments limit", 4.0*Sigma);
+    psMetadataAddF32(analysis, PS_LIST_TAIL, "PSF_CLUMP_GRID_SCALE", PS_META_REPLACE, "clump grid", 0.1*PS_SQR(Sigma));
+    psMetadataAddF32(analysis, PS_LIST_TAIL, "MOMENTS_SX_MAX", PS_META_REPLACE, "moments limit", 2.0*PS_SQR(Sigma));
+    psMetadataAddF32(analysis, PS_LIST_TAIL, "MOMENTS_SY_MAX", PS_META_REPLACE, "moments limit", 2.0*PS_SQR(Sigma));
+    psMetadataAddF32(analysis, PS_LIST_TAIL, "MOMENTS_GAUSS_SIGMA", PS_META_REPLACE, "moments limit", Sigma);
+    psMetadataAddF32(analysis, PS_LIST_TAIL, "PSF_MOMENTS_RADIUS", PS_META_REPLACE, "moments limit", 4.0*Sigma);
 
     psLogMsg ("psphot", 3, "using window function with sigma = %f\n", Sigma);
     return true;
 }
+
+// if we use the footprints, the output peaks list contains both old and new peaks,
+// otherwise it only contains the new peaks.
+
Index: trunk/psphot/src/psphotStack.c
===================================================================
--- trunk/psphot/src/psphotStack.c	(revision 26894)
+++ trunk/psphot/src/psphotStack.c	(revision 26894)
@@ -0,0 +1,35 @@
+# include "psphotStandAlone.h"
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+    pmErrorRegister();                  // register psModule's error codes/messages
+    psphotInit();
+
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psphotStackArguments (argc, argv);
+    assert(config);
+
+    psphotVersionPrint();
+
+    // load input data (config and images (signal, noise, mask)
+    if (!psphotStackParseCamera (config)) {
+        psErrorStackPrint(stderr, "Error setting up the camera\n");
+        exit (psphotGetExitStatus());
+    }
+
+    // call psphot for each readout
+    if (!psphotStacImageLoop (config)) {
+        psErrorStackPrint(stderr, "Error in the psphot image loop\n");
+        exit (psphotGetExitStatus());
+    }
+
+    psLogMsg ("psphot", 3, "complete psphot run: %f sec\n", psTimerMark ("complete"));
+
+    psErrorCode exit_status = psphotGetExitStatus();
+    psphotCleanup (config);
+    exit (exit_status);
+}
+
+// all functions which return to this level must raise one of the top-level error codes if they
+// exit with an error.  these error codes are used to specify the program exit status
Index: trunk/psphot/src/psphotStackArguments.c
===================================================================
--- trunk/psphot/src/psphotStackArguments.c	(revision 26894)
+++ trunk/psphot/src/psphotStackArguments.c	(revision 26894)
@@ -0,0 +1,159 @@
+# include "psphotStandAlone.h"
+
+static void usage(pmConfig *config, int exitCode);
+static void writeHelpInfo(pmConfig* config, FILE* ofile);
+
+pmConfig *psphotStackArguments(int argc, char **argv) {
+
+    int N;
+    bool status;
+
+    // print help info
+    if (psArgumentGet(argc, argv, "-help")) writeHelpInfo(argv[0], config, stdout);
+    if (psArgumentGet(argc, argv, "-h")) writeHelpInfo(argv[0], config, stdout);
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSPHOT_RECIPE);
+    if (config == NULL) {
+      psErrorStackPrint(stderr, "Can't read site configuration");
+	exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    // -version and -dumpconfig arguments
+    PSARGUMENTS_INSTANTIATE_GENERICS( psphot, config, argc, argv );
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSPHOT recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, PSPHOT_RECIPE);
+
+    // Number of threads is handled
+    PSARGUMENTS_INSTANTIATE_THREADSARG( psphot, config, argc, argv )
+
+    // photcode : used in output to supplement header data (argument or recipe?)
+    if ((N = psArgumentGet (argc, argv, "-photcode"))) {
+        if (argc <= N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // visual : interactive display mode
+    if ((N = psArgumentGet (argc, argv, "-visual"))) {
+        psArgumentRemove (N, &argc, argv);
+        pmVisualSetVisual(true);
+    }
+
+    // break : used from recipe throughout psphotReadout to stop processing early
+    if ((N = psArgumentGet (argc, argv, "-break"))) {
+	if (argc <= N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "BREAK_POINT", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // find the input data file (an mdc file)
+    if ((N = psArgumentGet(argc, argv, "-input"))) {
+	if (argc <= N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove(N, &argc, argv);
+
+        unsigned int numBad = 0;                     // Number of bad lines
+        psMetadata *inputs = psMetadataConfigRead(NULL, &numBad, argv[N], false); // Input file info
+        if (!inputs || numBad > 0) {
+	    psErrorStackPrint(stderr, "Unable to cleanly read MDC file with inputs.");
+	    exit(PS_EXIT_CONFIG_ERROR);
+        }
+        psMetadataAddMetadata(config->arguments, PS_LIST_TAIL, "INPUTS", 0, "Metadata with input details", inputs);
+        psFree(inputs);
+
+        psArgumentRemove(N, &argc, argv);
+    }
+
+    if (argc != 2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Expected to see one more argument; saw %d", argc - 1);
+	usage(config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psphot", 1, "Done with psphotArguments...\n");
+    return (config);
+}
+
+static void writeHelpInfo(pmConfig* config, FILE* ofile)
+{
+  fprintf(ofile,
+	  "Usage: psphotStack -input (input.mdc) outroot\n"
+	  "\n"
+	  "where:\n"
+	  "  FileNameList is a text file containing filenames, one per line\n"
+	  "  MaskFileNameList is a text file of mask filenames, one per line\n"
+	  "  VarFileNameList is a text file of variance filenames, one per line\n"
+	  "  OutFileBaseName is the 'root name' for output files\n"
+	  "\n"
+	  "additional options:\n"
+	  "  -chip nn[,nn,...]\n"
+	  "     select detector chips to process; default is all.\n"
+	  "     Indices correspond to zero-based offset in the FPA metadata table.\n"
+	  "  -photcode PhotoCodeName\n"
+	  "     specify photocode\n"
+	  "  -region RegionString\n"
+	  "     specify analysis region.  String is of form '[x0:x1,y0:y1]'\n"
+	  "     To use this option you must define a default in psphot.config\n"
+	  "  -visual\n"
+	  "     turns on interactive display mode\n"
+	  "  -dumpconfig CfgFileName\n"
+          "     causes config info to be dumped to the named file.\n"
+	  "  -break NOTHING|BACKMDL|PEAKS|MOMENTS|PSFMODEL|ENSEMBLE|PASS1\n"
+	  "     choose a point at which to exit processing early\n"
+	  "  -nthreads n\n"
+	  "     set number of parallel threads of execution\n"
+	  "  -F OldFileRule ReplacementFileRule\n"
+	  "     change file naming rule; e.g. '-F PSPHOT.OUTPUT PSPHOT.OUT.CMF.MEF'\n"
+	  "  -D name stringval\n"
+	  "     set a string-valued config parameter\n"
+	  "  -Di name intval\n"
+	  "     set an integer-valued config parameter\n"
+	  "  -Df name fval\n"
+	  "     set a float-valued config parameter\n"
+	  "  -Db name boolval\n"
+	  "     set a boolean-valued config parameter\n"
+	  "  -v, -vv, -vvv\n"
+	  "     set increasing levels of verbosity\n"
+	  "  -logfmt FormatString\n"
+	  "     set format string used for log messages\n"
+	  "  -trace Fac Lvl\n"
+	  "     set tracing for facility Fac to integer Lvl, e.g. '-trace err 10'\n"
+	  "  -trace-levels\n"
+	  "     print current trace levels\n");
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(PS_EXIT_SUCCESS);
+}
+
+static void usage(pmConfig *config,      // Configuration
+		  int exitCode
+		  ) 
+{
+    fprintf(stderr, 
+	    "Usage: psphotStack -input input.mdc outroot\n"
+	    "Try 'psphotStack -help' for more options and explanation\n");
+
+    if (exitCode != PS_EXIT_SUCCESS) psErrorStackPrint(stderr, "Error reading arguments\n");
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(exitCode);
+}
+
Index: trunk/psphot/src/psphotStackImageLoop.c
===================================================================
--- trunk/psphot/src/psphotStackImageLoop.c	(revision 26894)
+++ trunk/psphot/src/psphotStackImageLoop.c	(revision 26894)
@@ -0,0 +1,36 @@
+# include "psphotStandAlone.h"
+
+# define ESCAPE(MESSAGE) { \
+  psError(PSPHOT_ERR_DATA, false, MESSAGE); \
+  psFree (view); \
+  return false; \
+}
+
+bool psphotImageLoop (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // XXX for now, just load the full set of images up front
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for fpa in psphot.");
+
+    // XXX for now, we assume there is only a single chip in the PHU:
+    psphotReadoutStack (config, view);
+
+
+    // fail if we failed to handle an error
+    if (psErrorCodeLast() != PS_ERR_NONE) psAbort ("failed to handle an error!");
+
+    psFree (view);
+    return true;
+}
+
+/* 
+   the easiest way to implement this is to assume we can pre-load the full set of images up front.
+   with 5 filters and 6000^2 (image, mask, var = 10 byte per pixel), we need 1.8GB, which is not too bad.
+ */
+
Index: trunk/psphot/src/psphotStackParseCamera.c
===================================================================
--- trunk/psphot/src/psphotStackParseCamera.c	(revision 26894)
+++ trunk/psphot/src/psphotStackParseCamera.c	(revision 26894)
@@ -0,0 +1,102 @@
+# include "psphotStandAlone.h"
+
+static pmFPAfile *defineFile(pmConfig *config, pmFPAfile *bind, const char *name, const char *filename, pmFPAfileType type);
+
+// define the needed / desired I/O files
+bool psphotStackParseCamera (pmConfig *config) {
+
+    bool status = false;
+
+    // the input images are defined as a set of metadatas in the INPUTS metadata folder
+    psMetadata *inputs = psMetadataLookupMetadata(&status, config->arguments, "INPUTS"); // The inputs info
+    if (!inputs) {
+	psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find inputs.");
+	return false;
+    }
+
+    psMetadata *item == NULL;
+    int nInputs = 0;
+    while ((item = psMetadataGetIndex(inputs, nInputs)) != NULL) {
+	if (item->type != PS_DATA_METADATA) {
+	    psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Component %s of the input metadata is not of type METADATA", item->name);
+	    return false;
+	}
+
+	psMetadata *input = item->data.md; // The input metadata of interest
+
+	// look for 'IMAGE', 'MASK', 'VARIANCE' in folder (only 'IMAGE' is required)
+
+	psString image = psMetadataLookupStr(NULL, input, "IMAGE"); // Name of image
+	if (!image || strlen(image) == 0) {
+	    psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Component %s lacks IMAGE of type STR", item->name);
+	    return false;
+	}
+	pmFPAfile *imageFile = defineFile(config, NULL, "PSPHOT.INPUT", image, PM_FPA_FILE_IMAGE); // File for image
+	if (!imageFile) {
+	    psError(PS_ERR_UNKNOWN, false, "Unable to define file from image %d (%s)", i, image);
+	    return false;
+	}
+
+	psString mask = psMetadataLookupStr(&status, input, "MASK"); // Name of mask
+	if (mask && strlen(mask) > 0) {
+	    if (!defineFile(config, imageFile, "PSPHOT.INPUT.MASK", mask, PM_FPA_FILE_MASK)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to define file from mask %d (%s)", i, mask);
+		return false;
+	    }
+	}
+
+	psString variance = psMetadataLookupStr(&mdok, input, "VARIANCE"); // Name of variance map
+	if (variance && strlen(variance) > 0) {
+	    if (!defineFile(config, imageFile, "PSPHOT.INPUT.VARIANCE", variance, PM_FPA_FILE_VARIANCE)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to define file from variance %d (%s)", i, variance);
+		return false;
+	    }
+	}
+	nInputs ++;
+    }
+    psMetadataRemoveKey(config->arguments, "FILENAMES");
+    psMetadataAddS32 (config->arguments, PS_LIST_TAIL, "PSPHOT.INPUT.NUM", PS_META_REPLACE, "number of inputs", nInputs);
+
+    // define the additional input/output files associated with psphot
+    // XXX figure out which files are needed by psphotStack
+    if (false && !psphotDefineFiles (config, input)) {
+        psError(PSPHOT_ERR_CONFIG, false, "Trouble defining the additional input/output files");
+        return false;
+    }
+
+    psTrace("psphot", 1, "Done with psphotStackParseCamera...\n");
+
+    psErrorClear();                     // some metadata lookup may have failed
+    return true;
+}
+
+// Define a file
+static pmFPAfile *defineFile(pmConfig *config, // Configuration
+                             pmFPAfile *bind, // File to which to bind
+                             const char *name, // Name of file rule
+                             const char *filename, // Name of file
+                             pmFPAfileType type // Type of file
+                             )
+{
+
+    psArray *dummy = psArrayAlloc(1);   // Dummy array of filenames for this FPA
+    dummy->data[0] = psStringCopy(filename);
+    psMetadataAddArray(config->arguments, PS_LIST_TAIL, "FILENAMES", PS_META_REPLACE,
+                       "Filenames for file rule definition", dummy);
+    psFree(dummy);
+
+    bool found = false;             // Found the file?
+    pmFPAfile *file = bind ? pmFPAfileBindFromArgs(&found, bind, config, name, "FILENAMES") :
+        pmFPAfileDefineFromArgs(&found, config, name, "FILENAMES");
+    if (!file || !found) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to define file %s from %s", name, filename);
+        return NULL;
+    }
+    if (file->type != type) {
+        psError(PS_ERR_IO, true, "%s is not of type %s", name, pmFPAfileStringFromType(type));
+        return NULL;
+    }
+
+    return file;
+}
+
Index: trunk/psphot/src/psphotStackReadout.c
===================================================================
--- trunk/psphot/src/psphotStackReadout.c	(revision 26894)
+++ trunk/psphot/src/psphotStackReadout.c	(revision 26894)
@@ -0,0 +1,207 @@
+# include "psphotInternal.h"
+
+bool psphotStackReadout (pmConfig *config, const pmFPAview *view) {
+
+    psTimerStart ("psphotReadout");
+
+    pmModelClassSetLimits(PM_MODEL_LIMITS_LAX); // allow models to have ugly fits (eg, central cusp)
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // set the photcode for this image (XXX currently goes into recipe, should it go into analysis?)
+    if (!psphotAddPhotcode (config, view)) {
+        psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // generate a background model (median, smoothed image)
+    if (!psphotSetMaskAndVariance (config, view, recipe)) {
+        return psphotReadoutCleanup (config, NULL, recipe, NULL, NULL, NULL);
+    }
+    if (!strcasecmp (breakPt, "NOTHING")) {
+	return psphotReadoutCleanup(config, NULL, recipe, NULL, NULL, NULL);
+    }
+
+    // optional break-point for processing
+    char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
+    PS_ASSERT_PTR_NON_NULL (breakPt, false);
+
+    // generate a background model (median, smoothed image)
+    if (!psphotModelBackground (config, view)) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    }
+    if (!psphotSubtractBackground (config, view)) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    }
+    if (!strcasecmp (breakPt, "BACKMDL")) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    }
+
+    // find the detections (by peak and/or footprint) in the image.
+    pmDetections *detections = psphotFindDetections (NULL, readout, recipe);
+    if (!detections) {
+        psLogMsg ("psphot", 3, "unable to find detections in this image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, NULL);
+    }
+
+    // construct sources and measure basic stats
+    psArray *sources = psphotSourceStats (config, readout, detections, true);
+    if (!sources) return false;
+    if (!strcasecmp (breakPt, "PEAKS")) {
+        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    }
+
+    // find blended neighbors of very saturated stars
+    // XXX merge this with Basic Deblend?
+    psphotDeblendSatstars (readout, sources, recipe);
+
+    // mark blended peaks PS_SOURCE_BLEND
+    if (!psphotBasicDeblend (sources, recipe)) {
+        psLogMsg ("psphot", 3, "failed on deblend analysis");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // classify sources based on moments, brightness
+    if (!psphotRoughClass (readout, sources, recipe, havePSF)) {
+        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+    if (!strcasecmp (breakPt, "MOMENTS")) {
+        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    }
+
+    if (!havePSF && !psphotImageQuality (recipe, sources)) {
+        psLogMsg("psphot", 3, "failed to measure image quality");
+        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    }
+
+    // if we were not supplied a PSF, choose one here
+    if (psf == NULL) {
+        // use bright stellar objects to measure PSF
+        // XXX if we do not have enough stars to generate the PSF, build one
+        // from the SEEING guess and model class
+        psf = psphotChoosePSF (readout, sources, recipe);
+        if (psf == NULL) {
+            psLogMsg ("psphot", 3, "failure to construct a psf model");
+            return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+        }
+        havePSF = true;
+    }
+    if (!strcasecmp (breakPt, "PSFMODEL")) {
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+    psphotVisualShowPSFModel (readout, psf);
+
+    // include externally-supplied sources
+    psphotLoadExtSources (config, view, sources);
+
+    // construct an initial model for each object, set the radius to fitRadius, set circular fit mask
+    psphotGuessModels (config, readout, sources, psf);
+
+    // linear PSF fit to source peaks, subtract the models from the image (in PSF mask)
+    psphotFitSourcesLinear (readout, sources, recipe, psf, FALSE);
+
+    // We have to place these visualizations here because the models are not realized until
+    // psphotGuessModels or fitted until psphotFitSourcesLinear.
+    psphotVisualShowPSFStars (recipe, psf, sources);
+
+    // identify CRs and extended sources
+    psphotSourceSize (config, readout, sources, recipe, psf, 0);
+    if (!strcasecmp (breakPt, "ENSEMBLE")) {
+        goto finish;
+    }
+    psphotVisualShowSatStars (recipe, psf, sources);
+
+    // non-linear PSF and EXT fit to brighter sources
+    // replace model flux, adjust mask as needed, fit, subtract the models (full stamp)
+    psphotBlendFit (config, readout, sources, psf);
+
+    // replace all sources
+    psphotReplaceAllSources (sources, recipe);
+
+    // linear fit to include all sources (subtract again)
+    psphotFitSourcesLinear (readout, sources, recipe, psf, TRUE);
+
+    // if we only do one pass, skip to extended source analysis
+    if (!strcasecmp (breakPt, "PASS1")) {
+        goto pass1finish;
+    }
+    // NOTE: possibly re-measure background model here with objects subtracted
+
+    // add noise for subtracted objects
+    psphotAddNoise (readout, sources, recipe);
+
+    // find fainter sources (pass 2)
+    detections = psphotFindDetections (detections, readout, recipe);
+
+    // remove noise for subtracted objects (ie, return to normal noise level)
+    psphotSubNoise (readout, sources, recipe);
+
+    // define new sources based on only the new peaks
+    psArray *newSources = psphotSourceStats (config, readout, detections, false);
+
+    // set source type
+    if (!psphotRoughClass (readout, newSources, recipe, havePSF)) {
+        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // create full input models, set the radius to fitRadius, set circular fit mask
+    psphotGuessModels (config, readout, newSources, psf);
+
+    // replace all sources so fit below applies to all at once
+    psphotReplaceAllSources (sources, recipe);
+
+    // merge the newly selected sources into the existing list
+    psphotMergeSources (sources, newSources);
+    psFree (newSources);
+
+    // linear fit to all sources
+    psphotFitSourcesLinear (readout, sources, recipe, psf, TRUE);
+
+pass1finish:
+
+    // measure source size for the remaining sources
+    psphotSourceSize (config, readout, sources, recipe, psf, 0);
+
+    psphotExtendedSourceAnalysis (readout, sources, recipe);
+
+    psphotExtendedSourceFits (readout, sources, recipe);
+
+finish:
+
+    // plot positive sources
+    // psphotSourcePlots (readout, sources, recipe);
+
+    // measure aperture photometry corrections
+    if (!psphotApResid (config, readout, sources, psf)) {
+        psLogMsg ("psphot", 3, "failed on psphotApResid");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // calculate source magnitudes
+    psphotMagnitudes(config, readout, view, sources, psf);
+
+    if (!psphotEfficiency(config, readout, view, psf, recipe, sources)) {
+        psErrorStackPrint(stderr, "Unable to determine detection efficiencies from fake sources");
+        psErrorClear();
+    }
+
+    // replace failed sources?
+    // psphotReplaceUnfitSources (sources);
+
+    // replace background in residual image
+    psphotSkyReplace (config, view);
+
+    // drop the references to the image pixels held by each source
+    psphotSourceFreePixels (sources);
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+}
+
Index: trunk/psphot/src/psphotSubtractBackground.c
===================================================================
--- trunk/psphot/src/psphotSubtractBackground.c	(revision 26618)
+++ trunk/psphot/src/psphotSubtractBackground.c	(revision 26894)
@@ -4,5 +4,5 @@
 // generate the median in NxN boxes, clipping heavily
 // linear interpolation to generate full-scale model
-bool psphotSubtractBackground (pmConfig *config, const pmFPAview *view, const char *filename)
+bool psphotSubtractBackgroundReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe)
 {
     bool status = true;
@@ -13,5 +13,6 @@
 
     // find the currently selected readout
-    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, filename);
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+
     pmFPA *inFPA = file->fpa;
     pmReadout *readout = pmFPAviewThisReadout (view, inFPA);
@@ -19,11 +20,9 @@
     psImage *mask  = readout->mask;
 
-    // find the currently selected readout
-    pmReadout *model = pmFPAfileThisReadout (config->files, view, "PSPHOT.BACKMDL");
+    // find the currently selected readout (XXX note that the model is saved on PSPHOT.BACKMDL regardless of 'filename'
+    pmFPAfile *modelFile = pmFPAfileSelectSingle(config->files, "PSPHOT.BACKMDL", index); // File of interest
+    assert (modelFile);
+    pmReadout *model = pmFPAviewThisReadout (view, modelFile->fpa);
     assert (model);
-
-    // select the appropriate recipe information
-    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
-    assert (recipe);
 
     // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
@@ -35,4 +34,5 @@
 
     // select background pixels, from output background file, or create
+    // XXX for now, we will only allow a single background image to be generated
     file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKGND");
     if (file) {
@@ -66,4 +66,5 @@
 
     // back-sub image pixels, from output background file (don't create if not requested)
+    // XXX for now, we will only allow a single background-subtracted image to be generated
     file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKSUB");
     if (file) {
@@ -109,5 +110,30 @@
     // the pmReadout selected in this function are all view on entries in config->files
 
+    // display the backsub and backgnd images
+    // move this inthe the subtract background loop
+    psphotVisualShowBackground (config, view, readout);
+
     npass ++;
     return true;
 }
+
+bool psphotSubtractBackground (pmConfig *config, const pmFPAview *view)
+{
+    bool status = false;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotSubtractBackgroundReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to subtract background for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
Index: trunk/psphot/src/psphotVisual.c
===================================================================
--- trunk/psphot/src/psphotVisual.c	(revision 26618)
+++ trunk/psphot/src/psphotVisual.c	(revision 26894)
@@ -60,4 +60,18 @@
 }
 
+bool psphotVisualEraseOverlays (int channel, char *overlay) {
+
+    int myKapa = psphotKapaChannel (channel);
+    if (!(strcasecmp (overlay, "all"))) {
+      KiiEraseOverlay (myKapa, "red");
+      KiiEraseOverlay (myKapa, "green");
+      KiiEraseOverlay (myKapa, "blue");
+      KiiEraseOverlay (myKapa, "yellow");
+      return true;
+    }
+    KiiEraseOverlay (myKapa, overlay);
+    return true;
+}
+
 bool psphotVisualShowMask (int kapaFD, psImage *inImage, const char *name, int channel) {
 
@@ -171,5 +185,5 @@
     if (kapa == -1) return false;
 
-    // psphotVisualShowMask (kapa, readout->mask, "mask", 2);
+    psphotVisualShowMask (kapa, readout->mask, "mask", 2);
     psphotVisualScaleImage (kapa, readout->variance, readout->mask, "variance", 1);
     psphotVisualScaleImage (kapa, readout->image, readout->mask, "image", 0);
@@ -204,5 +218,5 @@
 }
 
-bool psphotVisualShowSignificance (psImage *image) {
+bool psphotVisualShowSignificance (psImage *image, float min, float max) {
 
     if (!pmVisualIsVisual()) return true;
@@ -211,5 +225,5 @@
     if (kapa == -1) return false;
 
-    psphotVisualRangeImage (kapa, image, "signif", 2, -1.0, 25.0*25.0);
+    psphotVisualRangeImage (kapa, image, "signif", 2, min, max);
 
     pmVisualAskUser(NULL);
@@ -247,36 +261,5 @@
         overlay[Noverlay].text = NULL;
         Noverlay ++;
-
-# if (0)
-        overlay[Noverlay].type = KII_OVERLAY_BOX;
-        overlay[Noverlay].x = peak->x;
-        overlay[Noverlay].y = peak->y;
-        overlay[Noverlay].dx = 1.0;
-        overlay[Noverlay].dy = 1.0;
-        overlay[Noverlay].angle = 0.0;
-        overlay[Noverlay].text = NULL;
-        Noverlay ++;
-
-        overlay[Noverlay].type = KII_OVERLAY_CIRCLE;
-        overlay[Noverlay].x = peak->xf;
-        overlay[Noverlay].y = peak->yf;
-        overlay[Noverlay].dx = 2.0;
-        overlay[Noverlay].dy = 2.0;
-        overlay[Noverlay].angle = 0.0;
-        overlay[Noverlay].text = NULL;
-        Noverlay ++;
-# endif
-    }
-
-# if (1)
-    overlay[Noverlay].type = KII_OVERLAY_BOX;
-    overlay[Noverlay].x = 10.0;
-    overlay[Noverlay].y = 10.0;
-    overlay[Noverlay].dx = 1.0;
-    overlay[Noverlay].dy = 1.0;
-    overlay[Noverlay].angle = 0.0;
-    overlay[Noverlay].text = NULL;
-    Noverlay ++;
-# endif
+    }
 
     KiiLoadOverlay (kapa, overlay, Noverlay, "red");
@@ -1905,20 +1888,25 @@
     psFree (ySAT);
     psFree (mSAT);
+    psFree (sSAT);
 
     psFree (xEXT);
     psFree (yEXT);
     psFree (mEXT);
+    psFree (sEXT);
 
     psFree (xPSF);
     psFree (yPSF);
     psFree (mPSF);
+    psFree (sPSF);
 
     psFree (xDEF);
     psFree (yDEF);
     psFree (mDEF);
+    psFree (sDEF);
 
     psFree (xCR);
     psFree (yCR);
     psFree (mCR);
+    psFree (sCR);
 
     pmVisualAskUser(NULL);
