Index: branches/meh_branches/ppstack_test/psphot/doc/sourcepixels.txt
===================================================================
--- branches/meh_branches/ppstack_test/psphot/doc/sourcepixels.txt	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/doc/sourcepixels.txt	(revision 33415)
@@ -0,0 +1,78 @@
+
+I was having some bad results because of poor choices for the window
+used for the fit or the subtraction.  In the past, these were the same
+size (or coupled), and as a result I would either use too many pixels
+(slow) or too small of a subtraction window (bad residuals).  But, the
+fit does not need to operate on all of the pixels used for the
+subtraction.  
+
+I would like to subtract extended sources out to (say) 4 kron radii,
+but fit out to (say) 1 or 2 kron radii.
+
+Here is where the window and/or the fit / subtraction apertures are
+set:
+
+* psphotSourceStats : when a source if first detected (new peak), the
+  source is allocated and the window pixels are defined in the initial
+  loop of psphotSourceStatsReadout.  The window is defined based on a
+  fixed radius of SKY_OUTER_RADIUS (a rather outdated name).  There is
+  no circular mask set at this point.
+
+  If the source seems saturated, I generate a new set of pixels using
+  a fixed radius (3 * PSF_MOMENTS_RADIUS = 12 * Sigma = 12 * 1.6 *
+  PSF_Sigma = 19*PSF sigma.  this is a bit arbitrary, but sure seems
+  big enough..)
+
+* psphotGuessModels : this function sets up the initial PSF-based
+  guess, and in the process sets the fit radius and window based on
+  either the value 'PSF_FIT_RADIUS' or on the radius at which the
+  model flux is a specified fraction of the sky noise.
+
+  NOTE : this also sets a separate radius for the SAT stars, but it is
+  probably too small (2 x fitRadius)
+
+* psphotFitSourcesLinear : this function sets a masked-based aperture
+  based on the value of model->fitRadius (currently disabled)
+
+* psphotBlendFits : EXT models have the fit radius and subtraction
+  window set by calls to psphotSetRadiusFootprint and
+  psphotSetRadiusModel.
+
+
+-- other notes -- 
+
+pmSourceRedefinePixels has a somewhat silly API: there is no need to
+pass in the readout since the current pixels point to their parent
+images.
+
+---
+
+psphot/src/psphotSourceStats.c:        pmSourceDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
+psphot/src/psphotSourceStats.c:        pmSourceDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
+psphot/src/psphotSourceStats.c:	    // only slightly: pmSourceRedefinePixels uses the readout to pass the pointers to
+psphot/src/psphotSourceStats.c:	    pmSourceRedefinePixels (source, &tmpReadout, source->peak->x, source->peak->y, BIG_RADIUS + 2);
+
+psphot/src/psphotMergeSources.c:      pmSourceDefinePixels (newSource, readout, newSource->peak->x, newSource->peak->y, OUTER);
+psphot/src/psphotMergeSources.c:	pmSourceRedefinePixels (sourceOut, readoutOut, sourceOut->peak->x, sourceOut->peak->y, sourceOut->modelPSF->fitRadius);
+psphot/src/psphotMergeSources.c:	      pmSourceRedefinePixels (sourceOut, readout, sourceOut->peak->x, sourceOut->peak->y, sourceOut->modelPSF->fitRadius);
+psphot/src/psphotEfficiency.c:                if (!pmSourceDefinePixels(source, readout, x, y, sourceRadius)) {
+psphot/src/psphotSourceMatch.c:	    pmSourceDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
+psphot/src/psphotExtendedSourceAnalysisByObject.c:	    pmSourceRedefinePixels (source, readout, source->peak->xf, source->peak->yf, 1.5*radius);
+psphot/src/psphotPetrosianStudy.c:      pmSourceDefinePixels (source, readout, Xo, Yo, 128);
+psphot/src/psphotReplaceUnfit.c:bool psphotRedefinePixels (pmConfig *config, const pmFPAview *view, const char *filerule)
+psphot/src/psphotReplaceUnfit.c:	if (!psphotRedefinePixelsReadout (config, view, filerule, i, recipe)) {
+psphot/src/psphotReplaceUnfit.c:bool psphotRedefinePixelsReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe) {
+psphot/src/psphotReplaceUnfit.c:      pmSourceRedefinePixels (source, readout, Xo, Yo, radius);
+psphot/src/psphotRadialAperturesByObject.c:	    // the original view; the following elements get destroyed by pmSourceRedefinePixels so save them:
+psphot/src/psphotRadialAperturesByObject.c:	    pmSourceRedefinePixels (source, readout, source->peak->xf, source->peak->yf, outerRadius + 2);
+psphot/src/psphotRadialAperturesByObject.c:	    pmSourceRedefinePixelsByRegion (source, readout, oldRegion);
+
+psphot/src/psphotRadiusChecks.c:    bool status = pmSourceRedefinePixels (source, readout, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->fitRadius);
+psphot/src/psphotRadiusChecks.c:    bool status = pmSourceRedefinePixels (source, readout, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->fitRadius);
+psphot/src/psphotRadiusChecks.c:    pmSourceRedefinePixels (source, readout, peak->xf, peak->yf, rawRadius);
+psphot/src/psphotRadiusChecks.c:    pmSourceRedefinePixels (source, readout, peak->xf, peak->yf, model->fitRadius);
+
+psphot/src/psphotExtendedSourceAnalysis.c:	pmSourceRedefinePixels (source, readout, source->peak->xf, source->peak->yf, 1.5*radius);
+psphot/src/psphotRadialApertures.c:	// the original view; the following elements get destroyed by pmSourceRedefinePixels so save them:
+psphot/src/psphotRadialApertures.c:	pmSourceRedefinePixels (source, readout, source->peak->xf, source->peak->yf, outerRadius + 2);
+psphot/src/psphotRadialApertures.c:	pmSourceRedefinePixelsByRegion (source, readout, oldRegion);
Index: branches/meh_branches/ppstack_test/psphot/src/Makefile.am
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/Makefile.am	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/Makefile.am	(revision 33415)
@@ -25,5 +25,5 @@
 libpsphot_la_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
 
-bin_PROGRAMS = psphot psphotForced psphotMakePSF psphotStack
+bin_PROGRAMS = psphot psphotForced psphotMakePSF psphotStack psphotModelTest
 # bin_PROGRAMS = psphotPetrosianStudy psphotTest psphotMomentsStudy 
 
@@ -43,4 +43,8 @@
 psphotStack_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
 psphotStack_LDADD = libpsphot.la
+
+psphotModelTest_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+psphotModelTest_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+psphotModelTest_LDADD = libpsphot.la
 
 # psphotMomentsStudy_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
@@ -102,4 +106,13 @@
 	psphotCleanup.c
 
+# a psphot-variant that simply generates the PSF model
+psphotModelTest_SOURCES = \
+        psphotModelTest.c            \
+	psphotModelTestArguments.c   \
+	psphotParseCamera.c        \
+	psphotImageLoop.c   \
+	psphotMosaicChip.c	   \
+	psphotCleanup.c
+
 # psphotTest_SOURCES = \
 #         psphotTest.c
@@ -117,4 +130,5 @@
 	psphotVisual.c                 \
 	psphotCullPeaks.c	       \
+	psphotFootprintSaddles.c       \
 	psphotVersion.c		       \
 	psphotModelGroupInit.c	       \
@@ -129,4 +143,5 @@
 	psphotMakePSFReadout.c	       \
 	psphotModelBackground.c	       \
+	psphotModelTestReadout.c       \
 	psphotMaskBackground.c	       \
 	psphotSubtractBackground.c     \
@@ -157,4 +172,5 @@
 	psphotExtendedSourceAnalysisByObject.c \
 	psphotExtendedSourceFits.c     \
+	psphotSersicModelClass.c       \
 	psphotKernelFromPSF.c	       \
 	psphotFitSet.c		       \
@@ -168,4 +184,6 @@
 	psphotRadialPlot.c	       \
 	psphotKronMasked.c             \
+	psphotKronIterate.c            \
+	psphotRadialProfileWings.c     \
 	psphotDeblendSatstars.c	       \
 	psphotMosaicSubimage.c	       \
@@ -184,5 +202,4 @@
 	psphotRadialBins.c	       \
 	psphotRadialApertures.c	       \
-	psphotRadialAperturesByObject.c \
 	psphotPetrosian.c	       \
         psphotPetrosianRadialBins.c    \
@@ -190,7 +207,4 @@
         psphotPetrosianVisual.c        \
 	psphotEfficiency.c
-
-# XXX need to fix this for the new apis
-#	psphotModelTest.c	       
 
 # re-instate these
Index: branches/meh_branches/ppstack_test/psphot/src/psphot.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphot.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphot.c	(revision 33415)
@@ -27,5 +27,5 @@
     }
 
-    psLogMsg ("psphot", 3, "complete psphot run: %f sec\n", psTimerMark ("complete"));
+    psLogMsg ("psphot", PS_LOG_WARN, "complete psphot run: %f sec\n", psTimerMark ("complete"));
 
     psErrorCode exit_status = psphotGetExitStatus();
Index: branches/meh_branches/ppstack_test/psphot/src/psphot.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphot.h	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphot.h	(revision 33415)
@@ -19,4 +19,5 @@
     PSPHOT_FORCED,
     PSPHOT_MAKE_PSF,
+    PSPHOT_MODEL_TEST,
 } psphotImageLoopMode;
 
@@ -107,6 +108,6 @@
 bool            psphotBlendFit_Threaded (psThreadJob *job);
 
-bool            psphotReplaceAllSources (pmConfig *config, const pmFPAview *view, const char *filerule);
-bool            psphotReplaceAllSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe);
+bool            psphotReplaceAllSources (pmConfig *config, const pmFPAview *view, const char *filerule, bool ignoreState);
+bool            psphotReplaceAllSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe, bool ignoreState);
 
 bool            psphotAddNoise (pmConfig *config, const pmFPAview *view, const char *filerule);
@@ -117,4 +118,5 @@
 bool            psphotExtendedSourceAnalysis (pmConfig *config, const pmFPAview *view, const char *filerule);
 bool            psphotExtendedSourceAnalysisReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe);
+bool            psphotExtendedSourceAnalysis_Threaded (psThreadJob *job);
 
 bool            psphotExtendedSourceFits (pmConfig *config, const pmFPAview *view, const char *filerule);
@@ -174,5 +176,7 @@
 
 // in psphotReplaceUnfit.c:
-bool            psphotRemoveAllSources (const psArray *sources, const psMetadata *recipe);
+bool            psphotRemoveAllSourcesByArray (const psArray *sources, const psMetadata *recipe);
+bool            psphotRemoveAllSources (pmConfig *config, const pmFPAview *view, const char *filerule, bool ignoreState);
+bool            psphotRemoveAllSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool ignoreState);
 bool            psphotReplaceUnfitSources (psArray *sources, psImageMaskType maskVal);
 
@@ -327,4 +331,7 @@
 bool psphotMakePSFReadout(pmConfig *config, const pmFPAview *view, const char *filerule);
 
+pmConfig *psphotModelTestArguments(int argc, char **argv);
+bool psphotModelTestReadout(pmConfig *config, const pmFPAview *view, const char *filerule);
+
 int psphotFileruleCount(const pmConfig *config, const char *filerule);
 bool psphotFileruleCountSet(const pmConfig *config, const char *filerule, int num);
@@ -372,13 +379,4 @@
     bool convolve;                      // Convolve images?
     psphotStackConvolveSource convolveSource;
-    // psArray *convImages, *convMasks, *convVariances; // Filenames for the temporary convolved images
-
-    // bool matchZPs;                      // Adjust relative fluxes based on transparency analysis?
-    // bool photometry;                    // Perform photometry?
-    // psMetadata *stats;                  // Statistics for output
-    // FILE *statsFile;                    // File to which to write statistics
-    // psArray *origImages, *origMasks, *origVariances; // Filenames of the original images
-    // psArray *origCovars;                // Original covariances matrices
-    // int quality;                        // Bad data quality flag
 
     // Prepare
@@ -387,14 +385,8 @@
     psVector *inputMask;                // Mask for inputs
 
-    float targetSeeing;                 // Target seeing FWHM
+    psVector *targetSeeing;		// Target seeing FWHMs
     psArray *sourceLists;               // Individual lists of sources for matching
     psVector *norm;                     // Normalisation for each image
     psArray *psfs;
-
-    // psVector *exposures;                // Exposure times
-    // float sumExposure;                  // Sum of exposure times
-    // float zp;                           // Zero point for output
-    // psVector *inputMask;                // Mask for inputs
-    // psArray *sources;                   // Matched sources
 
     // Convolve
@@ -402,16 +394,4 @@
     psArray *regions;                   // PSF-matching regions --- required in the stacking
     psVector *matchChi2;                // chi^2 for stamps from matching
-    psVector *weightings;               // Combination weightings for images (1/noise^2)
-    // psArray *cells;                     // Cells for convolved images --- a handle for reading again
-    // int numCols, numRows;               // Size of image
-    // psArray *convCovars;                // Convolved covariance matrices
-
-    // Combine initial
-    // pmReadout *outRO;                   // Output readout
-    // pmReadout *expRO;                   // Exposure readout
-    // psArray *inspect;                   // Array of arrays of pixels to inspect
-
-    // Rejection
-    // psArray *rejected;                  // Rejected pixels
 } psphotStackOptions;
 
@@ -420,19 +400,23 @@
 bool psphotStackMatchPSFsReadout (pmConfig *config, const pmFPAview *view, psphotStackOptions *options, int index);
 bool psphotStackMatchPSFsPrepare (pmConfig *config, const pmFPAview *view, psphotStackOptions *options, int index);
-bool psphotStackMatchPSFsNext (bool *smoothAgain, pmConfig *config, const pmFPAview *view, const char *filerule, int lastSize);
-bool psphotStackMatchPSFsNextReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe, psVector *fwhmValues, int lastSize);
+
+bool psphotStackMatchPSFsNext (pmConfig *config, const pmFPAview *view, const char *filerule, int lastSize);
+bool psphotStackMatchPSFsNextReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, int lastSize);
+int psphotStackMatchPSFsEntries (pmConfig *config, const pmFPAview *view, const char *filerule);
 
 // psphotStackMatchPSFsUtils
-psVector *SetOptWidths (bool *optimum, psMetadata *recipe);
-pmReadout *makeFakeReadout(pmConfig *config, pmReadout *raw, psArray *sources, pmPSF *psf, psImageMaskType maskVal, int fullSize);
-bool rescaleData(pmReadout *readout, pmConfig *config, psphotStackOptions *options, int index);
-bool renormKernel(pmReadout *readout, psphotStackOptions *options, int index);
+// psVector *SetOptWidths (bool *optimum, psMetadata *recipe);
+// pmReadout *makeFakeReadout(pmConfig *config, pmReadout *raw, psArray *sources, pmPSF *psf, psImageMaskType maskVal, int fullSize);
+// bool rescaleData(pmReadout *readout, pmConfig *config, psphotStackOptions *options, int index);
+// bool renormKernel(pmReadout *readout, psphotStackOptions *options, int index);
 bool saveMatchData (pmReadout *readout, psphotStackOptions *options, int index);
 bool matchKernel(pmConfig *config, pmReadout *cnv, pmReadout *raw, psphotStackOptions *options, int index);
-bool dumpImageDiff(pmReadout *readoutConv, pmReadout *readoutFake, pmReadout *readoutRef, int index, char *rootname);
-bool dumpImage(pmReadout *readoutOut, pmReadout *readoutRef, int index, char *rootname);
-bool loadKernel (pmConfig *config, pmReadout *readoutCnv, psphotStackOptions *options, int index);
-
-pmPSF *psphotStackPSF(const pmConfig *config, int numCols, int numRows, const psArray *psfs, const psVector *inputMask);
+// bool dumpImageDiff(pmReadout *readoutConv, pmReadout *readoutFake, pmReadout *readoutRef, int index, char *rootname);
+// bool dumpImage(pmReadout *readoutOut, pmReadout *readoutRef, int index, char *rootname);
+// bool loadKernel (pmConfig *config, pmReadout *readoutCnv, psphotStackOptions *options, int index);
+
+bool psphotStackRenormaliseVariance(const pmConfig *config, pmReadout *readout);
+
+bool psphotStackPSF(const pmConfig *config, psphotStackOptions *options);
 
 psphotStackOptions *psphotStackOptionsAlloc (int num);
@@ -443,7 +427,9 @@
 bool psphotCopySourcesReadout (pmConfig *config, const pmFPAview *view, const char *ruleOut, const char *ruleSrc, int index);
 
-bool psphotRadialApertures (pmConfig *config, const pmFPAview *view, const char *filerule);
-bool psphotRadialAperturesReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe);
-bool psphotRadialApertureSource (pmSource *source, psMetadata *recipe, psImageMaskType maskVal, const psVector *radMax, int entry);
+bool psphotRadialApertures (pmConfig *config, const pmFPAview *view, const char *filerule, int entry);
+bool psphotRadialAperturesReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe, int entry);
+bool psphotRadialApertures_Threaded (psThreadJob *job);
+bool psphotRadialApertureSource (pmSource *source, pmReadout *readout, int entry, psVector *pixRadius2, psVector *pixFlux, psVector *pixVer);
+// bool psphotRadialApertureSource (pmSource *source, int entry);
 
 bool psphotExtendedSourceAnalysisByObject (pmConfig *config, psArray *objects, const pmFPAview *view, const char *filerule);
@@ -469,3 +455,29 @@
 psArray *psphotSourceChildrenByObject (pmConfig *config, const pmFPAview *view, const char *filerule, psArray *objectsSrc);
 
+bool psphotSourceParents (pmConfig *config, const pmFPAview *view, const char *ruleOut, const char *ruleSrc);
+bool psphotSourceParentsReadout (pmConfig *config, const pmFPAview *view, const char *ruleOut, const char *ruleSrc, int index);
+
+bool psphotCopyPeaks (pmConfig *config, const pmFPAview *view, const char *ruleOut, const char *ruleSrc);
+bool psphotCopyPeaksReadout (pmConfig *config, const pmFPAview *view, const char *ruleOut, const char *ruleSrc, int index);
+
+bool psphotSersicModelClassGuessPCM (pmPCMdata *pcm, pmSource *source);
+void psphotSersicModelClassInit ();
+void psphotSersicModelClassCleanup ();
+
+bool psphotSetRadiusMoments (float *fitRadius, float *windowRadius, pmReadout *readout, pmSource *source, psImageMaskType markVal);
+bool psphotSetRadiusMomentsExact (float *fitRadius, float *windowRadius, pmReadout *readout, pmSource *source, psImageMaskType markVal);
+
+bool psphotFootprintSaddles(pmReadout *readout, psArray *footprints);
+bool psphotMaskFootprint (pmReadout *readout, pmSource *source, psImageMaskType markVal);
+
+bool psphotKronIterate (pmConfig *config, const pmFPAview *view, const char *filerule);
+bool psphotKronIterateReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, pmReadout *readout, psArray *sources, pmPSF *psf);
+bool psphotKronIterate_Threaded (psThreadJob *job);
+
+bool psphotRadialProfileWings (pmConfig *config, const pmFPAview *view, const char *filerule);
+bool psphotRadialProfileWingsReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, pmReadout *readout, psArray *sources);
+bool psphotRadialProfileWings_Threaded (psThreadJob *job);
+
+bool psphotStackObjectsSelectForAnalysis (pmConfig *config, const pmFPAview *view, const char *filerule, psArray *objects);
+
 #endif
Index: branches/meh_branches/ppstack_test/psphot/src/psphotAddNoise.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotAddNoise.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotAddNoise.c	(revision 33415)
@@ -85,7 +85,7 @@
     }
     if (add) {
-        psLogMsg ("psphot.noise", PS_LOG_INFO, "add noise for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.noise"));
+        psLogMsg ("psphot.noise", PS_LOG_WARN, "add noise for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.noise"));
     } else {
-        psLogMsg ("psphot.noise", PS_LOG_INFO, "sub noise for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.noise"));
+        psLogMsg ("psphot.noise", PS_LOG_WARN, "sub noise for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.noise"));
     }
 
Index: branches/meh_branches/ppstack_test/psphot/src/psphotApResid.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotApResid.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotApResid.c	(revision 33415)
@@ -1,4 +1,4 @@
 # include "psphotInternal.h"
-# define DEBUG
+// # define DEBUG
 
 # define SKIPSTAR(MSG) { psTrace ("psphot", 3, "invalid : %s", MSG); continue; }
@@ -9,4 +9,7 @@
 {
     bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Aperture Residuals ---");
 
     // select the appropriate recipe information
@@ -151,6 +154,7 @@
 
         // wait for the threads to finish and manage results
-        if (!psThreadPoolWait (false)) {
+        if (!psThreadPoolWait (false, true)) {
             psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+            psFree(cellGroups);
             return false;
         }
@@ -347,5 +351,5 @@
 
     psLogMsg ("psphot.apresid", PS_LOG_DETAIL, "aperture residual: %f +/- %f\n", psf->ApResid, psf->dApResid);
-    psLogMsg ("psphot.apresid", PS_LOG_INFO, "measure full-frame aperture residuals for %d sources: %f sec\n", Npsf, psTimerMark ("psphot.apresid"));
+    psLogMsg ("psphot.apresid", PS_LOG_WARN, "measure full-frame aperture residuals for %d sources: %f sec\n", Npsf, psTimerMark ("psphot.apresid"));
 
     psFree (xPos);
Index: branches/meh_branches/ppstack_test/psphot/src/psphotBlendFit.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotBlendFit.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotBlendFit.c	(revision 33415)
@@ -5,4 +5,7 @@
 {
     bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Fit Sources (Non-Linear) ---");
 
     // select the appropriate recipe information
@@ -148,5 +151,5 @@
 
         // wait for the threads to finish and manage results
-        if (!psThreadPoolWait (false)) {
+        if (!psThreadPoolWait (false, true)) {
             psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
 	    psFree (fitOptions);
@@ -186,5 +189,5 @@
     psFree (fitOptions);
 
-    psLogMsg ("psphot.psphotBlendFit", PS_LOG_INFO, "fit models: %f sec for %d objects (%d psf, %d ext, %d failed, %ld skipped)\n", psTimerMark ("psphot.fit.nonlinear"), Nfit, Npsf, Next, Nfail, sources->n - Nfit);
+    psLogMsg ("psphot.psphotBlendFit", PS_LOG_WARN, "fit models: %f sec for %d objects (%d psf, %d ext, %d failed, %ld skipped)\n", psTimerMark ("psphot.fit.nonlinear"), Nfit, Npsf, Next, Nfail, sources->n - Nfit);
 
     psphotVisualShowResidualImage (readout, false);
@@ -233,4 +236,16 @@
         pmSource *source = sources->data[i];
 
+# if (0)
+# define TEST_X 34
+# define TEST_Y 28
+   
+	if ((fabs(source->peak->xf - TEST_X) < 5) && (fabs(source->peak->yf - TEST_Y) < 5)) {
+	    fprintf (stderr, "test object\n");
+	}
+
+# undef TEST_X
+# undef TEST_Y
+# endif
+
         // skip non-astronomical objects (very likely defects)
         if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
@@ -246,6 +261,9 @@
 
         // limit selection to some SN limit
-        if (sqrt(source->peak->detValue) < FIT_SN_LIM) continue;
-
+        if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
+	    if (source->moments->KronFlux < FIT_SN_LIM * source->moments->KronFluxErr) continue;
+	} else {
+	    if (sqrt(source->peak->detValue) < FIT_SN_LIM) continue;
+	}
         // exclude sources outside optional analysis region
         if (source->peak->xf < AnalysisRegion.x0) continue;
@@ -267,5 +285,5 @@
         }
 
-        // replace object in image
+        // replace object in image & remove excess noise
         if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
             pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
@@ -305,5 +323,5 @@
         Nfail ++;
 
-        // re-subtract the object, leave local sky
+        // re-subtract the object, leave local sky, re-bump noise
         pmSourceCacheModel (source, maskVal);
         pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
Index: branches/meh_branches/ppstack_test/psphot/src/psphotChoosePSF.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotChoosePSF.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotChoosePSF.c	(revision 33415)
@@ -5,4 +5,7 @@
 {
     bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Choose PSF ---");
 
     // select the appropriate recipe information
@@ -376,5 +379,5 @@
 
     char *modelName = pmModelClassGetName (psf->type);
-    psLogMsg ("psphot.pspsf", PS_LOG_INFO, "select psf model: %f sec\n", psTimerMark ("psphot.choose.psf"));
+    psLogMsg ("psphot.pspsf", PS_LOG_WARN, "select psf model: %f sec\n", psTimerMark ("psphot.choose.psf"));
     psLogMsg ("psphot.pspsf", PS_LOG_INFO, "psf model %s, ApResid: %f +/- %f\n", modelName, psf->ApResid, psf->dApResid);
 
@@ -443,8 +446,8 @@
             psVectorAppend (fwhmMinor, FWHM_MINOR);
 
-	    if (modelPSF->params->n >= 7) {
+	    if (modelPSF->params->n > 7) {
 	      psVectorAppend (psfExtra1, modelPSF->params->data.F32[7]);
 	    }
-	    if (modelPSF->params->n >= 8) {
+	    if (modelPSF->params->n > 8) {
 	      psVectorAppend (psfExtra2, modelPSF->params->data.F32[8]);
 	    }
@@ -517,5 +520,5 @@
     }
 
-    // psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "ANGLE",    PS_META_REPLACE, "PSF angle",           axes.theta);
+    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);
 
Index: branches/meh_branches/ppstack_test/psphot/src/psphotEfficiency.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotEfficiency.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotEfficiency.c	(revision 33415)
@@ -99,5 +99,5 @@
 
 /// Generate a fake image and add it in to the existing readout
-static bool effGenerate(psImage **xSrc, psImage **ySrc, // Positions of sources
+static pmReadout *effGenerate(psImage **xSrc, psImage **ySrc, // Positions of sources
                         const pmReadout *ro,            // Readout of interest
                         const pmPSF *psf,               // Point-spread function
@@ -152,5 +152,5 @@
         psFree(xAll);
         psFree(yAll);
-        return false;
+        return NULL;
     }
     psFree(magAll);
@@ -161,7 +161,7 @@
 
     psBinaryOp(ro->image, ro->image, "+", fakeRO->image);
-    psFree(fakeRO);
-
-    return true;
+
+    // return the readout so we can subtract it later
+    return fakeRO;
 }
 
@@ -169,4 +169,7 @@
 {
     bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Efficiency ---");
 
     // select the appropriate recipe information
@@ -252,5 +255,5 @@
 
     // remove all sources, adding noise for subtracted sources
-    psphotRemoveAllSources(realSources, recipe);
+    psphotRemoveAllSourcesByArray(realSources, recipe);
 
 #if TESTING
@@ -290,6 +293,6 @@
 
     psImage *xFake = NULL, *yFake = NULL; // Coordinates of sources, each bin in a row
-    if (!effGenerate(&xFake, &yFake, readout, psf, magOffsets,
-                     numSources, magLim, radius, minFlux)) {
+    pmReadout *fakeRO = effGenerate(&xFake, &yFake, readout, psf, magOffsets, numSources, magLim, radius, minFlux);
+    if (!fakeRO) {
         psError(PS_ERR_UNKNOWN, false, "Unable to generate fake sources");
         psFree(xFake);
@@ -416,4 +419,5 @@
     psFree(significance);
 
+    // psphotFitSourcesLinearReadout subtracts the model fits
     if (!psphotFitSourcesLinearReadout(recipe, readout, fakeSourcesAll, psf, true)) {
         psError(PS_ERR_UNKNOWN, false, "Unable to perform linear fit on fake sources.");
@@ -427,4 +431,5 @@
     psf->ApTrend = NULL;
 
+    // measure the magnitudes and fluxes for the sources
     if (!psphotMagnitudesReadout(config, recipe, view, readout, fakeSourcesAll, psf)) {
         psError(PS_ERR_UNKNOWN, false, "Unable to measure magnitudes of fake sources.");
@@ -433,4 +438,14 @@
         psf->ApTrend = apTrend; // Casting away const!
         return false;
+    }
+
+    // replace the subtracted model fits
+    for (int i = 0; i < fakeSourcesAll->n; i++) {
+	pmSource *source = fakeSourcesAll->data[i];
+
+	// replace other sources?
+	if (!(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED)) continue;
+	
+	pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
     }
     psFree(fakeSourcesAll);
@@ -515,4 +530,8 @@
     psFree(fakeSources);
 
+    // subtract the faked sources from the original image
+    psBinaryOp(readout->image, readout->image, "-", fakeRO->image);
+    psFree(fakeRO);
+
     pmDetEff *de = pmDetEffAlloc(magLim, numSources, numBins); // Detection efficiency
     de->magOffsets = psVectorCopy(NULL, magOffsets, PS_TYPE_F32);
@@ -529,5 +548,5 @@
     psFree(de);
 
-    psLogMsg("psphot", PS_LOG_INFO, "Detection efficiency: %lf sec\n", psTimerClear("psphot.fake"));
+    psLogMsg("psphot", PS_LOG_WARN, "Detection efficiency: %lf sec\n", psTimerClear("psphot.fake"));
 
     return true;
Index: branches/meh_branches/ppstack_test/psphot/src/psphotEllipticalContour.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotEllipticalContour.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotEllipticalContour.c	(revision 33415)
@@ -127,6 +127,4 @@
 psF32 psphotEllipticalContourFunc (psVector *deriv, const psVector *params, const psVector *coord) {
 
-    static int pass = 0;
-
     psF32 *par = params->data.F32;
 
@@ -145,8 +143,5 @@
 
     // value is X
-    // if (coord->data.F32[1] == 0) {
-    if (pass == 0) {
-	pass = 1;
-
+    if (coord->data.F32[1] < 0.5) {
 	float value = par[PAR_RMIN]*cs_alpha*r;
 
@@ -161,8 +156,5 @@
 
     // value is Y
-    // if (coord->data.F32[1] == 1) {
-    if (pass == 1) {
-	pass = 0;
-
+    if (coord->data.F32[1] > 0.5) {
 	float value = par[PAR_RMIN]*sn_alpha*r;
 
Index: branches/meh_branches/ppstack_test/psphot/src/psphotExtendedSourceAnalysis.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotExtendedSourceAnalysis.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotExtendedSourceAnalysis.c	(revision 33415)
@@ -2,5 +2,4 @@
 
 // measure the elliptical radial profile and use this to measure the petrosian parameters for the sources
-// XXX this function needs to be threaded
 
 // for now, let's store the detections on the readout->analysis for each readout
@@ -8,4 +7,7 @@
 {
     bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Extended Source Analysis (Petrosians) ---");
 
     // select the appropriate recipe information
@@ -59,7 +61,9 @@
     }
 
-    // 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);
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
 
     // get the sky noise from the background analysis; if this is missing, get the user-supplied value
@@ -70,4 +74,120 @@
     }
 
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortByFlux);
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion *AnalysisRegion = psRegionAlloc(0,0,0,0);
+    *AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (*AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    // 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_EXTENDED_ANALYSIS");
+
+            psArrayAdd(job->args, 1, readout);
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+            psArrayAdd(job->args, 1, AnalysisRegion);
+            psArrayAdd(job->args, 1, recipe);
+
+            PS_ARRAY_ADD_SCALAR(job->args, skynoise, PS_TYPE_F32);
+
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Next
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Npetro
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nannuli
+
+// set this to 0 to run without threading
+# if (1)	    
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+		psFree(AnalysisRegion);
+                return false;
+            } 
+# else
+	    if (!psphotExtendedSourceAnalysis_Threaded(job)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+		psFree(AnalysisRegion);
+		return false;
+	    }
+	    psScalar *scalar = NULL;
+	    scalar = job->args->data[5];
+	    Next += scalar->data.S32;
+	    scalar = job->args->data[6];
+	    Npetro += scalar->data.S32;
+	    scalar = job->args->data[7];
+	    Nannuli += scalar->data.S32;
+	    psFree(job);
+# endif
+	}
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false, true)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+	    psFree(AnalysisRegion);
+            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[5];
+		Next += scalar->data.S32;
+		scalar = job->args->data[6];
+		Npetro += scalar->data.S32;
+		scalar = job->args->data[7];
+		Nannuli += scalar->data.S32;
+            }
+            psFree(job);
+	}
+    }
+    psFree (cellGroups);
+    psFree(AnalysisRegion);
+
+    psLogMsg ("psphot", PS_LOG_WARN, "extended source analysis: %f sec for %d objects\n", psTimerMark ("psphot.extended"), Next);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d petrosian\n", Npetro);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d annuli\n", Nannuli);
+
+    psphotVisualShowResidualImage (readout, false);
+
+    bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+    if (doPetrosian) {
+	psphotVisualShowPetrosians (sources);
+    }
+
+    return true;
+}
+
+bool psphotExtendedSourceAnalysis_Threaded (psThreadJob *job) {
+
+    bool status;
+
+    int Next = 0;
+    int Npetro = 0;
+    int Nannuli = 0;
+
+    // arguments: readout, sources, models, region, psfSize, maskVal, markVal
+    pmReadout *readout      = job->args->data[0];
+    psArray *sources        = job->args->data[1];
+    psRegion *region        = job->args->data[2];
+    psMetadata *recipe      = job->args->data[3];
+
+    float skynoise          = PS_SCALAR_VALUE(job->args->data[4],F32);
+
     // S/N limit to perform full non-linear fits
     float SN_LIM = psMetadataLookupF32 (&status, recipe, "EXTENDED_SOURCE_SN_LIM");
@@ -78,11 +198,7 @@
     bool doPetroStars   = psMetadataLookupBool (&status, recipe, "PETROSIAN_FOR_STARS");
 
-    // source analysis is done in S/N order (brightest first)
-    sources = psArraySort (sources, pmSourceSortByFlux);
-
-    // 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");
+    // 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);
 
     // choose the sources of interest
@@ -90,4 +206,9 @@
 
 	pmSource *source = sources->data[i];
+
+	// if we have checked the source validity on the basis of the object set, then 
+	// we either skip these tests below or we skip the source completely
+	if (source->tmpFlags & PM_SOURCE_TMPF_PETRO_SKIP) continue;
+	if (source->tmpFlags & PM_SOURCE_TMPF_PETRO_KEEP) goto keepSource;
 
 	// skip PSF-like and non-astronomical objects
@@ -104,12 +225,21 @@
 
 	// limit selection to some SN limit
-	assert (source->peak); // how can a source not have a peak?
-	if (sqrt(source->peak->detValue) < SN_LIM) continue;
-
-	// limit selection by analysis region
-	if (source->peak->x < AnalysisRegion.x0) continue;
-	if (source->peak->y < AnalysisRegion.y0) continue;
-	if (source->peak->x > AnalysisRegion.x1) continue;
-	if (source->peak->y > AnalysisRegion.y1) continue;
+	// assert (source->peak); // how can a source not have a peak?
+	// limit selection to some SN limit
+	bool skipSource = false;
+	if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
+	    skipSource = (source->moments->KronFlux < SN_LIM * source->moments->KronFluxErr);
+	} else {
+	    skipSource = (sqrt(source->peak->detValue) < SN_LIM);
+	}
+	if (skipSource) continue;
+
+	// limit selection by analysis region (this automatically apply
+	if (source->peak->x < region->x0) continue;
+	if (source->peak->y < region->y0) continue;
+	if (source->peak->x > region->x1) continue;
+	if (source->peak->y > region->y1) continue;
+
+    keepSource:
 
 	// replace object in image
@@ -159,15 +289,15 @@
     }
 
-    psLogMsg ("psphot", PS_LOG_INFO, "extended source analysis: %f sec for %d objects\n", psTimerMark ("psphot.extended"), Next);
-    psLogMsg ("psphot", PS_LOG_INFO, "  %d petrosian\n", Npetro);
-    psLogMsg ("psphot", PS_LOG_INFO, "  %d annuli\n", Nannuli);
-
-    psphotVisualShowResidualImage (readout, false);
-
-    if (doPetrosian) {
-	psphotVisualShowPetrosians (sources);
-    }
-
-    // fprintf (stderr, "xsrc : tried %ld objects\n", sources->n);
+    psScalar *scalar = NULL;
+
+    // change the value of a scalar on the array (wrap this and put it in psArray.h)
+    scalar = job->args->data[5];
+    scalar->data.S32 = Next;
+
+    scalar = job->args->data[6];
+    scalar->data.S32 = Npetro;
+
+    scalar = job->args->data[7];
+    scalar->data.S32 = Nannuli;
 
     return true;
Index: branches/meh_branches/ppstack_test/psphot/src/psphotExtendedSourceFits.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotExtendedSourceFits.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotExtendedSourceFits.c	(revision 33415)
@@ -6,4 +6,7 @@
     bool status = true;
 
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Extended Source Fits ---");
+
     // select the appropriate recipe information
     psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
@@ -12,5 +15,5 @@
     // perform full extended source non-linear fits?
     if (!psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_FITS")) {
-        psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source measurements\n");
+        psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source fits\n");
         return true;
     }
@@ -45,4 +48,6 @@
     int Nfail = 0;
 
+    psphotSersicModelClassInit();
+
     psTimerStart ("psphot.extended");
 
@@ -103,6 +108,6 @@
 
       if (item->type != PS_DATA_METADATA) {
+        // XXX we could cull the bad entries or build a validated model folder
         psAbort ("Invalid type for EXTENDED_SOURCE_MODEL entry %s, not a metadata folder", item->name);
-        // XXX we could cull the bad entries or build a validated model folder
       }
 
@@ -213,5 +218,5 @@
 
         // wait for the threads to finish and manage results
-        if (!psThreadPoolWait (false)) {
+        if (!psThreadPoolWait (false, true)) {
             psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
 	    psFree(AnalysisRegion);
@@ -247,5 +252,9 @@
     psFree(AnalysisRegion);
 
-    psLogMsg ("psphot", PS_LOG_INFO, "extended source model fits: %f sec for %d objects\n", psTimerMark ("psphot.extended"), Next);
+    psphotSersicModelClassCleanup();
+
+    psphotVisualShowResidualImage (readout, false);
+
+    psLogMsg ("psphot", PS_LOG_WARN, "extended source model fits: %f sec for %d objects\n", psTimerMark ("psphot.extended"), Next);
     psLogMsg ("psphot", PS_LOG_INFO, "  %d convolved models (%d passed)\n", Nconvolve, NconvolvePass);
     psLogMsg ("psphot", PS_LOG_INFO, "  %d plain models (%d passed)\n", Nplain, NplainPass);
@@ -265,8 +274,6 @@
     int Nplain = 0;
     int NplainPass = 0;
-    bool savePics = false;
-    float radius;
+    float fitRadius, windowRadius;
     psScalar *scalar = NULL;
-    pmMoments psfMoments;
 
     // arguments: readout, sources, models, region, psfSize, maskVal, markVal
@@ -275,9 +282,7 @@
     psMetadata *models      = job->args->data[2];
     psRegion *region        = job->args->data[3];
-    int psfSize             = PS_SCALAR_VALUE(job->args->data[4],PS_TYPE_IMAGE_MASK_DATA);
+    int psfSize             = PS_SCALAR_VALUE(job->args->data[4],S32);
     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);
-
-    // pthread_t tid = pthread_self();     // Thread identifier
 
     // Define source fitting parameters for extended source fits
@@ -299,5 +304,5 @@
 
         // skip PSF-like and non-astronomical objects
-        if (source->type == PM_SOURCE_TYPE_STAR) continue;
+        if (!(source->mode & PM_SOURCE_MODE_EXT_LIMIT)) continue;
         if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
         if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
@@ -309,10 +314,4 @@
         if (source->peak->y > region->y1) continue;
 
-        // if model is NULL, we don't have a starting guess
-        // XXX use the parameters guessed from moments
-        // if (source->modelEXT == NULL) continue;
-
-	// fprintf (stderr, "fit %d,%d in thread %d\n", source->peak->x, source->peak->y, (int) tid);
-
         // replace object in image
         if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
@@ -321,47 +320,11 @@
         Next ++;
 
-	// set the radius based on the footprint (also sets the mask pixels)
-	if (!psphotSetRadiusFootprint(&radius, readout, source, markVal, 1.0)) {
-	    fprintf (stderr, "skipping (1) %f, %f\n", source->peak->xf, source->peak->yf);
-	    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
-	    // XXX raise an error of some kind
-	    continue;
-	}
-
-	// XXX note that this changes the source moments that are published...
-	// recalculate the source moments using the larger extended-source moments radius
-	// at this stage, skip Gaussian windowing, and do not clip pixels by S/N
-	// this uses the footprint to judge both radius and aperture?
-	// XXX save the psf-based moments for output
-	psfMoments = *source->moments;
-	if (!pmSourceMoments (source, radius, 0.0, 0.0, 0.0, maskVal)) {
-	    // subtract the best fit from the object, leave local sky
-	    fprintf (stderr, "skipping (2) %f, %f\n", source->peak->xf, source->peak->yf);
-	    *source->moments = psfMoments;
-	    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
-	    // XXX raise an error flag of some kind
-	    continue;
-	}
-
-        // save the modelFlux here in case we need to subtract it (for failure)
-        psImage *modelFluxStart = psMemIncrRefCounter (source->modelFlux);
-	if (!modelFluxStart) {
-	    pmSourceCacheModel (source, maskVal);
-	    modelFluxStart = psMemIncrRefCounter (source->modelFlux);
-	    if (!modelFluxStart) {
-		fprintf (stderr, "skipping (3) %f, %f\n", source->peak->xf, source->peak->yf);
-		*source->moments = psfMoments;
-		pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
-		// XXX raise an error of some kind?
-		continue;
-	    }
-	}
-	
-        if (savePics) {
-          psphotSaveImage (NULL, readout->image, "image.xp.fits");
-        }
-
-        // array to store the pointers to the model flux images while the models are being fitted
-        psArray *modelFluxes = psArrayAllocEmpty (models->list->n);
+	// set the fit radius based on the first radial moment (also sets the mask pixels)
+	psphotSetRadiusMomentsExact(&fitRadius, &windowRadius, readout, source, markVal); // NOTE : 6 allocs
+
+	// UPDATE : we have changed the moments calculation.  There is now an iteration within 
+	// psphotKronMasked to determine moments appropriate for a larger object.  The values
+	// Mrf, KronFlux, and KronFluxErr are calculated for the iterated radius.  The other
+	// values are left at the psf-based values.
 
         // allocate the array to store the model fits
@@ -380,10 +343,17 @@
 
           // check the SNlim and skip model if source is too faint
-          float SNlim = psMetadataLookupF32 (&status, model, "SNLIM_VALUE");
+          float FIT_SN_LIM = psMetadataLookupF32 (&status, model, "SNLIM_VALUE");
           assert (status);
 
           // limit selection to some SN limit
           // assert (source->peak); // how can a source not have a peak?
-          if (sqrt(source->peak->detValue) < SNlim) {
+	  // limit selection to some SN limit
+	  bool skipSource = false;
+	  if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
+	      skipSource = (source->moments->KronFlux < FIT_SN_LIM * source->moments->KronFluxErr);
+	  } else {
+	      skipSource = (sqrt(source->peak->detValue) < FIT_SN_LIM);
+	  }
+          if (skipSource) {
 	      Nfaint ++;
 	      continue;
@@ -398,11 +368,9 @@
           assert (status);
 
-          // XXX psTraceSetLevel ("psLib.math.psMinimizeLMChi2", 6);
-          // XXX psTraceSetLevel ("psphot.psphotModelWithPSF_LMM", 6);
-
           // fit the model as convolved or not
           pmModel *modelFit = NULL;
           if (convolved) {
-              modelFit = psphotFitPCM (readout, source, fitOptions, modelType, maskVal, markVal, psfSize);
+	      // NOTE : 4 more allocs to here
+              modelFit = psphotFitPCM (readout, source, fitOptions, modelType, maskVal, markVal, psfSize); // NOTE : 2313 allocs in here
               if (!modelFit) {
                   psTrace ("psphot", 5, "failed to fit psf-conv model for object at %f, %f", source->moments->Mx, source->moments->My);
@@ -410,5 +378,6 @@
                   continue;
               }
-              psTrace ("psphot", 4, "fit psf-conv model for %f, %f : %s chisq = %f\n", source->moments->Mx, source->moments->My, pmModelClassGetName (modelFit->type), modelFit->chisq);
+              psTrace ("psphot", 4, "fit psf-conv model for %f, %f : %s chisq = %f (npix: %d, niter: %d)\n", 
+		       source->moments->Mx, source->moments->My, pmModelClassGetName (modelFit->type), modelFit->chisq, modelFit->nPix, modelFit->nIter);
               Nconvolve ++;
               if (!(modelFit->flags & (PM_MODEL_STATUS_BADARGS | PM_MODEL_STATUS_NONCONVERGE | PM_MODEL_STATUS_OFFIMAGE))) {
@@ -425,6 +394,6 @@
                   continue;
               }
-              pmSourceCacheModel (source, maskVal);
-              psTrace ("psphot", 4, "fit plain model for %f, %f : %s chisq = %f\n", source->moments->Mx, source->moments->My, pmModelClassGetName (modelFit->type), modelFit->chisq);
+              psTrace ("psphot", 4, "fit plain model for %f, %f : %s chisq = %f (npix: %d, niter: %d)\n", 
+		       source->moments->Mx, source->moments->My, pmModelClassGetName (modelFit->type), modelFit->chisq, modelFit->nPix, modelFit->nIter);
               Nplain ++;
               if (!(modelFit->flags & (PM_MODEL_STATUS_BADARGS | PM_MODEL_STATUS_NONCONVERGE | PM_MODEL_STATUS_OFFIMAGE))) {
@@ -434,9 +403,21 @@
           }
 
-          // save each of the model flux images and store the best
-          psArrayAdd (modelFluxes, 4, source->modelFlux);
+	  // XXX deprecate?
+	  // XXX pmSourceExtFitPars *extFitPars = pmSourceExtFitParsAlloc();
+	  // XXX extFitPars->Mxx = source->moments->Mxx;
+	  // XXX extFitPars->Mxy = source->moments->Mxy;
+	  // XXX extFitPars->Myy = source->moments->Myy;
+	  // XXX extFitPars->Mrf = source->moments->Mrf;
+	  // XXX extFitPars->Mrh = source->moments->Mrh;
+	  // XXX extFitPars->peakMag = (source->peak->rawFlux > 0) ? -2.5*log10(source->peak->rawFlux) : NAN;
+	  
+	  // save kron mag, but assign apMag & psfMag on output (not yet calculated)
+	  // XXX extFitPars->krMag = -2.5*log10(source->moments->KronFlux);
+
+          // psArrayAdd (source->extFitPars, 4, extFitPars);
+	  // psFree (extFitPars);
 
           // test for fit quality / result
-	  modelFit->fitRadius = radius;
+	  modelFit->fitRadius = fitRadius;
           psArrayAdd (source->modelFits, 4, modelFit);
 
@@ -463,27 +444,16 @@
         }
 
+	// clear the circular mask
+	psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); 
+
         if (minModel == -1) {
-          // re-subtract the object, leave local sky
+          // no valid extended fit; re-subtract the object, leave local sky
           psTrace ("psphot", 5, "failed to fit extended source model to object at %f, %f", source->moments->Mx, source->moments->My);
 
-          // replace original model, subtract it
-          psFree (source->modelFlux);
-          source->modelFlux = modelFluxStart;
-
-	  *source->moments = psfMoments;
+	  // ensure the modelEXT is cached
+	  pmSourceCacheModel (source, maskVal);
+
           pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
 
-          psFree (modelFluxes);
-
-          if (savePics) {
-              psphotSaveImage (NULL, readout->image, "image.xp.fits");
-              char key[10];
-              fprintf (stdout, "continue? ");
-              if (!fgets (key, 8, stdin)) {
-                  psWarning("Couldn't read anything.");
-              } else if (key[0] == 'n') {
-                  savePics = false;
-              }
-          }
           continue;
         }
@@ -493,35 +463,21 @@
         source->modelEXT = psMemIncrRefCounter (source->modelFits->data[minModel]);
 
-        // save the modelFlux for the best model
-        psFree (source->modelFlux);
-        source->modelFlux = psMemIncrRefCounter (modelFluxes->data[minModel]);
-
-	// replace the original moments
-	*source->moments = psfMoments;
+	// adjust the window so the subtraction covers the faint wings
+	psphotSetRadiusMoments(&fitRadius, &windowRadius, readout, source, markVal);
+
+	// cache the model flux
+	if (source->modelEXT->isPCM) {
+	    pmPCMCacheModel (source, maskVal, psfSize);
+	} else {
+	    pmSourceCacheModel (source, maskVal);
+	}
 
         // subtract the best fit from the object, leave local sky
         pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
 
-        // the initial model flux is no longer needed
-        psFree (modelFluxStart);
-        psFree (modelFluxes);
-
         psTrace ("psphot", 4, "best ext model for %f, %f : %s chisq = %f\n", source->moments->Mx, source->moments->My, pmModelClassGetName (source->modelEXT->type), source->modelEXT->chisq);
         psTrace ("psphot", 5, "extended source model for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
-
-        if (savePics) {
-          psphotSaveImage (NULL, readout->image, "image.xm.fits");
-          char key[10];
-          fprintf (stdout, "continue? ");
-          if (!fgets (key, 8, stdin)) {
-              psWarning("Couldn't read anything.");
-          } else if (key[0] == 'n') {
-              savePics = false;
-          }
-        }
     }
     psFree (fitOptions);
-
-    // fprintf (stderr, "xfit : tried %ld objects\n", sources->n);
 
     // change the value of a scalar on the array (wrap this and put it in psArray.h)
Index: branches/meh_branches/ppstack_test/psphot/src/psphotFake.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotFake.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotFake.c	(revision 33415)
@@ -166,5 +166,5 @@
 
     // remove all sources, adding noise for subtracted sources
-    psphotRemoveAllSources(realSources, recipe);
+    psphotRemoveAllSourcesByArray(realSources, recipe);
     psphotAddNoise(readout, realSources, recipe);
 
Index: branches/meh_branches/ppstack_test/psphot/src/psphotFindDetections.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotFindDetections.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotFindDetections.c	(revision 33415)
@@ -7,4 +7,7 @@
 {
     bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Find Detections ---");
 
     // select the appropriate recipe information
Index: branches/meh_branches/ppstack_test/psphot/src/psphotFindFootprints.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotFindFootprints.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotFindFootprints.c	(revision 33415)
@@ -35,7 +35,12 @@
         psArray *tmp = pmFootprintArrayGrow(footprints, growRadius);
         psImageConvolveSetThreads(oldThreads);
-        psLogMsg ("psphot", PS_LOG_MINUTIA, "grow footprint coverage by %d pixels, %ld footprints become %ld footprints: %f sec\n", growRadius, footprints->n, tmp->n, psTimerMark ("psphot.footprints"));
-        psFree(footprints);
-        footprints = tmp;
+        psLogMsg ("psphot", PS_LOG_MINUTIA, "grow footprint coverage by %d pixels, %ld footprints become %ld footprints: %f sec\n", growRadius, footprints->n, tmp ? tmp->n : 0, psTimerMark ("psphot.footprints"));
+        if (tmp) {
+            psFree(footprints);
+            footprints = tmp;
+        } else {
+            psLogMsg ("psphot", PS_LOG_WARN, "pmFootprintArray grow returned NULL\n");
+        }
+            
     }
 
@@ -58,5 +63,16 @@
     psphotCullPeaks(readout, significance, recipe, detections->footprints);
     detections->peaks = pmFootprintArrayToPeaks(detections->footprints);
-    psLogMsg ("psphot", PS_LOG_INFO, "%ld peaks, %ld total footprints: %f sec\n", detections->peaks->n, detections->footprints->n, psTimerMark ("psphot.footprints"));
+
+    // psphotFootprintSaddles (readout, detections->footprints);
+    // 
+    // int nSaddle = 0;
+    // for (int i = 0; i < detections->peaks->n; i++) {
+    // 	pmPeak *peak = detections->peaks->data[i];
+    // 	
+    // 	if (peak->saddlePoints) nSaddle += peak->saddlePoints->n;
+    // }
+    // fprintf (stderr, "%d saddle points for %ld peaks\n", nSaddle, detections->peaks->n);
+
+    psLogMsg ("psphot", PS_LOG_WARN, "%ld peaks, %ld total footprints: %f sec\n", detections->peaks->n, detections->footprints->n, psTimerMark ("psphot.footprints"));
 
     return detections;
Index: branches/meh_branches/ppstack_test/psphot/src/psphotFindPeaks.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotFindPeaks.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotFindPeaks.c	(revision 33415)
@@ -68,5 +68,5 @@
         pmPeaksWriteText (peaks, output);
     }
-    psLogMsg ("psphot", PS_LOG_INFO, "%ld peaks: %f sec\n", peaks->n, psTimerMark ("psphot.peaks"));
+    psLogMsg ("psphot", PS_LOG_WARN, "%ld peaks: %f sec\n", peaks->n, psTimerMark ("psphot.peaks"));
 
     return peaks;
Index: branches/meh_branches/ppstack_test/psphot/src/psphotFitSourcesLinear.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotFitSourcesLinear.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotFitSourcesLinear.c	(revision 33415)
@@ -17,4 +17,7 @@
     bool status = true;
 
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Fit Source (Linear) ---");
+
     // select the appropriate recipe information
     psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
@@ -23,6 +26,11 @@
     int num = psphotFileruleCount(config, filerule);
 
+    // skip the chisq image (optionally?)
+    int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
+    if (!status) chisqNum = -1;
+
     // loop over the available readouts
     for (int i = 0; i < num; i++) {
+        if (i == chisqNum) continue; // skip chisq image
 
         // find the currently selected readout
@@ -126,4 +134,7 @@
     for (int i = 0; i < sources->n; i++) {
         pmSource *source = sources->data[i];
+
+	// psAssert (source->peak, "source without peak??");
+	// psAssert (source->peak->footprint, "peak without footprint??");
 
         // turn this bit off and turn it on again if we pass this test
@@ -190,4 +201,5 @@
         pmModel *model = pmSourceGetModel (&isPSF, source);
 
+	// clear the 'mark' pixels and remask on the fit aperture 
         psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
         psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, model->fitRadius, "OR", markVal);
@@ -203,6 +215,4 @@
     }
     psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "built fitSources: %f sec (%ld objects)\n", psTimerMark ("psphot.linear"), sources->n);
-
-    // fprintf (stderr, "****** Nsat : %d ********\n", Nsat);
 
     if (fitSources->n == 0) {
@@ -330,9 +340,8 @@
     psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "solve matrix: %f sec (%d elements)\n", psTimerMark ("psphot.linear"), sparse->Nelem);
 
-    // XXXX **** philosophical question:
-    // we measure bright objects in three passes: 1) linear fit; 2) non-linear fit; 3) linear fit:
-    // should retain the chisq and errors from the intermediate non-linear fit?
-    // the non-linear fit provides better values for the position errors, and for
-    // extended sources, the shape errors
+    // Philosophical question: we measure bright objects in three passes: 1) linear fit; 2)
+    // non-linear fit; 3) linear fit: should we retain the chisq and errors from the
+    // intermediate non-linear fit?  the non-linear fit provides better values for the position
+    // errors, and for extended sources, the shape errors
 
     // adjust I0 for fitSources and subtract
@@ -349,5 +358,8 @@
         model->dparams->data.F32[PM_PAR_I0] = errors->data.F32[i];
 
-        // subtract object
+	// clear the 'mark' pixels so the subtraction covers the full window
+        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
+
+        // subtract object & add noise
         pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
     }
@@ -374,5 +386,5 @@
     psFree (border);
 
-    psLogMsg ("psphot.ensemble", PS_LOG_INFO, "measure ensemble of PSFs: %f sec\n", psTimerMark ("psphot.linear"));
+    psLogMsg ("psphot.ensemble", PS_LOG_WARN, "measure ensemble of PSFs: %f sec\n", psTimerMark ("psphot.linear"));
 
     psphotVisualPlotChisq (sources);
Index: branches/meh_branches/ppstack_test/psphot/src/psphotFitSourcesLinearStack.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotFitSourcesLinearStack.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotFitSourcesLinearStack.c	(revision 33415)
@@ -64,4 +64,20 @@
           // turn this bit off and turn it on again if we keep this source
           source->mode &= ~PM_SOURCE_MODE_LINEAR_FIT;
+
+	  // skip non-astronomical objects (very likely defects)
+	  if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+	  if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+	  // do not include CRs in the full ensemble fit
+	  if (source->mode & PM_SOURCE_MODE_CR_LIMIT) continue;
+
+	  // do not include MOMENTS_FAILURES in the fit
+	  if (source->mode & PM_SOURCE_MODE_MOMENTS_FAILURE) continue;
+
+	  if (final) {
+	      if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) continue;
+	  } else {
+	      // if (source->mode & PM_SOURCE_MODE_BLEND) continue;
+	  }
 
           // generate model for sources without, or skip if we can't
@@ -83,5 +99,17 @@
 	  }
 
-          source->mode |= PM_SOURCE_MODE_LINEAR_FIT;
+	  bool isPSF = false;
+	  pmModel *model = pmSourceGetModel (&isPSF, source);
+
+	  // clear the 'mark' pixels and remask on the fit aperture 
+	  psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
+	  psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, model->fitRadius, "OR", markVal);
+
+	  // we call this function multiple times. for the first time, we have only PSF models for all objects
+	  // the second time has extended sources.  If we ever fit the PSF model, we should raise this bit
+	  source->mode |= PM_SOURCE_MODE_LINEAR_FIT;
+	  if (isPSF) {
+	      source->mode |= PM_SOURCE_MODE_PSFMODEL;
+	  }	    
           psArrayAdd (fitSources, 100, source);
         }
@@ -166,4 +194,7 @@
         model->params->data.F32[PM_PAR_I0] = norm->data.F32[i];
         model->dparams->data.F32[PM_PAR_I0] = errors->data.F32[i];
+
+	// clear the 'mark' pixels so the subtraction covers the full window
+        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
 
         // subtract object
@@ -190,5 +221,5 @@
     psFree (errors);
 
-    psLogMsg ("psphot.ensemble", PS_LOG_INFO, "measure ensemble of PSFs: %f sec\n", psTimerMark ("psphot.linear"));
+    psLogMsg ("psphot.ensemble", PS_LOG_WARN, "measure ensemble of PSFs: %f sec\n", psTimerMark ("psphot.linear"));
     return true;
 }
Index: branches/meh_branches/ppstack_test/psphot/src/psphotFootprintSaddles.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotFootprintSaddles.c	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotFootprintSaddles.c	(revision 33415)
@@ -0,0 +1,129 @@
+# include "psphotInternal.h"
+# ifndef ROUND
+# define ROUND(X) ((int) ((X) + 0.5*SIGN(X)))
+# endif
+
+psVector *getSaddlePoint (pmReadout *readout, int X1, int Y1, int X2, int Y2, bool swapcoords);
+
+// for each footprint with multiple detections, find the complete set of saddles, that is, the
+// local minima which lie on the lines between the peaks (same as a 'Coll' but less homophonic
+// with 'cull')
+bool psphotFootprintSaddles(pmReadout *readout, psArray *footprints) {  // array of pmFootprints
+    
+# if (PM_PEAKS_CULL_WITH_SMOOTHED_IMAGE)
+    psLogMsg ("psphot", PS_LOG_INFO, "Finding saddle points from footprints using the smoothed image");
+# else
+    psLogMsg ("psphot", PS_LOG_INFO, "Finding saddle points from footprints using the raw (unsmoothed) image");
+# endif
+
+    int xMax = readout->image->numCols - 1;
+    int yMax = readout->image->numRows - 1;
+
+    // FILE *f = fopen ("saddlepoints.dat", "w");
+
+    for (int i = 0; i < footprints->n; i++) {
+	pmFootprint *fp = footprints->data[i];
+	if (fp->peaks == NULL) continue;
+	if (fp->peaks->n < 2) continue;
+
+	// loop over all peaks in the footprint:
+	for (int is = 0; is < fp->peaks->n; is++) {
+
+	    pmPeak *startPeak = fp->peaks->data[is];
+	    if (!startPeak->saddlePoints) startPeak->saddlePoints = psArrayAllocEmpty (4);
+	
+	    // loop over all peaks in the footprint, skipping this one
+	    for (int ie = is + 1; ie < fp->peaks->n; ie++) {
+		if (ie == is) continue;
+
+		pmPeak *endPeak = fp->peaks->data[ie];
+		if (!endPeak->saddlePoints) endPeak->saddlePoints = psArrayAllocEmpty (4);
+
+		// step across all pixels between the startPeak and the endPeak, using a
+		// bresenham-like line-drawing operation
+		
+		int X1 = PS_MIN(xMax, PS_MAX(0, ROUND(startPeak->xf)));
+		int Y1 = PS_MIN(yMax, PS_MAX(0, ROUND(startPeak->yf)));
+		int X2 = PS_MIN(xMax, PS_MAX(0, ROUND(endPeak->xf)));
+		int Y2 = PS_MIN(yMax, PS_MAX(0, ROUND(endPeak->yf)));
+		
+		int dX = X2 - X1;
+		int dY = Y2 - Y1;
+
+		bool FlipCoords = (abs(dX) < abs(dY));
+		bool FlipDirect = FlipCoords ? (Y1 > Y2) : (X1 > X2);
+
+		psVector *saddlePoint = NULL;
+		if (!FlipDirect && !FlipCoords) saddlePoint = getSaddlePoint (readout, X1, Y1, X2, Y2, false);
+		if ( FlipDirect && !FlipCoords) saddlePoint = getSaddlePoint (readout, X2, Y2, X1, Y1, false);
+		if (!FlipDirect &&  FlipCoords) saddlePoint = getSaddlePoint (readout, Y1, X1, Y2, X2, true);
+		if ( FlipDirect &&  FlipCoords) saddlePoint = getSaddlePoint (readout, Y2, X2, Y1, X1, true);
+		psAssert (saddlePoint, "failed to get saddlePoint");
+
+		// fprintf (f, "%f %f | %d %d | %f %f\n", startPeak->xf, startPeak->yf, saddlePoint->data.S32[0], saddlePoint->data.S32[1], endPeak->xf, endPeak->yf);
+
+		psArrayAdd (startPeak->saddlePoints, 4, saddlePoint);
+		psArrayAdd (endPeak->saddlePoints, 4, saddlePoint);
+		psFree (saddlePoint);
+		// NOTE : all saddlePoints for the startPeak are also saddlePoints for the endPeak
+	    }
+	}
+    }
+    // fclose (f);
+    return true;
+}
+
+// psLogMsg ("psphot", PS_LOG_INFO, "%ld peaks, %ld total footprints: %f sec\n", detections->peaks->n, detections->footprints->n, 
+
+psVector *getSaddlePoint (pmReadout *readout, int X1, int Y1, int X2, int Y2, bool swapcoords) {
+
+    psAssert (X1 >= 0, "invalid");
+    psAssert (Y1 >= 0, "invalid");
+    psAssert (X1 < readout->image->numCols, "invalid");
+    psAssert (Y1 < readout->image->numRows, "invalid");
+
+    psAssert (X2 >= 0, "invalid");
+    psAssert (Y2 >= 0, "invalid");
+    psAssert (X2 < readout->image->numCols, "invalid");
+    psAssert (Y2 < readout->image->numRows, "invalid");
+
+    psVector *saddlePoint = psVectorAlloc(2, PS_TYPE_F32);
+
+    int dX = X2 - X1;
+    int dY = Y2 - Y1;
+		
+    float minValue = swapcoords ? readout->image->data.F32[X1][Y1] : readout->image->data.F32[Y1][X1];
+    saddlePoint->data.S32[0] = swapcoords ? Y1 : X1;
+    saddlePoint->data.S32[1] = swapcoords ? X1 : Y1;
+
+    int Y = Y1;
+    int e = 0;
+    for (int X = X1; X <= X2; X++) {
+	if (swapcoords) {
+	    float newValue = readout->image->data.F32[X][Y];
+	    if (newValue < minValue) {
+		minValue = newValue;
+		saddlePoint->data.S32[0] = Y;
+		saddlePoint->data.S32[1] = X;
+	    }
+	} else {
+	    float newValue = readout->image->data.F32[Y][X];
+	    if (newValue < minValue) {
+		minValue = newValue;
+		saddlePoint->data.S32[0] = X;
+		saddlePoint->data.S32[1] = Y;
+	    }
+	}
+	e += dY;
+	int e2 = 2 * e;
+	if (e2 > dX) {
+	    Y++;
+	    e -= dX;
+	} 
+	if (e2 < -dX) {
+	    Y--;
+	    e += dX;
+	}
+    }
+    return (saddlePoint);
+}
Index: branches/meh_branches/ppstack_test/psphot/src/psphotForcedReadout.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotForcedReadout.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotForcedReadout.c	(revision 33415)
@@ -55,10 +55,11 @@
     psphotLoadExtSources (config, view, filerule);
 
-    // construct an initial model for each object, set the radius to fitRadius, set circular fit mask
-    psphotGuessModels (config, view, filerule);
-
     // merge the newly selected sources into the existing list
     // NOTE: merge OLD and NEW
     psphotMergeSources (config, view, filerule);
+
+    // Construct an initial model for each object, set the radius to fitRadius, set circular
+    // fit mask.  NOTE: only applied to sources without guess models
+    psphotGuessModels (config, view, filerule);
 
     // linear PSF fit to source peaks, subtract the models from the image (in PSF mask)
Index: branches/meh_branches/ppstack_test/psphot/src/psphotGuessModels.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotGuessModels.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotGuessModels.c	(revision 33415)
@@ -46,5 +46,5 @@
     psAssert (detections, "missing detections?");
 
-    psArray *sources = detections->newSources;
+    psArray *sources = detections->allSources;
     psAssert (sources, "missing sources?");
 
@@ -112,5 +112,7 @@
         // wait here for the threaded jobs to finish
         // fprintf (stderr, "wait for threads (%d, %d)\n", jx, jy);
-        if (!psThreadPoolWait (false)) {
+        if (!psThreadPoolWait (false, true)) {
+            // harvest our jobs
+            psFree(cellGroups);
             psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
             return false;
@@ -141,5 +143,5 @@
     psFree (cellGroups);
 
-    psLogMsg ("psphot.models", 4, "built models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.models"));
+    psLogMsg ("psphot.models", PS_LOG_WARN, "built models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.models"));
     return true;
 }
@@ -159,4 +161,7 @@
     for (int i = 0; i < sources->n; i++) {
         pmSource *source = sources->data[i];
+
+	// do not redo sources already guessed
+	if (source->tmpFlags & PM_SOURCE_TMPF_MODEL_GUESS) continue;
 
         // this is used to mark sources for which the model is measured. We check later that
@@ -169,4 +174,6 @@
         if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
         if (!source->peak) continue;
+
+	// psAssert (source->peak->footprint, "peak without footprint??");
 
         nSrc ++;
Index: branches/meh_branches/ppstack_test/psphot/src/psphotImageLoop.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotImageLoop.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotImageLoop.c	(revision 33415)
@@ -129,4 +129,11 @@
 		    }
 		    break;
+		  case PSPHOT_MODEL_TEST:
+		    if (!psphotModelTestReadout (config, view, "PSPHOT.INPUT")) {
+			psError(psErrorCodeLast(), false, "failure in psphotReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+			psFree (view);
+			return false;
+		    }
+		    break;
 		}
             }
Index: branches/meh_branches/ppstack_test/psphot/src/psphotImageQuality.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotImageQuality.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotImageQuality.c	(revision 33415)
@@ -289,5 +289,5 @@
 #endif
 
-    psLogMsg ("psphot", PS_LOG_INFO, "Image Quality Stats from %ld psf stars : FWHM (major, minor) [pixels]: %f, %f\n",
+    psLogMsg ("psphot", PS_LOG_WARN, "Image Quality Stats from %ld psf stars : FWHM (major, minor) [pixels]: %f, %f\n",
               M2->n, fwhm_major, fwhm_minor);
 
Index: branches/meh_branches/ppstack_test/psphot/src/psphotKronIterate.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotKronIterate.c	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotKronIterate.c	(revision 33415)
@@ -0,0 +1,453 @@
+# include "psphotInternal.h"
+# ifndef ROUND
+# define ROUND(X) ((int) ((X) + 0.5*SIGN(X)))
+# endif
+
+bool psphotKronWindowSetSource(pmSource *source, psImage *kronWindow, bool useKronRadius, bool insert);
+bool psphotKronWindowMag (pmSource *source, psImage *kronWindow, float radius, float minKronRadius, psImageMaskType maskVal);
+
+bool psphotKronIterate (pmConfig *config, const pmFPAview *view, const char *filerule)
+{
+    bool status = true;
+
+    // return true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Kron Iterate ---");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psphotFileruleCount(config, filerule);
+
+    // skip the chisq image (optionally?)
+    int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
+    if (!status) chisqNum = -1;
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (i == chisqNum) continue; // skip chisq image
+
+        // find the currently selected readout
+        pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, i); // 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 ? detections->newSources : detections->allSources;
+        psAssert (sources, "missing sources?");
+
+        pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+        // psAssert (psf, "missing psf?");
+
+        if (!psphotKronIterateReadout (config, recipe, view, readout, sources, psf)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to measure magnitudes for %s entry %d", filerule, i);
+            return false;
+        }
+    }
+    return true;
+}
+
+int psphotKapaChannel (int channel);
+bool psphotVisualShowMask (int kapaFD, psImage *inImage, const char *name, int channel);
+bool psphotVisualRangeImage (int kapaFD, psImage *inImage, const char *name, int channel, float min, float max);
+
+bool psphotKronIterateReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, pmReadout *readout, psArray *sources, pmPSF *psf) {
+
+    bool status = false;
+
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping masked kron");
+        return true;
+    }
+
+    psTimerStart ("psphot.kron");
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    float RADIUS = psMetadataLookupF32 (&status, readout->analysis, "PSF_MOMENTS_RADIUS");
+    if (!status) {
+        RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    }
+
+    float MIN_KRON_RADIUS = psMetadataLookupF32 (&status, readout->analysis, "MOMENTS_MIN_KRON");
+    if (!status) {
+        MIN_KRON_RADIUS = 0.25*RADIUS;
+    }
+
+    // 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;
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortByFlux);
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping blend");
+        return true;
+    }
+
+    // generate the window image: multiply the flux by this to downweight neighbors
+    psImage *kronWindow = psImageAlloc (readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+    psImageInit (kronWindow, 1.0);
+
+    // start with the currently known moments (Mxx, Myy, Mxy) and generate a window image
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+	// set a window function for each source based on the moments
+	// (this skips really bad sources (no peak, no moments, DEFECT)
+	psphotKronWindowSetSource (source, kronWindow, false, true);
+    }
+
+    // threaded measurement of the source magnitudes
+    // 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_KRON_ITERATE");
+
+            psArrayAdd(job->args, 1, readout);
+            psArrayAdd(job->args, 1, kronWindow);
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+            PS_ARRAY_ADD_SCALAR(job->args, markVal,            PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, maskVal,            PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, RADIUS,             PS_TYPE_F32);
+            PS_ARRAY_ADD_SCALAR(job->args, MIN_KRON_RADIUS,    PS_TYPE_F32);
+
+// set this to 0 to run without threading
+# if (1)
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+                return false;
+            }
+# else
+	    if (!psphotKronIterate_Threaded(job)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+		psFree(AnalysisRegion);
+		return false;
+	    }
+	    psFree(job);
+# endif
+        }
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false, true)) {
+            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) {
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            }
+            psFree(job);
+        }
+    }
+    psFree (cellGroups);
+    psFree (kronWindow);
+
+    psLogMsg ("psphot.kron", PS_LOG_WARN, "measure masked kron magnitudes : %f sec for %ld objects\n", psTimerMark ("psphot.kron"), sources->n);
+    return true;
+}
+
+bool psphotKronIterate_Threaded (psThreadJob *job) {
+
+    pmReadout *readout              = job->args->data[0];
+    psImage *kronWindow             = job->args->data[1];
+    psArray *sources                = job->args->data[2];
+    psImageMaskType markVal         = PS_SCALAR_VALUE(job->args->data[3],PS_TYPE_IMAGE_MASK_DATA);
+    psImageMaskType maskVal         = PS_SCALAR_VALUE(job->args->data[4],PS_TYPE_IMAGE_MASK_DATA);
+    float RADIUS                    = PS_SCALAR_VALUE(job->args->data[5],F32);
+    float MIN_KRON_RADIUS           = PS_SCALAR_VALUE(job->args->data[6],F32);
+
+    // XXX TEST : set iteration to 1
+    for (int j = 0; j < 1; j++) {
+	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) continue;
+
+	    // replace object in image
+	    bool reSubtract = false;
+	    if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+		pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+		reSubtract = true;
+	    }
+
+	    // use S/N to control max window size
+	    // float kronSN = source->moments->KronFlux / source->moments->KronFluxErr;
+
+	    // maxWindow -> 1.5*RADIUS for kronSN = 5.0, keeping S.B. constant (kronSN ~ flux)
+	    // (kronSN / maxWindow^2) = (5.0 / (1.5 RADIUS)^2)
+	    // maxWindow = 1.5 * RADIUS * sqrt(kronSN / 5.0)
+	    // XXX float maxWindow = (isfinite(kronSN) && (kronSN > 5.0)) ? 1.5 * RADIUS * sqrt(kronSN / 5.0) : 1.5*RADIUS;
+
+	    // iterate to the window radius
+	    // XXX float windowRadius = PS_MIN(PS_MAX(RADIUS, 4.0*source->moments->Mrf), maxWindow);
+
+	    // XXX TEST : use a window based on the radial profile numbers: max is skyRadius, min is RADIUS
+	    // if we lack the skyRadius (eg MATCHED sources), go to the default value
+	    float maxWindow = isfinite(source->skyRadius) ? source->skyRadius : RADIUS;
+	    float windowRadius = PS_MAX(RADIUS, maxWindow);
+
+	    // re-allocate image, weight, mask arrays for each peak with box big enough to fit BIG_RADIUS
+	    pmSourceRedefinePixels (source, readout, source->peak->x, source->peak->y, windowRadius + 2);
+	    psAssert (source->pixels, "WTF?");
+
+	    // clear the window function for this source based on the moments
+	    psphotKronWindowSetSource (source, kronWindow, (j > 0), false);
+
+	    // this function populates moments->Mrf,KronFlux,KronFluxErr
+	    psphotKronWindowMag (source, kronWindow, windowRadius, MIN_KRON_RADIUS, maskVal);
+	    psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
+
+	    // set a window function for each source based on the moments
+	    psphotKronWindowSetSource (source, kronWindow, true, true);
+
+	    // if we subtracted it above, re-subtract the object, leave local sky
+	    if (reSubtract) {
+		pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+	    }
+	}
+    }
+    return true;
+}
+
+bool psphotKronWindowMag (pmSource *source, psImage *kronWindow, float radius, float minKronRadius, psImageMaskType maskVal) {
+
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(radius, 0.0, false);
+
+    psF32 R2 = PS_SQR(radius);
+    float rsigma2 = 0.5 / PS_SQR(radius/2.0);
+
+    // a note about coordinates: coordinates of objects throughout psphot refer to the primary
+    // image coordinates.  the source->pixels image has an offset relative to its parent of
+    // col0,row0: a pixel (x,y) in the primary image has coordinates of (x-col0, y-row0) in
+    // this subimage.  we subtract off the peak coordinates, adjusted to this subimage, to have
+    // minimal round-off error in the sums.  since these values are subtracted just to minimize
+    // the dynamic range and are added back below, the exact value does not matter. these are
+    // (int) so they can be used in the image index below.
+
+    // Now calculate higher-order moments, using the above-calculated first moments to adjust coordinates
+    // Xn  = SUM (x - xc)^n * (z - sky)
+
+    psF32 RF = 0.0;
+    psF32 RS = 0.0;
+
+    // the peak position is less accurate but less subject to extreme deviations
+    float dX = source->moments->Mx - source->peak->xf;
+    float dY = source->moments->My - source->peak->yf;
+    float dR = hypot(dX, dY);
+    float Xo = (dR < 2.0) ? source->moments->Mx : source->peak->xf;
+    float Yo = (dR < 2.0) ? source->moments->My : source->peak->yf;
+
+    // center of mass in subimage.  Note: the calculation below uses pixel index, so we correct
+    // xCM, yCM from pixel coords to pixel index here.
+    psF32 xCM = Xo - 0.5 - source->pixels->col0; // coord of peak in subimage
+    psF32 yCM = Yo - 0.5 - source->pixels->row0; // coord of peak in subimage
+
+    int Xwo = source->pixels->col0;
+    int Ywo = source->pixels->row0;
+
+    psF32 **vPix = source->pixels->data.F32;
+    psF32 **vWin = kronWindow->data.F32;
+    psF32 **vWgt = source->variance->data.F32;
+    
+    psImageMaskType **vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA;
+
+    for (psS32 row = 0; row < source->pixels->numRows ; row++) {
+
+	psF32 yDiff = row - yCM;
+	if (fabs(yDiff) > radius) continue;
+
+	// coordinate of mirror pixel
+	int yFlip = yCM - yDiff;
+	if (yFlip < 0) continue;
+	if (yFlip >= source->pixels->numRows) continue;
+
+	for (psS32 col = 0; col < source->pixels->numCols ; col++) {
+	    // check mask and value for this pixel
+	    if (vMsk && (vMsk[row][col] & maskVal)) continue;
+	    if (isnan(vPix[row][col])) continue;
+
+	    psF32 xDiff = col - xCM;
+	    if (fabs(xDiff) > radius) continue;
+
+	    // coordinate of mirror pixel
+	    int xFlip = xCM - xDiff;
+	    if (xFlip < 0) continue;
+	    if (xFlip >= source->pixels->numCols) continue;
+
+	    // check mask and value for mirror pixel
+	    if (vMsk && (vMsk[yFlip][xFlip] & maskVal)) continue;
+	    if (isnan(vPix[yFlip][xFlip])) continue;
+
+	    // radius is just a function of (xDiff, yDiff)
+	    psF32 r2  = PS_SQR(xDiff) + PS_SQR(yDiff);
+	    if (r2 > R2) continue;
+
+	    // flux * window
+	    float z = r2 * rsigma2;
+	    assert (z >= 0.0);
+
+	    // weight by window image and wide Gaussian
+	    float weight1  = vWin[row+Ywo][col+Xwo]*exp(-z);
+	    float weight2  = vWin[yFlip+Ywo][xFlip+Xwo]*exp(-z);
+
+	    float fDiff1 = vPix[row][col]*weight1;
+	    float fDiff2 = vPix[yFlip][xFlip]*weight2;
+
+	    float pDiff = (fDiff1 > 0.0) ? sqrt(fabs(fDiff1*fDiff2)) : -sqrt(fabs(fDiff1*fDiff2));
+
+	    // Kron Flux uses the 1st radial moment (maybe Gaussian windowed?)
+	    psF32 rf = pDiff * sqrt(r2);
+	    psF32 rs = 0.5 * (fDiff1 + fDiff2);
+
+	    RF  += rf;
+	    RS  += rs;
+	}
+    }
+
+    // Saturate the 1st radial moment
+    float Mrf = MAX(minKronRadius, RF/RS);
+    if (sqrt(source->peak->detValue) < 10.0) {
+	Mrf = MIN (radius, Mrf);
+    }
+
+    // Calculate the Kron magnitude (make this block optional?)
+    float radKron  = 2.5*Mrf;
+    float radKron2 = radKron*radKron;
+
+    int nKronPix = 0;
+    float Sum = 0.0;
+    float Var = 0.0;
+    float Win = 0.0;
+
+    for (psS32 row = 0; row < source->pixels->numRows ; row++) {
+
+	psF32 yDiff = row - yCM;
+	if (fabs(yDiff) > radKron) continue;
+
+	for (psS32 col = 0; col < source->pixels->numCols ; col++) {
+	    // check mask and value for this pixel
+	    if (vMsk && (vMsk[row][col] & maskVal)) continue;
+	    if (isnan(vPix[row][col])) continue;
+
+	    psF32 xDiff = col - xCM;
+	    if (fabs(xDiff) > radKron) continue;
+
+	    // radKron is just a function of (xDiff, yDiff)
+	    psF32 r2  = PS_SQR(xDiff) + PS_SQR(yDiff);
+	    if (r2 > radKron2) continue;
+
+	    float weight1  = vWin[row+Ywo][col+Xwo];
+	    float fDiff1 = vPix[row][col]*weight1;
+
+	    float pDiff = fDiff1;
+	    psF32 wDiff = vWgt[row][col] * weight1;
+
+	    Sum += pDiff;
+	    Var += wDiff;
+	    Win += weight1;
+	    nKronPix ++;
+	}
+    }
+
+    source->moments->Mrf = Mrf;
+    source->moments->KronFlux    = Sum;
+    source->moments->KronFluxErr = sqrt(Var);
+
+    return true;
+}
+
+bool psphotKronWindowSetSource(pmSource *source, psImage *kronWindow, bool useKronRadius, bool insert) {
+
+    if (!source) return false;
+    if (!source->peak) return false; // XXX how can we have a peak-less source?
+    if (!source->moments) return false;
+    if (source->type == PM_SOURCE_TYPE_DEFECT) return false;
+    if (source->type == PM_SOURCE_TYPE_SATURATED) return false;
+    psAssert(kronWindow, "need a window");
+
+    // we have a source with moments Mx, My, Mxx, Mxy, Myy.  we just need to define a Gaussian that has 
+    // these values (and peak of 1.0)
+
+    int Nx = kronWindow->numCols;
+    int Ny = kronWindow->numRows;
+
+    float Xo = source->moments->Mx;
+    float Yo = source->moments->My;
+
+    float Mxx = source->moments->Mxx;
+    float Mxy = source->moments->Mxy;
+    float Myy = source->moments->Myy;
+
+    float Mmajor = 0.5*(Mxx + Myy) + 0.5*sqrt(PS_SQR(Mxx - Myy) + 4.0*PS_SQR(Mxy));
+    float Mminor = 0.5*(Mxx + Myy) - 0.5*sqrt(PS_SQR(Mxx - Myy) + 4.0*PS_SQR(Mxy));
+
+    // Mxx, Mxy, Myy define the elliptical shape, but Mrf defines the width 
+    float scale = PS_SQR(0.5 * source->moments->Mrf) / Mmajor;
+
+    float Sxx = scale * Mmajor * Mminor / Myy; // sigma_x^2
+    float Sxy = Mxy / (scale * Mmajor * Mminor);
+    float Syy = scale * Mmajor * Mminor / Mxx; // sigma_y^2
+
+    float Smajor = sqrt(Mmajor);
+
+    int minX = PS_MIN(PS_MAX(Xo - 5*Smajor, 0), Nx - 1);
+    int maxX = PS_MIN(PS_MAX(Xo + 5*Smajor, 0), Nx - 1);
+    int minY = PS_MIN(PS_MAX(Yo - 5*Smajor, 0), Ny - 1);
+    int maxY = PS_MIN(PS_MAX(Yo + 5*Smajor, 0), Ny - 1);
+
+    float rMxx = 0.5 / Sxx;
+    float rMyy = 0.5 / Syy;
+    
+    for (int iy = minY; iy < maxY; iy++) {
+	for (int ix = minX; ix < maxX; ix++) {
+
+	    float dX = (ix + 0.5 - Xo);
+	    float dY = (iy + 0.5 - Yo);
+
+	    float z = rMxx * PS_SQR(dX) + rMyy * PS_SQR(dY) + Sxy*dX*dY;
+
+	    float f = insert ? 1.001 - exp(-z) : 1.0 / (1.001 - exp(-z));
+
+	    kronWindow->data.F32[iy][ix] *= f;
+	}
+    }
+    return true;
+}
Index: branches/meh_branches/ppstack_test/psphot/src/psphotKronMasked.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotKronMasked.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotKronMasked.c	(revision 33415)
@@ -1,4 +1,8 @@
 # include "psphotInternal.h"
-
+# ifndef ROUND
+# define ROUND(X) ((int) ((X) + 0.5*SIGN(X)))
+# endif
+
+bool psphotKronMask (pmSource *source, psImage *kronMask, psImageMaskType markVal);
 bool psphotKronMag (pmSource *source, float radius, float minKronRadius, psImageMaskType maskVal);
 
@@ -45,4 +49,8 @@
 }
 
+int psphotKapaChannel (int channel);
+bool psphotVisualShowMask (int kapaFD, psImage *inImage, const char *name, int channel);
+bool psphotVisualRangeImage (int kapaFD, psImage *inImage, const char *name, int channel, float min, float max);
+
 bool psphotKronMaskedReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, pmReadout *readout, psArray *sources, pmPSF *psf) {
 
@@ -72,4 +80,6 @@
     }
 
+    float EXT_FIT_MAX_RADIUS = psMetadataLookupF32 (&status, recipe, "EXT_FIT_MAX_RADIUS");
+
     // bit-masks to test for good/bad pixels
     psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
@@ -83,9 +93,14 @@
     maskVal |= markVal;
 
+    // psphotSaveImage (NULL, readout->mask, "kron.unmasked.fits");
+
+    pmSourcePhotometryMode photMode = PM_SOURCE_PHOT_PSFONLY;
+
+    // XXX tmp visualization
+    // int kapa = psphotKapaChannel (1);
+
     // generate the mask image: increment counter for every source overlapping the pixel
-    psImage *kronMask = psImageAlloc (readout->image->numCols, readout->image->numRows, PS_TYPE_U8);
+    psImage *kronMask = psImageAlloc (readout->image->numCols, readout->image->numRows, PS_TYPE_S32);
     psImageInit (kronMask, 0);
-    int Nx = kronMask->numCols;
-    int Ny = kronMask->numRows;
     for (int i = 0; i < sources->n; i++) {
 
@@ -96,45 +111,59 @@
         if (!source->moments) continue;
 
-        // replace object in image
-        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
-            pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
-        }
-
-	psphotKronMag (source, RADIUS, MIN_KRON_RADIUS, maskVal);
-
-        // re-subtract the object, leave local sky
-        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
-
-	continue;
-
-	// XXX skip this code
-	// generate the pixel masks
-	// int Xo = source->moments->Mx;
-	// int Yo = source->moments->My;
-	float dX = source->moments->Mx - source->peak->xf;
-	float dY = source->moments->My - source->peak->yf;
-	float dR = hypot(dX, dY);
-	
-	float Xo = (dR < 2.0) ? source->moments->Mx : source->peak->xf;
-	float Yo = (dR < 2.0) ? source->moments->My : source->peak->yf;
-	
-	int Kr = 2.5*source->moments->Mrf;
-	int Kr2 = Kr*Kr;
-	
-	for (int iy = Yo - Kr; iy < Yo + Kr + 1; iy++) {
-	    if (iy < 0) continue;
-	    if (iy >= Ny) continue;
-	    for (int ix = Xo - Kr; ix < Xo + Kr + 1; ix++) {
-		if (ix < 0) continue;
-		if (ix >= Nx) continue;
-
-		if (PS_SQR(ix - Xo) + PS_SQR(iy - Yo) > Kr2) continue;
-		if (kronMask->data.U8[iy][ix] < 0xff) {
-		    kronMask->data.U8[iy][ix] ++;
-		}
-	    }
-	}
-    }		
+	// replace object in image
+	if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+	    pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+	}
+
+	// save the PSF-based values
+	// source->moments->KronFluxPSF    = source->moments->KronFlux;
+	// source->moments->KronFluxPSFErr = source->moments->KronFluxErr;
+	// source->moments->KronRadiusPSF  = source->moments->Mrf;
+
+	// iterate to the window radius
+	float windowRadius = RADIUS;
+	for (int j = 0; j < 4; j++) {
+	    // XXX use some S/N criterion to limit?
+	    // if (source->moments->KronFlux / source->moments->KronFluxErr > 10.0) {
+	    // windowRadius = PS_MAX (RADIUS, 10*source->moments->Mrf);
+	    // }
+
+	    // re-allocate image, weight, mask arrays for each peak with box big enough to fit BIG_RADIUS
+	    pmSourceRedefinePixels (source, readout, source->peak->x, source->peak->y, windowRadius + 2);
+
+	    // mask the pixels not contained by the footprint
+	    // if (psphotMaskFootprint (readout, source, markVal)) {
+	    // 	// psphotVisualShowMask (kapa, source->maskObj, "mask", 2);
+	    // 	// psphotVisualRangeImage (kapa, source->pixels, "image", 1, -50, 300);
+	    // 	// fprintf (stderr, "masked\n");
+	    // }	    
+
+	    // mask the pixels associated with near neighbors
+	    if (psphotKronMask (source, kronMask, markVal)) {
+		// psphotVisualShowMask (kapa, source->maskObj, "mask", 2);
+		// psphotVisualRangeImage (kapa, source->pixels, "image", 1, -50, 300);
+	     	// fprintf (stderr, "masked\n");
+	    }	    
+	    // this function populates moments->Mrf,KronFlux,KronFluxErr
+	    // XXX what about KronInner, KronOuter, etc?
+	    psphotKronMag (source, windowRadius, MIN_KRON_RADIUS, maskVal);
+	    windowRadius = PS_MIN(PS_MAX(RADIUS, 4.0*source->moments->Mrf), EXT_FIT_MAX_RADIUS);
+	    psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
+	}
+        pmSourceMagnitudes (source, psf, photMode, maskVal, markVal, source->apRadius);
+	float kmag = -2.5*log10(source->moments->KronFlux);
+	if (source->psfMag - kmag > 0.25) {
+	    // psphotVisualShowMask (kapa, source->maskObj, "mask", 1);
+	    // psphotVisualRangeImage (kapa, source->pixels, "image", 0, -50, 300);
+	    // fprintf (stderr, "psf: %f, kron: %f, dmag: %f\n", source->psfMag, kmag, source->psfMag - kmag);
+	    // fprintf (stderr, "continue\n");
+	}
+	// re-subtract the object, leave local sky
+	pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+
+    // psphotSaveImage (NULL, readout->mask, "kron.masked.fits");
     // psphotSaveImage (NULL, kronMask, "kronmask.fits");
+
     psLogMsg ("psphot.kron", PS_LOG_DETAIL, "measure masked kron magnitudes : %f sec for %ld objects\n", psTimerMark ("psphot.kron"), sources->n);
 
@@ -142,4 +171,6 @@
     return true;
 }
+
+# define WEIGHTED 0
 
 bool psphotKronMag (pmSource *source, float radius, float minKronRadius, psImageMaskType maskVal) {
@@ -150,7 +181,9 @@
     PS_ASSERT_FLOAT_LARGER_THAN(radius, 0.0, false);
 
-    psF32 Sum = 0.0;
-    psF32 Var = 0.0;
     psF32 R2 = PS_SQR(radius);
+
+# if (WEIGHTED)
+    float rsigma2 = 0.5 / PS_SQR(radius / 4.0); // use a Gaussian window with sigma = R_window / 2
+# endif
 
     // a note about coordinates: coordinates of objects throughout psphot refer to the primary
@@ -168,5 +201,5 @@
     psF32 RS = 0.0;
 
-# if (1)
+    // the peak position is less accurate but less subject to extreme deviations
     float dX = source->moments->Mx - source->peak->xf;
     float dY = source->moments->My - source->peak->yf;
@@ -174,8 +207,4 @@
     float Xo = (dR < 2.0) ? source->moments->Mx : source->peak->xf;
     float Yo = (dR < 2.0) ? source->moments->My : source->peak->yf;
-# else
-    float Xo = source->moments->Mx;
-    float Yo = source->moments->My;
-# endif
 
     // center of mass in subimage.  Note: the calculation below uses pixel index, so we correct
@@ -210,9 +239,14 @@
 	    if (r2 > R2) continue;
 
+# if (WEIGHTED)
+	    float z = r2 * rsigma2;
+	    assert (z >= 0.0);
+	    float weight  = exp(-z);
+	    psF32 pDiff = *vPix * weight;
+# else
 	    psF32 pDiff = *vPix;
-
-	    Sum += pDiff;
-
-	    // Kron Flux uses the 1st radial moment (NOT Gaussian windowed)
+# endif
+
+	    // Kron Flux uses the 1st radial moment (maybe Gaussian windowed?)
 	    psF32 rf = pDiff * sqrt(r2);
 	    psF32 rs = pDiff;
@@ -234,5 +268,6 @@
 
     int nKronPix = 0;
-    Sum = Var = 0.0;
+    float Sum = 0.0;
+    float Var = 0.0;
 
     for (psS32 row = 0; row < source->pixels->numRows ; row++) {
@@ -263,6 +298,14 @@
 	    if (r2 > radKron2) continue;
 
+# if (WEIGHTED)
+	    float z = r2 * rsigma2;
+	    assert (z >= 0.0);
+	    float weight  = exp(-z);
+	    psF32 pDiff = *vPix * weight;
+	    psF32 wDiff = *vWgt * weight;
+# else
 	    psF32 pDiff = *vPix;
 	    psF32 wDiff = *vWgt;
+# endif
 
 	    Sum += pDiff;
@@ -272,13 +315,173 @@
     }
 
-    source->moments->Mrh = Mrf;
-
-    // XXX for a test, save the old values here:
-    source->moments->KronFouter    = source->moments->KronFlux;
-    source->moments->KronCoreErr = source->moments->KronFluxErr;
-
-    source->moments->KronFlux    = Sum       * M_PI * PS_SQR(radKron) / nKronPix;
-    source->moments->KronFluxErr = sqrt(Var) * M_PI * PS_SQR(radKron) / nKronPix;
+    source->moments->Mrf = Mrf;
+    source->moments->KronFlux    = Sum;
+    source->moments->KronFluxErr = sqrt(Var);
+    // source->moments->KronFlux    = Sum       * M_PI * PS_SQR(radKron) / nKronPix;
+    // source->moments->KronFluxErr = sqrt(Var) * M_PI * PS_SQR(radKron) / nKronPix;
 
     return true;
 }
+
+enum {Y_P, Y_M, X_P, X_M};
+
+bool psphotKronMask (pmSource *source, psImage *kronMask, psImageMaskType markVal) {
+
+    int X, Y, e, dXs, dYs;
+
+    if (!source->peak) return false;
+    if (!source->peak->saddlePoints) return false;
+    if (!source->peak->saddlePoints->n) return false;
+
+    // return true;
+
+    int xMax = source->maskObj->numCols;
+    int yMax = source->maskObj->numRows;
+    // int xOff = source->maskObj->col0;
+    // int yOff = source->maskObj->row0;
+
+    int Xo = ROUND(source->peak->xf) - source->maskObj->col0;
+    int Yo = ROUND(source->peak->yf) - source->maskObj->row0;
+
+    for (int i = 0; i < source->peak->saddlePoints->n; i++) {
+
+	psVector *saddlePoint = source->peak->saddlePoints->data[i];
+	int Xs = ROUND(saddlePoint->data.S32[0]) - source->maskObj->col0;
+	int Ys = ROUND(saddlePoint->data.S32[1]) - source->maskObj->row0;
+	
+	// fprintf (stderr, "mask %d,%d @ %d,%d (%d,%d)\n", Xo, Yo, Xs, Ys, saddlePoint->data.S32[0], saddlePoint->data.S32[1]);
+
+	// We want to mask the pixels between the edge of the image and the line perpendicular
+	// to the connecting line.  Call dX,dY = (Xs-Xo,Ys-Yo).  There are 4 cases: 
+	// +y : dY > 0, fabs(dY) > fabs(dX)
+	// -y : dY < 0, fabs(dY) > fabs(dX)
+	// +x : dX > 0, fabs(dX) > fabs(dY)
+	// -x : dX < 0, fabs(dX) > fabs(dY)
+
+	int dX = Xs - Xo;
+	int dY = Ys - Yo;
+
+	// +y : dY > 0, fabs(dY) > fabs(dX)
+	// -y : dY < 0, fabs(dY) > fabs(dX)
+	if (fabs(dY) > fabs(dX)) {
+	    // points to right of saddle
+	    Y = Ys;
+	    e = 0;
+	    dXs = (dY > 0) ? +dY : -dY;  // this forces dXs > 0
+	    dYs = (dY > 0) ? -dX : +dX;
+	    for (X = Xs; X < xMax; X++) {
+		if (dY > 0) {
+		    for (int Ym = Y; Ym < yMax; Ym++) {
+			source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[Ym][X] |= markVal;
+			// kronMask->data.S32[Ym+yOff][X+xOff] = source->id;
+		    }
+		} else {
+		    for (int Ym = Y; Ym >= 0; Ym--) {
+			source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[Ym][X] |= markVal;
+			// kronMask->data.S32[Ym+yOff][X+xOff] = source->id;
+		    }
+		}
+		e += dYs;
+		int e2 = 2 * e;
+		if (e2 > dXs) {
+		    Y ++;
+		    e -= dXs;
+		}
+		if (e2 < -dXs) {
+		    Y --;
+		    e += dXs;
+		}
+		if (Y >= yMax) break;
+		if (Y < 0) break;
+	    }
+	    // points to left of saddle
+	    Y = Ys;
+	    e = 0;
+	    for (X = Xs; X >= 0; X--) {
+		if (dY > 0) {
+		    for (int Ym = Y; Ym < yMax; Ym++) {
+			source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[Ym][X] |= markVal;
+			// kronMask->data.S32[Ym+yOff][X+xOff] = source->id;
+		    }
+		} else {
+		    for (int Ym = Y; Ym >= 0; Ym--) {
+			source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[Ym][X] |= markVal;
+			// kronMask->data.S32[Ym+yOff][X+xOff] = source->id;
+		    }
+		}
+		e -= dYs;
+		int e2 = 2 * e;
+		if (e2 > dXs) {
+		    Y ++;
+		    e -= dXs;
+		}
+		if (e2 < -dXs) {
+		    Y --;
+		    e += dXs;
+		}
+		if (Y >= yMax) break;
+		if (Y < 0) break;
+	    }
+	} else {
+	    // points to right of saddle
+	    X = Xs;
+	    e = 0;
+	    dXs = (dX > 0) ? -dY : +dY;
+	    dYs = (dX > 0) ? +dX : -dX;  // this forces dYs > 0
+	    for (Y = Ys; Y < yMax; Y++) {
+		if (dX > 0) {
+		    for (int Xm = X; Xm < xMax; Xm++) {
+			source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[Y][Xm] |= markVal;
+			// kronMask->data.S32[Y+yOff][Xm+xOff] = source->id;
+		    }
+		} else {
+		    for (int Xm = X; Xm >= 0; Xm--) {
+			source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[Y][Xm] |= markVal;
+			// kronMask->data.S32[Y+yOff][Xm+xOff] = source->id;
+		    }
+		}
+		e += dXs;
+		int e2 = 2 * e;
+		if (e2 > dYs) {
+		    X ++;
+		    e -= dYs;
+		}
+		if (e2 < -dYs) {
+		    X --;
+		    e += dYs;
+		}
+		if (X >= yMax) break;
+		if (X < 0) break;
+	    }
+	    // points to left of saddle
+	    X = Xs;
+	    e = 0;
+	    for (Y = Ys; Y >= 0; Y--) {
+		if (dX > 0) {
+		    for (int Xm = X; Xm < xMax; Xm++) {
+			source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[Y][Xm] |= markVal;
+			// kronMask->data.S32[Y+yOff][Xm+xOff] = source->id;
+		    }
+		} else {
+		    for (int Xm = X; Xm >= 0; Xm--) {
+			source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[Y][Xm] |= markVal;
+			// kronMask->data.S32[Y+yOff][Xm+xOff] = source->id;
+		    }
+		}
+		e -= dXs;
+		int e2 = 2 * e;
+		if (e2 > dYs) {
+		    X ++;
+		    e -= dYs;
+		}
+		if (e2 < -dYs) {
+		    X --;
+		    e += dYs;
+		}
+		if (X >= yMax) break;
+		if (X < 0) break;
+	    }
+	}
+    }
+    return true;
+}
Index: branches/meh_branches/ppstack_test/psphot/src/psphotLoadSRCTEXT.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotLoadSRCTEXT.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotLoadSRCTEXT.c	(revision 33415)
@@ -73,5 +73,5 @@
 	    dPAR[PM_PAR_I0]   = 0.0;
 
-	    pmPSF_AxesToModel (PAR, axes);
+	    pmPSF_AxesToModel (PAR, axes, modelType);
 
 	    float peakFlux    = 1.0;
Index: branches/meh_branches/ppstack_test/psphot/src/psphotMagnitudes.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotMagnitudes.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotMagnitudes.c	(revision 33415)
@@ -4,4 +4,7 @@
 {
     bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Magnitudes ---");
 
     // select the appropriate recipe information
@@ -126,5 +129,5 @@
 
         // wait for the threads to finish and manage results
-        if (!psThreadPoolWait (false)) {
+        if (!psThreadPoolWait (false, true)) {
             psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
             return false;
@@ -146,5 +149,5 @@
     psFree (cellGroups);
 
-    psLogMsg ("psphot.magnitudes", PS_LOG_DETAIL, "measure magnitudes : %f sec for %ld objects (%d with apertures)\n", psTimerMark ("psphot.mags"), sources->n, Nap);
+    psLogMsg ("psphot.magnitudes", PS_LOG_WARN, "measure magnitudes : %f sec for %ld objects (%d with apertures)\n", psTimerMark ("psphot.mags"), sources->n, Nap);
     return true;
 }
@@ -306,5 +309,5 @@
 
         // wait for the threads to finish and manage results
-        if (!psThreadPoolWait (false)) {
+        if (!psThreadPoolWait (false, true)) {
             psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
             return false;
Index: branches/meh_branches/ppstack_test/psphot/src/psphotMergeSources.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotMergeSources.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotMergeSources.c	(revision 33415)
@@ -520,4 +520,176 @@
 }
 
+// copy the newPeaks from the detections of one pmFPAfile to another
+bool psphotCopyPeaks (pmConfig *config, const pmFPAview *view, const char *ruleOut, const char *ruleSrc)
+{
+    bool status = true;
+
+    int num = psphotFileruleCount(config, ruleSrc);
+
+    // skip the chisq image because it is a duplicate of the detection version
+    int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
+    if (!status) chisqNum = -1;
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (i == chisqNum) continue; // skip chisq image
+        if (!psphotCopyPeaksReadout (config, view, ruleOut, ruleSrc, i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to copy peaks from %s to %s entry %d", ruleSrc, ruleOut, i);
+            return false;
+        }
+    }
+    return true;
+}
+
+// add newly detected peaks to the existing list of sources
+bool psphotCopyPeaksReadout (pmConfig *config, const pmFPAview *view, const char *ruleOut, const char *ruleSrc, int index) {
+
+    bool status;
+
+    // find the currently selected readout
+    pmFPAfile *fileSrc = pmFPAfileSelectSingle(config->files, ruleSrc, index); // File of interest
+    psAssert (fileSrc, "missing file?");
+
+    pmReadout *readoutSrc = pmFPAviewThisReadout(view, fileSrc->fpa);
+    psAssert (readoutSrc, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readoutSrc->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    // find the currently selected readout
+    pmFPAfile *fileOut = pmFPAfileSelectSingle(config->files, ruleOut, index); // File of interest
+    psAssert (fileOut, "missing file?");
+
+    pmReadout *readoutOut = pmFPAviewThisReadout(view, fileOut->fpa);
+    psAssert (readoutOut, "missing readout?");
+
+    // generate a new detection structure for the output filerule
+    pmDetections *detectionsOut = psMetadataLookupPtr (&status, readoutOut->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detectionsOut, "missing PSPHOT.DETECTIONS?");
+
+    psAssert (detectionsOut->peaks, "programming error");
+    psAssert (!detectionsOut->oldPeaks, "programming error");
+    psAssert (detections->peaks, "programming error");
+
+    // save the OUT existing peaks on oldPeaks
+    detectionsOut->oldPeaks = detectionsOut->peaks;
+    detectionsOut->peaks = psArrayAllocEmpty(detections->peaks->n);
+
+    for (int i = 0; i < detections->peaks->n; i++) {
+	psAssert (detections->peaks->data[i], "programming error");
+	pmPeak *peak = pmPeakAlloc (0, 0, 0.0, PM_PEAK_LONE);
+	pmPeakCopy(peak, detections->peaks->data[i]);
+	psArrayAdd (detectionsOut->peaks, 100, peak);
+	psFree (peak);
+    }
+    return true;
+}
+
+// create source parents children from ruleSrc for ruleOut for orphans
+bool psphotSourceParents (pmConfig *config, const pmFPAview *view, const char *ruleOut, const char *ruleSrc)
+{
+    bool status = true;
+
+    int num = psphotFileruleCount(config, ruleSrc);
+
+    // skip the chisq image because it is a duplicate of the detection version
+    int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
+    if (!status) chisqNum = -1;
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (i == chisqNum) continue; // skip chisq image
+        if (!psphotSourceParentsReadout (config, view, ruleOut, ruleSrc, i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to copy sources from %s to %s entry %d", ruleSrc, ruleOut, i);
+            return false;
+        }
+    }
+    return true;
+}
+
+// create source parents from ruleSrc for ruleOut for orphaned children for this readout.  
+bool psphotSourceParentsReadout (pmConfig *config, const pmFPAview *view, const char *ruleOut, const char *ruleSrc, int index) {
+
+    bool status;
+    int nParents = 0;
+    int nNonOrphans = 0;
+
+    // find the currently selected readout
+    pmFPAfile *fileSrc = pmFPAfileSelectSingle(config->files, ruleSrc, index); // File of interest
+    psAssert (fileSrc, "missing file?");
+
+    pmReadout *readoutSrc = pmFPAviewThisReadout(view, fileSrc->fpa);
+    psAssert (readoutSrc, "missing readout?");
+
+    pmDetections *detectionsSrc = psMetadataLookupPtr (&status, readoutSrc->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detectionsSrc, "missing detections?");
+
+    psArray *sourcesSrc = detectionsSrc->allSources;
+    psAssert (sourcesSrc, "missing sources?");
+
+    // find the currently selected readout
+    pmFPAfile *fileOut = pmFPAfileSelectSingle(config->files, ruleOut, index); // File of interest
+    psAssert (fileOut, "missing file?");
+
+    pmReadout *readoutOut = pmFPAviewThisReadout(view, fileOut->fpa);
+    psAssert (readoutOut, "missing readout?");
+
+    // generate a new detection structure for the output filerule
+    pmDetections *detectionsOut = psMetadataLookupPtr (&status, readoutOut->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detectionsOut, "missing PSPHOT.DETECTIONS?");
+
+    // loop over the sources, redefine their pixels to point at the new filerule image,
+    // copy the source data, and add a reference back to the original source
+    
+    // copy the sources from sourceSrcs to the new detection structure
+    for (int i = 0; i < sourcesSrc->n; i++) {
+      pmSource *sourceSrc = sourcesSrc->data[i];
+      if (sourceSrc->parent) {
+	  nNonOrphans ++;
+	  continue; // Not an orphan
+      }
+
+      pmSource *sourceOut = pmSourceCopy(sourceSrc);
+      sourceOut->parent = sourceSrc;
+      
+      // keep the original source flags
+      sourceOut->seq      = sourceSrc->seq;
+      sourceOut->type     = sourceSrc->type;
+      sourceOut->mode     = sourceSrc->mode;
+      sourceOut->mode2    = sourceSrc->mode2;
+      sourceOut->tmpFlags = sourceSrc->tmpFlags;
+
+      // does this copy all model data? (NO)
+      sourceOut->modelPSF = pmModelCopy(sourceSrc->modelPSF);
+      sourceOut->modelEXT = pmModelCopy(sourceSrc->modelEXT);
+
+      if (sourceSrc->modelFits) {
+	  sourceOut->modelFits = psArrayAlloc(sourceSrc->modelFits->n);
+	  for (int j = 0; j < sourceSrc->modelFits->n; j++) {
+	      sourceOut->modelFits->data[j] = pmModelCopy(sourceSrc->modelFits->data[j]);
+	  }
+      }
+
+      // drop the references to the original image pixels:
+      pmSourceFreePixels (sourceOut);
+
+      // allocate image, weight, mask for the new image for each peak
+      if (sourceOut->modelPSF) {
+	pmSourceRedefinePixels (sourceOut, readoutOut, sourceOut->peak->x, sourceOut->peak->y, sourceOut->modelPSF->fitRadius);
+      }
+
+      // child sources have not been subtracted in this image, but this flag may be raised if
+      // they were subtracted in the parent's image
+      sourceOut->tmpFlags &= ~PM_SOURCE_TMPF_SUBTRACTED;
+
+      nParents ++;
+      psArrayAdd (detectionsOut->allSources, 100, sourceOut);
+      psFree (sourceOut);
+    }
+    psLogMsg ("psphot", 3, "%d parents created, %d unorphaned children, %ld input vs %ld output", nParents, nNonOrphans, sourcesSrc->n, detectionsOut->allSources->n);
+
+    return true;
+}
+
 // create source children from ruleSrc for ruleOut
 bool psphotSourceChildren (pmConfig *config, const pmFPAview *view, const char *ruleOut, const char *ruleSrc)
@@ -542,7 +714,7 @@
 }
 
-// create source children from ruleSrc for ruleOut for this entry.  XXX currently, this is only
+// Create source children from ruleSrc for ruleOut for this entry.  Currently, this is only
 // used by psphotStackReadout (sources go on allSources so that psphotChoosePSF can be called
-// repeatedly)
+// repeatedly).
 bool psphotSourceChildrenReadout (pmConfig *config, const pmFPAview *view, const char *ruleOut, const char *ruleSrc, int index) {
 
@@ -569,15 +741,10 @@
     psAssert (readoutOut, "missing readout?");
 
-    // generate a new detection structure for the output filerule
-    pmDetections *detectionsOut = psMetadataLookupPtr (&status, readoutOut->analysis, "PSPHOT.DETECTIONS");
-    if (!detectionsOut) {
-	detectionsOut = pmDetectionsAlloc();
-	detectionsOut->allSources = psArrayAllocEmpty (100);
-	// save detections on the readout->analysis
-	if (!psMetadataAddPtr (readoutOut->analysis, PS_LIST_TAIL, "PSPHOT.DETECTIONS", PS_META_REPLACE | PS_DATA_UNKNOWN, "psphot detections", detectionsOut)) {
-	    psError (PSPHOT_ERR_CONFIG, false, "problem saving detections on readout");
-	    return false;
-	}
-	psFree(detectionsOut); // a copy remains on the analysis metadata
+    // replace any existing DETECTION container on readoutOut->analysis with the new one
+    pmDetections *detectionsOut = pmDetectionsAlloc();
+    detectionsOut->allSources = psArrayAllocEmpty (100);
+    if (!psMetadataAddPtr (readoutOut->analysis, PS_LIST_TAIL, "PSPHOT.DETECTIONS", PS_META_REPLACE | PS_DATA_UNKNOWN, "psphot detections", detectionsOut)) {
+	psError (PSPHOT_ERR_CONFIG, false, "problem saving detections on readout");
+	return false;
     }
 
@@ -593,4 +760,5 @@
       
       // keep the original source flags
+      sourceOut->seq      = sourceSrc->seq;
       sourceOut->type     = sourceSrc->type;
       sourceOut->mode     = sourceSrc->mode;
@@ -612,8 +780,8 @@
       pmSourceFreePixels (sourceOut);
 
+      // XXX do we need to skip the Chisq image sources?
+
       // allocate image, weight, mask for the new image for each peak
-      if (sourceOut->modelPSF) {
-	pmSourceRedefinePixels (sourceOut, readoutOut, sourceOut->peak->x, sourceOut->peak->y, sourceOut->modelPSF->fitRadius);
-      }
+      pmSourceRedefinePixels (sourceOut, readoutOut, sourceOut->peak->x, sourceOut->peak->y, sourceOut->windowRadius);
 
       // child sources have not been subtracted in this image, but this flag may be raised if
@@ -624,13 +792,14 @@
       psFree (sourceOut);
     }
-    psLogMsg ("psphot", 3, "%ld known sources supplied", detectionsOut->allSources->n);
-
-
-    return true;
-}
-
-// create source children associated with 'filerule' from the objectsSrc.  XXX currently, this
-// is only used by psphotStackReadout (sources go on allSources so that psphotChoosePSF can be
-// called repeatedly)
+    psLogMsg ("psphot", 3, "created %ld children", detectionsOut->allSources->n);
+
+    psFree(detectionsOut); // a copy remains on the analysis metadata
+
+    return true;
+}
+
+// create source children associated with 'filerule' from the objectsSrc.  returns a new object
+// array containing the child sources.  XXX currently, this is only used by psphotStackReadout
+// (sources go on allSources so that psphotChoosePSF can be called repeatedly)
 psArray *psphotSourceChildrenByObject (pmConfig *config, const pmFPAview *view, const char *filerule, psArray *objectsSrc) {
 
@@ -652,4 +821,5 @@
 	psAssert (readout, "missing readout?");
 
+	// create DETECTIONS containers for each image, in case one lacks it
 	pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
 	if (!detections) {
@@ -665,4 +835,6 @@
 	    psAssert (detections, "missing detections?");
 	}
+
+	// we need to save the new sources on the detection arrays of the appropriate image
 	detArrays->data[i] = psMemIncrRefCounter(detections);
 	readouts->data[i] = psMemIncrRefCounter(readout);
@@ -695,7 +867,8 @@
 
 	    pmSource *sourceOut = pmSourceCopy(sourceSrc);
+	    sourceOut->parent = sourceSrc;
+
+	    // save on the output object array at the same location
 	    objectOut->sources->data[i] = sourceOut;
-
-	    sourceOut->parent = sourceSrc;
 
 	    // keep the original source flags and sequence ID (if set)
@@ -720,5 +893,5 @@
 	    pmSourceFreePixels (sourceOut);
 
-	    // set the output readotu
+	    // set the output readout
 	    int index = sourceOut->imageID;
 	    if (index >= readouts->n) continue; // skip the sources generated by the chisq image
Index: branches/meh_branches/ppstack_test/psphot/src/psphotModelBackground.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotModelBackground.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotModelBackground.c	(revision 33415)
@@ -145,4 +145,5 @@
     psMetadataAddPtr(analysis, PS_LIST_TAIL, "PSPHOT.BACKGROUND.BINNING", PS_DATA_UNKNOWN | PS_META_REPLACE, "Background binning", binning);
 
+    psVector *dQ = psVectorAllocEmpty (100, PS_TYPE_F32);
     psF32 **modelData = model->data.F32;
     psF32 **modelStdevData = modelStdev->data.F32;
@@ -216,4 +217,6 @@
                 }
                 modelStdevData[iy][ix] = psStatsGetValue(stats, statsOptionWidth);
+		// fprintf (stderr, "dQ : %f - %f - %f = %f\n", stats->robustLQ, stats->robustMedian, stats->robustUQ, stats->robustUQ + stats->robustLQ - 2*stats->robustMedian);
+		psVectorAppend (dQ, stats->robustUQ + stats->robustLQ - 2*stats->robustMedian);
 
                 // supply sample to plotting routing
@@ -322,9 +325,13 @@
     }
 
-    psLogMsg ("psphot", PS_LOG_INFO, "built background image: %f sec\n", psTimerMark ("psphot.background"));
-
-    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "SKY_MEAN", PS_META_REPLACE, "sky mean", Value);
+    psLogMsg ("psphot", PS_LOG_WARN, "built background image: %f sec\n", psTimerMark ("psphot.background"));
+
+    psStats *statsDQ = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+    psVectorStats (statsDQ, dQ, NULL, NULL, 0);
+
+    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);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "SKY_DQ",    PS_META_REPLACE, "sky quartile slope", statsDQ->sampleMedian);
+    psLogMsg ("psphot", PS_LOG_INFO, "image sky : mean %f, stdev %f, dQ %f", Value, ValueStdev, statsDQ->sampleMedian);
 
     // measure image and background stats and save for later output
@@ -334,6 +341,8 @@
                                       PS_STAT_MAX);
     psImageStats (statsBck, model, NULL, 0);
-    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "MSKY_MN", PS_META_REPLACE, "sky model mean",          statsBck->sampleMean);
+    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_DEV", PS_META_REPLACE, "sky stdev",               ValueStdev);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "MSKY_DQ",  PS_META_REPLACE, "sky quartile slope",      statsDQ->sampleMedian);
     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);
@@ -343,4 +352,7 @@
               statsBck->min, statsBck->sampleMean, statsBck->max, statsBck->sampleStdev);
 
+    psFree(statsDQ);
+    psFree(dQ);
+
     psFree(stats);
     psFree(statsBck);
@@ -401,4 +413,7 @@
     int num = psphotFileruleCount(config, filerule);
 
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Model Background ---");
+
     // loop over the available readouts
     for (int i = 0; i < num; i++) {
Index: branches/meh_branches/ppstack_test/psphot/src/psphotModelTest.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotModelTest.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotModelTest.c	(revision 33415)
@@ -1,250 +1,36 @@
-# include "psphotInternal.h"
-# define PM_SOURCE_FIT_PSF_X_EXT PM_SOURCE_FIT_PSF_AND_SKY
+# include "psphotStandAlone.h"
 
-// XXX add more test information?
-bool psphotModelTest (pmConfig *config, const pmFPAview *view, const char *filerule, psMetadata *recipe) {
+int main (int argc, char **argv) {
 
-    bool status;
-    int modelType = -1;
-    float obsMag, fitMag, value;
-    char name[64];
-    pmPSF *psf = NULL;
-    pmSourceFitMode fitMode;
+    psMemInit();
+    psTimerStart ("complete");
+    pmErrorRegister();                  // register psModule's error codes/messages
+    psphotInit();
 
-    // bit-masks to test for good/bad pixels
-    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
-    assert (maskVal);
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psphotModelTestArguments (argc, argv);
+    assert(config);
 
-    // bit-mask to mark pixels not used in analysis
-    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
-    assert (markVal);
+    psphotVersionPrint();
 
-    // maskVal is used to test for rejected pixels, and must include markVal
-    maskVal |= markVal;
-
-    // run model fitting tests on a single source?
-    if (!psMetadataLookupBool (&status, recipe, "TEST_FIT")) return false;
-
-    psTimerStart ("modelTest");
-
-    // find the currently selected readout
-    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
-    PS_ASSERT_PTR_NON_NULL (readout, false);
-
-    // use poissonian errors or local-sky errors
-    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, filerule);
-    if (!status) POISSON_ERRORS = true;
-    pmSourceFitModelInit (15, 0.1, 1.0, POISSON_ERRORS);
-
-    // find the various fitting parameters (try test values first)
-    float INNER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_INNER_RADIUS");
-    if (!status || !isfinite(INNER)) {
-        INNER = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
-    }
-    float OUTER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_OUTER_RADIUS");
-    if (!status || !isfinite(OUTER)) {
-        OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
-    }
-    float RADIUS = psMetadataLookupF32 (&status, recipe, "TEST_FIT_RADIUS");
-    if (!status || !isfinite(RADIUS)) {
-        RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
-    }
-    float mRADIUS = psMetadataLookupF32 (&status, recipe, "TEST_MOMENTS_RADIUS");
-    if (!status || !isfinite(mRADIUS)) {
-        mRADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    // load input data (config and images (signal, noise, mask)
+    if (!psphotParseCamera (config)) {
+        psErrorStackPrint(stderr, "Error setting up the camera\n");
+        exit (psphotGetExitStatus());
     }
 
-    // define the source of interest
-    float xObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_X");
-    float yObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_Y");
-    if (!isfinite(xObj) || !isfinite(yObj)) psAbort ("object position is not defined");
-
-    // what fitting mode to use?
-    fitMode = PM_SOURCE_FIT_EXT;
-    char *fitModeWord = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODE");
-    if (fitModeWord && !strcasecmp (fitModeWord, "PSF")) fitMode = PM_SOURCE_FIT_PSF;
-    if (fitModeWord && !strcasecmp (fitModeWord, "CONV")) fitMode = PM_SOURCE_FIT_PSF_X_EXT;
-    if (fitModeWord && !strcasecmp (fitModeWord, "DEFAULT")) fitMode = PM_SOURCE_FIT_EXT;
-
-    // construct the source structures
-    pmSource *source = pmSourceAlloc();
-    source->peak = pmPeakAlloc (xObj, yObj, 0, 0);
-    pmSourceDefinePixels (source, readout, xObj, yObj, OUTER);
-
-    // in fitMode, psf sets the model type
-    if (fitMode == PM_SOURCE_FIT_PSF) {
-        psf = psphotLoadPSF (config, view, recipe);
-        if (!psf) psAbort("PSF_INPUT_FILE not supplied");
-        modelType = psf->type;
-        source->type = PM_SOURCE_TYPE_STAR;
-    }
-    if (fitMode == PM_SOURCE_FIT_EXT) {
-        // find the model: supplied by user or first in the PSF_MODEL list
-        char *modelName  = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODEL");
-        if (!status || !strcasecmp (modelName, "DEFAULT")) {
-            // get the list pointers for the PSF_MODEL entries
-
-            psList *list = NULL;
-            psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
-            if (mdi == NULL) psAbort("missing PSF_MODEL selection");
-            if (mdi->type == PS_DATA_STRING) {
-                list = psListAlloc(NULL);
-                psListAdd (list, PS_LIST_HEAD, mdi);
-            } else {
-                if (mdi->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
-                list = psMemIncrRefCounter(mdi->data.list);
-            }
-
-            // take the first list element
-            psMetadataItem *item = psListGet (list, PS_LIST_HEAD);
-            modelName = item->data.V;
-        }
-        modelType = pmModelClassGetType (modelName);
-        if (modelType < 0) psAbort("unknown model %s", modelName);
-        source->type = PM_SOURCE_TYPE_EXTENDED;
-    }
-    if (fitMode == PM_SOURCE_FIT_PSF_X_EXT) {
-        // we need to load BOTH a psf and an ext model
-        psf = psphotLoadPSF (config, view, recipe);
-        if (!psf) psAbort("PSF_INPUT_FILE not supplied");
-
-        // find the model: supplied by user or first in the PSF_MODEL list
-        char *modelName  = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODEL");
-        if (!status || !strcasecmp (modelName, "DEFAULT")) {
-            // get the list pointers for the PSF_MODEL entries
-
-            psList *list = NULL;
-            psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
-            if (mdi == NULL) psAbort("missing PSF_MODEL selection");
-            if (mdi->type == PS_DATA_STRING) {
-                list = psListAlloc(NULL);
-                psListAdd (list, PS_LIST_HEAD, mdi);
-            } else {
-                if (mdi->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
-                list = psMemIncrRefCounter(mdi->data.list);
-            }
-
-            // take the first list element
-            psMetadataItem *item = psListGet (list, PS_LIST_HEAD);
-            modelName = item->data.V;
-        }
-        modelType = pmModelClassGetType (modelName);
-        if (modelType < 0) psAbort("unknown model %s", modelName);
-        source->type = PM_SOURCE_TYPE_EXTENDED;
+    // call psphot for each readout
+    if (!psphotImageLoop (config, PSPHOT_MODEL_TEST)) {
+        psErrorStackPrint(stderr, "Error in the psphot image loop\n");
+        exit (psphotGetExitStatus());
     }
 
-    // find the local sky
-    status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER, maskVal, markVal);
-    if (!status) psAbort("pmSourceLocalSky error");
+    psLogMsg ("psphot", 3, "complete psphot run: %f sec\n", psTimerMark ("complete"));
 
-    // get the source moments
-    status = pmSourceMoments (source, mRADIUS, 0.0, 1.0, maskVal);
-    if (!status) psAbort("psSourceMoments error");
-    source->peak->value = source->moments->Peak;
+    psErrorCode exit_status = psphotGetExitStatus();
+    psphotCleanup (config);
+    exit (exit_status);
+}
 
-    fprintf (stderr, "sum: %f @ (%f, %f)\n", source->moments->Sum, source->moments->Mx, source->moments->My);
-    fprintf (stderr, "moments: %f, %f - %f\n", source->moments->Mxx, source->moments->Myy, source->moments->Mxy);
-
-    psEllipseMoments moments;
-    moments.x2 = source->moments->Mxx;
-    moments.y2 = source->moments->Myy;
-    moments.xy = source->moments->Mxy;
-    psEllipseAxes axes = psEllipseMomentsToAxes (moments, 20.0);
-
-    fprintf (stderr, "axes: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
-
-    // get the initial model parameter guess
-    pmModel *model = pmSourceModelGuess (source, modelType);
-    source->modelEXT = model;
-
-    // if any parameters are defined by the user, take those values
-    int nParams = pmModelClassParameterCount (modelType);
-    psF32 *params = model->params->data.F32;
-    params[PM_PAR_XPOS] = xObj; // XXX use the user-supplied value,
-    params[PM_PAR_YPOS] = yObj; // XXX or use the centroid
-    for (int i = 0; i < nParams; i++) {
-        if (i == PM_PAR_XPOS) continue;
-        if (i == PM_PAR_YPOS) continue;
-
-        sprintf (name, "TEST_FIT_PAR%d", i);
-        value = psMetadataLookupF32 (&status, recipe, name);
-        if (status && isfinite (value)) {
-            params[i] = value;
-        }
-    }
-
-    float area = params[4]*params[5];
-    fprintf (stderr, "peak: %f @ (%f, %f)\n", source->moments->Sum*area, (double)source->peak->x, (double)source->peak->y);
-
-    // for PSF fitting, set the shape parameters based on the PSF & source position
-    if (fitMode == PM_SOURCE_FIT_PSF) {
-        source->modelPSF = pmModelFromPSF (model, psf);
-        psFree (model);
-        model = source->modelPSF;
-        params = model->params->data.F32;
-    }
-
-    // list model input shape
-    psEllipseShape shape;
-    shape.sx  = 1.4 / model->params->data.F32[4];
-    shape.sy  = 1.4 / model->params->data.F32[5];
-    shape.sxy = model->params->data.F32[6];
-    axes = psEllipseShapeToAxes (shape, 20.0);
-
-    fprintf (stderr, "guess: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
-
-    fprintf (stderr, "input parameters: \n");
-    for (int i = 0; i < nParams; i++) {
-        fprintf (stderr, "%d : %f\n", i, params[i]);
-    }
-
-    // define the pixels used for the fit
-    psImageKeepCircle (source->maskObj, xObj, yObj, RADIUS, "OR", markVal);
-    psphotSaveImage (NULL, source->maskObj, "mask1.fits");
-
-    char *fitset = psMetadataLookupStr (&status, recipe, "TEST_FIT_SET");
-    if (status) {
-        status = psphotFitSet (source, model, fitset, fitMode, maskVal);
-        exit (0);
-    }
-
-    if (fitMode == PM_SOURCE_FIT_PSF_X_EXT) {
-        // build the psf for the object
-        source->modelPSF = pmModelFromPSF (model, psf);
-        source->modelEXT = model;
-
-	// what fraction of the PSF is used? (radius in pixels : 2 -> 5x5 box)
-	int psfSize = psMetadataLookupS32 (&status, recipe, "PCM_BOX_SIZE");
-	assert (status);
-
-        model = psphotPSFConvModel (readout, source, modelType, maskVal, markVal, psfSize);
-        params = model->params->data.F32;
-    } else {
-        status = pmSourceFitModel (source, model, fitMode, maskVal);
-    }
-
-    // measure the source mags
-    pmSourcePhotometryModel (&fitMag, model);
-    pmSourcePhotometryAper  (NULL, &obsMag, NULL, NULL, model, source->pixels, source->variance, source->maskObj, maskVal);
-    fprintf (stderr, "ap: %f, fit: %f, apmifit: %f, nIter: %d\n", obsMag, fitMag, obsMag - fitMag, model->nIter);
-
-    // write out positive object
-    psphotSaveImage (NULL, source->pixels, "object.fits");
-
-    // subtract object, leave local sky
-    // pmModelSub (source->pixels, source->maskObj, model, PM_MODEL_OP_FULL, maskVal);
-    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
-
-    fprintf (stderr, "output parameters: \n");
-    for (int i = 0; i < nParams; i++) {
-        fprintf (stderr, "%d : %f\n", i, params[i]);
-    }
-
-    // write out
-    psphotSaveImage (NULL, source->pixels, "resid.fits");
-    psphotSaveImage (NULL, source->maskObj, "mask.fits");
-
-    psLogMsg ("psphot", PS_LOG_INFO, "model test : %f sec\n", psTimerMark ("modelTest"));
-
-    exit (0);
-}
+// 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: branches/meh_branches/ppstack_test/psphot/src/psphotModelTestArguments.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotModelTestArguments.c	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotModelTestArguments.c	(revision 33415)
@@ -0,0 +1,189 @@
+# include "psphotStandAlone.h"
+
+static void writeHelpInfo(const char* program, pmConfig* config, FILE* ofile)
+{
+  fprintf(ofile,
+	  "Usage: one of the following\n"
+	  "%s -file fname1[,fname2,...] -mask maskfile1[,maskfile2,...]\n"
+	  "     -variance varfile1[,varfile2,...] OutFileBaseName\n"
+	  "\n"
+	  "%s -list FileNameList [-masklist MaskFileNameList] \n"
+	  "     -variancelist VarFileNameList OutFileBaseName\n"
+	  "\n"
+	  "%s -help\n"
+	  "\n"
+	  "%s -version\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"
+	  "  -psf PsfFile1[,PsfFile2,...] or -psflist PsfFileNameList\n"
+	  "     specify PSF rather than letting %s estimate it\n"
+	  "  -src SrcFile1[,SrcFile2,...] or -srclist SrcFileNameList\n"
+	  "     specify additional sources for PSF generation\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",
+	  program,program,program,program,program);
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(PS_EXIT_SUCCESS);
+}
+
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments, // Command-line arguments
+                  pmConfig *config,      // Configuration
+		  int exitCode
+		  ) 
+{
+  fprintf(stderr,
+	  "Usage: one of the following\n"
+	  "%s -file fname1[,fname2,...] -mask maskfile1[,maskfile2,...]\n"
+	  "     -variance varfile1[,varfile2,...] OutFileBaseName\n"
+	  "\n"
+	  "%s -list FileNameList [-masklist MaskFileNameList] \n"
+	  "     -variancelist VarFileNameList OutFileBaseName\n"
+	  "\n"
+	  "Try '%s -help' for more options and explanation\n",
+	  program,program,program);
+    if (exitCode != PS_EXIT_SUCCESS)
+      psErrorStackPrint(stderr, "Error reading arguments\n");
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(exitCode);
+}
+
+pmConfig *psphotModelTestArguments(int argc, char **argv) {
+
+    int N;
+    bool status;
+
+    // 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);
+    }
+
+    PS_ARGUMENTS_GENERIC( 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
+    PS_ARGUMENTS_THREADS( psphot, config, argc, argv )
+
+    // visual : interactive display mode
+    if ((N = psArgumentGet (argc, argv, "-visual"))) {
+        psArgumentRemove (N, &argc, argv);
+        pmVisualSetVisual(true);
+    }
+
+    // break : used from recipe throughout psphotReadout
+    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);
+    }
+
+    // analysis region : overrides recipe value, used in psphotReadout/psphotEnsemblePSF
+    if ((N = psArgumentGet (argc, argv, "-region"))) {
+	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, "ANALYSIS_REGION", 0, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet (argc, argv, "-chip"))) {
+	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 (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // if these command-line options are supplied, load the file name lists into config->arguments
+    // override any configuration-specified source for these files
+    //
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK",       "-mask",     "-masklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "VARIANCE",   "-variance", "-variancelist");
+
+    // an input list of sources to use is allowed, but not required
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "SRC", "-src", "-srclist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "SRCTEXT", "-srctext", "-srctextlist");
+
+    if (argc == 1) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Too few arguments: %d", argc);
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (psArgumentGet(argc, argv, "-help") ||
+	psArgumentGet(argc, argv, "-h"))
+      writeHelpInfo(argv[0], config, stdout);
+      
+    // the input file is a required argument; if not found, we will exit
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (!status) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "pmConfigFileSetsMD failed to parse arguments");
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (argc != 2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Expected to see one more argument; saw %d", argc - 1);
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psphot", 1, "Done with psphotModelTestArguments...\n");
+    return (config);
+}
Index: branches/meh_branches/ppstack_test/psphot/src/psphotModelTestReadout.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotModelTestReadout.c	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotModelTestReadout.c	(revision 33415)
@@ -0,0 +1,221 @@
+# include "psphotInternal.h"
+# define PM_SOURCE_FIT_PSF_X_EXT PM_SOURCE_FIT_PSF_AND_SKY
+
+bool psphotModelTestReadout (pmConfig *config, const pmFPAview *view, const char *filerule) {
+
+    bool status;
+
+    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;
+    }
+
+    // remove cruft from the input analysis structure
+    if (!psphotCleanInputs (config, view, filerule)) {
+        psError (PSPHOT_ERR_PROG, false, "trouble setting up the inputs");
+        return false;
+    }
+
+    // set the photcode for this image
+    if (!psphotAddPhotcode (config, view, filerule)) {
+        psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // Generate the mask and weight images, including the user-defined analysis region of interest
+    if (!psphotSetMaskAndVariance (config, view, filerule)) {
+        return psphotReadoutCleanup(config, view, filerule);
+    }
+
+    // generate a background model (median, smoothed image)
+    if (!psphotModelBackground (config, view, filerule)) {
+        return psphotReadoutCleanup (config, view, filerule);
+    }
+
+    if (!psphotSubtractBackground (config, view, filerule)) {
+        return psphotReadoutCleanup (config, view, filerule);
+    }
+
+    float MIN_KRON_RADIUS = 5.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;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, 0); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, filerule);
+    if (!status) POISSON_ERRORS = true;
+
+    // find the various fitting parameters (try test values first)
+    float INNER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_INNER_RADIUS");
+    if (!status || !isfinite(INNER)) {
+        INNER = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+    }
+    float OUTER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_OUTER_RADIUS");
+    if (!status || !isfinite(OUTER)) {
+        OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    }
+    float RADIUS = psMetadataLookupF32 (&status, recipe, "TEST_FIT_RADIUS");
+    if (!status || !isfinite(RADIUS)) {
+        RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    }
+    float mRADIUS = psMetadataLookupF32 (&status, recipe, "TEST_MOMENTS_RADIUS");
+    if (!status || !isfinite(mRADIUS)) {
+        mRADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    }
+
+    // define the source of interest
+   float xObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_X");
+    float yObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_Y");
+    if (!isfinite(xObj) || !isfinite(yObj)) psAbort ("object position is not defined");
+
+    // construct the source structures
+    pmSource *source = pmSourceAlloc();
+    source->peak = pmPeakAlloc (xObj, yObj, 0, 0);
+    pmSourceDefinePixels (source, readout, xObj, yObj, OUTER);
+
+    // find the model: supplied by user or first in the PSF_MODEL list
+    char *modelName  = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODEL");
+    int modelType = pmModelClassGetType (modelName);
+    if (modelType < 0) psAbort("unknown model %s", modelName);
+    source->type = PM_SOURCE_TYPE_EXTENDED;
+
+    // find the local sky
+    status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER, maskVal, markVal);
+    if (!status) psAbort("pmSourceLocalSky error");
+
+    { 
+	// XXX I want to test an iterative aperture for brighter sources
+	float radius = mRADIUS;
+	for (int i = 0; i < 10; i++) {
+
+	    // get the source moments
+	    status = pmSourceMoments (source, radius, 0.0, 0.0, MIN_KRON_RADIUS, maskVal);
+	    if (!status) psAbort("psSourceMoments error");
+
+	    float oldRadius = radius;
+	    radius = source->moments->Mrf * RADIUS;
+	    
+	    fprintf (stderr, "%d %f  %f  %f\n", i, oldRadius, radius, source->moments->Mrf);
+	}
+	exit (0);
+    }
+
+    // get the source moments
+    status = pmSourceMoments (source, mRADIUS, 0.0, 0.0, MIN_KRON_RADIUS, maskVal);
+    if (!status) psAbort("psSourceMoments error");
+
+    fprintf (stderr, "sum: %f @ (%f, %f)\n", source->moments->Sum, source->moments->Mx, source->moments->My);
+    fprintf (stderr, "moments: %f, %f - %f\n", source->moments->Mxx, source->moments->Myy, source->moments->Mxy);
+
+    psEllipseMoments moments;
+    moments.x2 = source->moments->Mxx;
+    moments.y2 = source->moments->Myy;
+    moments.xy = source->moments->Mxy;
+    psEllipseAxes axes = psEllipseMomentsToAxes (moments, 20.0);
+
+    fprintf (stderr, "axes: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
+
+    // get the initial model parameter guess
+    pmModel *model = pmSourceModelGuess (source, modelType);
+    source->modelEXT = model;
+
+    // if any parameters are defined by the user, take those values
+    int nParams = pmModelClassParameterCount (modelType);
+    psF32 *params = model->params->data.F32;
+    params[PM_PAR_XPOS] = xObj; // XXX use the user-supplied value,
+    params[PM_PAR_YPOS] = yObj; // XXX or use the centroid
+    for (int i = 0; i < nParams; i++) {
+        if (i == PM_PAR_XPOS) continue;
+        if (i == PM_PAR_YPOS) continue;
+
+	char name[32];
+        sprintf (name, "TEST_FIT_PAR%d", i);
+        float value = psMetadataLookupF32 (&status, recipe, name);
+        if (status && isfinite (value)) {
+            params[i] = value;
+        }
+    }
+
+    float area = params[4]*params[5];
+    fprintf (stderr, "peak: %f @ (%f, %f)\n", source->moments->Sum*area, (double)source->peak->x, (double)source->peak->y);
+
+    // list model input shape
+    psEllipseShape shape;
+    shape.sx  = 1.4 / model->params->data.F32[4];
+    shape.sy  = 1.4 / model->params->data.F32[5];
+    shape.sxy = model->params->data.F32[6];
+    axes = psEllipseShapeToAxes (shape, 20.0);
+
+    fprintf (stderr, "guess: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
+
+    fprintf (stderr, "input parameters: \n");
+    for (int i = 0; i < nParams; i++) {
+        fprintf (stderr, "%d : %f\n", i, params[i]);
+    }
+
+    // define the pixels used for the fit
+    psImageKeepCircle (source->maskObj, xObj, yObj, RADIUS, "OR", markVal);
+    psphotSaveImage (NULL, source->maskObj, "mask1.fits");
+
+    float SKY_SIG = psMetadataLookupF32(&status, readout->analysis, "SKY_STDEV");
+
+    // options which modify the behavior of the model fitting
+    pmSourceFitOptions *fitOptions = pmSourceFitOptionsAlloc();
+    fitOptions->nIter         = psMetadataLookupS32(&status, recipe, "PSF_FIT_ITER"); // Maximum number of fit iterations
+    fitOptions->minTol        = psMetadataLookupF32 (&status, recipe, "PSF_FIT_MIN_TOL"); // Fit tolerance
+    fitOptions->maxTol        = psMetadataLookupF32 (&status, recipe, "PSF_FIT_MAX_TOL"); // Fit tolerance
+    fitOptions->maxChisqDOF   = psMetadataLookupF32 (&status, recipe, "PSF_FIT_MAX_CHISQ"); // Fit tolerance
+    fitOptions->poissonErrors = POISSON_ERRORS;
+    fitOptions->weight        = PS_SQR(SKY_SIG);
+    fitOptions->mode          = PM_SOURCE_FIT_EXT;
+    fitOptions->covarFactor   = psImageCovarianceFactorForAperture(readout->covariance, 10.0); // Covariance matrix
+
+    status = pmSourceFitModel (source, model, fitOptions, maskVal);
+
+    // measure the source mags
+    float fitMag = NAN;
+    float fitFlux = NAN;
+    float obsMag = NAN;
+    pmSourcePhotometryModel (&fitMag, &fitFlux, model);
+    pmSourcePhotometryAper  (NULL, &obsMag, NULL, NULL, model, source->pixels, source->variance, source->maskObj, maskVal);
+    fprintf (stderr, "ap: %f, fit: %f, apmifit: %f, nIter: %d\n", obsMag, fitMag, obsMag - fitMag, model->nIter);
+
+    // write out positive object
+    psphotSaveImage (NULL, source->pixels, "object.fits");
+
+    // subtract object, leave local sky
+    // pmModelSub (source->pixels, source->maskObj, model, PM_MODEL_OP_FULL, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+    fprintf (stderr, "output parameters: \n");
+    for (int i = 0; i < nParams; i++) {
+        fprintf (stderr, "%d : %f\n", i, params[i]);
+    }
+
+    // write out
+    psphotSaveImage (NULL, source->pixels, "resid.fits");
+    psphotSaveImage (NULL, source->maskObj, "mask.fits");
+
+    psLogMsg ("psphot", PS_LOG_INFO, "model test : %f sec\n", psTimerMark ("modelTest"));
+
+    exit (0);
+}
Index: branches/meh_branches/ppstack_test/psphot/src/psphotOutput.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotOutput.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotOutput.c	(revision 33415)
@@ -325,4 +325,7 @@
     psMetadataItemSupplement (&status, header, analysis, "MSKY_NY");
 
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_DEV");
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_DQ");
+
     psMetadataItemSupplement (&status, header, analysis, "DETEFF.MAGREF");
 
@@ -404,4 +407,71 @@
     return true;
 }
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotDumpTest (pmConfig *config, const pmFPAview *view, const char *filerule)
+{
+    static int npass = 0;
+    char filename[64];
+
+    // XXX dump tests are disabled unless this is commented out:
+    return true;
+
+    bool status = true;
+
+    int num = psphotFileruleCount(config, filerule);
+
+    snprintf (filename, 64, "testdump.%02d.dat", npass);
+    FILE *f = fopen (filename, "w");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+
+        // find the currently selected readout
+        pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, i); // 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 ? detections->newSources : detections->allSources;
+        psAssert (sources, "missing sources?");
+
+	if (detections->newSources) {
+	    fprintf (f, "## --- from new sources ---\n");
+	} else {
+	    fprintf (f, "## --- from all sources ---\n");
+	}
+
+	for (int i = 0; i < sources->n; i++) {
+	    pmSource *source = sources->data[i];
+	    if (!source) continue;
+
+	    pmPeak *peak = source->peak;
+	    if (!peak) continue;
+
+	    // XXX only dump a given region
+	    // if (peak->xf < 20) continue;
+	    // if (peak->yf < 20) continue;
+	    // if (peak->xf > 40) continue;
+	    // if (peak->yf > 70) continue;
+
+	    float Msum = source->moments ? source->moments->Sum : NAN;
+	    float Mx   = source->moments ? source->moments->Mx : NAN;
+	    float My   = source->moments ? source->moments->My : NAN;
+	    // float Npix = source->moments ? source->moments->nPixels : NAN;
+	    float Io = source->modelPSF ? source->modelPSF->params->data.F32[PM_PAR_I0] : NAN;
+	    fprintf (f, "%d %f %f  : %f %f : %f %f\n", source->imageID, peak->xf, peak->yf, Mx, My, Msum, Io);
+	}
+    }
+    fclose (f);
+    npass ++;
+
+    return true;
+}
+
+# if (0)
 bool psphotDumpTest (pmConfig *config, const pmFPAview *view, const char *filerule, char *filename) {
 
@@ -446,3 +516,3 @@
     return true;
 }
-
+# endif
Index: branches/meh_branches/ppstack_test/psphot/src/psphotPSFConvModel.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotPSFConvModel.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotPSFConvModel.c	(revision 33415)
@@ -43,5 +43,5 @@
     // adjust the pixels based on the footprint
     float radius = psphotSetRadiusEXT (readout, source, markVal);
-    if (!pmSourceMoments (source, radius, 0.0, 0.0, maskVal)) return false;
+    if (!pmSourceMoments (source, radius, 0.25*radius, 0.0, maskVal)) return false;
 
     // XXX test : modify the Io, SXX, SYY terms based on the psf SXX, SYY terms:
Index: branches/meh_branches/ppstack_test/psphot/src/psphotPetrosianProfile.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotPetrosianProfile.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotPetrosianProfile.c	(revision 33415)
@@ -38,5 +38,5 @@
     // convert the isophotal radius vs angle measurements to an elliptical contour
     if (!psphotEllipticalContour (source, petrosian)) {
-	psLogMsg ("psphot", 3, "failed to measure elliptical contour");
+	// psLogMsg ("psphot", 3, "failed to measure elliptical contour");
 	psFree (petrosian);
 	return false;
Index: branches/meh_branches/ppstack_test/psphot/src/psphotPetrosianStats.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotPetrosianStats.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotPetrosianStats.c	(revision 33415)
@@ -143,15 +143,8 @@
     // if we failed to reach the PETROSIAN_RATIO, use the lowest significant ratio instead (flag this!)
     if (!anyPetro) {
-	// interpolate Rvec between i-1 and i to PETROSIAN_RATIO to get flux (Fvec) and radius (rvec)
-	if (lowestSignificantRadius == 0) { 
-	    // assume Fmax @ R = 0.0
-	    petRadius    = InterpolateValues     (1.0, 0.0, petRatio->data.F32[lowestSignificantRadius], refRadius->data.F32[lowestSignificantRadius], PETROSIAN_RATIO);
-	    petRadiusErr = InterpolateValuesErrX (1.0, 0.0, petRatio->data.F32[lowestSignificantRadius], refRadius->data.F32[lowestSignificantRadius], PETROSIAN_RATIO, 0.0, petRatioErr->data.F32[lowestSignificantRadius]);
-
-	} else {
-	    int n0 = lowestSignificantRadius-1;
-	    int n1 = lowestSignificantRadius;
-	    petRadius    = InterpolateValues     (petRatio->data.F32[n0], refRadius->data.F32[n0], petRatio->data.F32[n1], refRadius->data.F32[n1], PETROSIAN_RATIO);
-	    petRadiusErr = InterpolateValuesErrX (petRatio->data.F32[n0], refRadius->data.F32[n0], petRatio->data.F32[n1], refRadius->data.F32[n1], PETROSIAN_RATIO, petRatioErr->data.F32[n0], petRatioErr->data.F32[n1]);
+	petRadius = refRadius->data.F32[lowestSignificantRadius];
+	petRadiusErr = NAN;
+	if (!isfinite(petRadius)) {
+	    fprintf (stderr, "nan pet radius\n");
 	}
     }
@@ -170,4 +163,7 @@
 		petArea    = InterpolateValues     (refRadius->data.F32[i-1], areaSum->data.F32[i-1], refRadius->data.F32[i], areaSum->data.F32[i], apRadius);
 		petApix    = InterpolateValues     (refRadius->data.F32[i-1], apixSum->data.F32[i-1], refRadius->data.F32[i], apixSum->data.F32[i], apRadius);
+		if (!isfinite(petFlux)) {
+		    fprintf (stderr, "nan pet flux\n");
+		}
 		break;
 	    }
@@ -188,6 +184,7 @@
 	if (!found50 && (fluxSum->data.F32[i] > flux50)) {
 	    if (i == 0) {
-		psWarning ("does this case make any sense? (fluxSum[0] %f > flux50 %f)", fluxSum->data.F32[i], flux50);
-		continue;
+		R50    = InterpolateValues     (fluxSum->data.F32[i], refRadius->data.F32[i], fluxSum->data.F32[i+1], refRadius->data.F32[i+1], flux50);
+		R50err = InterpolateValuesErrX (fluxSum->data.F32[i], refRadius->data.F32[i], fluxSum->data.F32[i+1], refRadius->data.F32[i+1], flux50, sqrt(fluxSumErr2->data.F32[i]), sqrt(fluxSumErr2->data.F32[i+1]));
+		found50 = true;
 	    } else {
 		R50    = InterpolateValues     (fluxSum->data.F32[i-1], refRadius->data.F32[i-1], fluxSum->data.F32[i], refRadius->data.F32[i], flux50);
@@ -198,6 +195,7 @@
 	if (!found90 && (fluxSum->data.F32[i] > flux90)) {
 	    if (i == 0) {
-		psWarning ("does this case make any sense? (fluxSum[0] %f > flux90 %f)", fluxSum->data.F32[i], flux90);
-		continue;
+		R90    = InterpolateValues     (fluxSum->data.F32[i], refRadius->data.F32[i], fluxSum->data.F32[i+1], refRadius->data.F32[i+1], flux90);
+		R90err = InterpolateValuesErrX (fluxSum->data.F32[i], refRadius->data.F32[i], fluxSum->data.F32[i+1], refRadius->data.F32[i+1], flux90, sqrt(fluxSumErr2->data.F32[i]), sqrt(fluxSumErr2->data.F32[i+1]));
+		found90 = true;
 	    } else {
 		R90    = InterpolateValues     (fluxSum->data.F32[i-1], refRadius->data.F32[i-1], fluxSum->data.F32[i], refRadius->data.F32[i], flux90);
Index: branches/meh_branches/ppstack_test/psphot/src/psphotRadialApertures.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotRadialApertures.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotRadialApertures.c	(revision 33415)
@@ -3,8 +3,14 @@
 bool psphotRadialAperturesSortFlux (psVector *radius, psVector *pixFlux, psVector *pixVar);
 
+// this function measures the radial aperture fluxes for the set of readouts.  this function
+// may be called multiple times, presumably for different versions of PSF-matched or unmatched images.  
+
 // for now, let's store the detections on the readout->analysis for each readout
-bool psphotRadialApertures (pmConfig *config, const pmFPAview *view, const char *filerule)
+bool psphotRadialApertures (pmConfig *config, const pmFPAview *view, const char *filerule, int entry)
 {
     bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Radial Apertures ---");
 
     // select the appropriate recipe information
@@ -20,7 +26,13 @@
     int num = psphotFileruleCount(config, filerule);
 
+    // skip the chisq image (optionally?)
+    int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
+    if (!status) chisqNum = -1;
+
     // loop over the available readouts
     for (int i = 0; i < num; i++) {
-	if (!psphotRadialAperturesReadout (config, view, filerule, i, recipe)) {
+        if (i == chisqNum) continue; // skip chisq image
+
+	if (!psphotRadialAperturesReadout (config, view, filerule, i, recipe, entry)) {
             psError (PSPHOT_ERR_CONFIG, false, "failed on measure extended source aperture-like parameters for %s entry %d", filerule, i);
 	    return false;
@@ -30,7 +42,15 @@
 }
 
+// these values are used by all threads repeatedly (and are not modified)
+static psVector *aperRadii = NULL;
+static psVector *aperRadii2 = NULL;
+static float outerRadius = NAN;
+static float SN_LIM = NAN;
+static psImageMaskType maskVal = 0;
+
 // aperture-like measurements for extended sources
 // flux in simple, circular apertures
-bool psphotRadialAperturesReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe) {
+// 'entry' tells us which of the matched-PSF images we are working on (0 == unmatched image, also non-stack psphot)
+bool psphotRadialAperturesReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe, int entry) {
 
     bool status;
@@ -63,17 +83,30 @@
     }
 
-    // radMax stores the upper bounds of the annuli
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    // aperRadii stores the upper bounds of the annuli
     // XXX keep the same name here as for the petrosian / elliptical apertures?
-    psVector *radMax = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.UPPER");
-    psAssert (radMax, "annular bins (RADIAL.ANNULAR.BINS.UPPER) are not defined in the recipe");
-    psAssert (radMax->n, "no valid annular bins (RADIAL.ANNULAR.BINS.UPPER) are define");
-    float outerRadius = radMax->data.F32[radMax->n - 1];
+    aperRadii = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.UPPER");
+    psAssert (aperRadii, "annular bins (RADIAL.ANNULAR.BINS.UPPER) are not defined in the recipe");
+    psAssert (aperRadii->n, "no valid annular bins (RADIAL.ANNULAR.BINS.UPPER) are define");
+
+    outerRadius = aperRadii->data.F32[aperRadii->n - 1];
+
+    // save the R^2 values as well for quicker comparison
+    aperRadii2 = psVectorAlloc(aperRadii->n, PS_TYPE_F32);
+    for (int i = 0; i < aperRadii->n; i++) {
+	aperRadii2->data.F32[i] = PS_SQR(aperRadii->data.F32[i]);
+    }
 
     // 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
+    maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
     assert (maskVal);
 
     // S/N limit to perform full non-linear fits
-    float SN_LIM = psMetadataLookupF32 (&status, recipe, "RADIAL_APERTURES_SN_LIM");
+    SN_LIM = psMetadataLookupF32 (&status, recipe, "RADIAL_APERTURES_SN_LIM");
 
     // source analysis is done in S/N order (brightest first)
@@ -81,8 +114,108 @@
     sources = psArraySort (sources, pmSourceSortByFlux);
 
+    // XXX make this consistent with entry 0 == unmatched
+    int nEntry = 1;
+    psVector *fwhmValues = psMetadataLookupVector(&status, readout->analysis, "STACK.PSF.FWHM.VALUES");
+    if (fwhmValues) {
+	psAssert (entry < fwhmValues->n, "inconsistent matched-PSF entry");
+	nEntry = fwhmValues->n;
+    }
+    if (entry > 0) {
+	psLogMsg ("psphot", PS_LOG_DETAIL, "Radial Apertures for matched image %s : PSF FWHM = %f pixels\n", file->name, fwhmValues->data.F32[entry]);
+    } else {
+	psLogMsg ("psphot", PS_LOG_DETAIL, "Radial Apertures for unmatched image %s\n", file->name);
+    }
+
     // 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");
+    psRegion *AnalysisRegion = psRegionAlloc(0,0,0,0);
+    *AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (*AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    // 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_RADIAL_APERTURES");
+
+            psArrayAdd(job->args, 1, readout);
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+            psArrayAdd(job->args, 1, AnalysisRegion);
+            PS_ARRAY_ADD_SCALAR(job->args, entry,  PS_TYPE_S32);
+            PS_ARRAY_ADD_SCALAR(job->args, nEntry, PS_TYPE_S32);
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nradial
+
+	    // set this to 0 to run without threading
+# if (1)	    
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+		psFree(AnalysisRegion);
+                return false;
+            } 
+# else
+	    if (!psphotRadialApertures_Threaded(job)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+		psFree(AnalysisRegion);
+		return false;
+	    }
+	    psScalar *scalar = NULL;
+	    scalar = job->args->data[5];
+	    Nradial += scalar->data.S32;
+	    psFree(job);
+# endif
+	}
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false, true)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+	    psFree(AnalysisRegion);
+            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[5];
+		Nradial += scalar->data.S32;
+            }
+            psFree(job);
+	}
+    }
+    psFree (cellGroups);
+    psFree(AnalysisRegion);
+    psFree (aperRadii2);
+
+    psLogMsg ("psphot", PS_LOG_WARN, "radial source apertures: %f sec for %d objects\n", psTimerMark ("psphot.radial"), Nradial);
+    return true;
+}
+ 
+bool psphotRadialApertures_Threaded (psThreadJob *job) {
+
+    int Nradial = 0;
+
+    // arguments: readout, sources, models, region, psfSize, maskVal, markVal
+    pmReadout *readout      = job->args->data[0];
+    psArray *sources        = job->args->data[1];
+    psRegion *region        = job->args->data[2];
+    int entry               = PS_SCALAR_VALUE(job->args->data[3],S32); // which psf-matched image are we working on? (0 == unmatched)
+    int nEntry              = PS_SCALAR_VALUE(job->args->data[4],S32); // total number of psf-matched images + 1 unmatched
+
+    // storage for the derived pixel values (these are passed into psphotRadialApertureSource)
+    psVector *pixRadius2 = psVectorAllocEmpty(100, PS_TYPE_F32);
+    psVector *pixFlux    = psVectorAllocEmpty(100, PS_TYPE_F32);
+    psVector *pixVar     = psVectorAllocEmpty(100, PS_TYPE_F32);
 
     // choose the sources of interest
@@ -90,4 +223,9 @@
 
 	pmSource *source = sources->data[i];
+
+	// if we have checked the source validity on the basis of the object set, then 
+	// we either skip these tests below or we skip the source completely
+	if (source->tmpFlags & PM_SOURCE_TMPF_RADIAL_SKIP) continue;
+	if (source->tmpFlags & PM_SOURCE_TMPF_RADIAL_KEEP) goto keepSource;
 
 	// skip PSF-like and non-astronomical objects
@@ -104,12 +242,21 @@
 
 	// limit selection by analysis region
-	if (source->peak->x < AnalysisRegion.x0) continue;
-	if (source->peak->y < AnalysisRegion.y0) continue;
-	if (source->peak->x > AnalysisRegion.x1) continue;
-	if (source->peak->y > AnalysisRegion.y1) continue;
+	if (source->peak->x < region->x0) continue;
+	if (source->peak->y < region->y0) continue;
+	if (source->peak->x > region->x1) continue;
+	if (source->peak->y > region->y1) continue;
+
+    keepSource:
 
 	// allocate pmSourceExtendedParameters, if not already defined
-	if (!source->radialAper) {
-	    source->radialAper = psArrayAlloc(1);
+	// XXX check that nPSFsizes is consistent with targets
+	if (source->parent) {
+	    if (!source->parent->radialAper) {
+		source->parent->radialAper = psArrayAlloc(nEntry);
+	    }
+	} else {
+	    if (!source->radialAper) {
+		source->radialAper = psArrayAlloc(nEntry);
+	    }
 	}
 
@@ -119,17 +266,7 @@
 	}
 
-	// we need to change the view for the radial aperture analysis, but we want to recover exactly
-	// the original view; the following elements get destroyed by pmSourceRedefinePixels so save them:
-	psImage *oldMaskObj   = psMemIncrRefCounter(source->maskObj);
-	psImage *oldModelFlux = psMemIncrRefCounter(source->modelFlux);
-	psImage *oldPSFimage  = psMemIncrRefCounter(source->psfImage);
-	psRegion oldRegion    = source->region;
-
 	Nradial ++;
 
-	// force source image to be a bit larger...
-	pmSourceRedefinePixels (source, readout, source->peak->xf, source->peak->yf, outerRadius + 2);
-
-	if (!psphotRadialApertureSource (source, recipe, maskVal, radMax, 0)) {
+	if (!psphotRadialApertureSource (source, readout, entry, pixRadius2, pixFlux, pixVar)) {
 	    psTrace ("psphot", 5, "failed to extract radial profile for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
 	} else {
@@ -137,19 +274,19 @@
 	}
 
-	pmSourceRedefinePixelsByRegion (source, readout, oldRegion);
-	psFree(source->maskObj);   source->maskObj   = oldMaskObj;
-	psFree(source->modelFlux); source->modelFlux = oldModelFlux;
-	psFree(source->psfImage);  source->psfImage  = oldPSFimage;
-	
 	// re-subtract the object, leave local sky
 	pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
     }
-
-    psLogMsg ("psphot", PS_LOG_INFO, "radial source apertures: %f sec for %d objects\n", psTimerMark ("psphot.radial"), Nradial);
+    psScalar *scalar = job->args->data[5];
+    scalar->data.S32 = Nradial;
+
+    psFree (pixRadius2);
+    psFree (pixFlux);
+    psFree (pixVar);
+
     return true;
 }
 
-bool psphotRadialApertureSource (pmSource *source, psMetadata *recipe, psImageMaskType maskVal, const psVector *aperRadii, int entry) {
-
+bool psphotRadialApertureSource (pmSource *source, pmReadout *readout, int entry, psVector *pixRadius2, psVector *pixFlux, psVector *pixVar) {
+					    
     // if we are a child source, save the results to the parent source radial aperture array
     psArray *radialAperSet = source->radialAper;
@@ -163,59 +300,68 @@
     radialAperSet->data[entry] = radialAper;
 
-    // storage for the derived pixel values
-    psVector *pixRadius2 = psVectorAllocEmpty(100, PS_TYPE_F32);
-    psVector *pixFlux    = psVectorAllocEmpty(100, PS_TYPE_F32);
-    psVector *pixVar     = psVectorAllocEmpty(100, PS_TYPE_F32);
+    // find the largest aperture of interest (use only apertures with inner radii <=
+    // source->skyRadius)
+    int lastAp = aperRadii->n;
+    for (int i = 0; i < aperRadii->n; i++) {
+	if (aperRadii->data.F32[i] < source->skyRadius) continue;
+	lastAp = i + 1;
+	break;
+    }
 
     // outer-most radius for initial truncation
-    float Rmax  = aperRadii->data.F32[aperRadii->n - 1];
+    float Rmax  = aperRadii->data.F32[lastAp - 1];
     float Rmax2 = PS_SQR(Rmax);
 
-    // store the R^2 values for the apertures
-    psVector *aperRadii2 = psVectorAlloc(aperRadii->n, PS_TYPE_F32);
-    for (int i = 0; i < aperRadii->n; i++) {
-	aperRadii2->data.F32[i] = PS_SQR(aperRadii->data.F32[i]);
-    }
+    // in this function, the operatins are relative to the full image (readout->image, etc)
 
     float xCM = NAN, yCM = NAN;
     if (pmSourcePositionUseMoments(source)) {
-	xCM = source->moments->Mx - 0.5 - source->pixels->col0; // coord of peak in subimage
-	yCM = source->moments->My - 0.5 - source->pixels->row0; // coord of peak in subimage
+	xCM = source->moments->Mx; // index coord of peak in readout
+	yCM = source->moments->My; // index coord of peak in readout
     } else {
-	xCM = source->peak->xf - 0.5 - source->pixels->col0; // coord of peak in subimage
-	yCM = source->peak->yf - 0.5 - source->pixels->row0; // coord of peak in subimage
-    }
+	xCM = source->peak->xf; // index coord of peak in readout
+	yCM = source->peak->yf; // index coord of peak in readout
+    }
+
+    int Nx = readout->image->numCols;
+    int Ny = readout->image->numRows;
+
+    pixRadius2->n = 0;
+    pixFlux->n = 0;
+    pixVar->n = 0;
 
     // one pass through the pixels to select the valid pixels and calculate R^2
-    for (int iy = 0; iy < source->pixels->numRows; iy++) {
-
-	float yDiff = iy - yCM;
-	if (fabs(yDiff) > Rmax) continue;
-
-	float *vPix = source->pixels->data.F32[iy];
-	float *vWgt = source->variance->data.F32[iy];
-	psImageMaskType  *vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[iy];
-
-	for (int ix = 0; ix < source->pixels->numCols; ix++, vPix++, vWgt++) {
-
-	    if (vMsk) {
-		if (*vMsk & maskVal) {
-		    vMsk++;
-		    continue;
-		}
-		vMsk++;
-	    }
-	    if (isnan(*vPix)) continue;
-
-	    float xDiff = ix - xCM;
-	    if (fabs(xDiff) > Rmax) continue;
+    for (int iy = -Rmax; iy < Rmax + 1; iy++) {
+
+	float yDiff = iy + 0.5 + yCM;  // y-coordinate at this offse
+	int yPix = (int) yDiff;
+
+	if (yPix < 0) continue;
+	if (yPix > Ny - 1) continue;
+	if (fabs(iy) > Rmax) continue;
+
+	float *vPix = readout->image->data.F32[yPix];
+	float *vWgt = readout->variance->data.F32[yPix];
+	psImageMaskType  *vMsk = readout->mask->data.PS_TYPE_IMAGE_MASK_DATA[yPix];
+
+	for (int ix = -Rmax; ix < Rmax + 1; ix++) {
+
+	    float xDiff = ix + 0.5 + xCM;  // x-coordinate at this offse
+	    int xPix = (int) xDiff;
+	    
+	    if (xPix < 0) continue;
+	    if (xPix > Nx - 1) continue;
+	    if (fabs(ix) > Rmax) continue;
+	    
+	    if (vMsk[xPix] & maskVal) continue;
+	    if (isnan(vPix[xPix])) continue;
 
 	    // radius is just a function of (xDiff, yDiff)
-	    float r2  = PS_SQR(xDiff) + PS_SQR(yDiff);
+	    float r2  = PS_SQR(ix) + PS_SQR(iy);
 	    if (r2 > Rmax2) continue;
 
 	    psVectorAppend(pixRadius2, r2);
-	    psVectorAppend(pixFlux, *vPix);
-	    psVectorAppend(pixVar, *vWgt);
+	    psVectorAppend(pixFlux, vPix[xPix]);
+	    psVectorAppend(pixVar, vWgt[xPix]);
 	}
     }
@@ -226,8 +372,15 @@
     psVector *fill    = psVectorAlloc(aperRadii->n, PS_TYPE_F32); // surface brightness of radial bin
 
+    // init the apertures of interest to 0.0, the rest go to NAN
     psVectorInit (flux,    0.0);
     psVectorInit (fluxStd, 0.0);
     psVectorInit (fluxErr, 0.0);
     psVectorInit (fill,    0.0);
+    for (int i = lastAp; i < flux->n; i++) {
+	flux->data.F32[i] = NAN;
+	fluxStd->data.F32[i] = NAN;
+	fluxErr->data.F32[i] = NAN;
+	fill->data.F32[i] = NAN;
+    }
 
     float *rPix2 = pixRadius2->data.F32;
@@ -236,6 +389,8 @@
 	int j = 0;
 	float *aRad2 = aperRadii2->data.F32;
-	for (; (*aRad2 < *rPix2) && (j < aperRadii2->n); j++, aRad2++);
-	for (; j < aperRadii2->n; j++, aRad2++) {
+	for (; (*aRad2 < *rPix2) && (j < lastAp); j++, aRad2++);
+
+	// XXX I can speed this up by only saving this single aperture
+	for (; j < lastAp; j++, aRad2++) {
 	    flux->data.F32[j]    += pixFlux->data.F32[i];
 	    fluxStd->data.F32[j] += PS_SQR(pixFlux->data.F32[i]);
@@ -249,7 +404,7 @@
        2) the fractional fill factor (count of valid pixels / effective area of the aperture
        3) the error on the flux within that aperture
-     */
-
-    for (int i = 0; i < flux->n; i++) {
+    */
+
+    for (int i = 0; i < lastAp; i++) {
 	// calculate the total flux for bin 'nOut'
 	float Area = M_PI*aperRadii2->data.F32[i];
@@ -261,5 +416,5 @@
 	// XXX report the total flux or the mask-corrected flux?
 	// flux->data.F32[i]    = SBmean * Area;
-	// fluxErr->data.F32[i] = sqrt(fluxErr->data.F32[i]) * Area / nPix;
+	// fluxErr->data.F32[i] = sqrt(fluxErr->data.F32[i]) * Area / Pinx;
 
 	fluxErr->data.F32[i] = sqrt(fluxErr->data.F32[i]);
@@ -271,16 +426,21 @@
     }
     
+# if (1)
     radialAper->flux = flux;
     radialAper->fluxStdev = fluxStd;
     radialAper->fluxErr = fluxErr;
     radialAper->fill = fill;
-
-    psFree (aperRadii2);
-    psFree (pixRadius2);
-    psFree (pixFlux);
-    psFree (pixVar);
+# else
+    // XXX TEST
+    psFree(flux);
+    psFree(fluxStd);
+    psFree(fluxErr);
+    psFree(fill);
+# endif
 
     return true;
 }
+
+/*** below is a test to use a sort to speed this up, not very successfully ***/
 
 static int nCalls = 0;
@@ -373,18 +533,18 @@
 // *** pmSourceRadialProfileSortPair is a utility function for sorting a pair of vectors
 # define COMPARE_VECT(A,B) (radius->data.F32[A] < radius->data.F32[B])
-# define SWAP_VECT(TYPE,A,B) { \
-  float tmp; \
-  if (A != B) { \
-    tmp = radius->data.F32[A]; \
-    radius->data.F32[A] = radius->data.F32[B];	\
-    radius->data.F32[B] = tmp; \
-    tmp = pixFlux->data.F32[A]; \
-    pixFlux->data.F32[A] = pixFlux->data.F32[B]; \
-    pixFlux->data.F32[B] = tmp; \
-    tmp = pixVar->data.F32[A]; \
-    pixVar->data.F32[A] = pixVar->data.F32[B]; \
-    pixVar->data.F32[B] = tmp; \
-  } \
-}
+# define SWAP_VECT(TYPE,A,B) {					\
+	float tmp;						\
+	if (A != B) {						\
+	    tmp = radius->data.F32[A];				\
+	    radius->data.F32[A] = radius->data.F32[B];		\
+	    radius->data.F32[B] = tmp;				\
+	    tmp = pixFlux->data.F32[A];				\
+	    pixFlux->data.F32[A] = pixFlux->data.F32[B];	\
+	    pixFlux->data.F32[B] = tmp;				\
+	    tmp = pixVar->data.F32[A];				\
+	    pixVar->data.F32[A] = pixVar->data.F32[B];		\
+	    pixVar->data.F32[B] = tmp;				\
+	}							\
+    }
 
 bool psphotRadialAperturesSortFlux (psVector *radius, psVector *pixFlux, psVector *pixVar) {
Index: branches/meh_branches/ppstack_test/psphot/src/psphotRadialAperturesByObject.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotRadialAperturesByObject.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotRadialAperturesByObject.c	(revision 33415)
@@ -162,7 +162,4 @@
 	    // re-subtract the object, leave local sky
 	    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
-
-	    // psLogMsg("psphot", PS_LOG_INFO, "radial apertures for %d", index);
-	    // psphotVisualShowImage(readout);
 	}
     }
Index: branches/meh_branches/ppstack_test/psphot/src/psphotRadialProfile.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotRadialProfile.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotRadialProfile.c	(revision 33415)
@@ -39,5 +39,5 @@
     // convert the isophotal radius vs angle measurements to an elliptical contour
     if (!psphotEllipticalContour (source)) {
-	psLogMsg ("psphot", 3, "failed to measure elliptical contour");
+	// psLogMsg ("psphot", 3, "failed to measure elliptical contour");
 	return false;
     }
Index: branches/meh_branches/ppstack_test/psphot/src/psphotRadialProfileWings.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotRadialProfileWings.c	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotRadialProfileWings.c	(revision 33415)
@@ -0,0 +1,414 @@
+# include "psphotInternal.h"
+
+// measure the radial profile outside of the core.  the goal is to find the point at which we
+// reach sky + X sigma
+
+bool psphotRadialProfileWingsSource (pmSource *source, pmReadout *readout, psImageMaskType maskVal);
+bool psphotRadialProfileFluxAtRadius (psVector *flux, psVector *fluxVar, pmSource *source, pmReadout *readout, float Radius, float dRadius, psImageMaskType maskVal);
+
+bool psphotRadialProfileWings (pmConfig *config, const pmFPAview *view, const char *filerule)
+{
+    bool status = true;
+
+    // return true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Radial Profile Wings ---");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psphotFileruleCount(config, filerule);
+
+    // skip the chisq image (optionally?)
+    int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
+    if (!status) chisqNum = -1;
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (i == chisqNum) continue; // skip chisq image
+
+        // find the currently selected readout
+        pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, i); // 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 ? detections->newSources : detections->allSources;
+        psAssert (sources, "missing sources?");
+
+        if (!psphotRadialProfileWingsReadout (config, recipe, view, readout, sources)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to measure magnitudes for %s entry %d", filerule, i);
+            return false;
+        }
+    }
+    return true;
+}
+
+// these are set before we fork off to threads and used by all threads as constant values
+static float MAX_RADIUS = NAN;
+static float MIN_RADIUS = NAN;
+static float SKY_STDEV  = NAN;
+// static FILE *file = NULL;
+
+bool psphotRadialProfileWingsReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, pmReadout *readout, psArray *sources) {
+
+    bool status = false;
+
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping radial profile wings");
+        return true;
+    }
+
+    psTimerStart ("psphot.wings");
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    // XXX is this a good recipe value to use for MAX RADIUS??
+    MAX_RADIUS = psMetadataLookupF32 (&status, recipe, "EXT_FIT_MAX_RADIUS");
+    if (!status) {
+        MAX_RADIUS = 50.0;
+    }
+
+    MIN_RADIUS = psMetadataLookupF32 (&status, readout->analysis, "PSF_MOMENTS_RADIUS");
+    if (!status) {
+        MIN_RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    }
+
+    // SKY_STDEV is the sigma of the sky model (ie, smoothed on large scales)
+    SKY_STDEV = psMetadataLookupF32 (&status, readout->analysis, "MSKY_SIG");
+    if (!status) {
+	SKY_STDEV = 1.0; // a crude default value (why would this not exist?)
+    }
+
+    // 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;
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortByFlux);
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping blend");
+        return true;
+    }
+
+    // file = fopen ("radii.dat", "w");
+
+    // threaded measurement of the source magnitudes
+    // 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_RADIAL_PROFILE_WINGS");
+
+            psArrayAdd(job->args, 1, readout);
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+            PS_ARRAY_ADD_SCALAR(job->args, maskVal,       PS_TYPE_IMAGE_MASK);
+
+// set this to 0 to run without threading
+# if (1)
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+                return false;
+            }
+# else
+	    if (!psphotRadialProfileWings_Threaded(job)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+		return false;
+	    }
+	    psFree(job);
+# endif
+        }
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false, true)) {
+            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) {
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            }
+            psFree(job);
+        }
+    }
+    psFree (cellGroups);
+
+    // fclose (file);
+
+    psLogMsg ("psphot.wings", PS_LOG_WARN, "measure radial profile wings : %f sec for %ld objects\n", psTimerMark ("psphot.wings"), sources->n);
+    return true;
+}
+
+bool psphotRadialProfileWings_Threaded (psThreadJob *job) {
+
+    pmReadout *readout              = job->args->data[0];
+    psArray *sources                = job->args->data[1];
+    psImageMaskType maskVal         = PS_SCALAR_VALUE(job->args->data[2],PS_TYPE_IMAGE_MASK_DATA);
+
+    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) continue;
+
+	// replace object in image
+	bool reSubtract = false;
+	if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+	    pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+	    reSubtract = true;
+	}
+
+	// re-allocate image, weight, mask arrays for each peak with box big enough to fit BIG_RADIUS
+	// XXX don't measure on subraster images:
+	// pmSourceRedefinePixels (source, readout, source->peak->x, source->peak->y, windowRadius + 2);
+	// psAssert (source->pixels, "WTF?");
+
+	// this function populates moments->Mrf,KronFlux,KronFluxErr
+	psphotRadialProfileWingsSource (source, readout, maskVal);
+
+	// if we subtracted it above, re-subtract the object, leave local sky
+	if (reSubtract) {
+	    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+	}
+    }
+    return true;
+}
+
+# define TEST_X 3158
+# define TEST_Y 3096
+
+float InterpolateValues (float X0, float Y0, float X1, float Y1, float X);
+
+// XXX use integer radius values?  the rings assume integer values, right? or do they?
+bool psphotRadialProfileWingsSource (pmSource *source, pmReadout *readout, psImageMaskType maskVal) {
+
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+
+    // psImageMaskType **vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA;
+
+    // XXX unclear if I should run this analysis on both 1st and 2nd pass for 1st pass objects,
+    // or just use the 1st pass value.  I think I should just use the 1st pass value, so skip
+    // any that have already been assigned
+    if (isfinite(source->skyRadius)) return true;
+
+    if (source->mode2 & PM_SOURCE_MODE2_MATCHED) return true; // skip matched sources (no signal)
+
+    // radii will be MIN_RADIUS to MAX_RADIUS in NN log steps:
+    float NSTEP = 25.0;
+    float MIN_DR = 2;
+    float NSIGMA = 1.0;
+    float THRESHOLD = 2.0*SKY_STDEV;
+    float alpha = pow ((MAX_RADIUS / MIN_RADIUS), 1.0/NSTEP) - 1.0;
+    float dRmax = MAX_RADIUS * alpha / (1.0 + alpha); // approximate size of last annulus, to get a rough size for vector allocation
+
+    int iter = 0;
+
+    psVector *flux = psVectorAllocEmpty(7*MAX_RADIUS*dRmax, PS_TYPE_F32);
+    psVector *fluxVar = psVectorAllocEmpty(7*MAX_RADIUS*dRmax, PS_TYPE_F32);
+
+    // should I just use sample median here?
+    psStats *fluxStats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
+    psStats *varStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+
+    float lastFlux = NAN; 
+    float lastRadius = NAN; 
+    float lastSlope = NAN; 
+    bool limit = false;
+    float limitRadius = NAN;
+    float limitFlux = NAN;
+    float limitSlope = NAN;
+
+    // note: radius is the inner radius of the annulus; outer radius = inner radius + dR
+    for (float radius = MIN_RADIUS; !limit && (radius < MAX_RADIUS); iter ++) {
+
+	float dR = (int)(radius * alpha);
+	if (dR < MIN_DR) dR = MIN_DR;
+	float outerRadius = radius + dR;
+	float meanRadius = (2.0/3.0) * (outerRadius*outerRadius*outerRadius - radius*radius*radius) / (PS_SQR(outerRadius) - PS_SQR(radius));
+
+	// extract a vector of the pixel values (signal, variance) at this radius + dR
+	psphotRadialProfileFluxAtRadius (flux, fluxVar, source, readout, radius, dR, maskVal);
+
+	psStatsInit (fluxStats);
+	psStatsInit (varStats);
+
+	if ((flux->n == 0) || (fluxVar->n == 0)) {
+	    flux->n = 0;
+	    fluxVar->n = 0;
+	    radius += dR;
+	    continue;
+	}
+
+	psVectorStats (fluxStats, flux, NULL, NULL, 0);
+	psVectorStats (varStats, fluxVar, NULL, NULL, 0);
+	
+	// get the mean / median flux statistic and significance here
+	float meanFlux = fluxStats->robustMedian;
+	float meanFluxError = sqrt(varStats->sampleMean / fluxVar->n);
+	// this is a bit crude on the flux error: the error. technically, it should be
+	// sqrt(\sum(variance)) / Npts.  I am using the identity \sum(variance) =
+	// \mean(variance) * Npts and cancelling the Npts term inside and out of the sqrt()
+	
+	float slope = NAN;
+	if (isfinite(lastFlux)) {
+	    slope = (meanFlux - lastFlux) / (meanRadius - lastRadius);
+	}
+
+	// fprintf (stderr, "%f %f : %f : %f %f  :  %f\n", source->peak->xf, source->peak->yf, radius, meanFlux, meanFluxError, slope);
+
+	if (!limit && (meanFlux - NSIGMA * meanFluxError < THRESHOLD)) {
+	    // dropped to sky level
+	    limit = true;
+	    // linearly interpolate to the radius at which we hit the sky
+	    if (isfinite(lastFlux)) {
+		limitRadius = InterpolateValues(lastFlux, lastRadius, meanFlux, meanRadius, 0.0);
+	    } else {
+		limitRadius = meanRadius;
+	    }
+	    limitFlux = meanFlux;
+	    limitSlope = slope;
+	}
+	if (!limit && isfinite(slope) && (fabs(slope) < 3.0)) { 
+	    // SB no longer changing.	    
+	    limit = true;
+	    // linearly interpolate to the radius at which we hit the sky, using the last flux and the limiting slope
+	    if (isfinite(lastFlux)) {
+		limitRadius = lastRadius + lastFlux / 3.0;
+	    } else {
+		limitRadius = meanRadius;
+	    }
+	    limitFlux = meanFlux;
+	    limitSlope = slope;
+	}
+
+	// completion criteria:
+	// 1) flux - NSIGMA * dflux <= sky
+	// 2) flux rising?
+	// 3) flux flat?
+
+	lastFlux = meanFlux;
+	lastRadius = meanRadius;
+	lastSlope = slope;
+
+	// reset the flux & fluxVar vector length to zero for re-use above:
+	flux->n = 0;
+	fluxVar->n = 0;
+	radius += dR;
+    }
+
+    if (!limit) {
+	limitRadius = lastRadius;
+	limitFlux = lastFlux;
+	limitSlope = lastSlope;
+    }
+    // fprintf (file, "%f %f : %f %f : %f\n", source->peak->xf, source->peak->yf, limitRadius, limitFlux, limitSlope);
+
+    psFree (flux);
+    psFree (fluxVar);
+    psFree (fluxStats);
+    psFree (varStats);
+
+    source->skyRadius = limitRadius;
+    source->skyFlux   = limitFlux;
+    source->skySlope  = limitSlope;
+
+    // save the max radius (and anything else?)
+    // source->moments->Mrf = Mrf;
+
+    return true;
+}
+
+// inline this?  macro this?
+bool psphotRadialProfileGetFlux (psVector *flux, psVector *fluxVar, pmReadout *readout, int xc, int yc, psImageMaskType maskVal) {
+
+    int Nx = readout->image->numCols;
+    int Ny = readout->image->numRows;
+
+    if (xc < 0) return false;
+    if (xc >= Nx) return false;
+    if (yc < 0) return false;
+    if (yc >= Ny) return false;
+    
+    if (readout->mask && (readout->mask->data.PS_TYPE_IMAGE_MASK_DATA[yc][xc] & maskVal)) return false;
+    psVectorAppend (flux, readout->image->data.F32[yc][xc]);
+    psVectorAppend (fluxVar, readout->variance->data.F32[yc][xc]);
+    return true;
+}
+
+// select the pixels at the given radius and extract the flux and variance into the given vectors
+// XXX should radius be 'int'?
+bool psphotRadialProfileFluxAtRadius (psVector *flux, psVector *fluxVar, pmSource *source, pmReadout *readout, float Radius, float dRadius, psImageMaskType maskVal) {
+
+    psAssert (flux, "must allocate output flux vector");
+    psAssert (fluxVar, "must allocate output fluxVar vector");
+
+    // the peak position is less accurate but less subject to extreme deviations
+    float dX = source->moments->Mx - source->peak->xf;
+    float dY = source->moments->My - source->peak->yf;
+    float dR = hypot(dX, dY);
+    float Xo = (dR < 2.0) ? source->moments->Mx : source->peak->xf;
+    float Yo = (dR < 2.0) ? source->moments->My : source->peak->yf;
+
+    for (int radius = Radius; radius < Radius + dRadius; radius ++) {
+
+	int x = 0;
+	int y = radius;
+	int d = 5 - 4*radius;
+
+	while (x <= y) {
+	    psphotRadialProfileGetFlux(flux, fluxVar, readout, (Xo + x), (Yo + y), maskVal);
+	    psphotRadialProfileGetFlux(flux, fluxVar, readout, (Xo + x), (Yo - y), maskVal);
+	    psphotRadialProfileGetFlux(flux, fluxVar, readout, (Xo + y), (Yo + x), maskVal);
+	    psphotRadialProfileGetFlux(flux, fluxVar, readout, (Xo - y), (Yo + x), maskVal);
+	    
+	    if (x > 0) {
+		psphotRadialProfileGetFlux(flux, fluxVar, readout, (Xo - x), (Yo + y), maskVal);
+		psphotRadialProfileGetFlux(flux, fluxVar, readout, (Xo - x), (Yo - y), maskVal);
+		psphotRadialProfileGetFlux(flux, fluxVar, readout, (Xo - y), (Yo - x), maskVal);
+		psphotRadialProfileGetFlux(flux, fluxVar, readout, (Xo + y), (Yo - x), maskVal);
+	    }
+	    
+	    if (d < 0) {
+		d = d + 8*x + 4;
+	    } else {
+		d = d + 8*(x-y) + 8;
+		y--;
+	    }
+	    x++;
+	}
+    }
+    return true;
+}
Index: branches/meh_branches/ppstack_test/psphot/src/psphotRadiusChecks.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotRadiusChecks.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotRadiusChecks.c	(revision 33415)
@@ -142,4 +142,64 @@
 }
 
+# define MIN_WINDOW 5.0
+# define SCALE1 5.0
+# define SCALE2 12.0
+
+// call this function whenever you (re)-define the EXT model
+// XXX this function does not shrink the window
+bool psphotSetRadiusMoments (float *fitRadius, float *windowRadius, pmReadout *readout, pmSource *source, psImageMaskType markVal) {
+
+    psAssert (source, "source not defined??");
+    psAssert (source->moments, "moments not defined??");
+
+    *fitRadius = SCALE1 * source->moments->Mrf;
+    *fitRadius = PS_MIN (PS_MAX(*fitRadius, MIN_WINDOW), EXT_FIT_MAX_RADIUS);
+
+    *windowRadius = SCALE2 * source->moments->Mrf;
+    *windowRadius = PS_MIN (PS_MAX(*windowRadius, 2.5*MIN_WINDOW), 2.5*EXT_FIT_MAX_RADIUS);
+
+    // redefine the pixels if needed
+    pmSourceRedefinePixels (source, readout, source->peak->xf, source->peak->yf, *windowRadius);
+
+    // set the mask to flag the excluded pixels
+    psImageKeepCircle (source->maskObj, source->peak->xf, source->peak->yf, *fitRadius, "OR", markVal);
+
+    return true;
+}
+# undef SCALE1
+# undef SCALE2
+# undef MIN_WINDOW
+
+# define MIN_WINDOW 5.0
+# define SCALE1 5.0
+# define PAD_WINDOW 3.0
+
+// call this function whenever you (re)-define the EXT model
+// XXX alternate function to set exactly the desired window size
+bool psphotSetRadiusMomentsExact (float *fitRadius, float *windowRadius, pmReadout *readout, pmSource *source, psImageMaskType markVal) {
+
+    psRegion newRegion;
+
+    psAssert (source, "source not defined??");
+    psAssert (source->moments, "moments not defined??");
+
+    *fitRadius = SCALE1 * source->moments->Mrf;
+    *fitRadius = PS_MIN (PS_MAX(*fitRadius, MIN_WINDOW), EXT_FIT_MAX_RADIUS);
+
+    *windowRadius = *fitRadius + PAD_WINDOW;
+
+    // check to see if new region is completely contained within old region
+    newRegion = psRegionForSquare (source->peak->xf, source->peak->yf, *windowRadius);
+    newRegion = psRegionForImage (readout->image, newRegion);
+
+    // redefine the pixels to match
+    pmSourceRedefinePixelsByRegion (source, readout, newRegion);
+
+    // set the mask to flag the excluded pixels
+    psImageKeepCircle (source->maskObj, source->peak->xf, source->peak->yf, *fitRadius, "OR", markVal);
+
+    return true;
+}
+
 // call this function whenever you (re)-define the EXT model
 bool psphotSetRadiusFootprint (float *radius, pmReadout *readout, pmSource *source, psImageMaskType markVal, float factor) {
@@ -181,4 +241,46 @@
 }
 
+// call this function whenever you (re)-define the EXT model
+bool psphotMaskFootprint (pmReadout *readout, pmSource *source, psImageMaskType markVal) {
+
+    psAssert (source, "source not defined??");
+    psAssert (source->peak, "peak not defined??");
+
+    pmPeak *peak = source->peak;
+
+    // set the radius based on the footprint:
+    if (!peak->footprint) return false;
+    pmFootprint *footprint = peak->footprint;
+    if (!footprint->spans) return false;
+    if (footprint->spans->n < 1) return false;
+
+    int Xo = source->maskObj->col0;
+    int Yo = source->maskObj->row0;
+
+    // mark all pixels 
+    for (int j = 0; j < source->maskObj->numRows; j++) {
+	for (int i = 0; i < source->maskObj->numCols; i++) {
+	    source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[j][i] |= markVal;
+	}
+    }
+
+    psImageMaskType clearVal = PS_NOT_IMAGE_MASK(markVal);
+
+    for (int j = 0; j < footprint->spans->n; j++) {
+        pmSpan *span = footprint->spans->data[j];
+
+	// mask the rows before and after each span
+	int minX = span->x0 - Xo - 2;
+	int maxX = span->x1 - Xo + 2;
+	int myY = span->y - Yo;
+
+	// unmark pixels inside the footprint
+	for (int i = minX; i <= maxX; i++) {
+	    source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[myY][i] &= clearVal;
+	}
+    }
+    return true;
+}
+
 // alternative EXT radius based on model guess (for use without footprints)
 bool psphotSetRadiusModel (pmModel *model, pmReadout *readout, pmSource *source, psImageMaskType markVal, bool deep) {
Index: branches/meh_branches/ppstack_test/psphot/src/psphotReadout.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotReadout.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotReadout.c	(revision 33415)
@@ -9,55 +9,4 @@
 }
 
-// for now, let's store the detections on the readout->analysis for each readout
-bool psphotDumpChisqs (pmConfig *config, const pmFPAview *view, const char *filerule)
-{
-    static int npass = 0;
-    char filename[64];
-
-    return true;
-
-    bool status = true;
-
-    int num = psphotFileruleCount(config, filerule);
-
-    snprintf (filename, 64, "chisq.%02d.dat", npass);
-    FILE *f = fopen (filename, "w");
-
-    // loop over the available readouts
-    for (int i = 0; i < num; i++) {
-
-        // find the currently selected readout
-        pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, i); // 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++) {
-	    pmSource *source = sources->data[i];
-	    if (!source) continue;
-
-	    pmModel *model = pmSourceGetModel (NULL, source);
-	    if (!model) continue;
-	
-	    if (source->mode & PM_SOURCE_MODE_NONLINEAR_FIT) {
-		fprintf (f, "%f %f %f %d %d %f  1 NONLINEAR\n", model->mag, model->params->data.F32[1], model->chisq, model->nDOF, model->nPix, model->chisqNorm);
-	    } else {
-		fprintf (f, "%f %f %f %d %d %f  0 LINEAR\n", model->mag, model->params->data.F32[1], model->chisq, model->nDOF, model->nPix, model->chisqNorm);
-	    }
-	}
-    }
-    fclose (f);
-    npass ++;
-
-    return true;
-}
-
 bool psphotReadout(pmConfig *config, const pmFPAview *view, const char *filerule) {
 
@@ -102,18 +51,4 @@
         return psphotReadoutCleanup (config, view, filerule);
     }
-
-# if (0)
-    // XXX test to mask outliers, not very helpful
-    // mask the high values in the image (with MARK)
-    if (!psphotMaskBackground (config, view, filerule)) {
-        return psphotReadoutCleanup (config, view, filerule);
-    }
-
-    // generate a background model (median, smoothed image)
-    if (!psphotModelBackground (config, view, filerule)) {
-        return psphotReadoutCleanup (config, view, filerule);
-    }
-# endif
-
     if (!psphotSubtractBackground (config, view, filerule)) {
         return psphotReadoutCleanup (config, view, filerule);
@@ -137,5 +72,7 @@
     }
 
-    // construct sources and measure basic stats (saved on detections->newSources)
+    // construct sources and measure moments and other basic stats (saved on detections->newSources)
+    // all sources use the auto-scaled window appropriate to a PSF, except for the saturated
+    // stars : these use a larger window (3x the basic window)
     if (!psphotSourceStats (config, view, filerule, true)) { // pass 1
         psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate sources");
@@ -153,5 +90,7 @@
 
     // mark blended peaks PS_SOURCE_BLEND (detections->newSources)
-    if (!psphotBasicDeblend (config, view, filerule)) {
+    // XXX I've deactivated this because it was preventing galaxies close to stars from being
+    // XXX fitted as an extended source.
+    if (false && !psphotBasicDeblend (config, view, filerule)) {
         psError (PSPHOT_ERR_UNKNOWN, false, "failed on deblend analysis");
         return psphotReadoutCleanup (config, view, filerule);
@@ -188,18 +127,27 @@
     // psphotLoadExtSources (config, view, filerule); // pass 1
 
-    // construct an initial model for each object, set the radius to fitRadius, set circular
-    // fit mask (detections->newSources)
-    psphotGuessModels (config, view, filerule); // pass 1
-
     // merge the newly selected sources into the existing list
     // NOTE: merge OLD and NEW
     psphotMergeSources (config, view, filerule);
 
+    // Construct an initial model for each object, set the radius to fitRadius, set circular
+    // fit mask.  NOTE: only applied to sources without guess models
+    // pass 1
+    if (!psphotGuessModels (config, view, filerule)) {
+        psLogMsg ("psphot", 3, "failure to Guess Model - pass 1");
+        return psphotReadoutCleanup (config, view, filerule);
+    }
+
     // linear PSF fit to source peaks, subtract the models from the image (in PSF mask)
     psphotFitSourcesLinear (config, view, filerule, false); // pass 1 (detections->allSources)
-    psphotDumpChisqs (config, view, filerule);
-
-    // XXX re-measure the kron mags with models subtracted
-    psphotKronMasked(config, view, filerule);
+
+    // measure the radial profiles to the sky
+    psphotRadialProfileWings (config, view, filerule);
+
+    // re-measure the kron mags with models subtracted.  this pass uses a circular window of size PSF_MOMENTS_RADIUS (same window used to measure the psf-scale moments)
+    
+    // but this is chosen above to be appropriate for the PSF objects (not galaxies)
+    // psphotKronMasked(config, view, filerule);
+    psphotKronIterate(config, view, filerule);
 
     // identify CRs and extended sources (only unmeasured sources are measured)
@@ -211,14 +159,13 @@
     // non-linear PSF and EXT fit to brighter sources
     // replace model flux, adjust mask as needed, fit, subtract the models (full stamp)
+    // XXX: can leave faulted job in done queue
     psphotBlendFit (config, view, filerule); // pass 1 (detections->allSources)
-    psphotDumpChisqs (config, view, filerule);
 
     // replace all sources
-    psphotReplaceAllSources (config, view, filerule); // pass 1 (detections->allSources)
+    psphotReplaceAllSources (config, view, filerule, false); // pass 1 (detections->allSources)
 
     // linear fit to include all sources (subtract again)
     // NOTE : apply to ALL sources (extended + psf)
     psphotFitSourcesLinear (config, view, filerule, true); // pass 2 (detections->allSources)
-    psphotDumpChisqs (config, view, filerule);
 
     // if we only do one pass, skip to extended source analysis
@@ -240,5 +187,5 @@
 	psphotSubNoise (config, view, filerule); // pass 1 (detections->allSources)
 
-	// define new sources based on only the new peaks
+	// define new sources based on only the new peaks & measure moments
 	// NOTE: new sources are saved on detections->newSources
 	psphotSourceStats (config, view, filerule, false); // pass 2 (detections->newSources)
@@ -251,11 +198,7 @@
 	}
 
-	// create full input models, set the radius to fitRadius, set circular fit mask
-	// NOTE: apply only to detections->newSources
-	psphotGuessModels (config, view, filerule); // pass 2 (detections->newSources)
-
 	// replace all sources so fit below applies to all at once
 	// NOTE: apply only to OLD sources (which have been subtracted)
-	psphotReplaceAllSources (config, view, filerule); // pass 2
+	psphotReplaceAllSources (config, view, filerule, false); // pass 2
 
 	// merge the newly selected sources into the existing list
@@ -264,7 +207,10 @@
 	psphotMergeSources (config, view, filerule); // (detections->newSources + detections->allSources -> detections->allSources)
 
+	// Construct an initial model for each object, set the radius to fitRadius, set circular
+	// fit mask.  NOTE: only applied to sources without guess models
+	psphotGuessModels (config, view, filerule); // pass 1
+
 	// NOTE: apply to ALL sources
 	psphotFitSourcesLinear (config, view, filerule, true); // pass 3 (detections->allSources)
-	psphotDumpChisqs (config, view, filerule);
     }
 
@@ -293,11 +239,7 @@
 	}
 
-	// create full input models, set the radius to fitRadius, set circular fit mask
-	// NOTE: apply only to detections->newSources
-	psphotGuessModels (config, view, filerule); // pass 2 (detections->newSources)
-
 	// replace all sources so fit below applies to all at once
 	// NOTE: apply only to OLD sources (which have been subtracted)
-	psphotReplaceAllSources (config, view, filerule); // pass 2
+	psphotReplaceAllSources (config, view, filerule, false); // pass 2
 
 	// merge the newly selected sources into the existing list
@@ -306,4 +248,8 @@
 	psphotMergeSources (config, view, filerule); // (detections->newSources + detections->allSources -> detections->allSources)
 
+	// Construct an initial model for each object, set the radius to fitRadius, set circular
+	// fit mask.  NOTE: only applied to sources without guess models
+	psphotGuessModels (config, view, filerule); // pass 1
+
 	// NOTE: apply to ALL sources
 	psphotFitSourcesLinear (config, view, filerule, true); // pass 3 (detections->allSources)
@@ -312,6 +258,10 @@
 pass1finish:
 
-    // XXX re-measure the kron mags with models subtracted
-    psphotKronMasked(config, view, filerule);
+    // measure the radial profiles to the sky (only measures new objects)
+    psphotRadialProfileWings (config, view, filerule);
+
+    // re-measure the kron mags with models subtracted
+    // psphotKronMasked(config, view, filerule);
+    psphotKronIterate(config, view, filerule);
 
     // measure source size for the remaining sources
@@ -321,5 +271,5 @@
     psphotExtendedSourceAnalysis (config, view, filerule); // pass 1 (detections->allSources)
     psphotExtendedSourceFits (config, view, filerule); // pass 1 (detections->allSources)
-    psphotRadialApertures(config, view, filerule);
+    psphotRadialApertures(config, view, filerule, 0);
 
 finish:
@@ -359,4 +309,6 @@
     }
 
+    psLogMsg ("psphot.readout", PS_LOG_WARN, "complete psphot readout : %f sec\n", psTimerMark ("psphotReadout"));
+
     // create the exported-metadata and free local data
     return psphotReadoutCleanup(config, view, filerule);
Index: branches/meh_branches/ppstack_test/psphot/src/psphotReadoutFindPSF.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotReadoutFindPSF.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotReadoutFindPSF.c	(revision 33415)
@@ -54,4 +54,8 @@
     }
 
+    // merge the newly selected sources into the existing list
+    // NOTE: merge OLD and NEW
+    psphotMergeSources (config, view, filerule); 
+
 # if 0
     // XXX if we want to determine the aperture residual correction here, we either
@@ -61,8 +65,4 @@
     psphotGuessModels (config, view, filerule);
 # endif
-
-    // merge the newly selected sources into the existing list
-    // NOTE: merge OLD and NEW
-    psphotMergeSources (config, view, filerule); 
 
 # if 0
Index: branches/meh_branches/ppstack_test/psphot/src/psphotReadoutForcedKnownSources.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotReadoutForcedKnownSources.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotReadoutForcedKnownSources.c	(revision 33415)
@@ -36,10 +36,11 @@
     }
 
-    // construct an initial model for each object
-    psphotGuessModels (config, view, filerule);
-
     // merge the newly selected sources into the existing list
     // NOTE: merge OLD and NEW
     psphotMergeSources (config, view, filerule); 
+
+    // Construct an initial model for each object, set the radius to fitRadius, set circular
+    // fit mask.  NOTE: only applied to sources without guess models
+    psphotGuessModels (config, view, filerule);
 
     // linear PSF fit to source peaks
Index: branches/meh_branches/ppstack_test/psphot/src/psphotReadoutKnownSources.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotReadoutKnownSources.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotReadoutKnownSources.c	(revision 33415)
@@ -48,10 +48,11 @@
     }
 
-    // construct an initial model for each object
-    psphotGuessModels (config, view, filerule);
-
     // merge the newly selected sources into the existing list
     // NOTE: merge OLD and NEW
     psphotMergeSources (config, view, filerule); 
+
+    // Construct an initial model for each object, set the radius to fitRadius, set circular
+    // fit mask.  NOTE: only applied to sources without guess models
+    psphotGuessModels (config, view, filerule);
 
     // linear PSF fit to source peaks
Index: branches/meh_branches/ppstack_test/psphot/src/psphotReadoutMinimal.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotReadoutMinimal.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotReadoutMinimal.c	(revision 33415)
@@ -59,9 +59,10 @@
     }
 
-    // construct an initial model for each object
-    psphotGuessModels (config, view, filerule);
-
     // merge the newly selected sources into the existing list
     psphotMergeSources (config, view, filerule);
+
+    // Construct an initial model for each object, set the radius to fitRadius, set circular
+    // fit mask.  NOTE: only applied to sources without guess models
+    psphotGuessModels (config, view, filerule);
 
     // linear PSF fit to source peaks
Index: branches/meh_branches/ppstack_test/psphot/src/psphotReplaceUnfit.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotReplaceUnfit.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotReplaceUnfit.c	(revision 33415)
@@ -9,9 +9,9 @@
 
     for (int i = 0; i < sources->n; i++) {
-      source = sources->data[i];
-
-      // replace other sources?
-      if (source->mode & PM_SOURCE_MODE_FAIL) goto replace;
-      continue;
+	source = sources->data[i];
+
+	// replace other sources?
+	if (source->mode & PM_SOURCE_MODE_FAIL) goto replace;
+	continue;
 
     replace:
@@ -23,5 +23,5 @@
 
 // for now, let's store the detections on the readout->analysis for each readout
-bool psphotReplaceAllSources (pmConfig *config, const pmFPAview *view, const char *filerule)
+bool psphotReplaceAllSources (pmConfig *config, const pmFPAview *view, const char *filerule, bool ignoreState)
 {
     bool status = true;
@@ -35,5 +35,5 @@
     // loop over the available readouts
     for (int i = 0; i < num; i++) {
-	if (!psphotReplaceAllSourcesReadout (config, view, filerule, i, recipe)) {
+	if (!psphotReplaceAllSourcesReadout (config, view, filerule, i, recipe, ignoreState)) {
 	    psError (PSPHOT_ERR_CONFIG, false, "failed to replace all sources for %s entry %d", filerule, i);
 	    return false;
@@ -43,8 +43,7 @@
 }
 
-bool psphotReplaceAllSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
-
-    bool status;
-    pmSource *source;
+bool psphotReplaceAllSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool ignoreState) {
+
+    bool status;
 
     psTimerStart ("psphot.replace");
@@ -75,10 +74,24 @@
 
     for (int i = 0; i < sources->n; i++) {
-      source = sources->data[i];
-
-      // replace other sources?
-      if (!(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED)) continue;
-
-      pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+	pmSource *source = sources->data[i];
+
+	if (ignoreState) {
+	    // rely on the type of source to decide if we subtract it or not
+
+	    // skip non-astronomical objects (very likely defects)
+	    if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+	    if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+      
+	    // do not include CRs in the full ensemble fit
+	    if (source->mode & PM_SOURCE_MODE_CR_LIMIT) continue;
+	
+	    // do not include MOMENTS_FAILURES in the fit
+	    if (source->mode & PM_SOURCE_MODE_MOMENTS_FAILURE) continue;
+	} else {
+	    // if we respect the state, do not replace unsubtracted sources
+	    if (!(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED)) continue;
+	}
+
+	pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
     }
 
@@ -88,5 +101,84 @@
 }
 
-bool psphotRemoveAllSources (const psArray *sources, const psMetadata *recipe) {
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotRemoveAllSources (pmConfig *config, const pmFPAview *view, const char *filerule, bool ignoreState)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psphotFileruleCount(config, filerule);
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotRemoveAllSourcesReadout (config, view, filerule, i, recipe, ignoreState)) {
+	    psError (PSPHOT_ERR_CONFIG, false, "failed to replace all sources for %s entry %d", filerule, i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotRemoveAllSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool ignoreState) {
+
+    bool status;
+
+    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
+    psAssert (maskVal, "missing mask value?");
+
+    // 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;
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+
+	if (ignoreState) {
+	    // rely on the type of source to decide if we subtract it or not
+
+	    // skip non-astronomical objects (very likely defects)
+	    if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+	    if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+      
+	    // do not include CRs in the full ensemble fit
+	    if (source->mode & PM_SOURCE_MODE_CR_LIMIT) continue;
+	
+	    // do not include MOMENTS_FAILURES in the fit
+	    if (source->mode & PM_SOURCE_MODE_MOMENTS_FAILURE) continue;
+	} else {
+	    // if we respect the state, only remove unsubtracted sources
+	    if ((source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED)) continue;
+	}
+
+	pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+
+    psphotVisualShowImage(readout);
+    psLogMsg ("psphot.replace", PS_LOG_INFO, "replaced models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.replace"));
+    return true;
+}
+
+bool psphotRemoveAllSourcesByArray (const psArray *sources, const psMetadata *recipe) {
 
     bool status;
@@ -100,10 +192,10 @@
 
     for (int i = 0; i < sources->n; i++) {
-      source = sources->data[i];
-
-      // replace other sources?
-      if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) continue;
-
-      pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+	source = sources->data[i];
+
+	// replace other sources?
+	if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) continue;
+
+	pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
     }
     psLogMsg ("psphot.replace", PS_LOG_INFO, "replaced models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.replace"));
@@ -158,17 +250,17 @@
 
     for (int i = 0; i < sources->n; i++) {
-      source = sources->data[i];
-
-      // sources have not yet been subtracted in this image (but this flag may be raised)
-      source->tmpFlags &= ~PM_SOURCE_TMPF_SUBTRACTED;
-      if (!source->modelPSF) continue;
-
-      float Xo = source->modelPSF->params->data.F32[PM_PAR_XPOS];
-      float Yo = source->modelPSF->params->data.F32[PM_PAR_YPOS];
-      float radius = source->modelPSF->fitRadius;
-
-      // force a redefine to this image
-      pmSourceFreePixels(source);
-      pmSourceRedefinePixels (source, readout, Xo, Yo, radius);
+	source = sources->data[i];
+
+	// sources have not yet been subtracted in this image (but this flag may be raised)
+	source->tmpFlags &= ~PM_SOURCE_TMPF_SUBTRACTED;
+	if (!source->modelPSF) continue;
+
+	float Xo = source->modelPSF->params->data.F32[PM_PAR_XPOS];
+	float Yo = source->modelPSF->params->data.F32[PM_PAR_YPOS];
+	float radius = source->modelPSF->fitRadius;
+
+	// force a redefine to this image
+	pmSourceFreePixels(source);
+	pmSourceRedefinePixels (source, readout, Xo, Yo, radius);
     }
     return true;
@@ -229,62 +321,63 @@
 
     for (int i = 0; i < sources->n; i++) {
-      source = sources->data[i];
-
-      // *** we need to cache the 'best' model, and we have 3 cases:
-      // 1) model is the psf model --> generate from the new psf
-      // 2) model is an unconvolved extended model --> just cache the copy (not perfect)
-      // 3) model is a convolved extended model --> re-generate
-
-      // use the 'best' model to cache the model (PSF or EXT : EXT may point at one of modelFits
-      bool isPSF = false;
-      pmModel *model = pmSourceGetModel(&isPSF, source);
-      if (!model) continue;
-
-      float radius = model->fitRadius; // save for future use below
-
-      // regenerate the PSF if the model is a PSF, or if we need the PSF for a PCM
-      if (isPSF || model->isPCM) {
-	  // the guess central intensity comes from the peak:
-	  float Io = source->peak->rawFlux;
-	  float Xo = source->modelPSF->params->data.F32[PM_PAR_XPOS];
-	  float Yo = source->modelPSF->params->data.F32[PM_PAR_YPOS];
-
-	  // generate a model for this object with Io = 1.0
-	  pmModel *modelPSF = pmModelFromPSFforXY(psf, Xo, Yo, Io);
-	  if (modelPSF == NULL) {
-	      psWarning ("Failed to determine PSF model for source at (%f,%f), skipping", Xo, Yo);
-	      continue;
-	  }
-
-	  // set the source PSF model
-	  psFree (source->modelPSF);
-	  source->modelPSF = modelPSF;
-	  source->modelPSF->fitRadius = radius;
-      }
-
-      if (model->isPCM) {
-	  psAssert(false, "this section is not complete");
-
-	  pmSourceCachePSF (source, maskVal);
-
-	  psKernel *psfKernel = pmPCMkernelFromPSF(source, psfSize);
-	  if (!psfKernel) { 
-	      psWarning ("no psf kernel");
-	  }
-
-	  // generate an image of the right size
-	  psImage *rawModelFlux = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
-	  psImageInit (rawModelFlux, 0.0);
+	source = sources->data[i];
+
+	// *** we need to cache the 'best' model, and we have 3 cases:
+	// 1) model is the psf model --> generate from the new psf
+	// 2) model is an unconvolved extended model --> just cache the copy (not perfect)
+	// 3) model is a convolved extended model --> re-generate
+
+	// use the 'best' model to cache the model (PSF or EXT : EXT may point at one of modelFits
+	bool isPSF = false;
+	pmModel *model = pmSourceGetModel(&isPSF, source);
+	if (!model) continue;
+
+	float radius = model->fitRadius; // save for future use below
+
+	// regenerate the PSF if the model is a PSF, or if we need the PSF for a PCM
+	if (isPSF || model->isPCM) {
+	    // the guess central intensity comes from the peak:
+	    float Io = source->peak->rawFlux;
+	    float Xo = source->modelPSF->params->data.F32[PM_PAR_XPOS];
+	    float Yo = source->modelPSF->params->data.F32[PM_PAR_YPOS];
+
+	    // generate a model for this object with Io = 1.0
+	    pmModel *modelPSF = pmModelFromPSFforXY(psf, Xo, Yo, Io);
+	    if (modelPSF == NULL) {
+		psWarning ("Failed to determine PSF model for source at (%f,%f), skipping", Xo, Yo);
+		continue;
+	    }
+
+	    // set the source PSF model
+	    psFree (source->modelPSF);
+	    source->modelPSF = modelPSF;
+	    source->modelPSF->fitRadius = radius;
+	    model = source->modelPSF;
+	}
+
+	if (model->isPCM) {
+	    psAssert(false, "this section is not complete");
+
+	    pmSourceCachePSF (source, maskVal);
+
+	    psKernel *psfKernel = pmPCMkernelFromPSF(source, psfSize);
+	    if (!psfKernel) { 
+		psWarning ("no psf kernel");
+	    }
+
+	    // generate an image of the right size
+	    psImage *rawModelFlux = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+	    psImageInit (rawModelFlux, 0.0);
 	  
-	  // insert the model image normalized to 1.0
-	  pmModelAdd (rawModelFlux, NULL, model, PM_MODEL_OP_FULL | PM_MODEL_OP_NORM, maskVal);
-
-	  psImageConvolveFFT (source->modelFlux, rawModelFlux, NULL, 0, psfKernel);
+	    // insert the model image normalized to 1.0
+	    pmModelAdd (rawModelFlux, NULL, model, PM_MODEL_OP_FULL | PM_MODEL_OP_NORM, maskVal);
+
+	    psImageConvolveFFT (source->modelFlux, rawModelFlux, NULL, 0, psfKernel);
 	  
-	  psFree (psfKernel);
-	  psFree (rawModelFlux);
-      }
-
-      pmSourceCacheModel (source, maskVal);  // ALLOC x14 (!)
+	    psFree (psfKernel);
+	    psFree (rawModelFlux);
+	}
+
+	pmSourceCacheModel (source, maskVal);  // ALLOC x14 (!)
     }
 
Index: branches/meh_branches/ppstack_test/psphot/src/psphotRoughClass.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotRoughClass.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotRoughClass.c	(revision 33415)
@@ -11,4 +11,7 @@
 {
     bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Rough Class ---");
 
     // select the appropriate recipe information
@@ -128,5 +131,5 @@
     psphotDumpMoments (recipe, sources);
 
-    psLogMsg ("psphot.roughclass", PS_LOG_INFO, "rough classification: %f sec\n", psTimerMark ("psphot.rough"));
+    psLogMsg ("psphot.roughclass", PS_LOG_WARN, "rough classification: %f sec\n", psTimerMark ("psphot.rough"));
 
     psphotVisualPlotMoments (recipe, readout->analysis, sources);
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.00.00.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.00.00.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.00.00.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: -0.445996271922, C1: 0.00257774203952
+ class->IndexToMajor->coeff[0] = -0.445996271922;
+ class->IndexToMajor->coeff[1] = 0.00257774203952;
+
+ // minor: C0: -0.442221950965, C1: 0.00310744939505
+ class->IndexToMinor->coeff[0] = -0.442221950965;
+ class->IndexToMinor->coeff[1] = 0.00310744939505;
+
+ // MagOffset: C0: -0.0096577152343, C1: -0.04709419632
+ class->IndexToTotalMag->coeff[0] = -0.0096577152343;
+ class->IndexToTotalMag->coeff[1] = -0.04709419632;
+
+ // KronMag: C0: -0.396760779784, C1: -0.0144654449553, C2: 0.00688206755937
+ class->KronToIndex->coeff[0] = -0.396760779784;
+ class->KronToIndex->coeff[1] = -0.0144654449553;
+ class->KronToIndex->coeff[2] = 0.00688206755937;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.00.01.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.00.01.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.00.01.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: -0.150653602233, C1: 0.0583316775309
+ class->IndexToMajor->coeff[0] = -0.150653602233;
+ class->IndexToMajor->coeff[1] = 0.0583316775309;
+
+ // minor: C0: -0.146015984633, C1: 0.0584414894493
+ class->IndexToMinor->coeff[0] = -0.146015984633;
+ class->IndexToMinor->coeff[1] = 0.0584414894493;
+
+ // MagOffset: C0: -0.0516576246324, C1: -0.06308349488
+ class->IndexToTotalMag->coeff[0] = -0.0516576246324;
+ class->IndexToTotalMag->coeff[1] = -0.06308349488;
+
+ // KronMag: C0: -1.28498429043, C1: 0.255293518143, C2: -0.0209474725898
+ class->KronToIndex->coeff[0] = -1.28498429043;
+ class->KronToIndex->coeff[1] = 0.255293518143;
+ class->KronToIndex->coeff[2] = -0.0209474725898;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.00.02.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.00.02.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.00.02.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: 0.0700235952113, C1: 0.0832659513906
+ class->IndexToMajor->coeff[0] = 0.0700235952113;
+ class->IndexToMajor->coeff[1] = 0.0832659513906;
+
+ // minor: C0: 0.0778318261346, C1: 0.082428734924
+ class->IndexToMinor->coeff[0] = 0.0778318261346;
+ class->IndexToMinor->coeff[1] = 0.082428734924;
+
+ // MagOffset: C0: -0.392754912322, C1: -0.0271438531326
+ class->IndexToTotalMag->coeff[0] = -0.392754912322;
+ class->IndexToTotalMag->coeff[1] = -0.0271438531326;
+
+ // KronMag: C0: -2.24835974564, C1: 0.543586359075, C2: -0.0521214274524
+ class->KronToIndex->coeff[0] = -2.24835974564;
+ class->KronToIndex->coeff[1] = 0.543586359075;
+ class->KronToIndex->coeff[2] = -0.0521214274524;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.00.03.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.00.03.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.00.03.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: 0.302234153612, C1: 0.0812072518454
+ class->IndexToMajor->coeff[0] = 0.302234153612;
+ class->IndexToMajor->coeff[1] = 0.0812072518454;
+
+ // minor: C0: 0.318735595658, C1: 0.0782450906783
+ class->IndexToMinor->coeff[0] = 0.318735595658;
+ class->IndexToMinor->coeff[1] = 0.0782450906783;
+
+ // MagOffset: C0: -0.442722048421, C1: -0.0779760825812
+ class->IndexToTotalMag->coeff[0] = -0.442722048421;
+ class->IndexToTotalMag->coeff[1] = -0.0779760825812;
+
+ // KronMag: C0: -4.13616754915, C1: 1.31952307132, C2: -0.135246127816
+ class->KronToIndex->coeff[0] = -4.13616754915;
+ class->KronToIndex->coeff[1] = 1.31952307132;
+ class->KronToIndex->coeff[2] = -0.135246127816;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.01.00.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.01.00.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.01.00.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: -0.206834286782, C1: -0.00161061048758
+ class->IndexToMajor->coeff[0] = -0.206834286782;
+ class->IndexToMajor->coeff[1] = -0.00161061048758;
+
+ // minor: C0: -0.206834286782, C1: -0.00161061048758
+ class->IndexToMinor->coeff[0] = -0.206834286782;
+ class->IndexToMinor->coeff[1] = -0.00161061048758;
+
+ // MagOffset: C0: -0.019943291705, C1: -0.032180165617
+ class->IndexToTotalMag->coeff[0] = -0.019943291705;
+ class->IndexToTotalMag->coeff[1] = -0.032180165617;
+
+ // KronMag: C0: -0.184713930007, C1: -0.0151666534408, C2: 0.00506733444285
+ class->KronToIndex->coeff[0] = -0.184713930007;
+ class->KronToIndex->coeff[1] = -0.0151666534408;
+ class->KronToIndex->coeff[2] = 0.00506733444285;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.01.01.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.01.01.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.01.01.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: 0.10199171121, C1: 0.0370901734428
+ class->IndexToMajor->coeff[0] = 0.10199171121;
+ class->IndexToMajor->coeff[1] = 0.0370901734428;
+
+ // minor: C0: -0.64374610969, C1: 0.0615101610281
+ class->IndexToMinor->coeff[0] = -0.64374610969;
+ class->IndexToMinor->coeff[1] = 0.0615101610281;
+
+ // MagOffset: C0: -0.0424533119058, C1: -0.051484524864
+ class->IndexToTotalMag->coeff[0] = -0.0424533119058;
+ class->IndexToTotalMag->coeff[1] = -0.051484524864;
+
+ // KronMag: C0: -0.79489754688, C1: 0.140962833359, C2: -0.0081614929638
+ class->KronToIndex->coeff[0] = -0.79489754688;
+ class->KronToIndex->coeff[1] = 0.140962833359;
+ class->KronToIndex->coeff[2] = -0.0081614929638;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.01.02.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.01.02.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.01.02.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: 0.253878808908, C1: 0.063184490483
+ class->IndexToMajor->coeff[0] = 0.253878808908;
+ class->IndexToMajor->coeff[1] = 0.063184490483;
+
+ // minor: C0: -0.386724307864, C1: 0.116445334002
+ class->IndexToMinor->coeff[0] = -0.386724307864;
+ class->IndexToMinor->coeff[1] = 0.116445334002;
+
+ // MagOffset: C0: -0.0872953973638, C1: -0.0728614138338
+ class->IndexToTotalMag->coeff[0] = -0.0872953973638;
+ class->IndexToTotalMag->coeff[1] = -0.0728614138338;
+
+ // KronMag: C0: -1.85587783211, C1: 0.447839373687, C2: -0.0374898116232
+ class->KronToIndex->coeff[0] = -1.85587783211;
+ class->KronToIndex->coeff[1] = 0.447839373687;
+ class->KronToIndex->coeff[2] = -0.0374898116232;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.01.03.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.01.03.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.01.03.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: 0.440682409806, C1: 0.0655243267517
+ class->IndexToMajor->coeff[0] = 0.440682409806;
+ class->IndexToMajor->coeff[1] = 0.0655243267517;
+
+ // minor: C0: -0.0588380249835, C1: 0.125842487938
+ class->IndexToMinor->coeff[0] = -0.0588380249835;
+ class->IndexToMinor->coeff[1] = 0.125842487938;
+
+ // MagOffset: C0: -0.559338014369, C1: -0.0595570268259
+ class->IndexToTotalMag->coeff[0] = -0.559338014369;
+ class->IndexToTotalMag->coeff[1] = -0.0595570268259;
+
+ // KronMag: C0: -3.19558263789, C1: 1.10848141303, C2: -0.127301553664
+ class->KronToIndex->coeff[0] = -3.19558263789;
+ class->KronToIndex->coeff[1] = 1.10848141303;
+ class->KronToIndex->coeff[2] = -0.127301553664;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.02.00.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.02.00.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.02.00.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: -0.0912134941416, C1: -0.000669340733451
+ class->IndexToMajor->coeff[0] = -0.0912134941416;
+ class->IndexToMajor->coeff[1] = -0.000669340733451;
+
+ // minor: C0: -0.0912134941416, C1: -0.000669340733451
+ class->IndexToMinor->coeff[0] = -0.0912134941416;
+ class->IndexToMinor->coeff[1] = -0.000669340733451;
+
+ // MagOffset: C0: -0.0221727399583, C1: -0.0223898759145
+ class->IndexToTotalMag->coeff[0] = -0.0221727399583;
+ class->IndexToTotalMag->coeff[1] = -0.0223898759145;
+
+ // KronMag: C0: -0.104030916493, C1: 0.000705683288561, C2: 0.00180404239128
+ class->KronToIndex->coeff[0] = -0.104030916493;
+ class->KronToIndex->coeff[1] = 0.000705683288561;
+ class->KronToIndex->coeff[2] = 0.00180404239128;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.02.01.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.02.01.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.02.01.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: 0.231254014758, C1: 0.0296484411276
+ class->IndexToMajor->coeff[0] = 0.231254014758;
+ class->IndexToMajor->coeff[1] = 0.0296484411276;
+
+ // minor: C0: -1.02822862517, C1: 0.0541915833825
+ class->IndexToMinor->coeff[0] = -1.02822862517;
+ class->IndexToMinor->coeff[1] = 0.0541915833825;
+
+ // MagOffset: C0: -0.038775941514, C1: -0.0429605069959
+ class->IndexToTotalMag->coeff[0] = -0.038775941514;
+ class->IndexToTotalMag->coeff[1] = -0.0429605069959;
+
+ // KronMag: C0: -0.598266540255, C1: 0.144570984447, C2: -0.0107033922884
+ class->KronToIndex->coeff[0] = -0.598266540255;
+ class->KronToIndex->coeff[1] = 0.144570984447;
+ class->KronToIndex->coeff[2] = -0.0107033922884;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.02.02.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.02.02.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.02.02.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: 0.390176771343, C1: 0.047444773551
+ class->IndexToMajor->coeff[0] = 0.390176771343;
+ class->IndexToMajor->coeff[1] = 0.047444773551;
+
+ // minor: C0: -0.65406916551, C1: 0.124100778941
+ class->IndexToMinor->coeff[0] = -0.65406916551;
+ class->IndexToMinor->coeff[1] = 0.124100778941;
+
+ // MagOffset: C0: -0.0701907061504, C1: -0.0669032246792
+ class->IndexToTotalMag->coeff[0] = -0.0701907061504;
+ class->IndexToTotalMag->coeff[1] = -0.0669032246792;
+
+ // KronMag: C0: -1.41116916222, C1: 0.339914508589, C2: -0.0257471931693
+ class->KronToIndex->coeff[0] = -1.41116916222;
+ class->KronToIndex->coeff[1] = 0.339914508589;
+ class->KronToIndex->coeff[2] = -0.0257471931693;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.02.03.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.02.03.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.02.03.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: 0.56620879654, C1: 0.0423613195169
+ class->IndexToMajor->coeff[0] = 0.56620879654;
+ class->IndexToMajor->coeff[1] = 0.0423613195169;
+
+ // minor: C0: -0.206040045432, C1: 0.120238710256
+ class->IndexToMinor->coeff[0] = -0.206040045432;
+ class->IndexToMinor->coeff[1] = 0.120238710256;
+
+ // MagOffset: C0: -0.620063142691, C1: 0.00335965878994
+ class->IndexToTotalMag->coeff[0] = -0.620063142691;
+ class->IndexToTotalMag->coeff[1] = 0.00335965878994;
+
+ // KronMag: C0: -2.34864255166, C1: 0.766720904286, C2: -0.0877245543247
+ class->KronToIndex->coeff[0] = -2.34864255166;
+ class->KronToIndex->coeff[1] = 0.766720904286;
+ class->KronToIndex->coeff[2] = -0.0877245543247;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.03.00.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.03.00.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.03.00.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: 0.00265274254899, C1: -0.00199264363239
+ class->IndexToMajor->coeff[0] = 0.00265274254899;
+ class->IndexToMajor->coeff[1] = -0.00199264363239;
+
+ // minor: C0: 0.00265274254899, C1: -0.00199264363239
+ class->IndexToMinor->coeff[0] = 0.00265274254899;
+ class->IndexToMinor->coeff[1] = -0.00199264363239;
+
+ // MagOffset: C0: -0.0238043163179, C1: -0.00786669933119
+ class->IndexToTotalMag->coeff[0] = -0.0238043163179;
+ class->IndexToTotalMag->coeff[1] = -0.00786669933119;
+
+ // KronMag: C0: -0.0238043163179, C1: -0.00786669933119, C2: -0.0877245543247
+ class->KronToIndex->coeff[0] = -0.0238043163179;
+ class->KronToIndex->coeff[1] = -0.00786669933119;
+ class->KronToIndex->coeff[2] = -0.0877245543247;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.03.01.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.03.01.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.03.01.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: 0.391301293845, C1: 0.0144842856382
+ class->IndexToMajor->coeff[0] = 0.391301293845;
+ class->IndexToMajor->coeff[1] = 0.0144842856382;
+
+ // minor: C0: 0.391301293845, C1: 0.0144842856382
+ class->IndexToMinor->coeff[0] = 0.391301293845;
+ class->IndexToMinor->coeff[1] = 0.0144842856382;
+
+ // MagOffset: C0: -0.0471179132852, C1: -0.0233493549339
+ class->IndexToTotalMag->coeff[0] = -0.0471179132852;
+ class->IndexToTotalMag->coeff[1] = -0.0233493549339;
+
+ // KronMag: C0: -0.264947544849, C1: 0.0691808996903, C2: -0.00434994287966
+ class->KronToIndex->coeff[0] = -0.264947544849;
+ class->KronToIndex->coeff[1] = 0.0691808996903;
+ class->KronToIndex->coeff[2] = -0.00434994287966;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.03.02.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.03.02.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.03.02.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: 0.526962575335, C1: 0.0325858343301
+ class->IndexToMajor->coeff[0] = 0.526962575335;
+ class->IndexToMajor->coeff[1] = 0.0325858343301;
+
+ // minor: C0: -1.02251162695, C1: 0.118560104626
+ class->IndexToMinor->coeff[0] = -1.02251162695;
+ class->IndexToMinor->coeff[1] = 0.118560104626;
+
+ // MagOffset: C0: -0.0716688127431, C1: -0.0502414483962
+ class->IndexToTotalMag->coeff[0] = -0.0716688127431;
+ class->IndexToTotalMag->coeff[1] = -0.0502414483962;
+
+ // KronMag: C0: -0.985092751487, C1: 0.272660295656, C2: -0.0202614753082
+ class->KronToIndex->coeff[0] = -0.985092751487;
+ class->KronToIndex->coeff[1] = 0.272660295656;
+ class->KronToIndex->coeff[2] = -0.0202614753082;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.03.03.h
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.03.03.h	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicGuess.03.03.h	(revision 33415)
@@ -0,0 +1,17 @@
+ // major: C0: 0.694732619604, C1: 0.0222519100811
+ class->IndexToMajor->coeff[0] = 0.694732619604;
+ class->IndexToMajor->coeff[1] = 0.0222519100811;
+
+ // minor: C0: -0.346745057966, C1: 0.0949650739266
+ class->IndexToMinor->coeff[0] = -0.346745057966;
+ class->IndexToMinor->coeff[1] = 0.0949650739266;
+
+ // MagOffset: C0: -0.754034109564, C1: 0.071069480015
+ class->IndexToTotalMag->coeff[0] = -0.754034109564;
+ class->IndexToTotalMag->coeff[1] = 0.071069480015;
+
+ // KronMag: C0: -1.16844402687, C1: 0.174859870338, C2: -0.00252564582698
+ class->KronToIndex->coeff[0] = -1.16844402687;
+ class->KronToIndex->coeff[1] = 0.174859870338;
+ class->KronToIndex->coeff[2] = -0.00252564582698;
+
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSersicModelClass.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSersicModelClass.c	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSersicModelClass.c	(revision 33415)
@@ -0,0 +1,658 @@
+# include "psphotInternal.h"
+
+psF64 psPolynomial1DSolve(const psPolynomial1D* poly, psF64 y, bool upper);
+void psphotSersicModelNorm (pmPCMdata *pcm, const pmSource *source);
+
+static psArray *classArray = NULL;
+static float PSFratioMin =  1.0;
+static float PSFratioMax =  4.0;
+static float ASPratioMin =  0.2;
+static float ASPratioMax =  1.0;
+
+// structure to define the sersic model guess information
+typedef struct {
+    float PSFratioMin;
+    float PSFratioMax;
+    float ASPratioMin;
+    float ASPratioMax;
+
+    float indexMin;
+    float indexMax;
+    float kronMin;
+    float kronMax;
+
+    bool KronUpper;
+    psPolynomial1D *KronToIndex;
+    psPolynomial1D *IndexToTotalMag;
+    psPolynomial1D *IndexToMajor;
+    psPolynomial1D *IndexToMinor;
+} psphotSersicModelClass;
+
+static void psphotSersicModelClassFree (psphotSersicModelClass *class) {
+    psFree(class->KronToIndex);
+    psFree(class->IndexToTotalMag);
+    psFree(class->IndexToMajor);
+    psFree(class->IndexToMinor);
+    return;
+}
+
+psphotSersicModelClass *psphotSersicModelClassAlloc() {
+
+    psphotSersicModelClass *class = (psphotSersicModelClass *) psAlloc(sizeof(psphotSersicModelClass));
+    psMemSetDeallocator(class, (psFreeFunc) psphotSersicModelClassFree);
+    
+    class->PSFratioMin = NAN;
+    class->PSFratioMax = NAN;
+    class->ASPratioMin = NAN;
+    class->ASPratioMax = NAN;
+    
+    class->KronUpper = false;
+    class->KronToIndex = NULL;
+    class->IndexToTotalMag = NULL;
+    class->IndexToMajor = NULL;
+    class->IndexToMinor = NULL;
+
+    return class;
+}
+
+void psphotSersicModelClassSetIndexRange(psphotSersicModelClass *class, float indexMin, float indexMax) {
+
+    class->indexMin = indexMin;
+    class->indexMax = indexMax;
+    float modelMinX = -0.5 * class->KronToIndex->coeff[1] / class->KronToIndex->coeff[2];
+    float modelMinY = psPolynomial1DEval (class->KronToIndex, modelMinX);
+    float kronIndexMin = psPolynomial1DEval (class->KronToIndex, class->indexMin);
+    float kronIndexMax = psPolynomial1DEval (class->KronToIndex, class->indexMax);
+
+    // if modelMinX is between indexMin and indexMax, use the modelMinY as the min/max kron value
+    // else, use the kron values at the min and max positions
+
+    if ((modelMinX > class->indexMin) && (modelMinX < class->indexMax)) {
+	if (class->KronToIndex->coeff[2] < 0.0) {
+	    class->kronMax = modelMinY - 0.001; // pad slightly to avoid falling off the curve
+	    class->kronMin = PS_MIN(kronIndexMin, kronIndexMax);
+	} else {
+	    class->kronMin = modelMinY + 0.001; // pad slightly to avoid falling off the curve
+	    class->kronMax = PS_MAX(kronIndexMin, kronIndexMax);
+	}
+    } else {
+	class->kronMin = PS_MIN(kronIndexMin, kronIndexMax);
+	class->kronMax = PS_MAX(kronIndexMin, kronIndexMax);
+    }
+
+    // KronUpper specifies which of the 2 quadratic solutions to accept:
+    if (modelMinX < class->indexMin) {
+	if (class->KronToIndex->coeff[2] < 0.0)	{
+	    class->KronUpper = false;
+	} else {
+	    class->KronUpper = true;
+	}	
+	return;
+    }
+    if (modelMinX > class->indexMax) {
+	if (class->KronToIndex->coeff[2] < 0.0)	{
+	    class->KronUpper = true;
+	} else {
+	    class->KronUpper = false;
+	}	
+	return;
+    }
+
+    if (fabs(modelMinX - class->indexMin) < fabs(modelMinX - class->indexMax)) {
+	if (class->KronToIndex->coeff[2] < 0.0)	{
+	    class->KronUpper = false;
+	} else {
+	    class->KronUpper = true;
+	}	
+	return;
+    }
+    if (class->KronToIndex->coeff[2] < 0.0)	{
+	class->KronUpper = true;
+    } else {
+	class->KronUpper = false;
+    }	
+    return;
+}
+
+/* the Sersic model class guess system uses a set of empirically-determined fits to several
+ * relationships: index vs psf-kron mags, total mag vs index, major axis vs index, minor axis
+ * vs index.  all are functions of the aspect ratio and of the ratio between the measure major
+ * axis and the size of the psf.  Below we have the fits for these different ranges.  
+
+ * aspect ratio values: 1.0, 0.5, 0.33, 0.2
+ * psf ratio values: 0.5, 1.0, 2.0, 4.0
+
+ * psf ratio ranges: 0.5-0.8,  0.8-1.5, 1.5-3.0, 3.0-4.0
+ * apt ratio ranges: 1.0-0.71, 0.71-0.41, 0.41-0.25, 0.25-0.20
+
+ */
+
+void psphotSersicModelClassInit () {
+
+    psphotSersicModelClass *class;
+
+    if (classArray) return;
+
+    // hardwired trends for now (move into the recipe?)
+    classArray = psArrayAllocEmpty (4);
+
+    // image.00.00.fit.dat: PSFratio : 0.5, ASPratio : 1.0
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  0.5;
+    class->PSFratioMax =  0.8;
+    class->ASPratioMin = 0.71;
+    class->ASPratioMax = 1.00;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.00.00.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.00.01.fit.dat: PSFratio : 1.0, ASPratio : 1.0
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  0.8;
+    class->PSFratioMax =  1.5;
+    class->ASPratioMin = 0.71;
+    class->ASPratioMax = 1.00;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.00.01.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.00.02.fit.dat: PSFratio : 2.0, ASPratio : 1.0
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  1.5;
+    class->PSFratioMax =  3.0;
+    class->ASPratioMin = 0.71;
+    class->ASPratioMax = 1.00;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.00.02.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+    
+    // image.00.03.fit.dat: PSFratio : 4.0, ASPratio : 1.0
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  3.0;
+    class->PSFratioMax =  4.0;
+    class->ASPratioMin = 0.71;
+    class->ASPratioMax = 1.00;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.00.03.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+    
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.01.00.fit.dat: PSFratio : 0.5, ASPratio : 0.5
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  0.5;
+    class->PSFratioMax =  0.8;
+    class->ASPratioMin = 0.41;
+    class->ASPratioMax = 0.71;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.01.00.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+    
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.01.01.fit.dat: PSFratio : 1.0, ASPratio : 0.5
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  0.8;
+    class->PSFratioMax =  1.5;
+    class->ASPratioMin = 0.41;
+    class->ASPratioMax = 0.71;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.01.01.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+    
+    // image.01.02.fit.dat: PSFratio : 2.0, ASPratio : 0.5
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  1.5;
+    class->PSFratioMax =  3.0;
+    class->ASPratioMin = 0.41;
+    class->ASPratioMax = 0.71;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.01.02.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+    
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.01.03.fit.dat: PSFratio : 4.0, ASPratio : 0.5
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  3.0;
+    class->PSFratioMax =  4.0;
+    class->ASPratioMin = 0.41;
+    class->ASPratioMax = 0.71;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.01.03.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+    
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.02.00.fit.dat: PSFratio : 0.5, ASPratio : 0.33
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  0.5;
+    class->PSFratioMax =  0.8;
+    class->ASPratioMin = 0.25;
+    class->ASPratioMax = 0.41;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.02.00.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+    
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.02.01.fit.dat: PSFratio : 1.0, ASPratio : 0.33
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  0.8;
+    class->PSFratioMax =  1.5;
+    class->ASPratioMin = 0.25;
+    class->ASPratioMax = 0.41;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.02.01.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+    
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.02.02.fit.dat: PSFratio : 2.0, ASPratio : 0.33
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  1.5;
+    class->PSFratioMax =  3.0;
+    class->ASPratioMin = 0.25;
+    class->ASPratioMax = 0.41;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.02.02.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+    
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.02.03.fit.dat: PSFratio : 4.0, ASPratio : 0.33
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  3.0;
+    class->PSFratioMax =  4.0;
+    class->ASPratioMin = 0.25;
+    class->ASPratioMax = 0.41;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.02.03.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+    
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.03.00.fit.dat: PSFratio : 0.5, ASPratio : 0.2
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  0.5;
+    class->PSFratioMax =  0.8;
+    class->ASPratioMin = 0.20;
+    class->ASPratioMax = 0.25;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.03.00.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+    
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.03.01.fit.dat: PSFratio : 1.0, ASPratio : 0.2
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  0.8;
+    class->PSFratioMax =  1.5;
+    class->ASPratioMin = 0.20;
+    class->ASPratioMax = 0.25;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.03.01.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+    
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.03.02.fit.dat: PSFratio : 2.0, ASPratio : 0.2
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  1.5;
+    class->PSFratioMax =  3.0;
+    class->ASPratioMin = 0.20;
+    class->ASPratioMax = 0.25;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.03.02.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+    
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+
+    // image.03.03.fit.dat: PSFratio : 4.0, ASPratio : 0.2
+    class = psphotSersicModelClassAlloc();
+
+    class->PSFratioMin =  3.0;
+    class->PSFratioMax =  4.0;
+    class->ASPratioMin = 0.20;
+    class->ASPratioMax = 0.25;
+    class->KronToIndex = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    class->IndexToTotalMag = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMajor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+    class->IndexToMinor = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+# include "psphotSersicGuess.03.03.h"    
+    psphotSersicModelClassSetIndexRange(class, 1.0, 4.0);
+    
+    psArrayAdd(classArray, 4, class);
+    psFree (class);
+}
+ 
+void psphotSersicModelClassCleanup () {
+    psFree (classArray);
+}
+
+# define TIMING false
+
+bool psphotSersicModelClassGuessPCM (pmPCMdata *pcm, pmSource *source) {
+
+    float t1, t2, t4, t5;
+    if (TIMING) { psTimerStart ("SersicGuess"); }
+
+    psF32 *PAR = pcm->modelConv->params->data.F32;
+
+    // XXX require: moments, psfMag, moments->kronFlux
+
+    // we attempt to guess the Sersic model parameters based on a number of non-parametric
+    // measurements: moments, kron mag, etc
+
+    // * first choose the model class based on (a) ratio of Moments Major axis to PSF major
+    // * axis and (b) moments axial ratio
+
+    // convert the moments to Major,Minor,Theta
+    psEllipseMoments moments;
+
+    moments.x2 = source->moments->Mxx;
+    moments.y2 = source->moments->Myy;
+    moments.xy = source->moments->Mxy;
+    
+    // limit axis ratio < 20.0
+    psEllipseAxes momentAxes = psEllipseMomentsToAxes (moments, 20.0);
+
+    // convert the PSF shape to Major,Minor,Theta
+    psEllipseShape shape;
+
+    // XXX make sure this is consistent with the re-definition of PM_PAR_SXX
+    shape.sx  = source->modelPSF->params->data.F32[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = source->modelPSF->params->data.F32[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = source->modelPSF->params->data.F32[PM_PAR_SXY];
+    psEllipseAxes psfAxes = psEllipseShapeToAxes (shape, 20.0);
+    
+    // get the PSFratio and the ASPratio : we use these to choose the model class
+    float PSFratio = source->moments->Mrf / (2.35 * psfAxes.major) ;
+    float ASPratio = momentAxes.minor / momentAxes.major;
+
+    // saturate PSFratio and ASPratio to min / max values
+    PSFratio = PS_MAX(PSFratio, PSFratioMin);
+    PSFratio = PS_MIN(PSFratio, PSFratioMax);
+    ASPratio = PS_MAX(ASPratio, ASPratioMin);
+    ASPratio = PS_MIN(ASPratio, ASPratioMax);
+
+    // find the containing model class:
+
+    if (TIMING) { t1 = psTimerMark ("SersicGuess"); }
+
+    psphotSersicModelClass *class = NULL;    
+    for (int i = 0; i < classArray->n; i++) {
+	psphotSersicModelClass *thisClass = classArray->data[i];
+	if (PSFratio < thisClass->PSFratioMin) continue;
+	if (PSFratio > thisClass->PSFratioMax) continue;
+	if (ASPratio < thisClass->ASPratioMin) continue;
+	if (ASPratio > thisClass->ASPratioMax) continue;
+	class = thisClass;
+	break;
+    }
+	
+    if (TIMING) { t2 = psTimerMark ("SersicGuess"); }
+
+    psAssert (class, "PSFratio and ASPratio must be in range");
+
+    // get the index guess from the KronMag - psfMag:
+
+    // get dKronMag & saturate dKronMag at limits of valid range
+    float dKronMag = -2.5*log10(source->moments->KronFlux) - source->psfMag;
+    dKronMag = PS_MIN(dKronMag, class->kronMax);
+    dKronMag = PS_MAX(dKronMag, class->kronMin);
+
+    // get index (saturate at valid ends)
+    float index = psPolynomial1DSolve (class->KronToIndex, dKronMag, class->KronUpper);
+    index = PS_MIN (index, class->indexMax);
+    index = PS_MAX (index, class->indexMin);
+    
+    // float totalMagOffset = psPolynomial1DEval (class->IndexToTotalMag, index);
+    // float totalMag = -2.5*log10(source->moments->KronFlux) + totalMagOffset;
+
+    // need to go from totalMag to Io in the sersic model
+    
+    float majorAxisFactor = psPolynomial1DEval (class->IndexToMajor, index);
+    float minorAxisFactor = psPolynomial1DEval (class->IndexToMinor, index);
+
+    momentAxes.major *= (1.0 + majorAxisFactor);
+    momentAxes.minor *= (1.0 + minorAxisFactor);
+
+    psEllipseShape extShapeGuess = psEllipseAxesToShape (momentAxes);
+    if (!isfinite(extShapeGuess.sx))  return false;
+    if (!isfinite(extShapeGuess.sy))  return false;
+    if (!isfinite(extShapeGuess.sxy)) return false;
+
+    // set the actual model parameters:
+
+    // index is standard sersic index (n = 1-4), but PAR7 = 1/2n
+    PAR[PM_PAR_7] = 0.5 / index;
+
+    // sky is zero (no longer fitted, but not yet deprecated)
+    PAR[PM_PAR_SKY]  = 0.0;
+
+    // set the model position
+    if (!pmModelSetPosition(&PAR[PM_PAR_XPOS], &PAR[PM_PAR_YPOS], source)) {
+      return false;
+    }
+    PAR[PM_PAR_SXX] = extShapeGuess.sx * M_SQRT2;
+    PAR[PM_PAR_SXY] = extShapeGuess.sxy;
+    PAR[PM_PAR_SYY] = extShapeGuess.sy * M_SQRT2;
+
+    // XXX this is a bit of a waste: calculate the flux with Io = 1.0, then renormalize.
+    PAR[PM_PAR_I0]  = 1.0;
+
+    if (TIMING) { t4 = psTimerMark ("SersicGuess"); }
+
+    // set the normalization by linear fit between model and data
+    psphotSersicModelNorm (pcm, source);
+
+    if (TIMING) { t5 = psTimerMark ("SersicGuess"); }
+
+    if (TIMING) {
+    	fprintf (stderr, "guess, t1: %6.4f, t2: %6.4f, t4: %6.4f, t5: %6.4f\n", t1, t2, t4, t5);
+    }
+
+    // float flux = pcm->modelConv->modelFlux(pcm->modelConv->params);
+    // float normMag = -2.5*log10(flux);
+    // float Io = pow(10.0, -0.4*(totalMag - normMag));
+    // PAR[PM_PAR_I0] = Io;
+
+    return true;
+}
+
+void psphotSersicModelNorm (pmPCMdata *pcm, const pmSource *source) {
+
+    psVector *params = pcm->modelConv->params;
+    // XXX : not needed? psAssert (params->data.F32[PM_PAR_I0] == 1.0, "not normalized?");
+
+    // generate the convolved model image
+    // working vector to store local coordinate
+    psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+
+    // create the convolved model in situ
+    psAssert (pcm->modelConvFlux, "not already allocated?");
+    psImageInit (pcm->modelConvFlux, 0.0);
+
+    // fill in the coordinate and value entries
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+
+            // Convert i/j to image space:
+            coord->data.F32[0] = (psF32) (j + source->pixels->col0);
+            coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+
+            pcm->modelConvFlux->data.F32[i][j] = pcm->modelConv->modelFunc (NULL, params, coord);
+        }
+    }
+    psFree(coord);
+    
+    psImageSmooth (pcm->modelConvFlux, pcm->sigma, pcm->nsigma);
+
+    float YYmod = 0.0;
+    float Ymod2 = 0.0;
+    bool usePoisson = false;
+
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+            // XXX are we doing the right thing with the mask?
+            // skip masked points
+            if (source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[i][j]) {
+                continue;
+            }
+            // skip zero-variance points
+            if (source->variance->data.F32[i][j] == 0) {
+                continue;
+            }
+            // skip nan value points
+            if (!isfinite(source->pixels->data.F32[i][j])) {
+                continue;
+            }
+
+            float ymodel  = pcm->modelConvFlux->data.F32[i][j];
+            float yweight = (usePoisson) ? 1.0 / source->variance->data.F32[i][j] : 1.0;
+
+	    YYmod += ymodel * source->pixels->data.F32[i][j] * yweight;
+	    Ymod2 += PS_SQR(ymodel) * yweight;
+        }
+    }
+
+    float Io = YYmod / Ymod2;
+
+    params->data.F32[PM_PAR_I0] *= Io;
+
+    return;
+}
+
+// Given a polynomial y = C0 + C1 x + C2 x^2 etc, solve for x given y
+// actually: this only solves 1st and 2nd order polynomials
+// for 2nd order, it always returns the (positive/negative) term
+
+psF64 psPolynomial1DSolve(
+    const psPolynomial1D* poly,
+    psF64 y,
+    bool upper)
+{
+    psF64 x, C;
+    switch (poly->nX) {
+      case 1:
+	// y = coeff[0] + coeff[1]*x 
+	// x = (y - coeff[0]) / coeff[1]
+	x = (y - poly->coeff[0]) / poly->coeff[1];
+	return x;
+	
+      case 2:
+	// y = coeff[0] + coeff[1]*x + coeff[2]*x^2
+	// x = -coeff[1] +/- sqrt(coeff[1]^2 - 4*coeff[0]*coeff[2]) / (2 coeff[0])
+	C = poly->coeff[0] - y;
+	if (upper) {
+	    x = (-poly->coeff[1] + sqrt(PS_SQR(poly->coeff[1]) - 4*poly->coeff[2]*C)) / (2.0 * poly->coeff[2]);
+	} else {
+	    x = (-poly->coeff[1] - sqrt(PS_SQR(poly->coeff[1]) - 4*poly->coeff[2]*C)) / (2.0 * poly->coeff[2]);
+	}
+	return x;
+
+      default:
+	psAbort("invalid polynomial for 1D solver");
+    }
+    psAbort("invalid polynomial for 1D solver");
+}
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSetThreads.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSetThreads.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSetThreads.c	(revision 33415)
@@ -30,4 +30,9 @@
     psFree(task);
 
+    task = psThreadTaskAlloc("PSPHOT_KRON_ITERATE", 7);
+    task->function = &psphotKronIterate_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
     task = psThreadTaskAlloc("PSPHOT_BLEND_FIT", 10);
     task->function = &psphotBlendFit_Threaded;
@@ -40,4 +45,19 @@
     psFree(task);
 
+    task = psThreadTaskAlloc("PSPHOT_EXTENDED_ANALYSIS", 8);
+    task->function = &psphotExtendedSourceAnalysis_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
+    task = psThreadTaskAlloc("PSPHOT_RADIAL_APERTURES", 6);
+    task->function = &psphotRadialApertures_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
+    task = psThreadTaskAlloc("PSPHOT_RADIAL_PROFILE_WINGS", 3);
+    task->function = &psphotRadialProfileWings_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
     return true;
 }
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSourceFits.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSourceFits.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSourceFits.c	(revision 33415)
@@ -211,5 +211,5 @@
 bool psphotFitBlob (pmReadout *readout, pmSource *source, psArray *newSources, pmPSF *psf, pmSourceFitOptions *fitOptions, psImageMaskType maskVal, psImageMaskType markVal) {
 
-    float radius;
+    float fitRadius, windowRadius;
     bool okEXT, okDBL;
     pmModel *ONE = NULL;
@@ -217,5 +217,5 @@
     pmModel *EXT = NULL;
     psArray *DBL = NULL;
-    pmMoments psfMoments;
+    // pmMoments psfMoments;
 
     // skip the source if we don't think it is extended
@@ -225,18 +225,17 @@
     if (source->type == PM_SOURCE_TYPE_SATURATED) return false;
 
+# define TEST_X -420.0
+# define TEST_Y 300.0
+   
+    if ((fabs(source->peak->xf - TEST_X) < 5) && (fabs(source->peak->yf - TEST_Y) < 5)) {
+	fprintf (stderr, "test galaxy\n");
+    }
+
+# undef TEST_X
+# undef TEST_Y
+
     // set the radius based on the footprint (also sets the mask pixels)
-    if (!psphotSetRadiusFootprint(&radius, readout, source, markVal, 1.0)) return false;
-
-    // XXX note that this changes the source moments that are published...
-    // XXX all published moments should use the same measurement
-    // recalculate the source moments using the larger extended-source moments radius
-    // at this stage, skip Gaussian windowing, and do not clip pixels by S/N
-    // this uses the footprint to judge both radius and aperture?
-    // XXX save the psf-based moments for output
-    psfMoments = *source->moments;
-    if (!pmSourceMoments (source, radius, 0.0, 0.5, 0.0, maskVal)) {
-      *source->moments = psfMoments;
-      return false;
-    }
+    if (!psphotSetRadiusMoments(&fitRadius, &windowRadius, readout, source, markVal)) return false;
+    // fprintf (stderr, "rad: %6.1f %6.1f  | %5.2f %5.2f %5.2f  ", source->peak->xf, source->peak->yf, source->moments->Mrf, fitRadius, windowRadius);
 
     psTrace ("psphot", 5, "trying blob...\n");
@@ -258,4 +257,5 @@
 	okDBL  = psphotEvalDBL (tmpSrc, DBL->data[0]);
 	okDBL &= psphotEvalDBL (tmpSrc, DBL->data[1]);
+	okDBL = false; // XXX this is failing badly...
 	// XXX should I keep / save the flags set in the eval functions?
 
@@ -263,7 +263,7 @@
 	ONE = DBL->data[0];
 	if (ONE) {
-	    if (!isfinite(ONE->params->data.F32[PM_PAR_I0])) psAbort("nan in fit");
+	    psAssert (isfinite(ONE->params->data.F32[PM_PAR_I0]), "nan in fit");
 	    chiDBL = ONE->chisqNorm; // save chisq for double-star/galaxy comparison
-	    ONE->fitRadius = radius;
+	    ONE->fitRadius = fitRadius;
 	}
 
@@ -271,6 +271,6 @@
 	ONE = DBL->data[1];
 	if (ONE) {
-	    if (!isfinite(ONE->params->data.F32[PM_PAR_I0])) psAbort("nan in fit");
-	    ONE->fitRadius = radius;
+	    psAssert (isfinite(ONE->params->data.F32[PM_PAR_I0]), "nan in fit");
+	    ONE->fitRadius = fitRadius;
 	}
     }
@@ -284,4 +284,5 @@
 	okEXT = psphotEvalEXT (tmpSrc, EXT);
 	chiEXT = EXT ? EXT->chisqNorm : NAN;
+	EXT->fitRadius = fitRadius;
     }
 
@@ -294,5 +295,4 @@
 
     if (okEXT && okDBL) {
-        psTrace ("psphot", 5, "blob chisq: %f vs %f\n", chiEXT, chiDBL);
         // XXX EAM : a bogus bias: need to examine this better
         if (3*chiEXT > chiDBL) goto keepDBL;
@@ -303,8 +303,10 @@
     if (!okEXT && okDBL) goto keepDBL;
 
+    psTrace ("psphot", 4, "both failed: blob chisq: %f vs %f for %f,%f\n", chiEXT, chiDBL, source->peak->xf, source->peak->yf);
+
     // both models failed; reject them both
     // XXX -- change type flags to psf in this case, and make sure we subtract it?
     // reset the psf moments
-    *source->moments = psfMoments;
+    // XXX *source->moments = psfMoments;
 
     psFree (EXT);
@@ -313,4 +315,5 @@
 
 keepEXT:
+    psTrace ("psphot", 4, "goto EXT : blob chisq: %f vs %f for %f,%f\n", chiEXT, chiDBL, source->peak->xf, source->peak->yf);
     // sub EXT
     psFree (DBL);
@@ -338,8 +341,9 @@
 
     // reset the psf moments
-    *source->moments = psfMoments;
+    // XXX *source->moments = psfMoments;
     return true;
 
 keepDBL:
+    psTrace ("psphot", 4, "goto DBL : blob chisq: %f vs %f for %f,%f\n", chiEXT, chiDBL, source->peak->xf, source->peak->yf);
     // sub DLB
     psFree (EXT);
@@ -354,4 +358,6 @@
     pmSource *newSrc = pmSourceCopy (source);
     newSrc->modelPSF = psMemIncrRefCounter (DBL->data[1]);
+    newSrc->peak->footprint = source->peak->footprint; // just a reference; the peak does not own the footprint
+    psArrayAdd(newSrc->peak->footprint->peaks, 1, newSrc->peak); // the footprint owns the peak
 
     // build cached models and subtract
@@ -372,11 +378,6 @@
 	    psLogMsg ("psphot", 1, "PAR %d : %f +/- %f\n", i, source->modelPSF->params->data.F32[i], source->modelPSF->dparams->data.F32[i]);
 	}
-	psphotVisualShowResidualImage (readout, false);
     }
 # endif
-
-    // reset the (original) psf moments
-    *source->moments = psfMoments;
-    *newSrc->moments = psfMoments;
 
     psArrayAdd (newSources, 100, newSrc);
@@ -386,6 +387,4 @@
 
 escape:
-    // reset the psf moments
-    *source->moments = psfMoments;
     psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); 
     psFree (tmpSrc);
@@ -476,11 +475,5 @@
     // for sersic models, use a grid search to choose an index, then float the params there
     if (modelType == pmModelClassGetType("PS_MODEL_SERSIC")) {
-	// for the test fits, use a somewhat smaller radius
-	psphotSetRadiusFootprint(&model->fitRadius, readout, source, markVal, 0.5);
     	psphotFitSersicIndex (model, readout, source, fitOptions, maskVal, markVal);
-    }
-
-    if (!psphotSetRadiusModel (model, readout, source, markVal, true)) {
-	psphotSetRadiusFootprint(&model->fitRadius, readout, source, markVal, 1.0);
     }
 
@@ -494,8 +487,10 @@
     pmSourceFitModel (source, model, &options, maskVal);
     // fprintf (stderr, "chisq: %f, nIter: %d, radius: %f, npix: %d\n", model->chisqNorm, model->nIter, model->fitRadius, model->nPix);
-
     // psTraceSetLevel("psLib.math.psMinimizeLMChi2", 0);
+
     return (model);
 }
+
+# define TIMING 0
 
 pmModel *psphotFitPCM (pmReadout *readout, pmSource *source, pmSourceFitOptions *fitOptions, pmModelType modelType, psImageMaskType maskVal, psImageMaskType markVal, int psfSize) {
@@ -517,4 +512,14 @@
 	return NULL;
     }
+
+# define TEST_X -540.0
+# define TEST_Y 540.0
+   
+    if ((fabs(source->peak->xf - TEST_X) < 5) && (fabs(source->peak->yf - TEST_Y) < 5)) {
+	psTraceSetLevel("psModules.objects.pmPCM_MinimizeChisq", 5);
+    }
+
+    float t1, t2, t4, t5;
+    if (TIMING) { psTimerStart ("psphotFitPCM"); }
 
     pmPCMdata *pcm = pmPCMinit (source, &options, model, maskVal, psfSize);
@@ -524,27 +529,25 @@
 	return model;
     }
-
-    // use the source moments, etc to guess basic model parameters
-    if (!pmSourceModelGuessPCM (pcm, source, maskVal, markVal)) {
-	psFree (pcm);
-        model->flags |= PM_MODEL_STATUS_BADARGS;
-	return model;
-    }
-
-    // for sersic models, use a grid search to choose an index, then float the params there
+    if (TIMING) { t1 = psTimerMark ("psphotFitPCM"); }
+
+    // NOTE : 65 allocs to here
+    // get the guess for sersic models 
     if (modelType == pmModelClassGetType("PS_MODEL_SERSIC")) {
-	// for the test fits, use a somewhat smaller radius
-	psphotSetRadiusFootprint(&model->fitRadius, readout, source, markVal, 0.5);
-
-    	if (!psphotFitSersicIndexPCM (pcm, readout, source, fitOptions, maskVal, markVal, psfSize)) {
+	// use the source moments, etc to guess basic model parameters
+	if (!psphotSersicModelClassGuessPCM (pcm, source)) {
 	    psFree (pcm);
 	    model->flags |= PM_MODEL_STATUS_BADARGS;
 	    return model;
 	}
-    }
-
-    if (!psphotSetRadiusModel (model, readout, source, markVal, true)) {
-	psphotSetRadiusFootprint(&model->fitRadius, readout, source, markVal, 1.0);
-    }
+    } else {
+	// use the source moments, etc to guess basic model parameters
+	if (!pmSourceModelGuessPCM (pcm, source, maskVal, markVal)) {
+	    psFree (pcm);
+	    model->flags |= PM_MODEL_STATUS_BADARGS;
+	    return model;
+	}
+    }
+
+    if (TIMING) { t2 = psTimerMark ("psphotFitPCM"); }
 
     if (modelType == pmModelClassGetType("PS_MODEL_SERSIC")) {
@@ -555,8 +558,18 @@
     // update the pcm elements if we have changed the circumstance (options.mode or source->pixels)
     pmPCMupdate(pcm, source, &options, model);
+    if (TIMING) { t4 = psTimerMark ("psphotFitPCM"); }
 
     // psTraceSetLevel("psLib.math.psMinimizeLMChi2", 5);
-    pmSourceFitPCM (pcm, source, &options, maskVal, markVal, psfSize);
-    // fprintf (stderr, "chisq: %f, nIter: %d, radius: %f, npix: %d\n", model->chisqNorm, model->nIter, model->fitRadius, model->nPix);
+    pmSourceFitPCM (pcm, source, &options, maskVal, markVal, psfSize);  // NOTE : 1687 allocs in here
+    if (TIMING) { t5 = psTimerMark ("psphotFitPCM"); }
+
+    if (TIMING) {
+	int nPixBig = source->pixels->numCols * source->pixels->numRows;
+    	fprintf (stderr, "psphotFitPCM : nIter: %2d, radius: %6.1f, npix: %5d of %5d, t1: %6.4f, t2: %6.4f, t4: %6.4f, t5: %6.4f\n", model->nIter, model->fitRadius, model->nPix, nPixBig, t1, t2, t4, t5);
+    }
+
+    if ((fabs(source->peak->xf - TEST_X) < 5) && (fabs(source->peak->yf - TEST_Y) < 5)) {
+	psTraceSetLevel("psModules.objects.pmPCM_MinimizeChisq", 0);
+    }
 
     // psTraceSetLevel("psLib.math.psMinimizeLMChi2", 0);
@@ -566,7 +579,12 @@
 }
 
+# undef TEST_X
+# undef TEST_Y
+
 // note that these should be 1/2n of the standard sersic index
-float indexGuess[] = {0.5, 0.33, 0.25, 0.167, 0.125, 0.083};
-# define N_INDEX_GUESS 6
+// float indexGuess[] = {0.8, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0};
+// float indexGuess[] = {0.5, 0.33, 0.25, 0.167, 0.125, 0.083};
+float indexGuess[] = {1.0, 2.0, 3.0, 4.0};
+# define N_INDEX_GUESS 4
 
 // A sersic model is very sensitive to the index.  attempt to find the index first by grid search in just the index
@@ -586,6 +604,13 @@
     float chiSquare[N_INDEX_GUESS];
 
+# define TEST_X -540.0
+# define TEST_Y 540.0
+   
+    if ((fabs(source->peak->xf - TEST_X) < 5) && (fabs(source->peak->yf - TEST_Y) < 5)) {
+	psTraceSetLevel("psModules.objects.pmPCM_MinimizeChisq", 5);
+    }
+
     for (int i = 0; i < N_INDEX_GUESS; i++) {
-	model->params->data.F32[PM_PAR_7] = indexGuess[i];
+	model->params->data.F32[PM_PAR_7] = 0.5/indexGuess[i];
 
 	if (!model->modelGuess(model, source)) {
@@ -594,12 +619,6 @@
 	}
 
-	// each time we change the model guess, we need to adjust the radius
-	// XXX this did not work : we do not need such a large radius -- just uses moments-based radius
-	if (false && !psphotSetRadiusModel (model, readout, source, markVal, false)) {
-	    psphotSetRadiusFootprint(&model->fitRadius, readout, source, markVal, 0.5);
-	}
-	
 	pmSourceFitModel (source, model, &options, maskVal);
-	// fprintf (stderr, "chisq: %f, nIter: %d, radius: %f, npix: %d\n", model->chisqNorm, model->nIter, model->fitRadius, model->nPix);
+	// fprintf (stderr, "index: %f, chisq: %f, nIter: %d, radius: %f, npix: %d\n", indexGuess[i], model->chisqNorm, model->nIter, model->fitRadius, model->nPix);
 
 	chiSquare[i] = model->chisqNorm;
@@ -616,12 +635,11 @@
     assert (iMin >= 0);
 
+    if ((fabs(source->peak->xf - TEST_X) < 5) && (fabs(source->peak->yf - TEST_Y) < 5)) {
+	psTraceSetLevel("psModules.objects.pmPCM_MinimizeChisq", 0);
+    }
+
     model->flags = PM_MODEL_STATUS_NONE; // do not attempt to handle failures here, let the next iteration deal with it
-    model->params->data.F32[PM_PAR_7] = indexGuess[iMin];
+    model->params->data.F32[PM_PAR_7] = 0.5/indexGuess[iMin];
     model->modelGuess(model, source);
-
-    // each time we change the model guess, we need to adjust the radius
-    // if (!psphotSetRadiusModel (model, readout, source, markVal, true)) {
-    // 	psphotSetRadiusFootprint(&model->fitRadius, readout, source, markVal);
-    // }
 
     return true;
@@ -648,4 +666,8 @@
     float xMin = NAN;
     float chiSquare[N_INDEX_GUESS];
+
+    if ((fabs(source->peak->xf - TEST_X) < 5) && (fabs(source->peak->yf - TEST_Y) < 5)) {
+	psTraceSetLevel("psModules.objects.pmPCM_MinimizeChisq", 5);
+    }
 
     for (int i = 0; i < N_INDEX_GUESS; i++) {
@@ -657,11 +679,6 @@
 	}
 
-	// each time we change the model guess, we need to adjust the radius
-	// XXX this did not work : we do not need such a large radius -- just uses moments-based radius
-	if (false && !psphotSetRadiusModel (model, readout, source, markVal, false)) {
-	    psphotSetRadiusFootprint(&model->fitRadius, readout, source, markVal, 0.5);
-	}
-	
 # if (0)
+	// this block is to test the relative speed of straight and PCM fits
 	pmSourceFitModel (source, model, &options, maskVal);
 # else
@@ -669,4 +686,5 @@
 	pmSourceFitPCM (pcm, source, &options, maskVal, markVal, psfSize);
 # endif
+	fprintf (stderr, "index: %f, chisq: %f, nIter: %d, radius: %f, npix: %d\n", indexGuess[i], model->chisqNorm, model->nIter, model->fitRadius, model->nPix);
 	// fprintf (stderr, "chisq: %f, nIter: %d, radius: %f, npix: %d\n", model->chisqNorm, model->nIter, model->fitRadius, model->nPix);
 
@@ -684,4 +702,8 @@
     assert (iMin >= 0);
     
+    if ((fabs(source->peak->xf - TEST_X) < 5) && (fabs(source->peak->yf - TEST_Y) < 5)) {
+	psTraceSetLevel("psModules.objects.pmPCM_MinimizeChisq", 0);
+    }
+
     model->flags = PM_MODEL_STATUS_NONE; // do not attempt to handle failures here, let the next iteration deal with it
     model->params->data.F32[PM_PAR_7] = indexGuess[iMin];
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSourceMatch.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSourceMatch.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSourceMatch.c	(revision 33415)
@@ -48,11 +48,7 @@
     pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
     psAssert (detections, "missing detections?");
-    psAssert (detections->newSources, "new sources not defined?");
-    psAssert (!detections->allSources, "all sources already defined?");
-
-    // XXX TEST:
-    if (detections->newSources) {
-        psphotMatchSourcesToObjects(objects, detections->newSources, RADIUS);
-    }
+    psAssert (detections->allSources, "all sources not defined?");
+
+    psphotMatchSourcesToObjects(objects, detections->allSources, RADIUS);
 
     return true;
@@ -71,6 +67,9 @@
     objects = psArraySort (objects, pmPhotObjSortByX); 
  
-    psVector *found = psVectorAlloc(sources->n, PS_TYPE_U8);
-    psVectorInit (found, 0);
+    psVector *foundSrc = psVectorAlloc(sources->n, PS_TYPE_U8);
+    psVectorInit (foundSrc, 0);
+
+    psVector *foundObj = psVectorAlloc(objects->n, PS_TYPE_U8);
+    psVectorInit (foundObj, 0);
 
     // match sources to existing objects
@@ -97,4 +96,11 @@
         if (dx > +1.02*RADIUS) NEXT2; 
  
+	/* this block will match a given detection to the closest object within range of that detection.
+	   XXX note that this matches ALL detections within range of the single object to that same object 
+	   this is bad, but I cannot just go in linear order (ie, mark off each object as they are
+	   used).  I should make a list of all Nobj * Ndet pairs in range and choose the matches
+	   based on their separations.  UGH
+	*/
+    
         // we are within match range, look for matches: 
 	int Jmin = -1;
@@ -102,4 +108,6 @@
         for (int J = j; (dx > -1.02*RADIUS) && (J < objects->n); J++) { 
  
+	    // skip objects that are already assigned:
+	    if (foundObj->data.U8[J]) continue;
 	    obj = objects->data[J]; 
 	    
@@ -121,8 +129,9 @@
 	}
 	obj = objects->data[Jmin]; 
+	foundObj->data.U8[Jmin] = 1;
 
 	// add to object
 	pmPhotObjAddSource (obj, src);
-	found->data.U8[i] = 1;
+	foundSrc->data.U8[i] = 1;
         i++; 
     } 
@@ -132,5 +141,5 @@
     for (i = 0; i < sources->n; i++) {
 
-	if (found->data.U8[i]) continue;
+	if (foundSrc->data.U8[i]) continue;
 
         pmSource *src = sources->data[i]; 
@@ -143,5 +152,6 @@
     psLogMsg ("psphot", PS_LOG_DETAIL, "matched sources (%ld vs %ld)", sources->n, objects->n);
 
-    psFree (found);
+    psFree (foundSrc);
+    psFree (foundObj);
     return true;
 } 
@@ -238,10 +248,12 @@
 	    
 	    // assign to a footprint on this readout->image
-	    peak->footprint = pmFootprintCopyData(footprint, readout->image);
-
-	    // the peak does not claim ownership of the footprint (it does not free it). save a copy of this 
-	    // footprint on detections->footprints so we can free it later
-	    psArrayAdd(detections->footprints, 100, peak->footprint); 
-	    psFree (peak->footprint);
+	    if (footprint) {
+		peak->footprint = pmFootprintCopyData(footprint, readout->image);
+
+		// the peak does not claim ownership of the footprint (it does not free it). save a copy of this 
+		// footprint on detections->footprints so we can free it later
+		psArrayAdd(detections->footprints, 100, peak->footprint); 
+		psFree (peak->footprint);
+	    }
 	    
 	    // create a new source
@@ -249,4 +261,5 @@
 	    source->imageID = index;
 	    source->mode2 |= PM_SOURCE_MODE2_MATCHED; // source is generated based on another image
+	    source->type = PM_SOURCE_TYPE_STAR; // until we know more, assume a PSF fit
 
 	    // add the peak
@@ -261,5 +274,5 @@
 	    peak->assigned = true;
 	    pmPhotObjAddSource(obj, source);
-	    psArrayAdd (detections->newSources, 100, source);
+	    psArrayAdd (detections->allSources, 100, source);
 	    psFree (source);
 	}
@@ -272,4 +285,5 @@
         pmPhotObj *obj = objects->data[i]; 
 	nSources += obj->sources->n;
+	psAssert (obj->sources->n == nImages, "failed to match sources?");
     }
     psLogMsg ("psphot", PS_LOG_DETAIL, "total of %d sources for %d images", nSources, nImages);
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSourceSize.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSourceSize.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSourceSize.c	(revision 33415)
@@ -45,4 +45,7 @@
     bool status = true;
 
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Source Size ---");
+
     // select the appropriate recipe information
     psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
@@ -153,5 +156,5 @@
 
     // 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"));
+    psLogMsg ("psphot.size", PS_LOG_WARN, "measure source sizes for %ld sources: %f sec\n", sources->n, psTimerMark ("psphot.size"));
 
     psphotVisualPlotSourceSize (recipe, readout->analysis, sources);
@@ -209,5 +212,5 @@
         pmSourceMagnitudes (source, psf, photMode, maskVal, markVal, source->apRadius);
 
-        float kMag = -2.5*log10(source->moments->KronFlux);
+        float kMag = -2.5*log10(source->moments->KronFluxPSF);
         float dMag = source->psfMag - kMag;
 
@@ -334,5 +337,5 @@
         psF32 Mxy = source->moments->Mxy;
 
-        float KronMag = -2.5*log10(source->moments->KronFlux);
+        float KronMag = -2.5*log10(source->moments->KronFluxPSF);
 
 	float Mminor = 0.5*(Mxx + Myy) - 0.5*sqrt(PS_SQR(Mxx - Myy) + 4.0*PS_SQR(Mxy));
@@ -509,5 +512,5 @@
         pmSourceSub (source, PM_MODEL_OP_FULL, options->maskVal);
 
-        float kMag = -2.5*log10(source->moments->KronFlux);
+        float kMag = -2.5*log10(source->moments->KronFluxPSF);
         float dMag = source->psfMag - kMag;
 
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSourceStats.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSourceStats.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSourceStats.c	(revision 33415)
@@ -10,4 +10,7 @@
     bool status = true;
 
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Source Stats ---");
+
     // select the appropriate recipe information
     psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
@@ -98,10 +101,8 @@
 
     // generate the array of sources, define the associated pixel
-    bool firstPass = false;
-    if (!detections->newSources) {
-        detections->newSources = psArrayAllocEmpty (peaks->n);
-	firstPass = true;
-    }
-    sources = detections->newSources;
+    psAssert (!detections->newSources, "programming error");
+    sources = detections->newSources = psArrayAllocEmpty (peaks->n);
+
+    bool firstPass = (detections->allSources == NULL);
 
     // if there are no peaks, we save the empty source array and return
@@ -121,4 +122,6 @@
         // add the peak
         source->peak = psMemIncrRefCounter(peak);
+
+	// psAssert (source->peak->footprint, "peak without footprint??");
 
         // allocate space for moments
@@ -213,5 +216,5 @@
 
         // wait for the threads to finish and manage results
-        if (!psThreadPoolWait (false)) {
+        if (!psThreadPoolWait (false, true)) {
             psError(PS_ERR_UNKNOWN, false, "Failure in thread job PSPHOT_SOURCE_STATS");
             psFree(detections->newSources);
@@ -239,5 +242,5 @@
     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"));
+    psLogMsg ("psphot", PS_LOG_WARN, "%ld sources, %d moments, %d faint, %d failed: %f sec\n", sources->n, Nmoments, Nfaint, Nfail, psTimerMark ("psphot.stats"));
 
     psphotVisualShowMoments (sources);
@@ -344,5 +347,5 @@
 
         // wait for the threads to finish and manage results
-        if (!psThreadPoolWait (false)) {
+        if (!psThreadPoolWait (false, true)) {
             psError(PS_ERR_UNKNOWN, false, "Failure in thread job PSPHOT_SOURCE_STATS");
             return NULL;
@@ -554,5 +557,5 @@
         // determine the PSF parameters from the source moment values
         pmPSFClump psfClump = pmSourcePSFClump (NULL, NULL, sources, PSF_SN_LIM, PSF_CLUMP_GRID_SCALE, MOMENTS_SX_MAX, MOMENTS_SY_MAX, MOMENTS_SX_MIN, MOMENTS_SY_MIN, MOMENTS_AR_MAX);
-        psLogMsg ("psphot", 3, "radius %.1f, nStars: %d of %d in clump, nSigma: %5.2f, X,  Y: %f, %f (%f, %f)\n", sigma[i], psfClump.nStars, psfClump.nTotal, psfClump.nSigma, psfClump.X, psfClump.Y, sqrt(psfClump.X) / sigma[i], sqrt(psfClump.Y) / sigma[i]);
+        psLogMsg ("psphot", 3, "sigma guess (pix) %.1f, nStars: %d of %d in clump, nSigma: %5.2f, X,  Y: %f, %f (%f, %f)\n", sigma[i], psfClump.nStars, psfClump.nTotal, psfClump.nSigma, psfClump.X, psfClump.Y, sqrt(psfClump.X) / sigma[i], sqrt(psfClump.Y) / sigma[i]);
 
 	Rmin[i] = pmSourceMinKronRadius(sources, PSF_SN_LIM);
Index: branches/meh_branches/ppstack_test/psphot/src/psphotStackChisqImage.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotStackChisqImage.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotStackChisqImage.c	(revision 33415)
@@ -13,4 +13,6 @@
     psAssert (chisqFile, "missing chisq image FPA?");
 
+    // the readout containing the chisq image is generated in the first pass of this loop and
+    // used by the successive passes
     pmReadout *chiReadout = NULL;
 
Index: branches/meh_branches/ppstack_test/psphot/src/psphotStackImageLoop.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotStackImageLoop.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotStackImageLoop.c	(revision 33415)
@@ -10,4 +10,5 @@
 bool UpdateHeadersForFPA (pmConfig *config, pmFPAview *view);
 bool UpdateHeadersForChip (pmConfig *config, pmFPAview *view);
+bool UpdateHeadersForReadout (pmConfig *config, pmFPAview *view);
 
 bool psphotStackImageLoop (pmConfig *config) {
@@ -52,5 +53,5 @@
 		psMemDump("load");
 
-		// PSF matching
+		// Generate the 1st PSF-matched image set (larger target PSFs are generated by smoothing this image)
 		if (!psphotStackMatchPSFs (config, view)) {
                     psError(psErrorCodeLast(), false, "failure in psphotStackMatchPSFs for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
@@ -66,4 +67,7 @@
                     return false;
 		}
+
+		UpdateHeadersForReadout(config, view);
+
 		psMemDump("psphot");
 	    }
@@ -101,4 +105,33 @@
 }
 
+bool UpdateHeadersForReadout (pmConfig *config, pmFPAview *view) {
+
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    bool useRaw = psMetadataLookupBool (NULL, recipe, "PSPHOT.STACK.USE.RAW");
+
+    int num = psphotFileruleCount(config, "PSPHOT.INPUT");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+
+	// find the currently selected readout
+	pmFPAfile *output = pmFPAfileSelectSingle(config->files, "PSPHOT.STACK.OUTPUT.IMAGE", i); // File of interest
+	psAssert (output, "missing file?");
+
+	pmFPAfile *inputRaw = pmFPAfileSelectSingle(config->files, "PSPHOT.STACK.INPUT.RAW", i); // File of interest
+	pmFPAfile *inputCnv = pmFPAfileSelectSingle(config->files, "PSPHOT.STACK.INPUT.CNV", i); // File of interest
+	pmFPAfile *input = useRaw ? inputRaw : inputCnv;
+	psAssert (input, "missing input file");
+
+	// just copy the input headers to the output headers, then update version info
+	pmReadout *inReadout = pmFPAviewThisReadout(view, input->fpa); ///< Chip in the input
+	pmReadout *outReadout = pmFPAviewThisReadout(view, output->fpa); ///< Chip in the output
+
+	psMetadata *header = psMetadataLookupPtr (NULL, inReadout->analysis, "PSPHOT.HEADER");
+	psMetadataAdd (outReadout->analysis, PS_LIST_TAIL, "PSPHOT.HEADER",  PS_DATA_METADATA, "header stats", header);
+    }
+    return true;
+}
+
 bool UpdateHeadersForChip (pmConfig *config, pmFPAview *view) {
 
@@ -131,4 +164,5 @@
 	    outHDU->header = psMetadataCopy(NULL, inHDU->header);
 	}
+	psphotVersionHeaderFull(outHDU->header);
 	outChip->toFPA = psMemIncrRefCounter(inChip->toFPA);
 	outChip->fromFPA = psMemIncrRefCounter(inChip->fromFPA);
Index: branches/meh_branches/ppstack_test/psphot/src/psphotStackMatchPSFs.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotStackMatchPSFs.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotStackMatchPSFs.c	(revision 33415)
@@ -25,6 +25,7 @@
     }
 
-    // loop over the available readouts (ignore chisq image)
+    // loop over the available readouts
     for (int i = 0; i < num; i++) {
+	// set up the PSF-matching parameters describing the input images
 	if (!psphotStackMatchPSFsPrepare (config, view, options, i)) {
             psError (PSPHOT_ERR_CONFIG, false, "failed to set PSF matching options for entry %d", i);
@@ -33,19 +34,12 @@
     }
 
-    // Generate target PSF
+    // XXX convolve == false might not be valid at the moment
     if (options->convolve) {
-        options->psf = psphotStackPSF(config, options->numCols, options->numRows, options->psfs, options->inputMask);
-        if (!options->psf) {
+	// Determine the 1st target PSF (either AUTO or defined by PSPHOT.STACK.TARGET.PSF.FWHM)
+	// NOTE: this also set the full list of target FWHMs (options->targetSeeing)
+        if (!psphotStackPSF(config, options)) {
             psError(psErrorCodeLast(), false, "Unable to determine output PSF.");
             return false;
         }
-        psMetadataAddPtr(config->arguments, PS_LIST_TAIL, "PSF.TARGET", PS_DATA_UNKNOWN, "Target PSF for stack", options->psf);
-        options->targetSeeing = pmPSFtoFWHM(options->psf, 0.5 * options->numCols, 0.5 * options->numRows); // FWHM for target
-        psLogMsg("psphotStack", PS_LOG_INFO, "Target seeing FWHM: %f\n", options->targetSeeing);
-
-	// XXX is this needed to supply the psf to psphot??
-        // pmChip *outChip = pmFPAfileThisChip(config->files, view, "PPSTACK.TARGET.PSF"); // Output chip
-        // psMetadataAddPtr(outChip->analysis, PS_LIST_TAIL, "PSPHOT.PSF", PS_DATA_UNKNOWN, "Target PSF", options->psf);
-        // outChip->data_exists = true;
     }
 
@@ -63,4 +57,5 @@
 
 // convolve the image to match desired PSF
+// XXX is this code consistent with 'convolve' = false?
 bool psphotStackMatchPSFsReadout (pmConfig *config, const pmFPAview *view, psphotStackOptions *options, int index) {
 
@@ -106,19 +101,28 @@
     // Image Matching (PSFs or just flux)
     if (options->convolve) {
-	matchKernel(config, readoutOut, readoutSrc, options, index);
+	if (!matchKernel(config, readoutOut, readoutSrc, options, index)){
+	    psError(psErrorCodeLast(), false, "Unable to match image PSF in readout.");
+	    return false;
+	}
 	saveMatchData(readoutOut, options, index);
     }
-    rescaleData(readoutOut, config, options, index);
+
+    // renormalize the stack variances to have sigma = 1.0
+    if (!psphotStackRenormaliseVariance(config, readoutOut)) {
+        psError(psErrorCodeLast(), false, "Unable to renormalise variance.");
+        return false;
+    }
 
     // save the output fwhm values in the readout->analysis.  we may have / will have multiple output PSF sizes,
     // so we save this in a vector.  if the vector is not yet defined, create it
-    bool mdok = false;
-    psVector *fwhmValues = psMetadataLookupVector(&mdok, readoutOut->analysis, "STACK.PSF.FWHM.VALUES");
-    if (!fwhmValues) {
-	fwhmValues = psVectorAllocEmpty(10, PS_TYPE_F32);
-	psMetadataAddVector(readoutOut->analysis, PS_LIST_TAIL, "STACK.PSF.FWHM.VALUES", PS_META_REPLACE, "PSF sizes", fwhmValues);
-	psFree(fwhmValues); // drops the extra copy
+    // NOTE: fwhmValues as defined here has 1 + nMatched PSF : 0 == unmatched
+    psVector *fwhmValues = psVectorAllocEmpty(10, PS_TYPE_F32);
+    psVectorAppend(fwhmValues, NAN); // XXX this corresponds to the unmatched image set
+    for (int i = 0; i < options->targetSeeing->n; i++) {
+	psVectorAppend(fwhmValues, options->targetSeeing->data.F32[i]);
     }
-    psVectorAppend(fwhmValues, options->targetSeeing);
+    psMetadataAddVector(readoutSrc->analysis, PS_LIST_TAIL, "STACK.PSF.FWHM.VALUES", PS_META_REPLACE, "PSF sizes", fwhmValues);
+    psMetadataAddVector(readoutOut->analysis, PS_LIST_TAIL, "STACK.PSF.FWHM.VALUES", PS_META_REPLACE, "PSF sizes", fwhmValues);
+    psFree(fwhmValues); // drops the extra copy
 
     return true;
Index: branches/meh_branches/ppstack_test/psphot/src/psphotStackMatchPSFsNext.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotStackMatchPSFsNext.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotStackMatchPSFsNext.c	(revision 33415)
@@ -1,3 +1,26 @@
 # include "psphotInternal.h"
+
+// NOTE : element 0 of fwhmValues if the unmatched image,  
+
+int psphotStackMatchPSFsEntries (pmConfig *config, const pmFPAview *view, const char *filerule) {
+
+    int nRadialEntries = 0;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, 0); // File of interest
+    psAssert (file, "missing file?");
+    
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+    
+    bool status = false;
+    psVector *fwhmValues = psMetadataLookupVector(&status, readout->analysis, "STACK.PSF.FWHM.VALUES");
+    if (!fwhmValues) {
+	return 1;
+    }
+    
+    nRadialEntries = fwhmValues->n;
+    return nRadialEntries;
+}
 
 // smooth the input image to match the next target PSF
@@ -5,25 +28,6 @@
 // and that the smoothing can use a 1D Gaussian kernel of width sqrt(TARGET^2 - CURRENT^2)
 // each subsequent call
-bool psphotStackMatchPSFsNext(bool *smoothAgain, pmConfig *config, const pmFPAview *view, const char *filerule, int lastSize)
+bool psphotStackMatchPSFsNext(pmConfig *config, const pmFPAview *view, const char *filerule, int lastSize)
 {
-    bool status = true;
-
-    // select the appropriate recipe information
-    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
-    psAssert (recipe, "missing recipe?");
-
-    psVector *fwhmValues = psMetadataLookupVector(&status, recipe, "PSPHOT.STACK.TARGET.PSF.FWHM"); // Magnitude offsets
-    if (!status) {
-	// must not be a vector, only one value requested
-	*smoothAgain = false;
-	return true;
-    }
-
-    if (lastSize + 1 >= fwhmValues->n) {
-	// all done with target FWHM values 
-	*smoothAgain = false;
-	return true;
-    }
-
     int num = psphotFileruleCount(config, filerule);
 
@@ -33,6 +37,6 @@
     // loop over the available readouts
     for (int i = 0; i < num; i++) {
-	if (!psphotStackMatchPSFsNextReadout (config, view, filerule, i, recipe, fwhmValues, lastSize)) {
-            psError (PSPHOT_ERR_CONFIG, false, "failed to smooth image %s (%d) to target PSF %f", filerule, i, fwhmValues->data.F32[lastSize+1]);
+	if (!psphotStackMatchPSFsNextReadout (config, view, filerule, i, lastSize)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to smooth image %s (%d) to target PSF", filerule, i);
 	    psImageConvolveSetThreads(oldThreads);
 	    return false;
@@ -44,7 +48,9 @@
 }
 
-bool psphotStackMatchPSFsNextReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe, psVector *fwhmValues, int lastSize) {
+bool psphotStackMatchPSFsNextReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, int lastSize) {
 
     bool status = false;
+
+    psTimerStart ("psphot.smooth");
 
     // find the currently selected readout
@@ -58,4 +64,8 @@
     psphotVisualShowImage(readout);
 
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
     // 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
@@ -66,4 +76,11 @@
         psWarning("PEAKS_MIN_GAUSS is not set in recipe; using default value");
         minGauss = 0.5;
+    }
+
+    psVector *fwhmValues = psMetadataLookupVector(&status, readout->analysis, "STACK.PSF.FWHM.VALUES");
+    psAssert (fwhmValues, "need target PSFs");
+
+    if (lastSize + 1 >= fwhmValues->n) {
+	return true;
     }
 
@@ -128,11 +145,4 @@
     // psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "SIGNIFICANCE_SCALE_FACTOR", PS_META_REPLACE, "Signicance scale factor", factor);
 
-    // save the output fwhm values in the readout->analysis.  we may have / will have multiple output PSF sizes,
-    // so we save this in a vector.  if the vector is not yet defined, create it
-
-    psVector *fwhmValuesOut = psMetadataLookupVector(&status, readout->analysis, "STACK.PSF.FWHM.VALUES");
-    psAssert(fwhmValuesOut, "should already exist..");
-    psVectorAppend(fwhmValuesOut, targetFWHM);
-
     // do not generate a PSF if we already were supplied one
     pmPSF *psfOld = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
Index: branches/meh_branches/ppstack_test/psphot/src/psphotStackMatchPSFsPrepare.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotStackMatchPSFsPrepare.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotStackMatchPSFsPrepare.c	(revision 33415)
@@ -4,5 +4,5 @@
 bool determineSeeing (pmPSF *psf, psphotStackOptions *options, int index);
 
-// set up the stacking parameters
+// set up the PSF-matching parameters describing the input images
 bool psphotStackMatchPSFsPrepare (pmConfig *config, const pmFPAview *view, psphotStackOptions *options, int index) {
 
Index: branches/meh_branches/ppstack_test/psphot/src/psphotStackMatchPSFsUtils.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotStackMatchPSFsUtils.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotStackMatchPSFsUtils.c	(revision 33415)
@@ -2,30 +2,10 @@
 # define ARRAY_BUFFER 16                 // Number to add to array at a time
 
-// XXX better name
-bool readImage(psImage **target, // Target for image
-	       const char *name, // Name of FITS file
-	       const pmConfig *config // Configuration
-    )
-{
-    psString resolved = pmConfigConvertFilename(name, config, false, false); // Resolved filename
-    psFits *fits = psFitsOpen(resolved, "r");
-    psFree(resolved);
-    if (!fits) {
-        psError(PSPHOT_ERR_IO, false, "Unable to open previously produced image: %s", name);
-        return false;
-    }
-    psImage *image = psFitsReadImage(fits, psRegionSet(0,0,0,0), 0); // Image of interest
-    if (!image) {
-        psError(PSPHOT_ERR_IO, false, "Unable to read previously produced image: %s", name);
-        psFitsClose(fits);
-        return false;
-    }
-    psFitsClose(fits);
-
-    psFree(*target);
-    *target = image;
-
-    return true;
-}
+psVector *SetOptWidths (bool *optimum, psMetadata *recipe);
+pmReadout *makeFakeReadout(pmConfig *config, pmReadout *raw, psArray *sources, pmPSF *psf, psImageMaskType maskVal, int fullSize);
+bool saveMatchData (pmReadout *readout, psphotStackOptions *options, int index);
+bool matchKernel(pmConfig *config, pmReadout *cnv, pmReadout *raw, psphotStackOptions *options, int index);
+bool dumpImageDiff(pmReadout *readoutConv, pmReadout *readoutFake, pmReadout *readoutRef, int index, char *rootname);
+bool dumpImage(pmReadout *readoutOut, pmReadout *readoutRef, int index, char *rootname);
 
 // Get coordinates from a source
@@ -148,6 +128,6 @@
 
 // Renormalise a readout's variance map
-bool stackRenormaliseReadout(const pmConfig *config, // Configuration
-                             pmReadout *readout      // Readout to renormalise
+bool psphotStackRenormaliseVariance(const pmConfig *config, // Configuration
+			      pmReadout *readout      // Readout to renormalise
     )
 {
@@ -180,59 +160,4 @@
     return pmReadoutVarianceRenormalise(readout, maskBad, num, minValid, maxValid);
 }
-
-// This is a hack to use the temporary convolved images and kernel generated previously.
-// This makes the 'matching' operation much faster, allowing debugging of the stack process easier.
-// It implicitly assumes the output root name is the same between invocations.
-
-# if (0)
-bool loadKernel (pmConfig *config, pmReadout *readoutCnv, psphotStackOptions *options, int index) {
-
-    // Read the convolution kernel from the saved file
-    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PPSTACK.CONV.KERNEL", index);
-    psAssert(file, "Require file");
-
-    pmFPAview *view = pmFPAviewAlloc(0); // View to readout of interest
-    view->chip = view->cell = view->readout = 0;
-    psString filename = pmFPAfileNameFromRule(file->filerule, file, view); // Filename of interest
-
-    // Read convolution kernel data
-    psString resolved = pmConfigConvertFilename(filename, config, false, false); // Resolved filename
-    psFree(filename);
-    psFits *fits = psFitsOpen(resolved, "r"); // FITS file for subtraction kernel
-    psFree(resolved);
-    if (!fits || !pmReadoutReadSubtractionKernels(readoutCnv, fits)) {
-	psError(PSPHOT_ERR_IO, false, "Unable to read previously produced kernel");
-	psFitsClose(fits);
-	return false;
-    }
-    psFitsClose(fits);
-
-    // read the convolved pixels (image, mask, variance) -- names are pre-defined
-    if (!readImage(&readoutCnv->image,    options->convImages->data[index],    config) ||
-	!readImage(&readoutCnv->mask,     options->convMasks->data[index],     config) ||
-	!readImage(&readoutCnv->variance, options->convVariances->data[index], config)) {
-	psError(PSPHOT_ERR_IO, false, "Unable to read previously produced image.");
-	return false;
-    }
-
-    // XXX ??? not sure what is happening here -- consult Paul
-    psRegion *region = psMetadataLookupPtr(NULL, readoutCnv->analysis, PM_SUBTRACTION_ANALYSIS_REGION); // Convolution region
-    pmSubtractionKernels *kernels = psMetadataLookupPtr(NULL, readoutCnv->analysis, PM_SUBTRACTION_ANALYSIS_KERNEL);
-
-    pmSubtractionAnalysis(readoutCnv->analysis, NULL, kernels, region, readoutCnv->image->numCols, readoutCnv->image->numRows);
-
-    psKernel *kernel = pmSubtractionKernel(kernels, 0.0, 0.0, false); // Convolution kernel
-
-    // update the covariance matrix 
-    // XXX why is this needed if we have correctly read the saved data?
-    bool oldThreads = psImageCovarianceSetThreads(true);              // Old thread setting
-    psKernel *covar = psImageCovarianceCalculate(kernel, readoutCnv->covariance); // Covariance matrix
-    psImageCovarianceSetThreads(oldThreads);
-    psFree(readoutCnv->covariance);
-    readoutCnv->covariance = covar;
-    psFree(kernel);
-    return true;
-}
-# endif
 
 bool dumpImage(pmReadout *readoutOut, pmReadout *readoutRef, int index, char *rootname) {
@@ -362,6 +287,6 @@
 	widthsCopy = psVectorCopy(NULL, widths, PS_TYPE_F32); // Copy of kernel widths
 
-	// we need to register the FWHM values for use downstream 
-	pmSubtractionSetFWHMs(options->targetSeeing, options->inputSeeing->data.F32[index]);
+	// we need to register the FWHM values for use by pmSubtraction code
+	pmSubtractionSetFWHMs(options->targetSeeing->data.F32[0], options->inputSeeing->data.F32[index]);
 
 	pmSubtractionParamScaleOptions(scale, scaleRef, scaleMin, scaleMax);
@@ -469,90 +394,4 @@
 }
 
-// Kernel normalisation for convolved readout
-bool renormKernel(pmReadout *readout, psphotStackOptions *options, int index) {
-
-    double sum = 0.0;           // Sum of chi^2
-    int num = 0;                // Number of measurements of chi^2
-    psString regex = NULL;      // Regular expression
-    psStringAppend(&regex, "^%s$", PM_SUBTRACTION_ANALYSIS_NORM);
-    psMetadataIterator *iter = psMetadataIteratorAlloc(readout->analysis, PS_LIST_HEAD, regex);
-    psFree(regex);
-    psMetadataItem *item = NULL;// Item from iteration
-    while ((item = psMetadataGetAndIncrement(iter))) {
-	assert(item->type == PS_TYPE_F32);
-	float norm = item->data.F32; // Normalisation
-	sum += norm;
-	num++;
-    }
-    psFree(iter);
-    float conv = sum/num;       // Mean normalisation from convolution
-    float stars = powf(10.0, -0.4 * options->norm->data.F32[index]); // Normalisation from stars
-    float renorm =  stars / conv; // Renormalisation to apply
-    psLogMsg("psphotStack", PS_LOG_INFO, "Renormalising image %d by %f (kernel: %f, stars: %f)\n", index, renorm, conv, stars);
-
-    psBinaryOp(readout->image, readout->image, "*", psScalarAlloc(renorm, PS_TYPE_F32));
-    psBinaryOp(readout->variance, readout->variance, "*", psScalarAlloc(PS_SQR(renorm), PS_TYPE_F32));
-    return true;
-}
-
-// adjust scaling for readout (remove background, ..., determine weighting)
-bool rescaleData(pmReadout *readout, pmConfig *config, psphotStackOptions *options, int index) {
-
-    psMetadata *stackRecipe = psMetadataLookupMetadata(NULL, config->recipes, "PPSTACK"); // ppStack recipe
-    psAssert(stackRecipe, "We've thrown an error on this before.");
-
-    // Look up appropriate values from the ppSub recipe
-    psMetadata *subRecipe = psMetadataLookupMetadata(NULL, config->recipes, "PPSUB"); // PPSUB recipe
-    psAssert(subRecipe, "recipe missing");
-
-    psString maskValStr = psMetadataLookupStr(NULL, subRecipe, "MASK.VAL"); // Name of bits to mask going in
-    psString maskBadStr = psMetadataLookupStr(NULL, stackRecipe, "MASK.BAD"); // Name of bits to mask for bad
-
-    psImageMaskType maskVal = pmConfigMaskGet(maskValStr, config); // Bits to mask going in to pmSubtractionMatch
-    psImageMaskType maskBad = pmConfigMaskGet(maskBadStr, config); // Bits to mask for bad pixels
-
-    // Ensure the background value is zero
-    psStats *bg = psStatsAlloc(PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV); // Statistics for background
-    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS); // Random number generator
-
-    // XXX why is this in config->arguments and not recipe?
-    if (!psMetadataLookupBool(NULL, config->arguments, "PPSTACK.SKIP.BG.SUB")) {
-	if (!psImageBackground(bg, NULL, readout->image, readout->mask, maskVal | maskBad, rng)) {
-	    psAbort("Can't measure background for image.");
-	    // XXX we used to clear error: why is this acceptable? psErrorClear(); 
-	}
-
-	float value = psStatsGetValue(bg, PS_STAT_ROBUST_MEDIAN);
-	float stdev = psStatsGetValue(bg, PS_STAT_ROBUST_STDEV);
-
-	psLogMsg("psphotStack", PS_LOG_INFO, "Correcting convolved image background by %lf (+/- %lf)", value, stdev);
-	psBinaryOp(readout->image, readout->image, "-", psScalarAlloc(value, PS_TYPE_F32));
-    }
-
-    if (!stackRenormaliseReadout(config, readout)) {
-        psFree(rng);
-        psFree(bg);
-        return false;
-    }
-
-    // Measure the variance level for the weighting
-    if (psMetadataLookupBool(NULL, stackRecipe, "WEIGHTS")) {
-        if (!psImageBackground(bg, NULL, readout->variance, readout->mask, maskVal | maskBad, rng)) {
-            psError(PSPHOT_ERR_DATA, false, "Can't measure mean variance for image.");
-            psFree(rng);
-            psFree(bg);
-            return false;
-        }
-        options->weightings->data.F32[index] = 1.0 / (psStatsGetValue(bg, PS_STAT_ROBUST_MEDIAN) * psImageCovarianceFactor(readout->covariance));
-    } else {
-        options->weightings->data.F32[index] = 1.0;
-    }
-    psLogMsg("psphotStack", PS_LOG_INFO, "Weighting for image %d is %f\n", index, options->weightings->data.F32[index]);
-
-    psFree(rng);
-    psFree(bg);
-    return true;
-}
-
 # define NOISE_FRACTION 0.01             // Set minimum flux to this fraction of noise
 # define SOURCE_MASK (PM_SOURCE_MODE_FAIL | PM_SOURCE_MODE_DEFECT | PM_SOURCE_MODE_SATURATED | PM_SOURCE_MODE_CR_LIMIT | PM_SOURCE_MODE_EXT_LIMIT) // Mask to apply to input sources
Index: branches/meh_branches/ppstack_test/psphot/src/psphotStackObjects.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotStackObjects.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotStackObjects.c	(revision 33415)
@@ -47,2 +47,107 @@
     return true;
 }
+
+// mark good vs bad objects
+bool psphotStackObjectsSelectForAnalysis (pmConfig *config, const pmFPAview *view, const char *filerule, psArray *objects) {
+
+    bool status = false;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, 0); // use the 0-index image to represent the image area
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    // 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");
+
+    // S/N limit to perform full non-linear fits
+    float SN_LIM_PETRO  = psMetadataLookupF32 (&status, recipe, "EXTENDED_SOURCE_SN_LIM");
+    float SN_LIM_RADIAL = psMetadataLookupF32 (&status, recipe, "RADIAL_APERTURES_SN_LIM");
+
+    bool doPetroStars   = psMetadataLookupBool (&status, recipe, "PETROSIAN_FOR_STARS");
+
+    for (int i = 0; i < objects->n; i++) {
+        pmPhotObj *object = objects->data[i];
+	if (!object) continue;
+	if (!object->sources) continue;
+
+	// we check each source for an object and keep the object if any source is valid
+
+	bool keepObjectRadial = false;
+	bool keepObjectPetro = false;
+	for (int j = 0; j < object->sources->n; j++) {
+
+	    pmSource *source = object->sources->data[j];
+	    if (!source) continue;
+	    if (!source->peak) continue;
+
+	    // skip PSF-like and non-astronomical objects
+	    if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+	    if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+	    if (source->mode & PM_SOURCE_MODE_DEFECT) continue;
+	    if (source->mode & PM_SOURCE_MODE_SATSTAR) continue;
+	    
+	    // limit selection by analysis region (this automatically apply
+	    if (source->peak->x < AnalysisRegion.x0) continue;
+	    if (source->peak->y < AnalysisRegion.y0) continue;
+	    if (source->peak->x > AnalysisRegion.x1) continue;
+	    if (source->peak->y > AnalysisRegion.y1) continue;
+	    
+	    // SN limit tests for RADIAL APERTURES:
+	    bool skipSourceRadial = false;
+	    if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
+		skipSourceRadial = (source->moments->KronFlux < SN_LIM_RADIAL * source->moments->KronFluxErr);
+	    } else {
+		skipSourceRadial = (sqrt(source->peak->detValue) < SN_LIM_RADIAL);
+	    }
+	    if (!skipSourceRadial) keepObjectRadial = true;
+
+	    // SN limit tests for PETRO
+	    bool skipSourcePetro = false;
+	    if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
+		skipSourcePetro = (source->moments->KronFlux < SN_LIM_PETRO * source->moments->KronFluxErr);
+	    } else {
+		skipSourcePetro = doPetroStars ? (sqrt(source->peak->detValue) < SN_LIM_PETRO) : true;
+	    }
+	    if (!skipSourcePetro) keepObjectPetro = true;
+
+	    keepObjectPetro = true;
+	}
+
+	for (int j = 0; j < object->sources->n; j++) {
+	    pmSource *source = object->sources->data[j];
+	    if (!source) continue;
+	    if (!source->peak) continue;
+
+	    // we have to set a bit in either case to tell psphotExtendedSourceAnalysis to
+	    // avoid the single-detection tests
+
+	    if (keepObjectPetro) {
+		source->tmpFlags |=  PM_SOURCE_TMPF_PETRO_KEEP;
+		source->tmpFlags &= ~PM_SOURCE_TMPF_PETRO_SKIP;
+	    } else {
+		source->tmpFlags |=  PM_SOURCE_TMPF_PETRO_SKIP;
+		source->tmpFlags &= ~PM_SOURCE_TMPF_PETRO_KEEP;
+	    }	    
+
+	    if (keepObjectRadial) {
+		source->tmpFlags |=  PM_SOURCE_TMPF_RADIAL_KEEP;
+		source->tmpFlags &= ~PM_SOURCE_TMPF_RADIAL_SKIP;
+	    } else {
+		source->tmpFlags |=  PM_SOURCE_TMPF_RADIAL_SKIP;
+		source->tmpFlags &= ~PM_SOURCE_TMPF_RADIAL_KEEP;
+	    }	    
+	}
+    }
+
+    psLogMsg ("psphot", PS_LOG_INFO, "marked good vs bad objects\n");
+    return true;
+}
Index: branches/meh_branches/ppstack_test/psphot/src/psphotStackOptions.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotStackOptions.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotStackOptions.c	(revision 33415)
@@ -19,5 +19,5 @@
     psFree (options->norm);
     psFree (options->matchChi2);
-    psFree (options->weightings);
+    psFree (options->targetSeeing);
 
     return;
@@ -36,5 +36,5 @@
     options->convolve = false;
     options->convolveSource = PSPHOT_CNV_SRC_NONE;
-    options->targetSeeing = NAN;
+    options->targetSeeing = NULL;
 
     options->psfs        = psArrayAlloc(num);
@@ -47,5 +47,4 @@
     options->norm        = psVectorAlloc(num, PS_TYPE_F32);
     options->matchChi2   = psVectorAlloc(num, PS_TYPE_F32); // chi^2 for stamps when matching
-    options->weightings  = psVectorAlloc(num, PS_TYPE_F32); // Combination weightings for images (1/noise^2)
 
     psVectorInit(options->inputMask,   0);
@@ -53,5 +52,4 @@
     psVectorInit(options->norm,        NAN);
     psVectorInit(options->matchChi2,   NAN);
-    psVectorInit(options->weightings,  NAN);
 
     return options;
Index: branches/meh_branches/ppstack_test/psphot/src/psphotStackPSF.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotStackPSF.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotStackPSF.c	(revision 33415)
@@ -1,8 +1,13 @@
 # include "psphotInternal.h"
 
-pmPSF *psphotStackPSF(const pmConfig *config, int numCols, int numRows, const psArray *psfs, const psVector *inputMask)
-{
+// determine the 1st target PSF (either AUTO or defined by PSPHOT.STACK.TARGET.PSF.FWHM)
+bool psphotStackPSF(const pmConfig *config, psphotStackOptions *options)  {
+
     bool mdok = false;
-    pmPSF *psf = NULL;
+
+    int numCols = options->numCols;
+    int numRows = options->numRows;
+    psArray *psfs = options->psfs;
+    psVector *inputMask = options->inputMask;
 
     // Get the recipe values
@@ -17,4 +22,6 @@
 	
     char *psfModel = psMetadataLookupStr(NULL, recipe, "PSF.MODEL"); // Model for PSF
+
+    options->targetSeeing = psVectorAllocEmpty(4, PS_TYPE_F32);
 
     if (autoPSF) {
@@ -38,58 +45,70 @@
 
 	// Solve for the target PSF
-	psf = pmPSFEnvelope(numCols, numRows, psfs, psfInstances, psfRadius, psfModel, psfOrder, psfOrder, maskVal);
-	if (!psf) {
+	options->psf = pmPSFEnvelope(numCols, numRows, psfs, psfInstances, psfRadius, psfModel, psfOrder, psfOrder, maskVal);
+	if (!options->psf) {
 	    psError(PSPHOT_ERR_PSF, false, "Unable to determine output PSF.");
-	    return NULL;
+	    return false;
 	}
 
-    } else {
-
-	// externally-defined PSF
-	// XXX need to test for compatibility of target with inputs
-
-	float targetFWHM = psMetadataLookupF32 (&mdok, psphotRecipe, "PSPHOT.STACK.TARGET.PSF.FWHM");
-	if (!mdok) {
-	    psVector *fwhmValues = psMetadataLookupVector(&mdok, psphotRecipe, "PSPHOT.STACK.TARGET.PSF.FWHM"); // Magnitude offsets
-	    psAssert (mdok, "missing psphot recipe value PSPHOT.STACK.TARGET.PSF.FWHM");
-	    targetFWHM = fwhmValues->data.F32[0];
-	}
-
-	// measured scale factors (fwhm = Sxx * 2.35 * scaleFactor / sqrt(2.0))
-	// GAUSS  : 1.000
-	// PGAUSS : 1.006
-	// QGAUSS : 1.151
-	// RGAUSS : 0.883
-	// PS1_V1 : 1.134
-	
-	float scaleFactor = NAN;
-	if (!strcmp(psfModel, "PS_MODEL_GAUSS")) {
-	    scaleFactor = 1.000;
-	}
-	if (!strcmp(psfModel, "PS_MODEL_PGAUSS")) {
-	    scaleFactor = 1.0006;
-	}
-	if (!strcmp(psfModel, "PS_MODEL_QGAUSS")) {
-	    scaleFactor = 1.151;
-	}
-	if (!strcmp(psfModel, "PS_MODEL_RGAUSS")) {
-	    scaleFactor = 0.883;
-	}
-	if (!strcmp(psfModel, "PS_MODEL_PS1_V1")) {
-	    scaleFactor = 1.134;
-	}
-	psAssert (isfinite(scaleFactor), "invalid model for PSF"); 
-
-	float Sxx = sqrt(2.0)*targetFWHM / 2.35 / scaleFactor;
-
-	// XXX probably should make the model type (and par 7) optional from recipe
-	// psf = pmPSFBuildSimple(psfModel, Sxx, Sxx, 0.0, 1.0);
-	psf = pmPSFBuildSimple(psfModel, Sxx, Sxx, 0.0, 0.2);
-	if (!psf) {
-	    psError(PSPHOT_ERR_PSF, false, "Unable to build dummy PSF.");
-	    return NULL;
-	}
+        psMetadataAddPtr(config->arguments, PS_LIST_TAIL, "PSF.TARGET", PS_DATA_UNKNOWN, "Target PSF for stack", options->psf);
+        float targetSeeing = pmPSFtoFWHM(options->psf, 0.5 * options->numCols, 0.5 * options->numRows); // FWHM for target
+        psVectorAppend(options->targetSeeing, targetSeeing);
+        psLogMsg("psphotStack", PS_LOG_INFO, "Target seeing FWHM (auto-scaled): %f\n", targetSeeing);
+	return true;
     }
 
-    return psf;
+    // externally-defined PSF
+    // XXX need to test for compatibility of target with inputs
+
+    // is a single target FWHM specified, or a set of values?  set up the vector options->targetSeeing and the local 1st value
+    float targetSeeing = psMetadataLookupF32 (&mdok, psphotRecipe, "PSPHOT.STACK.TARGET.PSF.FWHM");
+    if (!mdok) {
+	psVector *fwhmValues = psMetadataLookupVector(&mdok, psphotRecipe, "PSPHOT.STACK.TARGET.PSF.FWHM"); // Magnitude offsets
+	psAssert (mdok, "missing psphot recipe value PSPHOT.STACK.TARGET.PSF.FWHM");
+	for (int i = 0; i < fwhmValues->n; i++) {
+	    psVectorAppend(options->targetSeeing, fwhmValues->data.F32[i]);
+	}	    
+	targetSeeing = fwhmValues->data.F32[0];
+    } else {
+        psVectorAppend(options->targetSeeing, targetSeeing);
+    }
+
+    // measured scale factors (fwhm = Sxx * 2.35 * scaleFactor / sqrt(2.0))
+    // GAUSS  : 1.000
+    // PGAUSS : 1.006
+    // QGAUSS : 1.151
+    // RGAUSS : 0.883
+    // PS1_V1 : 1.134
+	
+    float scaleFactor = NAN;
+    if (!strcmp(psfModel, "PS_MODEL_GAUSS")) {
+	scaleFactor = 1.000;
+    }
+    if (!strcmp(psfModel, "PS_MODEL_PGAUSS")) {
+	scaleFactor = 1.0006;
+    }
+    if (!strcmp(psfModel, "PS_MODEL_QGAUSS")) {
+	scaleFactor = 1.151;
+    }
+    if (!strcmp(psfModel, "PS_MODEL_RGAUSS")) {
+	scaleFactor = 0.883;
+    }
+    if (!strcmp(psfModel, "PS_MODEL_PS1_V1")) {
+	scaleFactor = 1.134;
+    }
+    psAssert (isfinite(scaleFactor), "invalid model for PSF"); 
+
+    float Sxx = sqrt(2.0)*targetSeeing / 2.35 / scaleFactor;
+
+    // XXX probably should make the model type (and par 7) optional from recipe
+    // psf = pmPSFBuildSimple(psfModel, Sxx, Sxx, 0.0, 1.0);
+    options->psf = pmPSFBuildSimple(psfModel, Sxx, Sxx, 0.0, 0.2);
+    if (!options->psf) {
+	psError(PSPHOT_ERR_PSF, false, "Unable to build dummy PSF.");
+	return false;
+    }
+
+    psMetadataAddPtr(config->arguments, PS_LIST_TAIL, "PSF.TARGET", PS_DATA_UNKNOWN, "Target PSF for stack", options->psf);
+    psLogMsg("psphotStack", PS_LOG_INFO, "Target seeing FWHM (1 of %ld): %f\n", options->targetSeeing->n, targetSeeing);
+    return true;
 }
Index: branches/meh_branches/ppstack_test/psphot/src/psphotStackReadout.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotStackReadout.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotStackReadout.c	(revision 33415)
@@ -44,4 +44,8 @@
 bool psphotStackReadout (pmConfig *config, const pmFPAview *view) {
 
+    psArray *objects = NULL; // used below after 'pass1finish' label
+
+    // 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");
 
@@ -56,12 +60,11 @@
     // optional break-point for processing
     char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
-    PS_ASSERT_PTR_NON_NULL (breakPt, false);
-
-    // XXX or do I set OUT to be a pmFPAfile pointing at the input of interest?
+    psAssert (breakPt, "configuration error: set BREAK_POINT");
+
+    // we have 3 relevant files: RAW (unconvolved), CNV (convolved stack), OUT (psf-matched stack)
+    // select which image (RAW or CNV) is used for analysis (RAW always used for detection)
     bool useRaw = psMetadataLookupBool (NULL, recipe, "PSPHOT.STACK.USE.RAW");
     char *STACK_SRC = useRaw ? STACK_RAW : STACK_CNV;
-    char *STACK_DET = STACK_RAW; // XXX optionally allow this to be CNV?
-
-    // we have 3 relevant files: RAW, CNV, OUT 
+    char *STACK_DET = STACK_RAW;
 
     // set the photcode for each image
@@ -71,6 +74,5 @@
     }
 
-    // Generate the mask and weight images
-    // XXX this should be done before we perform the convolutions
+    // Generate the mask and weight images (if not supplied) and set mask bits
     if (!psphotSetMaskAndVariance (config, view, STACK_DET)) {
 	return psphotReadoutCleanup (config, view, STACK_SRC);
@@ -81,6 +83,4 @@
 
     // generate a background model (median, smoothed image)
-    // XXX I think this is not defined correctly for an array of images.
-    // XXX probably need to subtract the model (same model?) for both RAW and OUT
     if (!psphotModelBackground (config, view, STACK_DET)) {
 	return psphotReadoutCleanup (config, view, STACK_SRC);
@@ -89,8 +89,12 @@
 	return psphotReadoutCleanup (config, view, STACK_SRC);
     }
+    if (!psphotSubtractBackground (config, view, STACK_SRC)) {
+	return psphotReadoutCleanup (config, view, STACK_SRC);
+    }
     if (!strcasecmp (breakPt, "BACKMDL")) {
 	return psphotReadoutCleanup (config, view, STACK_SRC);
     }
 
+    // also make the chisq detection image
     if (!psphotStackChisqImage(config, view, STACK_DET, STACK_SRC)) {
         psError (PSPHOT_ERR_UNKNOWN, false, "failure to generate chisq image");
@@ -109,5 +113,7 @@
     }
 
-    // copy the detections from DET to SRC 
+    // If DET and SRC are different images, copy the detections from DET to SRC.  This 'copy'
+    // is just a copy of the container pointer; the sources on both DET and SRC are the same
+    // memory objects
     if (strcmp(STACK_SRC, STACK_DET)) {
 	if (!psphotCopySources (config, view, STACK_SRC, STACK_DET)) {
@@ -122,77 +128,35 @@
         return psphotReadoutCleanup (config, view, STACK_SRC);
     }
-
-    if (!strcasecmp (breakPt, "TEST1")) {
-	return psphotReadoutCleanup (config, view, STACK_SRC);
-    }
-
+    if (!strcasecmp (breakPt, "PEAKS")) {
+	return psphotReadoutCleanup (config, view, STACK_SRC);
+    }
+    // psphotDumpTest (config, view, STACK_SRC);
     psMemDump("sourcestats");
-
-    // generate the objects (object unify the sources from the different images)
-    // XXX this could just match the detections for the chisq image, and not bother measuring the
-    // source stats in that case...
-    psArray *objects = psphotMatchSources (config, view, STACK_SRC);
-
-    psMemDump("matchsources");
-
-    if (!strcasecmp (breakPt, "TEST2")) {
-	psFree(objects);
-	return psphotReadoutCleanup (config, view, STACK_SRC);
-    }
-
-    // construct sources for the newly-generated sources (from other images)
-    if (!psphotSourceStats (config, view, STACK_SRC, false)) { // pass 1
-	psFree(objects);
-        psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate sources");
-        return psphotReadoutCleanup (config, view, STACK_SRC);
-    }
-
-    psMemDump("sourcestats");
-
-    // 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, STACK_SRC);
-    // }
-
-    // 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, STACK_SRC);
-    // }
 
     // classify sources based on moments, brightness
     // only run this on detections from the input images, not chisq image
     if (!psphotRoughClass (config, view, STACK_SRC)) {
-	psFree(objects);
         psError (PSPHOT_ERR_UNKNOWN, false, "failed to determine rough classifications");
 	return psphotReadoutCleanup (config, view, STACK_SRC);
     }
+
     // if we were not supplied a PSF model, determine the IQ stats here (detections->newSources)
     // only run this on detections from the input images, not chisq image
     if (!psphotImageQuality (config, view, STACK_SRC)) { // pass 1
-	psFree(objects);
         psError (PSPHOT_ERR_UNKNOWN, false, "failed to measure image quality");
         return psphotReadoutCleanup (config, view, STACK_SRC);
     }
     if (!strcasecmp (breakPt, "MOMENTS")) {
-	psFree(objects);
-	return psphotReadoutCleanup (config, view, STACK_SRC);
-    }
-
-    // use bright stellar objects to measure PSF if we were supplied a PSF for any input file,
-    // this step is skipped
+	return psphotReadoutCleanup (config, view, STACK_SRC);
+    }
+
+    // use bright stellar objects to measure PSF
     if (!psphotChoosePSF (config, view, STACK_SRC, true)) { // pass 1
-	psFree(objects);
         psLogMsg ("psphot", 3, "failure to construct a psf model");
         return psphotReadoutCleanup (config, view, STACK_SRC);
     }
     if (!strcasecmp (breakPt, "PSFMODEL")) {
-	psFree(objects);
-        return psphotReadoutCleanup (config, view, STACK_SRC);
-    }
-
-    // construct an initial model for each object, set the radius to fitRadius, set circular fit mask
-    psphotGuessModels (config, view, STACK_SRC);
+        return psphotReadoutCleanup (config, view, STACK_SRC);
+    }
 
     // merge the newly selected sources into the existing list
@@ -200,33 +164,141 @@
     psphotMergeSources (config, view, STACK_SRC);
 
+    // Construct an initial model for each object, set the radius to fitRadius, set circular
+    // fit mask.  NOTE: only applied to sources without guess models
+    psphotGuessModels (config, view, STACK_SRC);
+
     // linear PSF fit to source peaks, subtract the models from the image (in PSF mask)
-    psphotFitSourcesLinearStack (config, objects, FALSE);
+    psphotFitSourcesLinear (config, view, STACK_SRC, false);
     psphotStackVisualFilerule(config, view, STACK_SRC);
 
+    // measure the radial profiles to the sky
+    psphotRadialProfileWings (config, view, STACK_SRC);
+
+    // re-measure the kron mags with models subtracted.  this pass starts with a circular
+    // window of size PSF_MOMENTS_RADIUS (same window used to measure the psf-scale moments)
+    // but iterates to an appropriately larger size
+    psphotKronIterate(config, view, STACK_SRC);
+
     // identify CRs and extended sources
-    psphotSourceSize (config, view, STACK_SRC, TRUE);
-
-    // XXX do we want to do a preliminary (unconvolved) model fit here, and then 
-    // do a second detection pass? (like standard psphot)
-
-    // measure aperture photometry corrections
-    if (!psphotApResid (config, view, STACK_SRC)) {
-	psFree (objects);
-        psLogMsg ("psphot", 3, "failed on psphotApResid");
-	return psphotReadoutCleanup (config, view, STACK_SRC);
-    }
+    psphotSourceSize (config, view, STACK_SRC, true);
+
+    // non-linear PSF and EXT fit to brighter sources
+    // replace model flux, adjust mask as needed, fit, subtract the models (full stamp)
+    psphotBlendFit (config, view, STACK_SRC); // pass 1 (detections->allSources)
+
+    // replace all sources (do NOT ignore subtraction state)
+    psphotReplaceAllSources (config, view, STACK_SRC, false); // pass 1 (detections->allSources)
+
+    // if we only do one pass, skip to extended source analysis
+    if (!strcasecmp (breakPt, "PASS1")) goto pass1finish;
+
+    // linear fit to include all sources (subtract again)
+    // NOTE : apply to ALL sources (extended + psf)
+    // NOTE 2 : this function subtracts the models from the given filerule (SRC), not DET
+    psphotFitSourcesLinear (config, view, STACK_SRC, true); // pass 2 (detections->allSources)
+
+    // NOTE: possibly re-measure background model here with objects subtracted / or masked
+
+    // NOTE: this block performs the 2nd pass low-significance PSF detection stage
+    { 
+	// if DET and SRC are different images, generate children sources for all sources in
+	// the SRC image.  This operation replaces the existing DETECTION container on DET
+	// which is currently a view to the one on SRC).  children sources go to
+	// det->allSources
+	if (strcmp(STACK_SRC, STACK_DET)) {
+	    psphotSourceChildren (config, view, STACK_DET, STACK_SRC); 
+
+	    //  subtract all sources from DET (this will subtract using the psf model for SRC, which
+	    //  will somewhat oversubtract the sources -- this is OK
+	    psphotRemoveAllSources (config, view, STACK_DET, false); // ignore subtraction state for sources
+	}
+
+	// add noise for subtracted objects
+	psphotAddNoise (config, view, STACK_DET); // 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, STACK_DET, false); // pass 2 (detections->peaks, detections->footprints)
+
+	// remove noise for subtracted objects (ie, return to normal noise level)
+	// NOTE: this needs to operate only on the OLD sources
+	psphotSubNoise (config, view, STACK_DET); // pass 1 (detections->allSources)
+
+	// if DET and SRC are different images, copy the detections from DET to SRC 
+	// (this operation just ensures the metadata container has a view on SRC as well
+	if (strcmp(STACK_SRC, STACK_DET)) {
+	    // replace all sources in DET
+	    psphotReplaceAllSources (config, view, STACK_DET, false); // ignore subtraction state for sources
+
+	    // copy the newly detected peaks from DET to SRC so SourceStats below can operate on them
+	    if (!psphotCopyPeaks (config, view, STACK_SRC, STACK_DET)) {
+		psError (PSPHOT_ERR_UNKNOWN, false, "failure in peak analysis");
+		return psphotReadoutCleanup (config, view, STACK_SRC);
+	    }
+	}
+
+	// define new sources based on only the new peaks & measure moments
+	// NOTE: new sources are saved on detections->newSources
+	psphotSourceStats (config, view, STACK_SRC, false); // pass 2 (detections->newSources)
+
+	// set source type
+	// NOTE: apply only to detections->newSources
+	if (!psphotRoughClass (config, view, STACK_SRC)) { // pass 2 (detections->newSources)
+	    psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+	    return psphotReadoutCleanup (config, view, STACK_SRC);
+	}
+
+	// replace all sources so fit below applies to all at once
+	// NOTE: apply only to OLD sources (which have been subtracted)
+	psphotReplaceAllSources (config, view, STACK_SRC, false); // pass 2
+
+	// merge the newly selected sources into the existing list
+	// NOTE: merge OLD and NEW
+	// XXX check on free of sources...
+	psphotMergeSources (config, view, STACK_SRC); // (detections->newSources + detections->allSources -> detections->allSources)
+
+	// Construct an initial model for each object, set the radius to fitRadius, set circular
+	// fit mask.  NOTE: only applied to sources without guess models
+	psphotGuessModels (config, view, STACK_SRC);
+    }
+
+pass1finish:
+
+    // generate the objects (objects unify the sources from the different images) NOTE: could
+    // this just match the detections for the chisq image, and not bother measuring the source
+    // stats in that case...?
+    objects = psphotMatchSources (config, view, STACK_SRC);
+    psMemDump("matchsources");
+
+    // Construct an initial model for each object, set the radius to fitRadius, set circular
+    // fit mask.  NOTE: only applied to sources without guess models
+    psphotGuessModels (config, view, STACK_SRC);
+
+    psphotStackObjectsUnifyPosition (objects);
+
+    psphotStackObjectsSelectForAnalysis (config, view, STACK_SRC, objects);
+
+    // NOTE: apply to ALL sources
+    psphotFitSourcesLinear (config, view, STACK_SRC, true); // pass 3 (detections->allSources)
+
+    // measure the radial profiles to the sky (only measures new objects)
+    psphotRadialProfileWings (config, view, STACK_SRC);
+
+    // re-measure the kron mags with models subtracted
+    // psphotKronMasked(config, view, STACK_SRC);
+    psphotKronIterate(config, view, STACK_SRC);
+
+    // measure source size for the remaining sources
+    // NOTE: applies only to NEW (unmeasured) sources
+    psphotSourceSize (config, view, STACK_SRC, false); // pass 2 (detections->allSources)
 
     psMemDump("psfstats");
 
-    psphotStackObjectsUnifyPosition (objects);
-
     // measure elliptical apertures, petrosians (objects sorted by S/N)
-    psphotExtendedSourceAnalysisByObject (config, objects, view, STACK_SRC); // pass 1 (detections->allSources)
+    // psphotExtendedSourceAnalysisByObject (config, objects, view, STACK_SRC); // pass 1 (detections->allSources)
+    psphotExtendedSourceAnalysis (config, view, STACK_SRC); // pass 1 (detections->allSources)
 
     // measure non-linear extended source models (exponential, deVaucouleur, Sersic) (sources sorted by S/N)
     psphotExtendedSourceFits (config, view, STACK_SRC); // pass 1 (detections->allSources)
-
-    // calculate source magnitudes
-    psphotMagnitudes(config, view, STACK_SRC);
 
     // create source children for the OUT filerule (for radial aperture photometry) 
@@ -238,8 +310,13 @@
     }
 
+    // measure circular, radial apertures (objects sorted by S/N)
+    // this forces photometry on the undetected sources from other images
+    psphotRadialApertures (config, view, STACK_SRC, 0); // XXX entry 0 == unmatched?
     psMemDump("extmeas");
 
-    bool smoothAgain = true;
-    for (int nMatchedPSF = 0; smoothAgain; nMatchedPSF++) {
+    int nRadialEntries = psphotStackMatchPSFsEntries(config, view, STACK_OUT);
+
+    for (int entry = 1; entry < nRadialEntries; entry++) {
+	// NOTE: entry 0 is the unmatched image set
 
 	// re-measure the PSF for the smoothed image (using entries in 'allSources')
@@ -253,15 +330,28 @@
 
 	// measure circular, radial apertures (objects sorted by S/N)
-	psphotRadialAperturesByObject (config, objectsRadial, view, STACK_OUT, nMatchedPSF); 
+	// entry 0 == unmatched? pass entry + 1?
+	psphotRadialApertures (config, view, STACK_OUT, entry); 
 
 	// replace the flux in the image so it is returned to its original state
-	psphotReplaceAllSources (config, view, STACK_OUT);
+	psphotReplaceAllSources (config, view, STACK_OUT, false);
 
 	// smooth to the next FWHM, or set 'smoothAgain' to false if no more 
-	psphotStackMatchPSFsNext(&smoothAgain, config, view, STACK_OUT, nMatchedPSF);
+	psphotStackMatchPSFsNext(config, view, STACK_OUT, entry);
 	psMemDump("matched");
     }
 
-    if (0 && !psphotEfficiency(config, view, STACK_OUT)) {
+    // measure aperture photometry corrections
+    if (!psphotApResid (config, view, STACK_SRC)) {
+	psFree (objects);
+        psLogMsg ("psphot", 3, "failed on psphotApResid");
+	return psphotReadoutCleanup (config, view, STACK_SRC);
+    }
+
+    // calculate source magnitudes
+    psphotMagnitudes(config, view, STACK_SRC);
+
+    // XXX NOTE: this function wants to have the PSF of the image, but we (so far) only measure the 
+    // PSF of the SRC image.  can we fake it by generating the PSF for DET as well (up above)? 
+    if (false && !psphotEfficiency(config, view, STACK_DET)) {
         psErrorStackPrint(stderr, "Unable to determine detection efficiencies from fake sources");
         psErrorClear();
Index: branches/meh_branches/ppstack_test/psphot/src/psphotSubtractBackground.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotSubtractBackground.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotSubtractBackground.c	(revision 33415)
@@ -103,5 +103,5 @@
         psphotSaveImage (NULL, image, name);
     }
-    psLogMsg ("psphot", PS_LOG_INFO, "subtracted background model: %f sec\n", psTimerMark ("psphot.background"));
+    psLogMsg ("psphot", PS_LOG_WARN, "subtracted background model: %f sec\n", psTimerMark ("psphot.background"));
 
     // the pmReadout selected in this function are all view on entries in config->files
Index: branches/meh_branches/ppstack_test/psphot/src/psphotTest.c
===================================================================
--- branches/meh_branches/ppstack_test/psphot/src/psphotTest.c	(revision 32133)
+++ branches/meh_branches/ppstack_test/psphot/src/psphotTest.c	(revision 33415)
@@ -85,5 +85,5 @@
 
     // wait for the threads to finish and manage results
-    if (!psThreadPoolWait (true)) {
+    if (!psThreadPoolWait (true, true)) {
         fprintf (stderr, "failure to run FillImage (2)");
         exit (1);
Index: branches/meh_branches/ppstack_test/psphot/test/tap_dense.pro
===================================================================
--- branches/meh_branches/ppstack_test/psphot/test/tap_dense.pro	(revision 33415)
+++ branches/meh_branches/ppstack_test/psphot/test/tap_dense.pro	(revision 33415)
@@ -0,0 +1,788 @@
+#!/usr/bin/env mana
+# -*-sh-*-
+
+$KAPA = kapa -noX
+
+# config for ppImage to generate chip, mask, weight
+$ppImageConfig = -recipe PPIMAGE PPIMAGE_N
+$ppImageConfig = $ppImageConfig -Db BACKGROUND T
+$ppImageConfig = $ppImageConfig -Db CHIP.FITS T
+$ppImageConfig = $ppImageConfig -Db CHIP.MASK.FITS T
+$ppImageConfig = $ppImageConfig -Db CHIP.VARIANCE.FITS T
+$ppImageConfig = $ppImageConfig -Db BASE.FITS F
+$ppImageConfig = $ppImageConfig -Db VARIANCE.BUILD T
+$ppImageConfig = $ppImageConfig -Db PHOTOM F
+
+# basic options for the these images (filter, location, obstype)
+$BaseOptions = -type OBJECT -filter r -skymags 20.86 -ra 270.70 -dec -23.70 -pa 0.0
+$BaseOptions = $BaseOptions -Df PSASTRO:DVO.GETSTAR.MAX.RHO 50000.0
+
+# options for the reference image
+$RefOptions = $BaseOptions -exptime 100.0 -seeing 1.0 -D PSF.MODEL PS_MODEL_GAUSS -Df STARS.DENSITY 10.0 -Df STARS.SIGMA.LIM 0.5
+
+# options for the repeated images
+$FakeOptions = $BaseOptions -exptime 30.0
+  
+# basic config for ppSim with randomly distributed stars and gridded galaxies
+$RealConfig = -camera SIMTEST -recipe PPSIM STACKTEST.RUN -D PSASTRO:PSASTRO.CATDIR catdir.ref
+$RealConfig = $RealConfig -Db STARS.FAKE T -Db STARS.REAL F -Db MATCH.DENSITY F -Db PSF.CONVOLVE F
+$RealConfig = $RealConfig -Df STARS.DENSITY 100.0
+$RealConfig = $RealConfig -Df STARS.SIGMA.LIM 2.5
+$RealConfig = $RealConfig -Db GALAXY.FAKE F -Df GALAXY.MAG 17.0
+$RealConfig = $RealConfig -Db GALAXY.GRID F -D GALAXY.MODEL PS_MODEL_SERSIC
+$RealConfig = $RealConfig -Df GALAXY.ARATIO.MIN 0.5
+$RealConfig = $RealConfig -Df GALAXY.ARATIO.MAX 0.5
+$RealConfig = $RealConfig -Df GALAXY.THETA.MAX 180
+$RealConfig = $RealConfig -Df GALAXY.INDEX.MIN 1
+$RealConfig = $RealConfig -Df GALAXY.INDEX.MAX 1
+$RealConfig = $RealConfig -Di GALAXY.GRID.DX 120
+$RealConfig = $RealConfig -Di GALAXY.GRID.DY 120
+
+# sample alternate options:
+# $ppSimOptions = $FakeOptions -D PSF.MODEL PS_MODEL_PS1_V1
+# $ppSimOptions = $FakeOptions -Df PSF.ARATIO 1.2
+# $ppSimOptions = $FakeOptions -Df PSF.THETA +30.0
+$ppSimOptions = $FakeOptions -D PSF.MODEL PS_MODEL_GAUSS
+
+list fwhm 
+ 1.0 
+ 1.1 
+ 1.2 
+ 1.5
+end
+
+macro fulltest
+  if ($0 != 2)
+    echo "USAGE: fulltest (Npass)"
+    break
+  end
+
+  mkref 
+
+  local i Npass imname stname
+
+  $Npass = $1
+
+  $imname = image.fg
+  $stname = stack.fg
+
+  for i 0 $Npass
+    sprintf N "%02d" $i
+    onestack $N
+  end
+end
+
+macro summary.stats
+  if ($0 != 2)
+    echo "USAGE: summary.stats (Npass)"
+    break
+  end
+
+  local i Npass base stack imname stname
+
+  $Npass = $1
+
+  exec /bin/rm -f psf.image.summary.stats 
+  exec /bin/rm -f psf.stack.inputs.summary.stats
+  exec /bin/rm -f psf.stack.conv.summary.stats
+  exec /bin/rm -f psf.stack.unconv.summary.stats
+
+  $imname = image.fg
+  $stname = stack.fg
+
+  for i 0 $Npass
+    sprintf base  "$imname.%02d/$imname.stats" $i
+    sprintf stack "$stname.%02d/$stname" $i
+    echo $i $base $stack
+    exec grep PSF_INST_MAG $base                   >> psf.image.summary.stats
+    exec grep PSF_INST_MAG $stack.conv.stats       >> psf.stack.inputs.summary.stats
+    exec grep PSF_INST_MAG $stack.pht.stats        >> psf.stack.conv.summary.stats
+    exec grep PSF_INST_MAG $stack.unconv.pht.stats >> psf.stack.unconv.summary.stats
+  end
+end
+
+# create a reference database of fake stars to be used by ppSim below
+macro mkref
+  exec rm -rf catdir.ref
+  exec rm -f refimage.fits
+  
+  # create an image with fake sources and insert the resulting cmf file into a dvodb
+  $RefConfig = -camera SIMTEST -recipe PPSIM STACKTEST.MAKE -D PSASTRO:PSASTRO.CATDIR catdir.ref -Db PSF.CONVOLVE T
+
+  exec ppSim $RefOptions $RefConfig refimage -nx 3000 -ny 3000
+  
+  file synth.photcodes found
+  if (not($found))
+    echo "making photcodes file"
+    mkphotcodes synth.photcodes
+  end
+
+  exec addstar -D CAMERA simtest -D CATDIR catdir.ref -accept-astrom -photcode SYNTH.r -D PHOTCODE_FILE synth.photcodes refimage.cmf
+  exec relphot -averages -D CATDIR catdir.ref -update -region 260 280 -33 -13
+end
+
+macro onestack
+  if ($0 != 2)
+    echo "USAGE: onestack (N)"
+    break
+  end
+
+  mkstack image.$1/image stack.$1/stack
+  ckstack image.$1/image stack.$1/stack
+  # rmstack image.$1/image stack.$1/stack
+end
+
+# create a realistic distribution of fake stars, GAUSS PSF
+macro mkstack
+  if ($0 != 3)
+    echo "USAGE: mkstack (rawbase) (stackbase)"
+    break
+  end
+
+  local i base stack
+
+  $base = $1
+  $stack = $2
+
+  dirname $base -var dir  
+  mkdir $dir
+
+  dirname $stack -var dir  
+  mkdir $dir
+
+  exec echo "INPUT MULTI" > $stack.mdc
+  exec echo "" >> $stack.mdc
+
+  for i 0 $fwhm:n
+    mkexp $base.$i $fwhm:$i
+
+    exec echo "INPUT METADATA" >> $stack.mdc
+    exec echo "IMAGE STR $base.$i.wrp.fits" >> $stack.mdc
+    exec echo "MASK STR $base.$i.wrp.mask.fits" >> $stack.mdc
+    exec echo "VARIANCE STR $base.$i.wrp.wt.fits" >> $stack.mdc
+    exec echo "PSF STR $base.$i.wrp.psf" >> $stack.mdc
+    exec echo "SOURCES STR $base.$i.wrp.cmf" >> $stack.mdc
+    exec echo "END" >> $stack.mdc
+    exec echo "" >> $stack.mdc
+  end
+
+  exec ppStack -threads 4 -input $stack.mdc $stack -Db TEMP.DELETE F -R PPSTACK.OUTPUT.VARIANCE FITS.TYPE NONE
+
+  basename $stack -var stackbase
+  for i 0 $fwhm:n
+    exec /bin/cp -f /tmp/$stackbase.$i.conv.im.fits $stack.$i.conv.im.fits
+    exec /bin/cp -f /tmp/$stackbase.$i.conv.var.fits $stack.$i.conv.var.fits
+    exec /bin/cp -f /tmp/$stackbase.$i.conv.mk.fits $stack.$i.conv.mk.fits
+  end  
+
+  # XXX note that the output convolved variance is missing the covariance
+  for i 0 $fwhm:n
+    exec psphot -file $stack.$i.conv.im.fits -mask $stack.$i.conv.mk.fits $stack.$i.conv
+  end  
+
+  # basic photometry for the stack and unconvolved stack
+  exec psphot -file $stack.fits -mask $stack.mask.fits -variance $stack.weight.fits $stack.pht
+  exec psphot -file $stack.unconv.fits -mask $stack.unconv.mask.fits -variance $stack.unconv.wt.fits $stack.unconv.pht
+end
+
+# run this on the files created with mkstack
+macro ckstack
+  if ($0 != 3)
+    echo "USAGE: mkstack (rawbase) (stackbase)"
+    break
+  end
+
+  local i ZPT base stack
+
+  $base = $1
+  $stack = $2
+
+  # generate plots and stats for the per-image chip analysis
+  # and the psf-matched convolved images
+  $ZPT = -2.5*log($fwhm:n)
+  for i 0 $fwhm:n
+    ckchip $base.$i.in.cmf $base.$i.cmf       $base.$i       0.0
+    ckwarp $base.$i.in.cmf $stack.$i.conv.cmf $stack.$i.conv $ZPT
+
+    stchip $base.$i.in.cmf $base.$i.cmf       $base.stats       0.0
+    stwarp $base.$i.in.cmf $stack.$i.conv.cmf $stack.conv.stats $ZPT
+
+    completeness $base.$i.in.cmf $base.$i.cmf $base.$i.complete.png
+  end
+
+  ckwarp $base.$i.in.cmf $stack.pht.cmf        $stack.pht        $ZPT
+  ckwarp $base.$i.in.cmf $stack.unconv.pht.cmf $stack.unconv.pht $ZPT
+
+  stwarp $base.$i.in.cmf $stack.pht.cmf        $stack.pht.stats  $ZPT
+  stwarp $base.$i.in.cmf $stack.unconv.pht.cmf $stack.unconv.pht.stats $ZPT
+end
+
+macro rmstack
+  if ($0 != 3)
+    echo "USAGE: mkstack (rawbase) (stackbase)"
+    break
+  end
+
+  local base stack
+  $base = $1
+  $stack = $2
+
+  for i 0 $fwhm:n
+    exec rm -f $base.$i.fits
+    exec rm -f $base.$i.ch.fits
+    exec rm -f $base.$i.ch.mk.fits
+    exec rm -f $base.$i.ch.wt.fits
+
+    exec rm -f $base.$i.wrp.fits
+    exec rm -f $base.$i.wrp.mask.fits
+    exec rm -f $base.$i.wrp.wt.fits
+
+    exec rm -f $stack.$i.conv.im.fits
+    exec rm -f $stack.$i.conv.mk.fits
+    exec rm -f $stack.$i.conv.wt.fits
+  end
+
+  exec rm -f $stack.fits
+  exec rm -f $stack.mask.fits
+  exec rm -f $stack.weight.fits
+
+  exec rm -f $stack.exp.fits
+  exec rm -f $stack.num.fits
+  exec rm -f $stack.expwt.fits
+
+  exec rm -f $stack.unconv.fits
+  exec rm -f $stack.unconv.mask.fits
+  exec rm -f $stack.unconv.wt.fits
+
+  exec rm -f $stack.unconv.exp.fits
+  exec rm -f $stack.unconv.num.fits
+  exec rm -f $stack.unconv.expwt.fits
+end
+
+# create a realistic distribution of fake stars, GAUSS PSF
+macro mkexp
+  if ($0 != 3)
+    echo "USAGE: mkexp basename fwhm"
+    break
+  end
+
+  local fwhm basename
+  $basename = $1
+  $fwhm = $2
+
+  # create the raw image
+  echo ppSim $ppSimOptions $RealConfig $basename -seeing $fwhm -nx 3000 -ny 3000
+  exec ppSim $ppSimOptions $RealConfig $basename -seeing $fwhm -nx 3000 -ny 3000
+  exec /bin/mv -f $basename.cmf $basename.in.cmf
+
+  # create the chip output
+  echo ppImage $ppImageConfig -file $basename.fits $basename
+  exec ppImage $ppImageConfig -file $basename.fits $basename
+end
+
+# compare two cmf files with extname Chip.psf 
+# things to compare:
+# * completeness (which sources in (1) are not detected in (2)
+# * positions (X_PSF, Y_PSF) 
+# * instrumental psf mags
+# * position errors (no input errors; use a model?)
+# * measured FWHM?
+# * kron mags (fluxes)
+# * etc, etc
+macro ckchip
+  if ($0 != 5)
+    echo "USAGE: ckchip (raw) (out) (output) (zpt_off)"
+    break
+  end
+
+  load.cmf $1 Chip.psf raw
+  load.cmf $2 Chip.psf out
+
+  # images generated with convolution will not have the right output positions
+  set X_raw = int(X_PSF_raw) + 0.5
+  set Y_raw = int(Y_PSF_raw) + 0.5
+  set M_raw = PSF_INST_MAG_raw + $4
+  set K_out = -2.5*log(KRON_FLUX_out)
+  match2d X_PSF_out Y_PSF_out X_PSF_raw Y_PSF_raw 1.5 -index1 index1 -index2 index2
+
+  local i NX NY nx ny N
+
+  device -n compare
+  resize 1000 1000
+
+  # plot trends as a function of mag
+  $NX = 2
+  $NY = 5
+  $nx = 0
+  $ny = 0
+  $N = 0
+  clear -s
+  for i 0 $pairs:n
+    section a$nx\$ny {$nx/$NX} {$ny/$NY} {1/$NX} {1/$NY}
+    show.pair $i
+    $ny ++
+    if ($ny == $NY)
+      $ny = 0
+      $nx ++
+    end
+    if ($nx == $NX)
+      png -name $3.$N.png
+      clear -s
+      $nx = 0
+      $ny = 0
+      $N ++
+    end
+  end
+
+  # plot (input - output) vs mag
+end
+
+macro stchip
+  if ($0 != 5)
+    echo "USAGE: stchip (raw) (out) (output) (zpt_off)"
+    break
+  end
+
+  load.cmf $1 Chip.psf raw
+  load.cmf $2 Chip.psf out
+
+  # images generated with convolution will not have the right output positions
+  set X_raw = int(X_PSF_raw) + 0.5
+  set Y_raw = int(Y_PSF_raw) + 0.5
+  set M_raw = PSF_INST_MAG_raw + $4
+  set K_out = -2.5*log(KRON_FLUX_out)
+  match2d X_PSF_out Y_PSF_out X_PSF_raw Y_PSF_raw 1.5 -index1 index1 -index2 index2
+
+  local i
+
+  for i 0 $spairs:n
+    stats.pair $i $3
+  end
+end
+
+# compare chip to warp
+macro ckwarp
+  if ($0 != 5)
+    echo "USAGE: ckwarp (raw) (out) (output) (zpt_off)"
+    break
+  end
+
+  load.cmf $1 Chip.psf raw
+  load.cmf $2 SkyChip.psf out
+  set X_raw = int(X_PSF_raw) + 0.5
+  set Y_raw = int(Y_PSF_raw) + 0.5
+  set M_raw = PSF_INST_MAG_raw + $4
+  set K_out = -2.5*log(KRON_FLUX_out)
+  match2d X_PSF_out Y_PSF_out X_PSF_raw Y_PSF_raw 1.0 -index1 index1 -index2 index2
+
+  local i nx ny NX NY N
+
+  device -n compare
+  resize 1000 1000
+
+  # plot trends as a function of mag
+  $NX = 2
+  $NY = 5
+  $nx = 0
+  $ny = 0
+  $N = 0
+  clear -s
+
+  for i 0 $pairs:n
+    section a$nx\$ny {$nx/$NX} {$ny/$NY} {1/$NX} {1/$NY}
+    show.pair $i
+    $ny ++
+    if ($ny == $NY)
+      $ny = 0
+      $nx ++
+    end
+    if ($nx == $NX)
+      png -name $3.$N.png
+      clear -s
+      $nx = 0
+      $ny = 0
+      $N ++
+    end
+  end
+end
+
+macro stwarp
+  if ($0 != 5)
+    echo "USAGE: stwarp (raw) (out) (output) (zpt_off)"
+    break
+  end
+
+  load.cmf $1 Chip.psf raw
+  load.cmf $2 SkyChip.psf out
+
+  # images generated with convolution will not have the right output positions
+  set X_raw = int(X_PSF_raw) + 0.5
+  set Y_raw = int(Y_PSF_raw) + 0.5
+  set M_raw = PSF_INST_MAG_raw + $4
+  set K_out = -2.5*log(KRON_FLUX_out)
+  match2d X_PSF_out Y_PSF_out X_PSF_raw Y_PSF_raw 1.5 -index1 index1 -index2 index2
+
+  local i
+
+  for i 0 $spairs:n
+    stats.pair $i $3
+  end
+end
+
+macro stats.pair
+  if ($0 != 3)
+    echo "USAGE: stats.pair (N) (output)"
+    break
+  end
+
+  list word -split $spairs:$1
+  if ($word:n != 8)
+    echo "invalid pair $1"
+    break
+  end
+
+  $Nr = $word:3
+
+  reindex v1 = $word:0 using index1
+  reindex v2 = $word:1 using index2
+  reindex rv = $word:2 using index$Nr
+
+  set delta = v1 - v2
+  subset d1 = delta if ($word:4 < rv) && (rv < $word:5) && (abs(delta) < $word:7)
+  subset d2 = delta if ($word:5 < rv) && (rv < $word:6) && (abs(delta) < $word:7)
+
+  vstats -q d1 -sigma-clip 3.0
+  $M1 = $MEAN
+  $S1 = $SIGMA
+  vstats -q d2 -sigma-clip 3.0
+  $M2 = $MEAN
+  $S2 = $SIGMA
+
+  output $2
+  fprintf "%-18s  %7.4f %7.4f  %7.4f %7.4f" $word:0  $M1 $S1  $M2 $S2
+  output stdout
+end
+
+macro show.pair
+  if ($0 != 2)
+    echo "USAGE: show.pair (N)"
+    break
+  end
+
+  list word -split $pairs:$1
+  if ($word:n != 7)
+    echo "invalid pair $1"
+    break
+  end
+
+  $Nr = $word:3
+
+  reindex v1 = $word:0 using index1
+  if ("$word:6" == "V") 
+    reindex v2 = $word:1 using index2
+  end
+  if ("$word:6" == "S") 
+    set v2 = $word:1 + zero(index1)
+  end
+  reindex rv = $word:2 using index$Nr
+
+  set delta = v1 - v2
+  if (("$word:4" == "def") || ("$word:5" == "def"))
+    lim rv delta; box; plot rv delta
+  else
+    lim rv $word:4 $word:5; box; plot rv delta
+  end
+  label -y '$word:0' -x '$word:2'
+end
+
+# This list is used to compare a pair of vectors (sans error) or a
+# vector and an expected (constant) value.  The last field defines a
+# vector or constant for the comparison.  It is assumed that the
+# vector sets have been loaded and matched with match2d to generate
+# index vectors 'index1' and index2'.  The macro 'show.pair' generates
+# a plot of the range vector vs (v1 - v2).  The indices for v1, v2 are
+# index1 and 2 respectively.  The index for the range vector is defined
+# by the integer following that vector.  the y-limits of the plot are
+# given by the last two numbers
+list pairs
+  X_PSF_out             X_raw                 PSF_INST_MAG_raw 2 -1.01 1.01 V
+  Y_PSF_out             Y_raw                 PSF_INST_MAG_raw 2 -1.01 1.01 V
+  X_PSF_out             X_PSF_raw             PSF_INST_MAG_raw 2 -1.01 1.01 V
+  Y_PSF_out             Y_PSF_raw             PSF_INST_MAG_raw 2 -1.01 1.01 V
+  X_PSF_SIG_out         X_PSF_SIG_raw         PSF_INST_MAG_raw 2 -1.01 1.01 V
+  Y_PSF_SIG_out         Y_PSF_SIG_raw         PSF_INST_MAG_raw 2 -1.01 1.01 V
+  #PSF_INST_MAG_out      PSF_INST_MAG_raw      PSF_INST_MAG_raw 2 -1.01 1.01 V
+  PSF_INST_MAG_out      M_raw                 PSF_INST_MAG_raw 2 -1.01 1.01 V
+  PSF_INST_MAG_SIG_out  PSF_INST_MAG_SIG_raw  PSF_INST_MAG_raw 2 -1.01 1.01 V
+  #PSF_INST_FLUX_out     PSF_INST_FLUX_raw     PSF_INST_MAG_raw 2   def  def V
+  #PSF_INST_FLUX_SIG_out PSF_INST_FLUX_SIG_raw PSF_INST_MAG_raw 2   def  def V
+  AP_MAG_out            M_raw                 PSF_INST_MAG_raw 2 -1.01 1.01 V
+  AP_MAG_RAW_out        M_raw                 PSF_INST_MAG_raw 2 -1.01 1.01 V
+  AP_MAG_RADIUS_out     0.0                   PSF_INST_MAG_raw 2 -0.01 20.1 S
+  SKY_out               0.0                   PSF_INST_MAG_raw 2   def  def S
+  SKY_SIGMA_out         0.0                   PSF_INST_MAG_raw 2   def  def S
+  PSF_CHISQ_out         1.0                   PSF_INST_MAG_raw 2   def  def S
+  CR_NSIGMA_out         0.0   		      PSF_INST_MAG_raw 2   def  def S
+  EXT_NSIGMA_out        0.0   		      PSF_INST_MAG_raw 2 -5.01 5.01 S
+  PSF_MAJOR_out         0.0   		      PSF_INST_MAG_raw 2 -0.01 5.01 S
+  PSF_MINOR_out         0.0   		      PSF_INST_MAG_raw 2 -0.01 5.01 S
+  PSF_THETA_out         0.0   		      PSF_INST_MAG_raw 2 -1.61 1.61 S
+  PSF_QF_out            0.0   		      PSF_INST_MAG_raw 2 -0.10 1.10 S
+  PSF_QF_PERFECT_out    0.0   		      PSF_INST_MAG_raw 2 -0.10 1.10 S
+  PSF_NDOF_out          0.0   		      PSF_INST_MAG_raw 2  def  def  S
+  PSF_NPIX_out          0.0   		      PSF_INST_MAG_raw 2  def  def  S
+  MOMENTS_XX_out        0.0   		      PSF_INST_MAG_raw 2 -0.01 3.01 S
+  MOMENTS_XY_out        0.0   		      PSF_INST_MAG_raw 2 -3.01 3.01 S
+  MOMENTS_YY_out        0.0   		      PSF_INST_MAG_raw 2 -0.01 3.01 S
+  MOMENTS_M3C_out       0.0   		      PSF_INST_MAG_raw 2 -3.01 3.01 S
+  MOMENTS_M3S_out       0.0   		      PSF_INST_MAG_raw 2 -3.01 3.01 S
+  MOMENTS_M4C_out       0.0   		      PSF_INST_MAG_raw 2 -2.01 2.01 S
+  MOMENTS_M4S_out       0.0   		      PSF_INST_MAG_raw 2 -2.01 2.01 S
+  MOMENTS_R1_out        0.0   		      PSF_INST_MAG_raw 2 -5.01 5.01 S
+  MOMENTS_RH_out        0.0   		      PSF_INST_MAG_raw 2 -5.01 5.01 S
+  K_out                 M_raw  		      PSF_INST_MAG_raw 2  def  def  V
+  KRON_FLUX_ERR_out     0.0   		      PSF_INST_MAG_raw 2  def  def  S
+  KRON_FLUX_INNER_out   0.0   		      PSF_INST_MAG_raw 2  def  def  S
+  KRON_FLUX_OUTER_out   0.0   		      PSF_INST_MAG_raw 2  def  def  S
+# CAL_PSF_MAG          CAL_PSF_MAG          none Mraw 2 -1.01 1.01 V
+# CAL_PSF_MAG_SIG      CAL_PSF_MAG_SIG      none Mraw 2 -1.01 1.01 V
+# RA_PSF               RA_PSF               none Mraw 2 -1.01 1.01 V
+# DEC_PSF              DEC_PSF              none Mraw 2 -1.01 1.01 V
+# PEAK_FLUX_AS_MAG     PEAK_FLUX_AS_MAG     none Mraw 2 -1.01 1.01 V
+# FLAGS                FLAGS                0.0  Mraw 2 -1.01 1.01 S
+# FLAGS2               FLAGS2               0.0  Mraw 2 -1.01 1.01 S
+end
+
+macro load.cmf
+  if ($0 != 4)
+   echo "load.cmf (filename) (ext) (label)"
+   break
+  end
+
+  data $1
+
+  # create the list of fields to load
+  delete -q myFields
+  for i 0 $fields:n
+    if ($?myFields) 
+      $myFields = $myFields $fields:$i
+    else
+      $myFields = $fields:$i
+    end
+  end
+
+  read -fits $2 $myFields
+
+  # rename the loaded vectors appending the supplied lable
+  for i 0 $fields:n
+    set $fields:$i\_$3 = $fields:$i
+    delete $fields:$i
+  end
+end
+
+# this list defines the fields to be loaded from file
+list fields
+  X_PSF              
+  Y_PSF              
+  X_PSF_SIG          
+  Y_PSF_SIG          
+  PSF_INST_MAG       
+  PSF_INST_MAG_SIG   
+  PSF_INST_FLUX      
+  PSF_INST_FLUX_SIG  
+  AP_MAG             
+  AP_MAG_RAW         
+  AP_MAG_RADIUS      
+  SKY                
+  SKY_SIGMA          
+  PSF_CHISQ          
+  CR_NSIGMA          
+  EXT_NSIGMA         
+  PSF_MAJOR          
+  PSF_MINOR          
+  PSF_THETA          
+  PSF_QF             
+  PSF_QF_PERFECT     
+  PSF_NDOF           
+  PSF_NPIX           
+  MOMENTS_XX         
+  MOMENTS_XY         
+  MOMENTS_YY         
+  MOMENTS_M3C        
+  MOMENTS_M3S        
+  MOMENTS_M4C        
+  MOMENTS_M4S        
+  MOMENTS_R1         
+  MOMENTS_RH         
+  KRON_FLUX          
+  KRON_FLUX_ERR      
+  KRON_FLUX_INNER    
+  KRON_FLUX_OUTER    
+#   CAL_PSF_MAG      
+#   CAL_PSF_MAG_SIG  
+#   RA_PSF           
+#   DEC_PSF          
+#   PEAK_FLUX_AS_MAG 
+#  FLAGS            
+#  FLAGS2           
+end
+
+# use these cmf entries to measure average stats of the given pairs
+list spairs
+  X_PSF_out             X_raw                 PSF_INST_MAG_raw 2 -15 -10 -8.5  0.1
+  Y_PSF_out             Y_raw                 PSF_INST_MAG_raw 2 -15 -10 -8.5  0.1
+  PSF_INST_MAG_out      M_raw                 PSF_INST_MAG_raw 2 -15 -10 -8.5  0.1
+  AP_MAG_out            M_raw                 PSF_INST_MAG_raw 2 -15 -10 -8.5  0.1
+end
+
+#  # XXX this is a hack: the cmf file created by ppSim is not
+#  # compatible with the image for warp (because one is a chip-mosaic
+#  # and the other is not)
+#  exec rm -f fix.hdr
+#  output fix.hdr
+#  echo "PSMOSAIC= 'CHIP    '           / Mosaicked level"
+#  output stdout 
+# fix the header to be compatible with the chip file (to avoid running psphot)
+# exec fits_insert $basename.cmf fix.hdr
+
+macro completeness
+ if ($0 != 4)
+   echo "USAGE: completeness (raw) (out) (output)"
+   break
+ end
+
+ load.cmf $1 Chip.psf raw
+ load.cmf $2 Chip.psf out
+ set X_raw = int(X_PSF_raw) + 0.5
+ set Y_raw = int(Y_PSF_raw) + 0.5
+ match2d X_PSF_raw Y_PSF_raw X_PSF_out Y_PSF_out 1.5 -index1 index1 -index2 index2 -closest
+
+ histogram PSF_INST_MAG_raw nMag -16.0 -3.0 0.25 -range dMag
+ set fMag = zero(dMag) 
+ for i 0 {dMag[]-1}
+  set inrange = (PSF_INST_MAG_raw > dMag[$i]) && (PSF_INST_MAG_raw <= dMag[$i+1])
+  subset all = index1 if (inrange)
+  subset got = index1 if (inrange) && (index1 >= 0)
+  if (all[] == 0)
+    fMag[$i] = 0
+  else 
+   fMag[$i] = got[] / all[]
+  end
+ end
+
+ device -n complete
+ resize 1000 600
+
+ clear -s
+
+ section default 0 0 1 1
+ lim dMag fMag; clear; box -ypad 5 +ypad 5 -ticks 1110; plot -x 1 dMag fMag
+ label -x mag_inst -y det_frac 
+
+ set found = (index1 >= 0)
+ plot PSF_INST_MAG_raw found -c red
+
+ set ldmag = log(PSF_INST_MAG_SIG_raw)
+ section overlay 0 0 1 1; lim dMag -5 1.2; box -ypad 5 +ypad 5 -ticks 1011 -labels 1001; plot PSF_INST_MAG_raw ldmag
+ label +y log(S/N)
+
+ png -name $3
+end
+
+macro show.dpair
+  if ($0 != 2)
+    echo "USAGE: show.dpair (N)"
+    break
+  end
+
+  list word -split $dpairs:$1
+  if ($word:n != 7)
+    echo "invalid dpair $1"
+    break
+  end
+
+  $Nr = $word:4
+
+  reindex v1 = $word:0 using index1
+  reindex dv = $word:1 using index1
+  reindex v2 = $word:2 using index2
+  reindex rv = $word:3 using index$Nr
+
+  set delta = (v1 - v2) / dv
+  if (("$word:5" == "def") || ("$word:6" == "def"))
+    lim rv delta; box; plot rv delta
+  else
+    lim rv $word:5 $word:6; box; plot rv delta
+  end
+  label -y '$word:0' -x '$word:3'
+end
+
+# this list is used to compare a pair of vectors with an error it is
+# assumed that the vector sets have been loaded and matched with
+# match2d to generate index vectors 'index1' and index2'.  the macro
+# show.dpair generates a plot of the range vector vs (v1 - v2) / dv1.
+# The indices for v1, dv1, and v2 are index1,1, and 2 respectively.  The
+# index for the range vector is defined by the integer following that
+# vector.  The y-limits of the plot are given by the last two numbers
+# (use 'def') for the full default range of the delta vector
+list dpairs
+  # v1              dv                v2   range 
+  X_PSF             X_PSF_SIG         Xraw Mraw  2 -10.0 10.0
+  Y_PSF             Y_PSF_SIG         Yraw Mraw  2 -10.0 10.0
+  PSF_INST_MAG      PSF_INST_MAG_SIG  Mraw Mraw  2 -10.0 10.0
+  PSF_INST_FLUX     PSF_INST_FLUX_SIG Fraw Mraw  2 -10.0 10.0
+  AP_MAG            PSF_INST_MAG_SIG  Mraw Mraw  2 -10.0 10.0
+  AP_MAG_RAW        PSF_INST_MAG_SIG  Mraw Mraw  2 -10.0 10.0
+end
+
+# if we run this test as a stand-alone program somewhere, we may need to create a local copy of the photcode file:
+macro mkphotcodes
+  if ($0 != 2)
+    echo "USAGE: mkphotcodes (filename)"
+    break
+  end
+
+  exec /bin/rm -f $1
+  output $1
+  echo "#                                           airmass      color                         astrometry  mag    photom  astrom mask    photom mask"
+  echo "# code  name                type    zero  slope offset c1    c2   slope   zero  equiv  sys scale   scale  sys     poor   bad     poor   bad"
+  echo "  1     g_SYNTH              sec   0.000  0.000 0.000     1     3 0.0000     0    21   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000"
+  echo "  2     r_SYNTH              sec   0.000  0.000 0.000     2     3 0.0000     0    22   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000"
+  echo "  3     i_SYNTH              sec   0.000  0.000 0.000     2     3 0.0000     0    23   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000"
+  echo "  4     z_SYNTH              sec   0.000  0.000 0.000     3     4 0.0000     0    24   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000"
+  echo "  5     y_SYNTH              sec   0.000  0.000 0.000     4     5 0.0000     0    25   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000"
+  echo "  3001  SYNTH.g              ref   0.000  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000"
+  echo "  3002  SYNTH.r              ref   0.000  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000"
+  echo "  3003  SYNTH.i              ref   0.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000"
+  echo "  3004  SYNTH.z              ref   0.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000"
+  echo "  3005  SYNTH.y              ref   0.000  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000"
+  output stdout
+end
+
+macro ckradialflux
+ data $1
+ read -fits Chip.xrad X_APER Y_APER PSF_FWHM APER_FLUX APER_FLUX_ERR APER_FLUX_STDEV APER_FILL
+ read -fits Chip.psf X_PSF Y_PSF PSF_INST_MAG
+ set mag = PSF_INST_MAG
+ # XXX include nradii in header
+ for i 0 11
+   set mA$i = -2.5*log(APER_FLUX:$i)
+   set dM$i = mA$i - PSF_INST_MAG
+   vstat dM$i
+   $DM$i = $MEDIAN
+ end
+ for i 1 11
+   $j = $i - 1
+   echo {$DM$i - $DM$j}
+ end
+
+ lim mag -2 2; clear; box
+ for i 0 11
+   plot mag dM$i -c black
+ end
+end
+
+if ($SCRIPT)
+  fulltest 4
+  exit 0
+end
