Index: branches/simmosaic_branches/psModules/src/astrom/pmAstrometryModel.c
===================================================================
--- branches/simmosaic_branches/psModules/src/astrom/pmAstrometryModel.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/astrom/pmAstrometryModel.c	(revision 27839)
@@ -725,5 +725,5 @@
     double X = Xo + RX*cos(POS - To)*cos(Po) + RY*sin(POS - To)*sin(Po);
     double Y = Yo + RY*sin(POS - To)*cos(Po) - RX*cos(POS - To)*sin(Po);
-    psLogMsg ("psModules.astrom", 4, "Boresite coords on reference chip: %f, %f\n", X, Y);
+    psLogMsg ("psModules.astrom", 4, "Boresite coords on reference chip: %f, %f pix = %f, %f sky\n", X, Y, PM_DEG_RAD*RA, PM_DEG_RAD*DEC);
 
     // get reference chip from name
Index: branches/simmosaic_branches/psModules/src/astrom/pmAstrometryObjects.c
===================================================================
--- branches/simmosaic_branches/psModules/src/astrom/pmAstrometryObjects.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/astrom/pmAstrometryObjects.c	(revision 27839)
@@ -38,4 +38,7 @@
 #include "pmAstrometryVisual.h"
 
+// XXX this is defined in pmPSFtry.h, which makes no sense
+float psVectorSystematicError (psVector *residuals, psVector *errors, float clipFraction);
+
 #define PM_ASTROMETRYOBJECTS_DEBUG 1
 
@@ -105,12 +108,12 @@
         }
 
-	if (found1->data.S8[i]) {
-	    i++;
-	    continue;
-	}
-	if (found2->data.S8[j]) {
-	    j++;
-	    continue;
-	}
+        if (found1->data.S8[i]) {
+            i++;
+            continue;
+        }
+        if (found2->data.S8[j]) {
+            j++;
+            continue;
+        }
 
         jStart = j;
@@ -125,8 +128,8 @@
                 continue;
             }
-	    if (found2->data.S8[j]) {
-		j++;
-		continue;
-	    }
+            if (found2->data.S8[j]) {
+                j++;
+                continue;
+            }
 
             // got a match; add to output list
@@ -135,6 +138,6 @@
             psFree (match);
 
-	    found1->data.S8[i] = 1;
-	    found2->data.S8[j] = 1;
+            found1->data.S8[i] = 1;
+            found2->data.S8[j] = 1;
 
             j++;
@@ -193,6 +196,6 @@
     psArray *matches = match_lists(x1, y1, x2, y2, sorted1, sorted2, RADIUS); \
     \
-    psFree((void *)sorted1); \
-    psFree((void *)sorted2); \
+    psFree(sorted1); \
+    psFree(sorted2); \
     psFree(x1); \
     psFree(y1); \
@@ -283,5 +286,6 @@
             return results;
         }
-        psTrace ("psModules.astrom", 3, "x resid: %f +/- %f (%ld of %ld)\n", results->xStats->clippedMean, results->xStats->clippedStdev, results->xStats->clippedNvalues, x->n);
+        // psTrace ("psModules.astrom", 3, "x resid: %f +/- %f (%ld of %ld)\n", results->xStats->clippedMean, results->xStats->clippedStdev, results->xStats->clippedNvalues, x->n);
+        psTrace ("psModules.astrom", 3, "x resid: %f +/- %f (%ld of %ld)\n", results->xStats->robustMedian, results->xStats->robustStdev, results->xStats->clippedNvalues, x->n);
 
         if (!psVectorClipFitPolynomial2D (map->y, results->yStats, mask, 0xff, y, wt, X, Y)) {
@@ -296,8 +300,73 @@
             return results;
         }
-        psTrace ("psModules.astrom", 3, "y resid: %f +/- %f (%ld of %ld)\n", results->yStats->clippedMean, results->yStats->clippedStdev, results->yStats->clippedNvalues, y->n);
+        // psTrace ("psModules.astrom", 3, "y resid: %f +/- %f (%ld of %ld)\n", results->yStats->clippedMean, results->yStats->clippedStdev, results->yStats->clippedNvalues, y->n);
+        psTrace ("psModules.astrom", 3, "y resid: %f +/- %f (%ld of %ld)\n", results->yStats->robustMedian, results->yStats->robustStdev, results->yStats->clippedNvalues, y->n);
     }
     results->xStats->clipIter = stats->clipIter;
     results->yStats->clipIter = stats->clipIter;
+
+    // *** calculate the 90%-ile and the systematic scatter for each direction.
+
+    // generate the X residual vector
+    psVector *xFit = psPolynomial2DEvalVector (map->x, X, Y);
+    if (!xFit) abort();
+    psVector *xRes = (psVector *) psBinaryOp (NULL, x, "-", xFit);
+    if (!xRes) abort();
+    psFree (xFit);
+
+    psVector *yFit = psPolynomial2DEvalVector (map->y, X, Y);
+    if (!yFit) abort();
+    psVector *yRes = (psVector *) psBinaryOp (NULL, y, "-", yFit);
+    if (!yRes) abort();
+    psFree (yFit);
+
+    // extract a high-quality subset (unmasked, S/N > XXX) and position errors
+    // XXX for now, generate a position error based on the magnitude error
+    psVector *xErr     = psVectorAllocEmpty (match->n, PS_TYPE_F32);
+    psVector *yErr     = psVectorAllocEmpty (match->n, PS_TYPE_F32);
+    psVector *xResGood = psVectorAllocEmpty (match->n, PS_TYPE_F32);
+    psVector *yResGood = psVectorAllocEmpty (match->n, PS_TYPE_F32);
+
+    for (int i = 0; i < match->n; i++) {
+        if (mask->data.PS_TYPE_VECTOR_MASK_DATA[i]) continue;
+        pmAstromMatch *pair = match->data[i];
+        pmAstromObj *rawStar = raw->data[pair->raw];
+        if (!isfinite(rawStar->dMag)) continue;
+        if (rawStar->dMag > 0.02) continue;
+
+        // two likely failure values: NAN or 0.0 --> use dMag in this case
+        float xErrValue, yErrValue;
+        if (isfinite(rawStar->chip->xErr) && (rawStar->chip->xErr > 0.0)) {
+            xErrValue = rawStar->chip->xErr;
+        } else {
+            xErrValue = PS_MAX(0.005, rawStar->dMag);
+        }
+        if (isfinite(rawStar->chip->yErr) && (rawStar->chip->yErr > 0.0)) {
+            yErrValue = rawStar->chip->yErr;
+        } else {
+            yErrValue = PS_MAX(0.005, rawStar->dMag);
+        }
+
+        psVectorAppend (xErr,     xErrValue);
+        psVectorAppend (yErr,     yErrValue);
+        psVectorAppend (xResGood, xRes->data.F32[i]);
+        psVectorAppend (yResGood, yRes->data.F32[i]);
+    }
+
+    results->dXsys = psVectorSystematicError (xResGood, xErr, 0.05);
+    results->dYsys = psVectorSystematicError (yResGood, yErr, 0.05);
+
+    results->dXrange = pmAstromVectorRange (xResGood, 0.1, 0.9, results->xStats->clippedStdev);
+    results->dYrange = pmAstromVectorRange (yResGood, 0.1, 0.9, results->yStats->clippedStdev);
+
+    psTrace ("psModules.astrom", 3, "dXsys: %f, dXrange: %f\n", results->dXsys, results->dXrange);
+    psTrace ("psModules.astrom", 3, "dYsys: %f, dYrange: %f\n", results->dYsys, results->dYrange);
+
+    psFree (xErr);
+    psFree (yErr);
+    psFree (xRes);
+    psFree (yRes);
+    psFree (xResGood);
+    psFree (yResGood);
 
     psFree (x);
@@ -311,4 +380,138 @@
 }
 
+// set the bin closest to the corresponding value.  if USE_END is +/- 1,
+// out-of-range saturates on lower/upper bin REGARDLESS of actual value
+#define PS_BIN_FOR_VALUE(RESULT, VECTOR, VALUE, USE_END) { \
+        psVectorBinaryDisectResult result; \
+        psScalar tmpScalar; \
+        tmpScalar.type.type = PS_TYPE_F32; \
+        tmpScalar.data.F32 = (VALUE); \
+        RESULT = psVectorBinaryDisect (&result, VECTOR, &tmpScalar); \
+        switch (result) { \
+          case PS_BINARY_DISECT_PASS: \
+            break; \
+          case PS_BINARY_DISECT_OUTSIDE_RANGE: \
+            psTrace("psModules.astrom", 6, "selected bin outside range"); \
+            if (USE_END == -1) { RESULT = 0; } \
+            if (USE_END == +1) { RESULT = VECTOR->n - 1; } \
+            break; \
+          case PS_BINARY_DISECT_INVALID_INPUT: \
+          case PS_BINARY_DISECT_INVALID_TYPE: \
+            psAbort ("programming error"); \
+            break; \
+        } }
+
+# define PS_BIN_INTERPOLATE(RESULT, VECTOR, BOUNDS, BIN, VALUE) { \
+        float dX, dY, Xo, Yo, Xt; \
+        if (BIN == BOUNDS->n - 1) { \
+            dX = 0.5*(BOUNDS->data.F32[BIN+1] - BOUNDS->data.F32[BIN-1]); \
+            dY = VECTOR->data.F32[BIN] - VECTOR->data.F32[BIN-1]; \
+            Xo = 0.5*(BOUNDS->data.F32[BIN+1] + BOUNDS->data.F32[BIN]); \
+            Yo = VECTOR->data.F32[BIN]; \
+        } else { \
+            dX = 0.5*(BOUNDS->data.F32[BIN+2] - BOUNDS->data.F32[BIN]); \
+            dY = VECTOR->data.F32[BIN+1] - VECTOR->data.F32[BIN]; \
+            Xo = 0.5*(BOUNDS->data.F32[BIN+1] + BOUNDS->data.F32[BIN]); \
+            Yo = VECTOR->data.F32[BIN]; \
+        } \
+        if (dY != 0) { \
+            Xt = (VALUE - Yo)*dX/dY + Xo; \
+        } else { \
+            Xt = Xo; \
+        } \
+        Xt = PS_MIN (BOUNDS->data.F32[BIN+1], PS_MAX(BOUNDS->data.F32[BIN], Xt)); \
+        psTrace("psModules.astrom", 6, "(Xo, Yo, dX, dY, Xt, Yt) is (%.2f %.2f %.2f %.2f %.2f %.2f)\n", \
+                Xo, Yo, dX, dY, Xt, VALUE); \
+        RESULT = Xt; }
+
+float pmAstromVectorRange (psVector *myVector, float minFrac, float maxFrac, float stdevGuess) {
+
+    psStats *stats = psStatsAlloc(PS_STAT_MIN | PS_STAT_MAX); // Statistics for min and max
+    psHistogram *histogram = NULL;      // Histogram of the data
+    psHistogram *cumulative = NULL;     // Cumulative histogram of the data
+    float min = NAN, max = NAN;         // Mimimum and maximum values
+
+    // Get the minimum and maximum values
+    if (!psVectorStats(stats, myVector, NULL, NULL, 0)) {
+        psFree(stats);
+        return NAN;
+    }
+    min = stats->min;
+    max = stats->max;
+    if (isnan(min) || isnan(max)) {
+        psFree(stats);
+        return NAN;
+    }
+
+    psTrace("psModules.astrom", 5, "Data min/max is (%.2f, %.2f)\n", min, max);
+
+    // If all data points have the same value, then we set the appropriate members of stats and return.
+    if (fabs(max - min) <= FLT_EPSILON) {
+        psFree (stats);
+        return 0.0;
+    }
+
+    // Define the histogram bin size.
+    float binSize = 0.001;
+    long numBins = PS_MAX(PS_MIN(100000, (max - min) / binSize), 2); // Number of bins
+    psTrace("psModules.astrom", 5, "Numbins is %ld\n", numBins);
+    psTrace("psModules.astrom", 5, "Creating a robust histogram from data range (%.2f - %.2f)\n", min, max);
+
+    // allocate the histogram containers
+    histogram = psHistogramAlloc(min, max, numBins);
+    cumulative = psHistogramAlloc(min, max, numBins);
+
+    if (!psVectorHistogram(histogram, myVector, NULL, NULL, 0)) {
+        // if psVectorHistogram returns false, we have a programming error
+        psAbort ("Unable to generate histogram");
+    }
+    if (psTraceGetLevel("psModules.astrom") >= 8) {
+        PS_VECTOR_PRINT_F32(histogram->bounds);
+        PS_VECTOR_PRINT_F32(histogram->nums);
+    }
+
+    // Convert the specific histogram to a cumulative histogram
+    // The cumulative histogram data points correspond to the UPPER bound value (N < Bin[i+1])
+    cumulative->nums->data.F32[0] = histogram->nums->data.F32[0];
+    for (long i = 1; i < histogram->nums->n; i++) {
+        cumulative->nums->data.F32[i] = cumulative->nums->data.F32[i-1] + histogram->nums->data.F32[i];
+        cumulative->bounds->data.F32[i-1] = histogram->bounds->data.F32[i];
+    }
+    if (psTraceGetLevel("psModules.astrom") >= 8) {
+        PS_VECTOR_PRINT_F32(cumulative->bounds);
+        PS_VECTOR_PRINT_F32(cumulative->nums);
+    }
+
+    // Find the bin which contains the first data point above the limit
+    long totalDataPoints = cumulative->nums->data.F32[numBins - 1];
+    psTrace("psModules.astrom", 6, "Total data points is %ld\n", totalDataPoints);
+
+    // find bin which is the lower bound of the limit value (value[bin] < f < value[bin+1]
+    long binMin;
+    PS_BIN_FOR_VALUE(binMin, cumulative->nums, minFrac * totalDataPoints, 0);
+    psTrace("psModules.astrom", 6, "The bin is %ld (%.4f to %.4f)\n", binMin, cumulative->bounds->data.F32[binMin], cumulative->bounds->data.F32[binMin+1]);
+
+    // Linear interpolation to the limit value in bin units
+    float valueMin;
+    PS_BIN_INTERPOLATE (valueMin, cumulative->nums, cumulative->bounds, binMin, totalDataPoints * minFrac);
+    psTrace("psModules.astrom", 6, "limit value is %f\n", valueMin);
+
+    // find bin which is the lower bound of the limit value (value[bin] < f < value[bin+1]
+    long binMax;
+    PS_BIN_FOR_VALUE(binMax, cumulative->nums, maxFrac * totalDataPoints, 0);
+    psTrace("psModules.astrom", 6, "The bin is %ld (%.4f to %.4f)\n", binMax, cumulative->bounds->data.F32[binMax], cumulative->bounds->data.F32[binMax+1]);
+
+    // Linear interpolation to the limit value in bin units
+    float valueMax;
+    PS_BIN_INTERPOLATE (valueMax, cumulative->nums, cumulative->bounds, binMax, totalDataPoints * maxFrac);
+    psTrace("psModules.astrom", 6, "limit value is %f\n", valueMax);
+
+    // Clean up
+    psFree(histogram);
+    psFree(cumulative);
+    psFree(stats);
+
+    return (valueMax - valueMin);
+}
 
 /******************************************************************************
@@ -634,17 +837,19 @@
         }
 
-# if 0
-        char line[16];
-        psFits *fits = psFitsOpen ("grid.image.fits", "w");
-        psFitsWriteImage (fits, NULL, gridNP, 0, NULL);
-        psFitsClose (fits);
-        fprintf (stderr, "wrote grid image, press return to continue\n");
-        fgets (line, 15, stdin);
-# endif
+        if (psTraceGetLevel("psModules.astrom") >= 5) {
+            char line[16];
+            psFits *fits = psFitsOpen ("grid.image.fits", "w");
+            psFitsWriteImage (fits, NULL, gridNP, 0, NULL);
+            psFitsClose (fits);
+            fprintf (stderr, "wrote grid image, press return to continue\n");
+            if (!fgets (line, 15, stdin)) {
+                fprintf(stderr, "Error waiting for RETURN.");
+            }
+        }
 
         // only check bins with at least 1/2 of max bin
         // XXX requiring at least 3 matches in bin
         int minNpts = PS_MAX (0.5*imStats->max, 5);
-        psTrace("psModule.astrom", 5, "minNpts: %d, min: %d, max: %d, median: %f, stdev: %f", minNpts, (int)(imStats->min), (int)(imStats->max), imStats->sampleMedian, imStats->sampleStdev);
+        psTrace("psModule.astrom", 4, "minNpts: %d, min: %d, max: %d, median: %f, stdev: %f", minNpts, (int)(imStats->min), (int)(imStats->max), imStats->sampleMedian, imStats->sampleStdev);
 
         // find the 'best' bin
@@ -687,5 +892,6 @@
 
         // XXX this function is crashing
-        // pmAstromVisualPlotGridMatch(raw, ref, gridNP, stats->offset.x, stats->offset.y, maxOffpix, Scale, Offset);
+        pmAstromVisualPlotGridMatch(raw, ref, gridNP, stats->offset.x, stats->offset.y, maxOffpix, Scale, Offset);
+        pmAstromVisualPlotGridMatchOverlay(raw, ref);
 
         psFree (imStats);
@@ -962,2 +1168,91 @@
 */
 
+/*****************************************************************************/
+static void pmAstromMatchInfoFree (pmAstromMatchInfo *info)
+{
+    if (info == NULL) return;
+    return;
+}
+
+
+/*****************************************************************************/
+pmAstromMatchInfo *pmAstromMatchInfoAlloc()
+{
+    pmAstromMatchInfo *info = psAlloc (sizeof(pmAstromMatchInfo));
+    psMemSetDeallocator(info, (psFreeFunc) pmAstromMatchInfoFree);
+
+    info->match = NULL;
+    info->radius = NAN;
+
+    return (info);
+}
+
+// generate a unique set of matches (choose closest match)
+psArray *pmAstromRadiusMatchUniq (psArray *rawstars, psArray *refstars, psArray *matches) {
+
+    // I have the matches between the refstars and the rawstars.
+    // For each refstar, find the single match which has the smallest radius and reject
+    // all others.
+
+    // create an array of refstars->n arrays, each containing all of the matches for the
+    // given refstar.
+
+    psArray *refstarMatches = psArrayAlloc (refstars->n);
+
+    for (int i = 0; i < matches->n; i++) {
+
+        pmAstromMatch *match = matches->data[i];
+
+        // accumulate this refstar match on the array for this refstar (create if needed)
+        psArray *refSet = refstarMatches->data[match->ref];
+        if (!refSet) {
+            refstarMatches->data[match->ref] = psArrayAllocEmpty (8);
+            refSet = refstarMatches->data[match->ref];
+        }
+
+        pmAstromMatchInfo *matchInfo = pmAstromMatchInfoAlloc();
+
+        pmAstromObj *refStar = refstars->data[match->ref];
+        pmAstromObj *rawStar = rawstars->data[match->raw];
+
+        matchInfo->match = match; // reference to the match of interest
+        matchInfo->radius = hypot (refStar->FP->x - rawStar->FP->x, refStar->FP->y - rawStar->FP->y);
+
+        psArrayAdd (refSet, 8, matchInfo); // matchInfo->match is just a reference
+        psFree (matchInfo);
+    }
+
+    // we now have a set of matches for each refstar and their distances; create a new set
+    // keeping only the closest entry for each match
+
+    psArray *unique = psArrayAllocEmpty (PS_MAX(16, matches->n / 2));
+    for (int i = 0; i < refstars->n; i++) {
+
+        psArray *refSet = refstarMatches->data[i];
+        if (!refSet) continue;
+        if (refSet->n == 0) continue; // not certain how this can happen...
+
+        if (refSet->n == 1) {
+            pmAstromMatchInfo *matchInfo = refSet->data[0];
+            psArrayAdd (unique, 32, matchInfo->match);
+            continue;
+        }
+
+        pmAstromMatchInfo *matchInfo = refSet->data[0];
+        float minRadius = matchInfo->radius;
+        pmAstromMatch *minMatch = matchInfo->match;
+        for (int j = 1; j < refSet->n; j++) {
+            pmAstromMatchInfo *matchInfo = refSet->data[j];
+            if (minRadius < matchInfo->radius) continue;
+            minMatch = matchInfo->match;
+            minRadius = matchInfo->radius;
+        }
+
+        psArrayAdd (unique, 32, minMatch); // minMatch is just a reference to a match on matches,
+    }
+
+    psLogMsg ("psModules.astrom", 3, "generate unique matches to reference stars: %ld matches -> %ld matches\n", matches->n, unique->n);
+    psFree (refstarMatches);
+
+    return unique;
+}
Index: branches/simmosaic_branches/psModules/src/astrom/pmAstrometryObjects.h
===================================================================
--- branches/simmosaic_branches/psModules/src/astrom/pmAstrometryObjects.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/astrom/pmAstrometryObjects.h	(revision 27839)
@@ -54,8 +54,19 @@
 typedef struct
 {
-    int raw;                             ///< What is this?
-    int ref;                             ///< What is this?
+    int raw;                             ///< reference to the rawstar entry
+    int ref;                             ///< reference to the refstar entry
 }
 pmAstromMatch;
+
+
+/*
+ * The pmAstromMatchInfo structure is used to generate a unique set of matches
+ */
+typedef struct
+{
+    pmAstromMatch *match;		///< reference to the match
+    float radius;			///< distance between the object
+}
+pmAstromMatchInfo;
 
 
@@ -85,4 +96,8 @@
     int     nMatch;                     ///<
     double  nSigma;                     ///<
+    double  dXsys;			///< systematic error in X
+    double  dYsys;			///< systematic error in Y
+    double  dXrange;			///< 10% - 90% range X residuals (unmasked, high S/N)
+    double  dYrange;			///< 10% - 90% range Y residuals (unmasked, high S/N)
 }
 pmAstromFitResults;
@@ -121,4 +136,5 @@
 );
 
+psArray *pmAstromRadiusMatchUniq (psArray *refstars, psArray *rawstars, psArray *matches);
 
 pmAstromStats *pmAstromStatsAlloc(void);
@@ -343,4 +359,6 @@
 );
 
+float pmAstromVectorRange (psVector *myVector, float minFrac, float maxFrac, float stdevGuess);
+
 /// @}
 #endif // PM_ASTROMETRY_OBJECTS_H
Index: branches/simmosaic_branches/psModules/src/astrom/pmAstrometryVisual.c
===================================================================
--- branches/simmosaic_branches/psModules/src/astrom/pmAstrometryVisual.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/astrom/pmAstrometryVisual.c	(revision 27839)
@@ -450,5 +450,5 @@
 
     graphdata.color = KapaColorByName ("red");
-    graphdata.style = 1;
+    graphdata.style = 0;
 
     //overplot clumpy regions excluded from analysis
@@ -905,6 +905,8 @@
     KapaPlotVector (kapa, gridNP->numCols, horizontalIndices, "x");
     KapaPlotVector (kapa, gridNP->numCols, horizHistSlice, "y");
+
     float xslice[2] = {offsetX - Scale / 2., offsetX - Scale / 2.};
     float yslice[2] = {-5, 100};
+    graphdata.style = 0;
     graphdata.color = KapaColorByName("red");
     KapaPrepPlot(kapa, 2, &graphdata);
@@ -927,6 +929,8 @@
     KapaPlotVector (kapa, gridNP->numRows, vertHistSlice, "x");
     KapaPlotVector (kapa, gridNP->numRows, verticalIndices, "y");
+
     yslice[0] = yslice[1] = offsetY - Scale / 2.;
     xslice[0] = -5; xslice[1] = 100;
+    graphdata.style = 0;
     graphdata.color = KapaColorByName("red");
     KapaPrepPlot(kapa, 2, &graphdata);
@@ -940,4 +944,105 @@
 } // end of pmAstromVisualPlotGridMatch
 
+
+bool pmAstromVisualPlotGridMatchOverlay (const psArray *raw,
+					 const psArray *ref)
+{
+    //make sure we want to plot this
+    if (!pmVisualIsVisual() || !plotGridMatch) return true;
+    if (!pmVisualInitWindow(&kapa2, "psastro:plots")){
+        return false;
+    }
+
+    Graphdata graphdata;
+    psVector *xPlot = psVectorAlloc (PS_MAX(raw->n, ref->n), PS_TYPE_F32); // x data points
+    psVector *yPlot = psVectorAlloc (PS_MAX(raw->n, ref->n), PS_TYPE_F32); // y data points
+    psVector *zPlot = psVectorAlloc (PS_MAX(raw->n, ref->n), PS_TYPE_F32); // y data points
+
+    // set up plot information
+    KapaClearPlots(kapa2);
+    KapaInitGraph(&graphdata);
+
+    KapaSetFont(kapa2, "helvetica", 14);
+    KapaBox(kapa2, &graphdata);
+    KapaSendLabel (kapa2, "X (FP)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa2, "Y (FP)", KAPA_LABEL_YM);
+    KapaSendLabel (kapa2, "pmAstromGridAngle residuals. Box: Correlation Peak.", KAPA_LABEL_XP);
+
+    // plot the REF data.  (also calculate the plot ranges, accumulate the plot vectors)
+    graphdata.xmin = +INT_MAX;
+    graphdata.xmax = -INT_MAX;
+    graphdata.ymin = +INT_MAX;
+    graphdata.ymax = -INT_MAX;
+    for (int i = 0; i < ref->n; i++) {
+        pmAstromObj *obj = ref->data[i];
+	graphdata.xmin = PS_MIN(graphdata.xmin, obj->FP->x);
+	graphdata.xmax = PS_MAX(graphdata.xmax, obj->FP->x);
+	graphdata.ymin = PS_MIN(graphdata.ymin, obj->FP->y);
+	graphdata.ymax = PS_MAX(graphdata.ymax, obj->FP->y);
+	xPlot->data.F32[i] = obj->FP->x;
+	yPlot->data.F32[i] = obj->FP->y;
+	zPlot->data.F32[i] = obj->Mag;
+    }
+    xPlot->n = yPlot->n = zPlot->n = ref->n;
+    KapaSetLimits(kapa2, &graphdata);
+
+    psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
+    psVectorStats (stats, zPlot, NULL, NULL, 0);
+    float zero = stats->sampleMedian + 3.0;
+    float range = 6.0;
+
+    for (int i = 0; i < zPlot->n; i++) {
+	float value = (zero - zPlot->data.F32[i]) / range;
+	zPlot->data.F32[i] = PS_MAX(0.0, PS_MIN(1.0, value));
+    }
+
+    // the point size will be scaled from the z vector
+    graphdata.style = 2;
+    graphdata.ptype = 7;
+    graphdata.size = -1;
+    graphdata.color = KapaColorByName ("black");
+
+    KapaPrepPlot   (kapa2, xPlot->n, &graphdata);
+    KapaPlotVector (kapa2, xPlot->n, xPlot->data.F32, "x");
+    KapaPlotVector (kapa2, yPlot->n, yPlot->data.F32, "y");
+    KapaPlotVector (kapa2, zPlot->n, zPlot->data.F32, "z");
+
+    // plot the RAW data (keep previous limits)
+    for (int i = 0; i < raw->n; i++) {
+        pmAstromObj *obj = raw->data[i];
+	xPlot->data.F32[i] = obj->FP->x;
+	yPlot->data.F32[i] = obj->FP->y;
+	zPlot->data.F32[i] = obj->Mag;
+    }
+    xPlot->n = yPlot->n = zPlot->n = raw->n;
+
+    psStatsInit(stats);
+    psVectorStats (stats, zPlot, NULL, NULL, 0);
+    zero = stats->sampleMedian + 3.0;
+    range = 6.0;
+
+    for (int i = 0; i < zPlot->n; i++) {
+	float value = (zero - zPlot->data.F32[i]) / range;
+	zPlot->data.F32[i] = PS_MAX(0.0, PS_MIN(1.0, value));
+    }
+
+    // the point size will be scaled from the z vector
+    graphdata.style = 2;
+    graphdata.ptype = 7;
+    graphdata.size = -1;
+    graphdata.color = KapaColorByName ("red");
+
+    KapaPrepPlot   (kapa2, xPlot->n, &graphdata);
+    KapaPlotVector (kapa2, xPlot->n, xPlot->data.F32, "x");
+    KapaPlotVector (kapa2, yPlot->n, yPlot->data.F32, "y");
+    KapaPlotVector (kapa2, zPlot->n, zPlot->data.F32, "z");
+
+    pmVisualAskUser(&plotGridMatch);
+    psFree(xPlot);
+    psFree(yPlot);
+    psFree(zPlot);
+    psFree(stats);
+    return true;
+}
 
 bool pmAstromVisualPlotTweak (psVector *xHist, // Smoothed Horizontal cut through the histogram
@@ -1209,6 +1314,11 @@
     KapaClearPlots (kapa2);
 
-    graphdata.color = KapaColorByName ("black");
-    graphdata.ptype = 2;
+    KapaSendLabel (kapa2, "X", KAPA_LABEL_XM);
+    KapaSendLabel (kapa2, "Y", KAPA_LABEL_YM);
+    KapaSendLabel (kapa2, "Chip Coordinates. Black = Raw Stars. Red = Ref Stars. Blue = Matched Stars", KAPA_LABEL_XP);
+
+    // X vs Y by mag (ref)
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 7;
     graphdata.style = 2;
 
@@ -1217,42 +1327,7 @@
     psFree (zVec);
 
-    xVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
-    yVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
-    zVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
-
-    // X vs Y by mag (raw)
-    n = 0;
-    for (int i = 0; i < rawstars->n; i++) {
-        pmAstromObj *raw = rawstars->data[i];
-        if (!isfinite(raw->Mag)) continue;
-        if (raw->Mag < iMagMin) continue;
-        if (raw->Mag > iMagMax) continue;
-
-        xVec->data.F32[n] = raw->chip->x;
-        yVec->data.F32[n] = raw->chip->y;
-        zVec->data.F32[n] = raw->Mag;
-        n++;
-    }
-    xVec->n = yVec->n = zVec->n = n;
-
-    KapaSendLabel (kapa2, "X", KAPA_LABEL_XM);
-    KapaSendLabel (kapa2, "Y", KAPA_LABEL_YM);
-    KapaSendLabel (kapa2,
-                   "Chip Coordinates. Black = Raw Stars. Red = Ref Stars. Blue = Matched Stars"
-                   , KAPA_LABEL_XP);
-    pmVisualTriplePlot (kapa2, &graphdata, xVec, yVec, zVec, false);
-
-    // X vs Y by mag (ref)
-    psFree (xVec);
-    psFree (yVec);
-    psFree (zVec);
-
     xVec = psVectorAlloc (refstars->n, PS_TYPE_F32);
     yVec = psVectorAlloc (refstars->n, PS_TYPE_F32);
     zVec = psVectorAlloc (refstars->n, PS_TYPE_F32);
-
-    graphdata.color = KapaColorByName ("red");
-    graphdata.ptype = 7;
-    graphdata.style = 2;
 
     n = 0;
@@ -1269,5 +1344,5 @@
     }
     xVec->n = yVec->n = zVec->n = n;
-    pmVisualTripleOverplot (kapa2, &graphdata, xVec, yVec, zVec, false);
+    pmVisualTriplePlot (kapa2, &graphdata, xVec, yVec, zVec, false);
 
     //rescale the graph to include all points
@@ -1282,4 +1357,35 @@
     graphdata.ymax = PS_MAX(ymax, graphdata.ymax);
     KapaSetLimits (kapa2, &graphdata);
+
+    bool plotTweak;
+    pmVisualAskUser(&plotTweak);
+
+    // X vs Y by mag (raw)
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.style = 2;
+
+    psFree (xVec);
+    psFree (yVec);
+    psFree (zVec);
+
+    xVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
+    yVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
+    zVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
+
+    n = 0;
+    for (int i = 0; i < rawstars->n; i++) {
+        pmAstromObj *raw = rawstars->data[i];
+        if (!isfinite(raw->Mag)) continue;
+        if (raw->Mag < iMagMin) continue;
+        if (raw->Mag > iMagMax) continue;
+
+        xVec->data.F32[n] = raw->chip->x;
+        yVec->data.F32[n] = raw->chip->y;
+        zVec->data.F32[n] = raw->Mag;
+        n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmVisualTripleOverplot (kapa2, &graphdata, xVec, yVec, zVec, false);
 
     //overplot matched stars in blue
Index: branches/simmosaic_branches/psModules/src/astrom/pmAstrometryVisual.h
===================================================================
--- branches/simmosaic_branches/psModules/src/astrom/pmAstrometryVisual.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/astrom/pmAstrometryVisual.h	(revision 27839)
@@ -45,4 +45,7 @@
                                   );
 
+
+bool pmAstromVisualPlotGridMatchOverlay (const psArray *raw,
+					 const psArray *ref);
 
 /**
Index: branches/simmosaic_branches/psModules/src/astrom/pmAstrometryWCS.c
===================================================================
--- branches/simmosaic_branches/psModules/src/astrom/pmAstrometryWCS.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/astrom/pmAstrometryWCS.c	(revision 27839)
@@ -289,4 +289,5 @@
     // test the CDELTi varient
     if (pcKeys) {
+        wcs->wcsCDkeys = 0;
         wcs->cdelt1 = psMetadataLookupF64 (&status, header, "CDELT1");
         wcs->cdelt2 = psMetadataLookupF64 (&status, header, "CDELT2");
@@ -334,4 +335,6 @@
     // test the CDi_j varient
     if (cdKeys) {
+        wcs->wcsCDkeys = 1;
+
         wcs->trans->x->coeff[1][0] = psMetadataLookupF64 (&status, header, "CD1_1"); // == PC1_1
         wcs->trans->x->coeff[0][1] = psMetadataLookupF64 (&status, header, "CD1_2"); // == PC1_2
@@ -375,42 +378,58 @@
     // XXX make it optional to write out CDi_j terms, or other versions
     // apply CDELT1,2 (degrees / pixel) to yield PCi,j terms of order unity
-    double cdelt1 = wcs->cdelt1;
-    double cdelt2 = wcs->cdelt2;
-    psMetadataAddF64 (header, PS_LIST_TAIL, "CDELT1", PS_META_REPLACE, "", cdelt1);
-    psMetadataAddF64 (header, PS_LIST_TAIL, "CDELT2", PS_META_REPLACE, "", cdelt2);
-
-    // test the PC00i00j varient:
-    psMetadataAddF64 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", wcs->trans->x->coeff[1][0] / cdelt1); // == PC1_1
-    psMetadataAddF64 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", wcs->trans->x->coeff[0][1] / cdelt2); // == PC1_2
-    psMetadataAddF64 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", wcs->trans->y->coeff[1][0] / cdelt1); // == PC2_1
-    psMetadataAddF64 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", wcs->trans->y->coeff[0][1] / cdelt2); // == PC2_2
-
-    // Elixir-style polynomial terms
-    // XXX currently, Elixir/DVO cannot accept mixed orders
-    // XXX need to respect the masks
-    // XXX is wcs->cdelt1,2 always consistent?
-    int fitOrder = wcs->trans->x->nX;
-    if (fitOrder > 1) {
+    if (!wcs->wcsCDkeys) { 
+
+      double cdelt1 = wcs->cdelt1;
+      double cdelt2 = wcs->cdelt2;
+      psMetadataAddF64 (header, PS_LIST_TAIL, "CDELT1", PS_META_REPLACE, "", cdelt1);
+      psMetadataAddF64 (header, PS_LIST_TAIL, "CDELT2", PS_META_REPLACE, "", cdelt2);
+      
+      // test the PC00i00j varient:
+      psMetadataAddF64 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", wcs->trans->x->coeff[1][0] / cdelt1); // == PC1_1
+      psMetadataAddF64 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", wcs->trans->x->coeff[0][1] / cdelt2); // == PC1_2
+      psMetadataAddF64 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", wcs->trans->y->coeff[1][0] / cdelt1); // == PC2_1
+      psMetadataAddF64 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", wcs->trans->y->coeff[0][1] / cdelt2); // == PC2_2
+      
+      // Elixir-style polynomial terms
+      // XXX currently, Elixir/DVO cannot accept mixed orders
+      // XXX need to respect the masks
+      // XXX is wcs->cdelt1,2 always consistent?
+      int fitOrder = wcs->trans->x->nX;
+      if (fitOrder > 1) {
         for (int i = 0; i <= fitOrder; i++) {
-            for (int j = 0; j <= fitOrder; j++) {
-                if (i + j < 2)
-                    continue;
-                if (i + j > fitOrder)
-                    continue;
-                sprintf (name, "PCA1X%1dY%1d", i, j);
-                psMetadataAddF64 (header, PS_LIST_TAIL, name, PS_META_REPLACE, "", wcs->trans->x->coeff[i][j] / pow(cdelt1, i) / pow(cdelt2, j));
-                sprintf (name, "PCA2X%1dY%1d", i, j);
-                psMetadataAddF64 (header, PS_LIST_TAIL, name, PS_META_REPLACE, "", wcs->trans->y->coeff[i][j] / pow(cdelt1, i) / pow(cdelt2, j));
-            }
+	  for (int j = 0; j <= fitOrder; j++) {
+	    if (i + j < 2)
+	      continue;
+	    if (i + j > fitOrder)
+	      continue;
+	    sprintf (name, "PCA1X%1dY%1d", i, j);
+	    psMetadataAddF64 (header, PS_LIST_TAIL, name, PS_META_REPLACE, "", wcs->trans->x->coeff[i][j] / pow(cdelt1, i) / pow(cdelt2, j));
+	    sprintf (name, "PCA2X%1dY%1d", i, j);
+	    psMetadataAddF64 (header, PS_LIST_TAIL, name, PS_META_REPLACE, "", wcs->trans->y->coeff[i][j] / pow(cdelt1, i) / pow(cdelt2, j));
+	  }
         }
         psMetadataAddS32 (header, PS_LIST_TAIL, "NPLYTERM", PS_META_REPLACE, "", fitOrder);
-    }
-
-    // remove any existing 'CDi_j style' wcs keywords
-    if (psMetadataLookup(header, "CD1_1")) {
+      }
+      
+      // remove any existing 'CDi_j style' wcs keywords
+      if (psMetadataLookup(header, "CD1_1")) {
         psMetadataRemoveKey(header, "CD1_1");
         psMetadataRemoveKey(header, "CD1_2");
         psMetadataRemoveKey(header, "CD2_1");
         psMetadataRemoveKey(header, "CD2_2");
+      }
+    } else {
+
+      psMetadataAddF64 (header, PS_LIST_TAIL, "CD1_1", PS_META_REPLACE, "", wcs->trans->x->coeff[1][0]);
+      psMetadataAddF64 (header, PS_LIST_TAIL, "CD1_2", PS_META_REPLACE, "", wcs->trans->x->coeff[0][1]);
+      psMetadataAddF64 (header, PS_LIST_TAIL, "CD2_1", PS_META_REPLACE, "", wcs->trans->y->coeff[1][0]);
+      psMetadataAddF64 (header, PS_LIST_TAIL, "CD2_2", PS_META_REPLACE, "", wcs->trans->y->coeff[0][1]);
+
+      if (psMetadataLookup(header, "PC001001")) {
+	psMetadataRemoveKey(header, "PC001001");
+	psMetadataRemoveKey(header, "PC001002");
+	psMetadataRemoveKey(header, "PC002001");
+	psMetadataRemoveKey(header, "PC002002");
+      }
     }
 
@@ -535,4 +554,6 @@
         fpa->toSky->R -= 2.0*M_PI;
 
+    fpa->wcsCDkeys = wcs->wcsCDkeys;
+
     psTrace ("psastro", 5, "toFPA: %f %f  (%f,%f),(%f,%f)\n",
              chip->toFPA->x->coeff[0][0], chip->toFPA->y->coeff[0][0],
@@ -648,6 +669,13 @@
     wcs->crval2 = fpa->toSky->D*PS_DEG_RAD;
 
+    // generate a transform that has 0.0 rotation:
+    // get the current posangle of the ref chip
+    // XXX average angles for x and y...
+    float angle = atan2 (toTPA->y->coeff[1][0], toTPA->x->coeff[1][0]);
+    // fprintf (stderr, "angle: %f\n", angle*PS_DEG_RAD);
+    psPlaneTransform *tpa1 = psPlaneTransformRotate (NULL, toTPA, angle);
+
     // given transformation, solve for coordinates which yields output coordinates of 0,0
-    psPlane *center = psPlaneTransformGetCenter (toTPA, tol);
+    psPlane *center = psPlaneTransformGetCenter (tpa1, tol);
     if (!center) {
         psError(PS_ERR_UNKNOWN, false, "Unable to solve for TPA center.");
@@ -657,7 +685,20 @@
     }
 
+    // generate transform with the original orientation (does this rotate about 'center'?) 
+    psPlaneTransform *tpa2 = psPlaneTransformRotate (NULL, tpa1, -1.0*angle);
+
+    // prove that the center coordinates give 0,0:
+    // float Xo = psPolynomial2DEval (tpa1->x, center->x, center->y);
+    // float Yo = psPolynomial2DEval (tpa1->x, center->x, center->y);
+    // fprintf (stderr, "tpa1: Xo, Yo: %f, %f\n", Xo, Yo);
+
+    // prove that the center coordinates give 0,0:
+    // Xo = psPolynomial2DEval (tpa2->x, center->x, center->y);
+    // Yo = psPolynomial2DEval (tpa2->x, center->x, center->y);
+    // fprintf (stderr, "tpa2: Xo, Yo: %f, %f\n", Xo, Yo);
+
     // create wcs transform from toFPA, resulting transformation has units of microns/pixel
     // adjust wcs transform to use center as reference coordinate
-    psPlaneTransformSetCenter (wcs->trans, toTPA, center->x, center->y);
+    psPlaneTransformSetCenter (wcs->trans, tpa2, center->x, center->y);
 
     // calculated center is crpix1,2
@@ -665,4 +706,6 @@
     wcs->crpix2 = center->y;
     psFree (center);
+    psFree (tpa1);
+    psFree (tpa2);
 
     // pdelt1,2 has units of degrees/micron
@@ -682,4 +725,5 @@
     wcs->cdelt2 = hypot (wcs->trans->y->coeff[1][0], wcs->trans->y->coeff[0][1]);
 
+    wcs->wcsCDkeys = fpa->wcsCDkeys;
     psFree (toTPA);
 
@@ -800,13 +844,13 @@
     int k=0;
     for (int j=0; j<nSamples; j++) {
-        double y = j * deltaY / nSamples;
+        double y = bounds->y0 + (j * deltaY / nSamples);
         for (int i=0; i<nSamples; i++) {
             psPlane *s = psPlaneAlloc();
-            s->x = i * deltaX / nSamples;
+            s->x = bounds->x0 + (i * deltaX / nSamples);
             s->y = y;
             psArraySet(src, k, s);
             psPlane *d = psPlaneTransformApply(NULL, trans, s);
             psArraySet(dst, k, d);
-            psFree(s);
+            psFree(s);  // drop our refs to s and d
             psFree(d);
             ++k;
@@ -821,7 +865,9 @@
     }
 
+#define noCOMPARE_TRANS
 #ifdef COMPARE_TRANS
     // compare the computed coordintes from this transform with the original
     psPlane *new = psPlaneAlloc();
+    printf("   i     chip_x  tpa_x     tpa_x_fit     dx         chip_y    tpa_y     tpa_y_fit     dy     dx > 0.5 || dy > 0.5\n");
     for (int i=0; i<psArrayLength(dst); i++) {
         psPlane *d = (psPlane *) psArrayGet(dst, i);
@@ -830,5 +876,9 @@
         new = psPlaneTransformApply(new, newTrans, s);
 
-        printf("%4d %f %f\n", i, 100.*(new->x - d->x)/d->x, 100.*(new->y - d->y)/d->y);
+        double xerr = new->x - d->x;
+        double yerr = new->y - d->y;
+        bool bigerr = (fabs(xerr) > .5) || (fabs(yerr) > .5);
+        printf("%4d %9.2f %9.2f %9.2f %9.4f     %9.2f %9.2f %9.2f %9.4f   %s\n"
+        , i, s->x, new->x, d->x, xerr, s->y, new->y, d->y, yerr, bigerr ? "BIGERR" : "");
     }
     psFree(new);
@@ -842,63 +892,175 @@
 }
 
-bool pmAstromLinearizeTransforms(pmFPA *fpa, pmChip *chip)
-{
-    psRegion    *chipBounds = pmChipPixels(chip);
-
-    psPlaneTransform *newToFPA = linearFitToTransform(chip->toFPA, chipBounds);
-    if (!newToFPA) {
-        psFree(chipBounds);
-        psError(PS_ERR_UNKNOWN, false, "linear fit for toFPA failed");
-        return false;
-    }
-
-    psFree(chip->toFPA);
-    chip->toFPA = newToFPA;
-
-    psFree(chip->fromFPA);
-    chip->fromFPA = psPlaneTransformInvert(NULL, chip->toFPA, *chipBounds, 50);
-    if (!chip->fromFPA) {
-        psError(PS_ERR_UNKNOWN, false, "failed to invert linear fit for toFPA");
-        return false;
-    }
-
-    psPlane *chip0 = psPlaneAlloc();
-    chip0->x = 0;
-    chip0->y = 0;
-    psPlane *chip1 = psPlaneAlloc();
-    chip1->x = chipBounds->x1;
-    chip1->y = chipBounds->y1;
-
-    // compute bounding region for fpa
-    psPlane *fpa0 = psPlaneTransformApply(NULL, newToFPA, chip0);
-    psPlane *fpa1 = psPlaneTransformApply(NULL, newToFPA, chip1);
-
-    psRegion *fpaBounds = psRegionAlloc(fpa0->x, fpa1->x, fpa0->y, fpa1->y);
-    psFree(chip0);
-    psFree(chip1);
-    psFree(fpa0);
-    psFree(fpa1);
-
-    psPlaneTransform *newToTPA = linearFitToTransform(fpa->toTPA, fpaBounds);
-    if (!newToTPA) {
-        psError(PS_ERR_UNKNOWN, false, "failed to perform linear fit to toTPA");
-        psFree(fpaBounds);
-        return false;
-    }
-    psFree(fpa->toTPA);
-    fpa->toTPA = newToTPA;
-
-    // XXX: is this region ok?
-    psFree(fpa->fromTPA);
-    fpa->fromTPA = psPlaneTransformInvert(NULL, fpa->toTPA, *fpaBounds, 50);
-    if (!fpa->fromTPA) {
-        psError(PS_ERR_UNKNOWN, false, "failed to invert linear fit to toTPA");
-        return false;
-    }
-
-    fpa->toSky->type = PS_PROJ_TAN;
-
-    psFree(chipBounds);
-    psFree(fpaBounds);
+bool pmAstromLinearizeTransforms(pmFPA *inFPA, pmChip *inChip, pmFPA *outFPA, pmChip *outChip, psRegion *outputBounds, double offset_x, double offset_y)
+{
+    PS_ASSERT_PTR_NON_NULL(inFPA, NULL);
+    PS_ASSERT_PTR_NON_NULL(inChip, NULL);
+
+    if (outFPA == NULL) {
+        outFPA = inFPA;
+    }
+    if (outChip == NULL) {
+        outChip = inChip;
+    }
+    if (outputBounds == NULL) {
+        outputBounds = pmChipPixels(outChip);
+    }
+
+    // First combine the "chip to FPA" and "FPA to TPA" into a single transformation
+    psPlaneTransform *chipToTPA = psPlaneTransformCombine(NULL, inChip->toFPA, inFPA->toTPA, *outputBounds, 50);
+    if (!chipToTPA) {
+        psError(PS_ERR_UNKNOWN, false, "failed to create chipToTPA");
+        return false;
+    }
+
+    // Next do the linear fit within the output boundary pixels
+    psPlaneTransform *chipToFPA = linearFitToTransform(chipToTPA, outputBounds);
+    psFree(chipToTPA);
+    if (!chipToFPA) {
+        psError(PS_ERR_UNKNOWN, false, "linear fit of chip to TPA transform failed");
+        return false;
+    }
+
+    // if requested,  change the center
+    psPlaneTransform *outToFPA;
+    if (offset_x != 0. && offset_y != 0.) {
+        outToFPA = psPlaneTransformSetCenter(NULL, chipToFPA, offset_x, offset_y);
+        psFree(chipToFPA);
+    } else {
+        outToFPA = chipToFPA;
+    }
+
+    psPlaneTransform *outFromFPA = psPlaneTransformInvert(NULL, outToFPA, *outputBounds, 50);
+    if (!outFromFPA) {
+        psFree(outToFPA);
+        psError(PS_ERR_UNKNOWN, false, "inversion of fit of output chip toFPA failed");
+        return false;
+    }
+
+    // Success. Now set the fpa's toTPA and fromTPA to identity and replace the chip's transforms.
+
+    psFree(outFPA->toTPA);
+    outFPA->toTPA =  psPlaneTransformIdentity(1);
+
+    psFree(outFPA->fromTPA);
+    outFPA->fromTPA = psPlaneTransformIdentity(1);
+
+    psFree(outChip->toFPA);
+    outChip->toFPA = outToFPA;
+
+    psFree(outChip->fromFPA);
+    outChip->fromFPA = outFromFPA;
+
+    // Finally, change the type for the projection.
+    outFPA->toSky->type = PS_PROJ_TAN;
+
+    return true;
+}
+
+bool pmAstromLinearizeToSky(pmFPA *inFPA, pmChip *inChip, pmFPA *outFPA, pmChip *outChip, psRegion *bounds)
+{
+    PS_ASSERT_PTR_NON_NULL(inFPA, NULL);
+    PS_ASSERT_PTR_NON_NULL(inChip, NULL);
+    PS_ASSERT_PTR_NON_NULL(outFPA, NULL);
+    PS_ASSERT_PTR_NON_NULL(outChip, NULL);
+    PS_ASSERT_PTR_NON_NULL(bounds, NULL);
+
+    // outFPA projection must be defined as the goal
+    
+    // the output transformations are:
+    // chip -> FPA : standard linear trans with needed rotation, etc
+    // FPA  -> TPA : identidy
+
+    int nSamples = 10;  // 10 samples in each dimension
+
+    double deltaX = (bounds->x1 - bounds->x0);
+    double deltaY = (bounds->y1 - bounds->y0);
+
+    psArray *src = psArrayAllocEmpty(nSamples * nSamples);
+    psArray *dst = psArrayAllocEmpty(nSamples * nSamples);
+
+    psPlane srcFP, srcTP;
+
+    for (int j = 0; j < nSamples; j++) {
+        double y = bounds->y0 + (j * deltaY / nSamples);
+        for (int i =  0; i < nSamples; i++) {
+
+	    psSphere srcSky;
+	    psPlane *srcChip = psPlaneAlloc();
+	    psPlane *dstTP = psPlaneAlloc();
+
+            srcChip->x = bounds->x0 + (i * deltaX / nSamples);
+            srcChip->y = y;
+
+	    psPlaneTransformApply (&srcFP, inChip->toFPA, srcChip);
+	    psPlaneTransformApply (&srcTP, inFPA->toTPA, &srcFP);
+	    psDeproject (&srcSky, &srcTP, inFPA->toSky);
+	    
+	    // fprintf (stderr, "%f %f | %f %f | %f %f | %f %f\n", srcChip->x, srcChip->y, srcFP.x, srcFP.y, srcTP.x, srcTP.y, srcSky.r*PS_DEG_RAD, srcSky.d*PS_DEG_RAD);
+
+	    psProject (dstTP, &srcSky, outFPA->toSky);
+
+            srcChip->x -= bounds->x0;
+            srcChip->y -= bounds->y0;
+	    psArrayAdd (src, 100, srcChip);
+	    psArrayAdd (dst, 100, dstTP);
+
+            psFree(srcChip);  // drop our refs to s and d
+            psFree(dstTP);
+        }
+    }
+
+    psPlaneTransform *newToFPA = psPlaneTransformAlloc(1, 1);
+    newToFPA->x->coeffMask[1][1] = 1;
+    newToFPA->y->coeffMask[1][1] = 1;
+
+    if (!psPlaneTransformFit(newToFPA, src, dst, 0, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "linear fit to transform failed");
+	psFree(src);
+	psFree(dst);
+        return NULL;
+    }
+    
+# if (0)
+    for (int i = 0; i < src->n; i++) {
+	
+	psSphere srcSky, dstSky;
+	psPlane *srcChip = src->data[i];
+	psPlane *dstTP   = dst->data[i];
+
+	psPlaneTransformApply (&srcFP, newToFPA, srcChip);
+	psDeproject (&srcSky, &srcFP, outFPA->toSky);
+	psDeproject (&dstSky, dstTP, outFPA->toSky);
+
+	double dX = (srcSky.r*PS_DEG_RAD - dstSky.r*PS_DEG_RAD)*3600.0;
+	double dY = (srcSky.d*PS_DEG_RAD - dstSky.d*PS_DEG_RAD)*3600.0;
+	fprintf (stderr, "%f %f | %f %f | %f %f | %f %f | %f %f | %f %f\n", dX, dY, srcChip->x, srcChip->y, srcFP.x, srcFP.y, dstTP->x, dstTP->y, srcSky.r*PS_DEG_RAD, srcSky.d*PS_DEG_RAD, dstSky.r*PS_DEG_RAD, dstSky.d*PS_DEG_RAD);
+
+    }
+# endif
+
+    psFree(src);
+    psFree(dst);
+
+    // this is a linear transformation
+    psPlaneTransform *newFromFPA = psPlaneTransformInvert(NULL, newToFPA, *bounds, 1);
+    if (!newFromFPA) {
+        psFree(newToFPA);
+        psError(PS_ERR_UNKNOWN, false, "inversion of fit of output chip toFPA failed");
+        return false;
+    }
+
+    // Success. Now set the fpa's toTPA and fromTPA to identity and replace the chip's transforms.
+    psFree(outChip->toFPA);
+    outChip->toFPA = newToFPA;
+
+    psFree(outChip->fromFPA);
+    outChip->fromFPA = newFromFPA;
+
+    psFree(outFPA->toTPA);
+    outFPA->toTPA =  psPlaneTransformIdentity(1);
+
+    psFree(outFPA->fromTPA);
+    outFPA->fromTPA = psPlaneTransformIdentity(1);
 
     return true;
@@ -922,4 +1084,5 @@
     wcs->trans = psPlaneTransformAlloc (nXorder, nYorder);
     wcs->toSky = NULL;
+    wcs->wcsCDkeys = 0;
 
     memset (wcs->ctype1, 0, PM_ASTROM_WCS_TYPE_SIZE);
Index: branches/simmosaic_branches/psModules/src/astrom/pmAstrometryWCS.h
===================================================================
--- branches/simmosaic_branches/psModules/src/astrom/pmAstrometryWCS.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/astrom/pmAstrometryWCS.h	(revision 27839)
@@ -23,4 +23,5 @@
     double crpix1, crpix2;
     double cdelt1, cdelt2;
+    bool wcsCDkeys;
     psProjection *toSky;
     psPlaneTransform *trans;
@@ -62,5 +63,6 @@
 bool pmAstromWriteBilevelMosaic (psMetadata *header, const pmFPA *fpa, double tol);
 
-bool pmAstromLinearizeTransforms(pmFPA *fpa, pmChip *);
+bool pmAstromLinearizeTransforms(pmFPA *inFPA, pmChip *inChip, pmFPA *outFPA, pmChip *outChip, psRegion *outputBounds, double offset_x, double offset_y);
+bool pmAstromLinearizeToSky(pmFPA *inFPA, pmChip *inChip, pmFPA *outFPA, pmChip *outChip, psRegion *bounds);
 
 // move to pslib
Index: branches/simmosaic_branches/psModules/src/camera/pmFPA.c
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPA.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPA.c	(revision 27839)
@@ -105,5 +105,5 @@
     psFree(fpa->concepts);
     psFree(fpa->analysis);
-    psFree((void *)fpa->camera);
+    psFree(fpa->camera);
     psFree(fpa->hdu);
 
@@ -378,4 +378,5 @@
     tmpFPA->toTPA = NULL;
     tmpFPA->toSky = NULL;
+    tmpFPA->wcsCDkeys = false;
 
     tmpFPA->analysis = psMetadataAlloc();
Index: branches/simmosaic_branches/psModules/src/camera/pmFPA.h
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPA.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPA.h	(revision 27839)
@@ -48,4 +48,5 @@
     psPlaneTransform *toTPA;  ///< Transformation from focal plane to tangent plane, or NULL
     psProjection *toSky;         ///< Projection from tangent plane to sky, or NULL
+    bool wcsCDkeys;
     // Information
     psMetadata *concepts;               ///< FPA-level concepts
Index: branches/simmosaic_branches/psModules/src/camera/pmFPABin.c
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPABin.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPABin.c	(revision 27839)
@@ -60,4 +60,7 @@
                         continue;
                     }
+                    if (!isfinite(inImage->data.F32[y][x])) {
+                        continue;
+                    }
                     sum += inImage->data.F32[y][x];
                     numPix++;
Index: branches/simmosaic_branches/psModules/src/camera/pmFPAMaskWeight.c
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPAMaskWeight.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPAMaskWeight.c	(revision 27839)
@@ -111,6 +111,6 @@
         // psError(PS_ERR_IO, true, "CELL.SATURATION is not set --- unable to set mask.\n");
         // return false;
-	psWarning("CELL.SATURATION is not set --- completely masking cell.\n");
-	saturation = NAN;
+        psWarning("CELL.SATURATION is not set --- completely masking cell.\n");
+        saturation = NAN;
     }
     float bad = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD"); // Bad level
@@ -118,10 +118,10 @@
         // psError(PS_ERR_IO, true, "CELL.BAD is not set --- unable to set mask.\n");
         // return false;
-	psWarning("CELL.BAD is not set --- completely masking cell.\n");
-	bad = NAN;
+        psWarning("CELL.BAD is not set --- completely masking cell.\n");
+        bad = NAN;
     }
     psTrace("psModules.camera", 5, "Saturation: %f, bad: %f\n", saturation, bad);
 
-    // if CELL.GAIN or CELL.READNOISE are not set, then the variance will be set to NAN; 
+    // if CELL.GAIN or CELL.READNOISE are not set, then the variance will be set to NAN;
     // in this case, we have to set the mask as well
     float gain = psMetadataLookupF32(&mdok, cell->concepts, "CELL.GAIN"); // Cell gain
@@ -140,6 +140,6 @@
     // completely mask if SATURATION or BAD are invalid
     if (isnan(saturation) || isnan(bad) || isnan(gain) || isnan(readnoise)) {
-	psImageInit(mask, badMask);
-	return true;
+        psImageInit(mask, badMask);
+        return true;
     }
 
@@ -230,5 +230,5 @@
         // return false;
         psWarning("CELL.GAIN is not set --- setting variance to NAN\n");
-	gain = NAN;
+        gain = NAN;
     }
     float readnoise = psMetadataLookupF32(&mdok, cell->concepts, "CELL.READNOISE"); // Cell read noise
@@ -237,5 +237,5 @@
         // return false;
         psWarning("CELL.READNOISE is not set --- setting variance to NAN\n");
-	readnoise = NAN;
+        readnoise = NAN;
     }
     // if we have a non-NAN readnoise, then we need to ensure it has been updated (not necessary if NAN)
@@ -248,10 +248,10 @@
     if (isnan(gain) || isnan(readnoise)) {
         if (!readout->variance) {
-	    // generate the image if needed
+            // generate the image if needed
             readout->variance = psImageAlloc(readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
         }
-	// XXX need to set the mask, if defined
+        // XXX need to set the mask, if defined
         psImageInit(readout->variance, NAN);
-	return true;
+        return true;
     }
 
@@ -262,5 +262,5 @@
 
         // a negative variance is non-sensical. if the image value drops below 1, the variance must be 1.
-	// XXX this calculation is wrong: limit is 1 e-, but this is in DN
+        // XXX this calculation is wrong: limit is 1 e-, but this is in DN
         readout->variance = (psImage*)psUnaryOp(readout->variance, readout->variance, "abs");
         readout->variance = (psImage*)psBinaryOp(readout->variance, readout->variance, "max",
@@ -276,9 +276,9 @@
     // apply a supplied readnoise map (NOTE: in DN, not electrons):
     if (noiseMap) {
-	psImage *rdVar = (psImage*)psBinaryOp(NULL, (const psPtr) noiseMap, "*", (const psPtr) noiseMap);
-	readout->variance = (psImage*)psBinaryOp(readout->variance, readout->variance, "+", rdVar);
-	psFree (rdVar);
+        psImage *rdVar = (psImage*)psBinaryOp(NULL, (const psPtr) noiseMap, "*", (const psPtr) noiseMap);
+        readout->variance = (psImage*)psBinaryOp(readout->variance, readout->variance, "+", rdVar);
+        psFree (rdVar);
     } else {
-	readout->variance = (psImage*)psBinaryOp(readout->variance, readout->variance, "+", psScalarAlloc(readnoise*readnoise/gain/gain, PS_TYPE_F32));
+        readout->variance = (psImage*)psBinaryOp(readout->variance, readout->variance, "+", psScalarAlloc(readnoise*readnoise/gain/gain, PS_TYPE_F32));
     }
 
@@ -362,6 +362,6 @@
 
 
-bool pmReadoutVarianceRenormPixels(const pmReadout *readout, psImageMaskType maskVal,
-                                 psStatsOptions meanStat, psStatsOptions stdevStat, psRandom *rng)
+bool pmReadoutVarianceRenormalise(const pmReadout *readout, psImageMaskType maskVal,
+                                  int sample, float minValid, float maxValid)
 {
     PM_ASSERT_READOUT_NON_NULL(readout, false);
@@ -370,264 +370,110 @@
 
     psImage *image = readout->image, *mask = readout->mask, *variance = readout->variance; // Readout parts
-
-    if (!psMemIncrRefCounter(rng)) {
-        rng = psRandomAlloc(PS_RANDOM_TAUS);
-    }
-
-    psStats *varianceStats = psStatsAlloc(meanStat);// Statistics for mean
-    if (!psImageBackground(varianceStats, NULL, variance, mask, maskVal, rng)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to measure mean variance for image");
-        psFree(varianceStats);
-        psFree(rng);
-        return false;
-    }
-    float meanVariance = varianceStats->robustMedian; // Mean variance
-    psFree(varianceStats);
-
-    psStats *imageStats = psStatsAlloc(stdevStat);// Statistics for mean
-    if (!psImageBackground(imageStats, NULL, image, mask, maskVal, rng)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to measure stdev of image");
-        psFree(imageStats);
-        psFree(rng);
-        return false;
-    }
-    float stdevImage = imageStats->robustStdev; // Standard deviation of image
-    psFree(imageStats);
-    psFree(rng);
-
-    float correction = PS_SQR(stdevImage) / meanVariance; // Correction to take variance to what it should be
-    psLogMsg("psModules.camera", PS_LOG_INFO, "Renormalising variance map by %f", correction);
-    psBinaryOp(variance, variance, "*", psScalarAlloc(correction, PS_TYPE_F32));
-
-    return true;
-}
-
-
-bool pmReadoutVarianceRenormPhot(const pmReadout *readout, psImageMaskType maskVal, int num, float width,
-                               psStatsOptions meanStat, psStatsOptions stdevStat, psRandom *rng)
-{
-    PM_ASSERT_READOUT_NON_NULL(readout, false);
-    PM_ASSERT_READOUT_IMAGE(readout, false);
-    PM_ASSERT_READOUT_VARIANCE(readout, false);
-
-    if (!psMemIncrRefCounter(rng)) {
-        rng = psRandomAlloc(PS_RANDOM_TAUS);
-    }
-
-    psImage *image = readout->image, *mask = readout->mask, *variance = readout->variance; // Readout images
-
-    // Measure background
-    psStats *bgStats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);// Statistics for background
-    if (!psImageBackground(bgStats, NULL, image, mask, maskVal, rng)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to measure background for image");
-        psFree(bgStats);
-        psFree(rng);
-        return false;
-    }
-    float bgMean = bgStats->robustMedian; // Background level
-    float bgNoise = bgStats->robustStdev; // Background standard deviation
-    psFree(bgStats);
-    psTrace("psModules.camera", 5, "Background is %f +/- %f\n", bgMean, bgNoise);
-
-
-    // Construct kernels for flux measurement
-    // We use N(0,width) and N(0,width/sqrt(2)) kernels, following psphotSignificanceImage.
-    float sigFactor = 4.0 * M_PI * PS_SQR(width); // Factor for conversion from im/wt ratio to significance
-    int size = RENORM_NUM_SIGMA, fullSize = 2 * size + 1; // Half-size and full size of Gaussian
-    psVector *gauss = psVectorAlloc(fullSize, PS_TYPE_F32); // Gaussian for weighting
-    psVector *gauss2 = psVectorAlloc(fullSize, PS_TYPE_F32); // Gaussian squared
-    for (int i = 0, x = -size; i < fullSize; i++, x++) {
-        gauss->data.F32[i] = expf(-0.5 * PS_SQR(x) / PS_SQR(width));
-        gauss2->data.F32[i] = expf(-PS_SQR(x) / PS_SQR(width));
-    }
-
-    // Size of image
-    int numCols = image->numCols, numRows = image->numRows; // Size of images
-    int xSize = numCols - fullSize, ySize = numRows - fullSize; // Size of consideration
-    int xOffset = size, yOffset = size;       // Offset to region of consideration
-
-    // Measure fluxes
-    float peakFlux = RENORM_PEAK * bgNoise;     // Peak flux for fake sources
-    psVector *noise = psVectorAlloc(num, PS_TYPE_F32); // Measurements of the noise
-    psVector *source = psVectorAlloc(num, PS_TYPE_F32); // Measurements of fake sources
-    psVector *guess = psVectorAlloc(num, PS_TYPE_F32); // Guess at significance
-    psVector *photMask = psVectorAlloc(num, PS_TYPE_VECTOR_MASK); // Mask for fluxes
-    for (int i = 0; i < num; i++) {
-        // Coordinates of interest
-        int xPix = psRandomUniform(rng) * xSize + xOffset + 0.5;
-        int yPix = psRandomUniform(rng) * ySize + yOffset + 0.5;
-        psAssert(xPix - size >= 0 && xPix + size < numCols &&
-                 yPix - size >= 0 && yPix + size < numRows,
-                 "Bad pixel position: %d,%d", xPix, yPix);
-
-        // Weighted aperture photometry
-        // This has the same effect as smoothing the image by the window function
-        float sumNoise = 0.0;       // Sum for noise measurement
-        float sumSource = 0.0;      // Sum for source measurement
-        float sumVariance = 0.0;      // Sum for variance measurement
-        float sumGauss = 0.0, sumGauss2 = 0.0; // Sums of Gaussian kernels
-        for (int v = 0, y = yPix - size; v < fullSize; v++, y++) {
-            float xSumNoise = 0.0;  // Sum for noise measurement in x
-            float xSumSource = 0.0; // Sum for source measurement in x
-            float xSumVariance = 0.0; // Sum for variance measurement in x
-            float xSumGauss = 0.0, xSumGauss2 = 0.0; // Sums of Gaussian kernels in x
-            float yGauss = gauss->data.F32[v]; // Value of Gaussian in y
-            for (int u = 0, x = xPix - size; u < fullSize; u++, x++) {
-                if (mask && mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskVal) {
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+
+    int xMin, xMax, yMin, yMax;         // Bounds of image
+    if (mask) {
+        xMin = numCols;
+        xMax = 0;
+        yMin = numRows;
+        yMax = 0;
+        for (int y = 0; y < numRows; y++) {
+            for (int x = 0; x < numCols; x++) {
+                if (mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskVal) {
                     continue;
                 }
-                float value = image->data.F32[y][x] - bgMean; // Value of image
-                float xGauss = gauss->data.F32[u]; // Value of Gaussian in x
-                float xGauss2 = gauss2->data.F32[u]; // Value of Gaussian^2 in x
-                xSumNoise += value * xGauss;
-                xSumSource += (value + peakFlux * xGauss * yGauss) * xGauss;
-                xSumVariance += variance->data.F32[y][x] * xGauss2;
-                xSumGauss += xGauss;
-                xSumGauss2 += xGauss2;
-            }
-            float yGauss2 = gauss2->data.F32[v]; // Value of Gaussian^2 in y
-            sumNoise += xSumNoise * yGauss;
-            sumSource += xSumSource * yGauss;
-            sumVariance += xSumVariance * yGauss2;
-            sumGauss += xSumGauss * yGauss;
-            sumGauss2 += xSumGauss2 * yGauss2;
-        }
-
-        photMask->data.PS_TYPE_VECTOR_MASK_DATA[i] = ((isfinite(sumNoise) && isfinite(sumSource) &&
-                                                isfinite(sumVariance) && sumGauss > 0 && sumGauss2 > 0) ?
-                                               0 : 0xFF);
-
-        float smoothImageNoise = sumNoise / sumGauss; // Value of smoothed image pixel for noise
-        float smoothImageSource = sumSource / sumGauss; // Value of smoothed image pixel for source
-        float smoothVariance = sumVariance / sumGauss2; // Value of smoothed variance pixel
-
-        noise->data.F32[i] = smoothImageNoise;
-        source->data.F32[i] = smoothImageSource;
-        guess->data.F32[i] = (sumSource > 0) ? sigFactor * PS_SQR(smoothImageSource) / smoothVariance : 0.0;
-        psTrace("psModules.camera", 10, "Flux %d (%d,%d): %f, %f, %f\n",
-                i, xPix, yPix, smoothImageNoise, smoothImageSource, smoothVariance);
-    }
-    psFree(gauss);
-    psFree(gauss2);
-    psFree(rng);
-
-    // Standard deviation of fluxes gives us the real significance
-    psStats *stdevStats = psStatsAlloc(stdevStat); // Statistics
-    if (!psVectorStats(stdevStats, noise, NULL, photMask, 0xFF)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to measure standard deviation of fluxes");
-        psFree(stdevStats);
-        psFree(noise);
-        psFree(source);
-        psFree(guess);
-        psFree(photMask);
+                xMin = PS_MIN(xMin, x);
+                xMax = PS_MAX(xMax, x);
+                yMin = PS_MIN(yMin, y);
+                yMax = PS_MAX(yMax, y);
+            }
+        }
+    } else {
+        xMin = 0;
+        xMax = numCols;
+        yMin = 0;
+        yMax = numRows;
+    }
+
+    int xNum = xMax - xMin, yNum = yMax - yMin; // Number of pixels
+
+    int numPix = xNum * yNum;                                  // Number of pixels
+    int num = PS_MIN(sample, numPix);                          // Number we care about
+    psVector *signoise = psVectorAllocEmpty(num, PS_TYPE_F32);   // Signal-to-noise values
+
+    if (num >= numPix) {
+        // We have an image smaller than Nsubset, so just loop over the image pixels
+        int index = 0;                  // Index for vector
+        for (int y = yMin; y < yMax; y++) {
+            for (int x = xMin; x < xMax; x++) {
+                if ((mask && mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskVal) ||
+                    !isfinite(image->data.F32[y][x]) || !isfinite(variance->data.F32[y][x])) {
+                    continue;
+                }
+
+                signoise->data.F32[index] = image->data.F32[y][x] / sqrtf(variance->data.F32[y][x]);
+                index++;
+            }
+        }
+        signoise->n = index;
+    } else {
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS); // Random number generator
+        int index = 0;                  // Index for vector
+        for (long i = 0; i < num; i++) {
+            // Pixel coordinates
+            int pixel = numPix * psRandomUniform(rng);
+            int x = xMin + pixel % xNum;
+            int y = yMin + pixel / xNum;
+
+            if ((mask && mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskVal) ||
+                !isfinite(image->data.F32[y][x]) || !isfinite(variance->data.F32[y][x])) {
+                continue;
+            }
+
+            signoise->data.F32[index] = image->data.F32[y][x] / sqrtf(variance->data.F32[y][x]);
+            index++;
+        }
+        signoise->n = index;
+        psFree(rng);
+    }
+
+    psStats *stats = psStatsAlloc(PS_STAT_ROBUST_STDEV); // Statistics
+
+    if (!psVectorStats(stats, signoise, NULL, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to measure statistics on S/N image");
+        psFree(signoise);
         return false;
     }
-    float stdev = psStatsGetValue(stdevStats, stdevStat); // Standard deviation of fluxes
-    psFree(stdevStats);
-    psFree(noise);
-    psTrace("psModules.camera", 5, "Standard deviation of fluxes is %f\n", stdev);
-
-    // Ratio of measured significance to guessed significance
-    psVector *ratio = psVectorAlloc(num, PS_TYPE_F32); // Ratio of measured to guess
-    for (int i = 0; i < num; i++) {
-        float measuredSig = PS_SQR(source->data.F32[i] / stdev); // Measured significance
-        ratio->data.F32[i] = measuredSig / guess->data.F32[i];
-        if (guess->data.F32[i] <= 0.0 || source->data.F32[i] <= 0.0 || !isfinite(ratio->data.F32[i])) {
-            photMask->data.PS_TYPE_VECTOR_MASK_DATA[i] = 0xFF;
-        }
-        psTrace("psModules.camera", 9, "Ratio %d: %f, %f, %f\n",
-                i, guess->data.F32[i], measuredSig, ratio->data.F32[i]);
-    }
-    psFree(source);
-    psFree(guess);
-
-    psStats *meanStats = psStatsAlloc(meanStat | stdevStat); // Statistics
-    if (!psVectorStats(meanStats, ratio, NULL, photMask, 0xFF)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to measure mean ratio");
-        psFree(meanStats);
-        psFree(ratio);
-        psFree(photMask);
+    psFree(signoise);
+
+    float covar = sqrtf(psImageCovarianceFactor(readout->covariance)); // Covariance factor
+    float correction = stats->robustStdev / covar; // Correction factor
+    psFree(stats);
+    psLogMsg("psModules.camera", PS_LOG_DETAIL, "Variance renormalisation factor is %f", correction);
+
+    // Check valid range of correction factor
+    if ((isfinite(minValid) && correction < minValid) || (isfinite(maxValid) && correction > maxValid)) {
+	psError(PS_ERR_UNKNOWN, true, "Variance renormalisation is outside valid range: %f vs %f:%f --- no correction made", correction, minValid, maxValid);
+	psMetadataAddF32(readout->analysis, PS_LIST_TAIL, PM_READOUT_ANALYSIS_RENORM, 0, "Renormalisation of variance", PS_SQR(correction));
         return false;
     }
-    float meanRatio = psStatsGetValue(meanStats, meanStat); // Mean ratio
-    psTrace("psModules.camera", 5, "Mean significance ratio is %f +/- %f\n",
-            meanRatio, psStatsGetValue(meanStats, stdevStat));
-    psFree(meanStats);
-    psFree(ratio);
-    psFree(photMask);
-
-    psLogMsg("psModules.camera", PS_LOG_INFO, "Renormalising variance map by %f", meanRatio);
-    psBinaryOp(variance, variance, "*", psScalarAlloc(meanRatio, PS_TYPE_F32));
-
-    return true;
-}
-
-
-bool pmReadoutVarianceRenorm(const pmReadout *readout, psImageMaskType maskVal, psStatsOptions meanStat,
-                           psStatsOptions stdevStat, int width, psRandom *rng)
-{
-    PM_ASSERT_READOUT_NON_NULL(readout, false);
-    PM_ASSERT_READOUT_IMAGE(readout, false);
-    PM_ASSERT_READOUT_VARIANCE(readout, false);
-    PS_ASSERT_INT_POSITIVE(width, false);
-
-    if (!psMemIncrRefCounter(rng)) {
-        rng = psRandomAlloc(PS_RANDOM_TAUS);
-    }
-
-    psImage *image = readout->image, *mask = readout->mask, *variance = readout->variance; // Readout images
-    int numCols = image->numCols, numRows = image->numRows; // Size of images
-    int xNum = numCols / width + 1, yNum = numRows / width + 1; // Number of renormalisation regions
-    float xSize = numCols / (float)xNum, ySize = numRows / (float)yNum; // Size of renormalisation regions
-
-    psStats *meanStats = psStatsAlloc(meanStat), *stdevStats = psStatsAlloc(stdevStat); // Statistics
-    psVector *buffer = NULL;
-
-    for (int j = 0; j < yNum; j++) {
-        // Bounds in y
-        int yMin = j * ySize;
-        int yMax = (j + 1) * ySize;
-        for (int i = 0; i < xNum; i++) {
-            // Bounds in x
-            int xMin = i * xSize;
-            int xMax = (i + 1) * xSize;
-
-            psRegion region = psRegionSet(xMin, xMax, yMin, yMax); // Region of interest
-            psImage *subImage = psImageSubset(image, region); // Sub-image of the image pixels
-            psImage *subVariance = psImageSubset(variance, region); // Sub image of the variance pixels
-            psImage *subMask = mask ? psImageSubset(mask, region) : NULL; // Sub-image of the mask pixels
-
-            if (!psImageBackground(stdevStats, &buffer, subImage, subMask, maskVal, rng) ||
-                !psImageBackground(meanStats, &buffer, subVariance, subMask, maskVal, rng)) {
-                // Nothing we can do about it, but don't want to keel over and die, so do our best to flag it.
-                psString regionStr = psRegionToString(region); // String with region
-                psWarning("Unable to measure statistics over %s", regionStr);
-                psFree(regionStr);
-                psErrorClear();
-                psImageInit(subVariance, NAN);
-                if (subMask) {
-                    psImageInit(subMask, maskVal);
-                }
-            } else {
-                double meanVar = psStatsGetValue(meanStats, meanStat); // Mean of variance map
-                double stdev = psStatsGetValue(stdevStats, stdevStat); // Standard deviation of image
-                psTrace("psModules.camera", 3,
-                        "Region [%d:%d,%d:%d] has variance %lf, but mean of variance map is %lf\n",
-                        xMin, xMax, yMin, yMax, PS_SQR(stdev), meanVar);
-                psBinaryOp(subVariance, subVariance, "*", psScalarAlloc(PS_SQR(stdev) / meanVar, PS_TYPE_F32));
-            }
-
-            psFree(subImage);
-            psFree(subVariance);
-            psFree(subMask);
-        }
-    }
-    psFree(meanStats);
-    psFree(stdevStats);
-    psFree(rng);
-    psFree(buffer);
-
-    return true;
-}
+
+    psImage *subImage = psImageSubset(variance, psRegionSet(xMin, xMax, yMin, yMax)); // Smaller image
+    psBinaryOp(subImage, subImage, "*", psScalarAlloc(PS_SQR(correction), PS_TYPE_F32));
+    psFree(subImage);
+
+    pmHDU *hdu = pmHDUFromReadout(readout); // HDU for readout
+    if (hdu)  {
+        psString history = NULL;
+        psStringAppend(&history, "Rescaled variance by %6.4f (stdev by %6.4f)",
+                       PS_SQR(correction), correction);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, history);
+        psFree(history);
+    }
+
+    return psMetadataAddF32(readout->analysis, PS_LIST_TAIL, PM_READOUT_ANALYSIS_RENORM, 0,
+                            "Renormalisation of variance", PS_SQR(correction));
+}
+
 
 
Index: branches/simmosaic_branches/psModules/src/camera/pmFPAMaskWeight.h
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPAMaskWeight.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPAMaskWeight.h	(revision 27839)
@@ -16,23 +16,5 @@
 /// @{
 
-#if 0
-/// Pixel mask values
-typedef enum {
-    PM_MASK_CLEAR    = 0x00,            ///< The pixel is not masked
-    PM_MASK_BLANK    = 0x01,            ///< The pixel is blank or has no (valid) data
-    PM_MASK_FLAT     = 0x02,            ///< The pixel is non-positive in the flat-field
-    PM_MASK_DETECTOR = 0x02,            ///< The detector pixel is bad (e.g., bad column, charge trap)
-    PM_MASK_SAT      = 0x04,            ///< The pixel is saturated in the image of interest
-    PM_MASK_BAD      = 0x04,            ///< The pixel is low in the image of interest
-    PM_MASK_RANGE    = 0x04,            ///< The pixel is out of range in the image of interest
-    PM_MASK_CR       = 0x08,            ///< The pixel is probably a CR
-    PM_MASK_SPARE1   = 0x10,            ///< Spare mask value
-    PM_MASK_SPARE2   = 0x20,            ///< Spare mask value
-    PM_MASK_SUSPECT  = 0x40,            ///< The pixel is suspected of being bad, but may not be
-    PM_MASK_MARK     = 0x80,            ///< The pixel is marked as temporarily ignored
-} pmMaskValue;
-#define PM_MASK_MARK 0x80            ///< The pixel is marked as temporarily ignored
-#define PM_MASK_SAT  0x04            ///< The pixel is saturated in the image of interest
-#endif
+#define PM_READOUT_ANALYSIS_RENORM "READOUT.RENORM" // Name on analysis metadata for renormalisation
 
 /// Set a temporary readout mask using CELL.SATURATION and CELL.BAD
@@ -54,5 +36,5 @@
 /// can't be generated.
 bool pmReadoutSetVariance(pmReadout *readout, ///< Readout for which to set variance
-			  const psImage *noiseMap, ///< 2D image of the read noise in DN
+                          const psImage *noiseMap, ///< 2D image of the read noise in DN
                           bool poisson    ///< Include poisson variance (in addition to read noise)?
     );
@@ -73,5 +55,5 @@
 /// with HDU entry).  This is intended for most operations.
 bool pmReadoutGenerateVariance(pmReadout *readout, ///< Readout for which to generate variance
-			  const psImage *noiseMap, ///< 2D image of the read noise in DN
+                          const psImage *noiseMap, ///< 2D image of the read noise in DN
                                bool poisson    ///< Include poisson variance (in addition to read noise)?
     );
@@ -83,5 +65,5 @@
                                    psImageMaskType sat, ///< Mask value to give saturated pixels
                                    psImageMaskType bad, ///< Mask value to give bad (low) pixels
-				   const psImage *noiseMap, ///< 2D image of the read noise in DN
+                                   const psImage *noiseMap, ///< 2D image of the read noise in DN
                                    bool poisson ///< Include poisson variance (in addition to read noise)?
     );
@@ -93,5 +75,5 @@
                                 psImageMaskType sat, ///< Mask value to give saturated pixels
                                 psImageMaskType bad, ///< Mask value to give bad (low) pixels
-				const psImage *noiseMap, ///< 2D image of the read noise in DN
+                                const psImage *noiseMap, ///< 2D image of the read noise in DN
                                 bool poisson ///< Include poisson variance (in addition to read noise)?
     );
@@ -100,36 +82,10 @@
 ///
 /// The variance map is adjusted so that the mean matches the actual pixel variance in the image
-bool pmReadoutVarianceRenormPixels(
-    const pmReadout *readout,           ///< Readout to normalise
-    psImageMaskType maskVal,                 ///< Value to mask
-    psStatsOptions meanStat,            ///< Statistic to measure the mean (of the variance map)
-    psStatsOptions stdevStat,           ///< Statistic to measure the stdev (of the image)
-    psRandom *rng                       ///< Random number generator
-    );
-
-/// Renormalise the variance map to match the actual photometry variance
-///
-/// The variance map is adjusted so that the actual significance of fake sources matches the
-/// guestimated significance
-bool pmReadoutVarianceRenormPhot(
+bool pmReadoutVarianceRenormalise(
     const pmReadout *readout,           ///< Readout to normalise
     psImageMaskType maskVal,            ///< Value to mask
-    int num,                            ///< Number of instances to measure over the image
-    float width,                        ///< Photometry width
-    psStatsOptions meanStat,            ///< Statistic to measure the mean
-    psStatsOptions stdevStat,           ///< Statistic to measure the stdev
-    psRandom *rng                       ///< Random number generator
-    );
-
-/// Renormalise the variance map to match the actual variance
-///
-/// The variance in the image is measured in patches, and the variance map is adjusted so that the mean for
-/// that patch corresponds.
-bool pmReadoutVarianceRenorm(const pmReadout *readout, // Readout to normalise
-                             psImageMaskType maskVal, // Value to mask
-                             psStatsOptions meanStat, // Statistic to measure the mean (of the variance map)
-                             psStatsOptions stdevStat, // Statistic to measure the stdev (of the image)
-                             int width,   // Width of patch (pixels)
-                             psRandom *rng // Random number generator (for sub-sampling images)
+    int sample,                         ///< Sample size
+    float minValid,                     ///< Minimum valid renormalisation, or NAN
+    float maxValid                      ///< Maximum valid renormalisation, or NAN
     );
 
Index: branches/simmosaic_branches/psModules/src/camera/pmFPAMosaic.c
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPAMosaic.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPAMosaic.c	(revision 27839)
@@ -626,16 +626,20 @@
     bool good = true;                   // Is everything good?
 
+    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+    const char *chipName = psMetadataLookupStr(NULL, cell->parent->concepts, "CHIP.NAME"); // Name of chip
+
     // Offset of the cell on the chip
     int x0Cell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.X0");
     if (!mdok) {
-        psError(PS_ERR_UNKNOWN, true, "CELL.X0 for cell is not set.\n");
+        psError(PS_ERR_UNKNOWN, true, "CELL.X0 for cell %s,%s is not set.\n", chipName, cellName);
         good = false;
     }
     int y0Cell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.Y0");
     if (!mdok) {
-        psError(PS_ERR_UNKNOWN, true, "CELL.Y0 for cell is not set.\n");
+        psError(PS_ERR_UNKNOWN, true, "CELL.Y0 for cell %s,%s is not set.\n", chipName, cellName);
         good = false;
     }
-    psTrace("psModules.camera", 5, "Cell %ld: x0=%d y0=%d\n", index, x0Cell, y0Cell);
+    psTrace("psModules.camera", 5, "Cell %s,%s (%ld): x0=%d y0=%d\n",
+            chipName, cellName, index, x0Cell, y0Cell);
 
     // Offset of the chip on the FPA
@@ -649,10 +653,10 @@
         x0Chip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.X0");
         if (!mdok) {
-            psError(PS_ERR_UNKNOWN, true, "CHIP.X0 for chip is not set.\n");
+            psError(PS_ERR_UNKNOWN, true, "CHIP.X0 for chip %s is not set.\n", chipName);
             good = false;
         }
         y0Chip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.Y0");
         if (!mdok) {
-            psError(PS_ERR_UNKNOWN, true, "CHIP.Y0 for chip is not set.\n");
+            psError(PS_ERR_UNKNOWN, true, "CHIP.Y0 for chip %s is not set.\n", chipName);
             good = false;
         }
@@ -662,5 +666,5 @@
     xBin->data.S32[index] = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XBIN");
     if (!mdok || xBin->data.S32[index] == 0) {
-        psError(PS_ERR_UNKNOWN, true, "CELL.XBIN for cell is not set.\n");
+        psError(PS_ERR_UNKNOWN, true, "CELL.XBIN for cell %s,%s is not set.\n", chipName, cellName);
         return false;
     } else if (xBin->data.S32[index] < *xBinMin) {
@@ -669,5 +673,5 @@
     yBin->data.S32[index] = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YBIN");
     if (!mdok || yBin->data.S32[index] == 0) {
-        psError(PS_ERR_UNKNOWN, true, "CELL.YBIN for cell is not set.\n");
+        psError(PS_ERR_UNKNOWN, true, "CELL.YBIN for cell %s,%s is not set.\n", chipName, cellName);
         return false;
     } else if (yBin->data.S32[index] < *yBinMin) {
@@ -678,10 +682,10 @@
     int xParityCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XPARITY");
     if (!mdok || (xParityCell != 1 && xParityCell != -1)) {
-        psError(PS_ERR_UNKNOWN, true, "CELL.XPARITY for cell is not set.\n");
+        psError(PS_ERR_UNKNOWN, true, "CELL.XPARITY for cell %s,%s is not set.\n", chipName, cellName);
         return false;
     }
     int yParityCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YPARITY");
     if (!mdok || (yParityCell != 1 && yParityCell != -1)) {
-        psError(PS_ERR_UNKNOWN, true, "CELL.YPARITY for cell is not set.\n");
+        psError(PS_ERR_UNKNOWN, true, "CELL.YPARITY for cell %s,%s is not set.\n", chipName, cellName);
         return false;
     }
@@ -693,10 +697,10 @@
         xParityChip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.XPARITY");
         if (!mdok || (xParityChip != 1 && xParityChip != -1)) {
-            psError(PS_ERR_UNKNOWN, true, "CHIP.XPARITY for chip is not set.\n");
+            psError(PS_ERR_UNKNOWN, true, "CHIP.XPARITY for chip %s is not set.\n", chipName);
             return false;
         }
         yParityChip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.YPARITY");
         if (!mdok || (yParityChip != 1 && yParityChip != -1)) {
-            psError(PS_ERR_UNKNOWN, true, "CHIP.YPARITY for chip is not set.\n");
+            psError(PS_ERR_UNKNOWN, true, "CHIP.YPARITY for chip %s is not set.\n", chipName);
             return false;
         }
@@ -740,5 +744,5 @@
 static bool chipMosaic(psImage **mosaicImage, // The mosaic image, to be returned
                        psImage **mosaicMask, // The mosaic mask, to be returned
-                       psImage **mosaicVariances, // The mosaic variances, to be returned
+                       psImage **mosaicVariance, // The mosaic variance, to be returned
                        int *xBinChip, int *yBinChip, // The binning in x and y, to be returned
                        const pmChip *chip, // Chip to mosaic
@@ -749,5 +753,5 @@
     assert(mosaicImage);
     assert(mosaicMask);
-    assert(mosaicVariances);
+    assert(mosaicVariance);
     assert(xBinChip);
     assert(yBinChip);
@@ -826,5 +830,5 @@
     if (allGood) {
         *mosaicImage = imageMosaic(images, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0, BLANK_VALUE);
-        *mosaicVariances = imageMosaic(variances, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0, BLANK_VALUE);
+        *mosaicVariance = imageMosaic(variances, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0, BLANK_VALUE);
         *mosaicMask = imageMosaic(masks, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0, blank);
     }
@@ -847,5 +851,5 @@
 static bool fpaMosaic(psImage **mosaicImage, // The mosaic image, to be returned
                       psImage **mosaicMask, // The mosaic mask, to be returned
-                      psImage **mosaicVariances, // The mosaic variances, to be returned
+                      psImage **mosaicVariance, // The mosaic variance, to be returned
                       int *xBinFPA, int *yBinFPA, // The binning in x and y, to be returned
                       const pmFPA *fpa,  // FPA to mosaic
@@ -857,5 +861,5 @@
     assert(mosaicImage);
     assert(mosaicMask);
-    assert(mosaicVariances);
+    assert(mosaicVariance);
     assert(xBinFPA);
     assert(yBinFPA);
@@ -960,5 +964,5 @@
     if (allGood) {
         *mosaicImage = imageMosaic(images, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0, BLANK_VALUE);
-        *mosaicVariances = imageMosaic(variances, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0, BLANK_VALUE);
+        *mosaicVariance = imageMosaic(variances, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0, BLANK_VALUE);
         *mosaicMask = imageMosaic(masks, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0, blank);
     }
@@ -1025,5 +1029,5 @@
     psImage *mosaicImage   = NULL;      // The mosaic image
     psImage *mosaicMask    = NULL;      // The mosaic mask
-    psImage *mosaicVariances = NULL;      // The mosaic variances
+    psImage *mosaicVariance = NULL;      // The mosaic variances
 
     // Find the HDU
@@ -1052,6 +1056,6 @@
         }
         if (hdu->variances) {
-            mosaicVariances = psImageSubset(hdu->variances->data[0], bounds);
-            if (!mosaicVariances) {
+            mosaicVariance = psImageSubset(hdu->variances->data[0], bounds);
+            if (!mosaicVariance) {
                 psError(PS_ERR_UNKNOWN, false, "Unable to select variance pixels.\n");
                 return false;
@@ -1061,5 +1065,5 @@
         // Case 2 --- we need to mosaic by cut and paste
         psTrace("psModules.camera", 1, "Case 2 mosaicking: cut and paste.\n");
-        if (!chipMosaic(&mosaicImage, &mosaicMask, &mosaicVariances, &xBin, &yBin, source, targetCell, blank)) {
+        if (!chipMosaic(&mosaicImage, &mosaicMask, &mosaicVariance, &xBin, &yBin, source, targetCell, blank)) {
             psError(PS_ERR_UNKNOWN, false, "Unable to mosaic cells.\n");
             return false;
@@ -1069,4 +1073,5 @@
     }
     psTrace("psModules.camera", 1, "xBin,yBin: %d,%d\n", xBin, yBin);
+
 
     // Set the concepts for the target cell
@@ -1090,9 +1095,31 @@
     target->parent->concepts = psMetadataCopy(target->parent->concepts, source->parent->concepts); // FPA lvl
 
+    // Average the covariances
+    psList *covariances = psListAlloc(NULL); // Input covariance matrices
+    for (int i = 0; i < source->cells->n; i++) {
+        pmCell *cell = source->cells->data[i]; // Cell of interest
+        if (!cell || !cell->data_exists) {
+            continue;
+        }
+        pmReadout *ro = cell->readouts->data[0]; // Readout of interest
+        if (!ro || !ro->covariance) {
+            continue;
+        }
+        psListAdd(covariances, PS_LIST_TAIL, ro->covariance);
+    }
+    psKernel *mosaicCovariance = NULL;  // Covariance for mosaic
+    if (psListLength(covariances) > 0) {
+        psArray *covarArray = psListToArray(covariances); // Array with covariances
+        mosaicCovariance = psImageCovarianceAverage(covarArray);
+        psFree(covarArray);
+    }
+    psFree(covariances);
+
     // Now make a new readout to go in the target cell
     pmReadout *newReadout = pmReadoutAlloc(targetCell); // New readout
     newReadout->image  = mosaicImage;
     newReadout->mask   = mosaicMask;
-    newReadout->variance = mosaicVariances;
+    newReadout->variance = mosaicVariance;
+    newReadout->covariance = mosaicCovariance;
     psFree(newReadout);                 // Drop reference
 
@@ -1334,4 +1361,32 @@
     target->concepts = psMetadataCopy(target->concepts, source->concepts);
 
+    // Average the covariances
+    psList *covariances = psListAlloc(NULL); // Input covariance matrices
+    for (int i = 0; i < covariances->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        if (!chip || !chip->data_exists) {
+            continue;
+        }
+        psArray *cells = chip->cells;   // Cells in chip
+        for (long j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[i]; // Cell of interest
+            if (!cell || !cell->data_exists) {
+                continue;
+            }
+            pmReadout *ro = cell->readouts->data[0]; // Readout of interest
+            if (!ro || !ro->covariance) {
+                continue;
+            }
+            psListAdd(covariances, PS_LIST_TAIL, ro->covariance);
+        }
+    }
+    psKernel *mosaicCovariances = NULL; // Covariance for mosaic
+    if (psListLength(covariances) > 0) {
+        psArray *covarArray = psListToArray(covariances); // Array with covariances
+        mosaicCovariances = psImageCovarianceAverage(covarArray);
+        psFree(covarArray);
+    }
+    psFree(covariances);
+
     // Now make a new readout to go in the new cell
     pmReadout *newReadout = pmReadoutAlloc(targetCell); // New readout
@@ -1339,4 +1394,5 @@
     newReadout->mask   = mosaicMask;
     newReadout->variance = mosaicVariances;
+    newReadout->covariance = mosaicCovariances;
     psFree(newReadout);                 // Drop reference
 
Index: branches/simmosaic_branches/psModules/src/camera/pmFPARead.c
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPARead.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPARead.c	(revision 27839)
@@ -296,5 +296,5 @@
                         psFits *fits,    // FITS file
                         int z,          // Plane number to read
-                        int *zMax,	// Max plane number in this cell
+                        int *zMax,      // Max plane number in this cell
                         int numScans,   // Number of scans to read at a time
                         fpaReadType type, // Type of image
@@ -314,5 +314,5 @@
         return true;
       } else {
-	return false;
+        return false;
       }
     }
@@ -387,4 +387,5 @@
                 psFree(regionString);
                 psFree(readout);
+                psFree(iter);
                 return false;
             }
@@ -485,5 +486,5 @@
                              psFits *fits, // FITS file
                              int z,     // Desired image plane
-			     int *zMax,	// Max plane number in this cell
+                             int *zMax, // Max plane number in this cell
                              int numScans, // Number of scans (row or col depends on CELL.READDIR); 0 for all
                              int overlap, // Number of scans (row/col) to overlap between scans
@@ -627,6 +628,6 @@
     // Blow away existing data.
     // Do this before returning, so that we're not returning data from a previous read
-    psFree(*image);
-    *image = NULL;
+//    psFree(*image);
+//    *image = NULL;
     *image = readoutReadComponent(*image, fits, trimsec, readdir, thisScan, lastScan, z, bad, pixelTypes[type]);
 
Index: branches/simmosaic_branches/psModules/src/camera/pmFPAWrite.c
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPAWrite.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPAWrite.c	(revision 27839)
@@ -307,5 +307,5 @@
 
         if (writeBlank || writeImage) {
-            if (!pmConceptsWriteFPA(fpa, true, config)) {
+	    if (!pmConceptsWriteFPA(fpa, true, config)) {
                 psError(PS_ERR_IO, false, "Unable to write concepts for FPA.\n");
                 return false;
Index: branches/simmosaic_branches/psModules/src/camera/pmFPAfile.c
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPAfile.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPAfile.c	(revision 27839)
@@ -22,4 +22,13 @@
 static int fileNum = 0;                 // Number of file
 
+static bool fpaFileFreeStrict = true;   // Strict checking when file has been freed?
+
+bool pmFPAfileFreeSetStrict(bool new)
+{
+    bool old = fpaFileFreeStrict;       // Old value, to return
+    fpaFileFreeStrict = new;
+    return old;
+}
+
 static void pmFPAfileFree(pmFPAfile *file)
 {
@@ -27,4 +36,6 @@
         return;
     }
+
+    psAssert(!fpaFileFreeStrict || file->fits == NULL, "File %s wasn't closed.", file->name);
 
     psTrace ("pmFPAfileFree", 5, "freeing %s\n", file->name);
@@ -40,7 +51,4 @@
     psFree (file->name);
 
-    if (file->fits != NULL) {
-        psFitsClose (file->fits);
-    }
     psFree(file->compression);
     psFree(file->options);
@@ -103,5 +111,6 @@
     file->save = false;
 
-    file->index = fileNum++;
+    file->fileIndex = fileNum++;
+    file->fileID = 0;
 
     file->imageId = 0;
@@ -364,6 +373,14 @@
         // Number of the file in list
         psString num = NULL;            // Number to use
-        psStringAppend(&num, "%d", file->index);
+        psStringAppend(&num, "%d", file->fileIndex);
         psStringSubstitute(&newRule, num, "{FILE.INDEX}");
+        psFree(num);
+    }
+
+    if (strstr(newRule, "{FILE.ID}")) {
+        // Number of the file in list
+        psString num = NULL;            // Number to use
+        psStringAppend(&num, "%03d", file->fileID);
+        psStringSubstitute(&newRule, num, "{FILE.ID}");
         psFree(num);
     }
@@ -519,4 +536,7 @@
     if (!strcasecmp(type, "SUBKERNEL"))     {
         return PM_FPA_FILE_SUBKERNEL;
+    }
+    if (!strcasecmp(type, "PATTERN")) {
+        return PM_FPA_FILE_PATTERN;
     }
 
@@ -563,4 +583,6 @@
       case PM_FPA_FILE_SUBKERNEL:
         return ("SUBKERNEL");
+      case PM_FPA_FILE_PATTERN:
+        return "PATTERN";
       default:
         return ("NONE");
@@ -625,5 +647,5 @@
     psFree(iter);
 
-    psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find instance %d of file %s", num, name);
+    psLogMsg("psModules.camera", PS_LOG_MINUTIA, "Unable to find instance %d of file %s", num, name);
     return NULL;
 }
Index: branches/simmosaic_branches/psModules/src/camera/pmFPAfile.h
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPAfile.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPAfile.h	(revision 27839)
@@ -48,4 +48,6 @@
     PM_FPA_FILE_ASTROM_REFSTARS,
     PM_FPA_FILE_SUBKERNEL,
+    PM_FPA_FILE_SRCTEXT,
+    PM_FPA_FILE_PATTERN,
 } pmFPAfileType;
 
@@ -109,5 +111,6 @@
     psString formatName;                // name of the camera format
 
-    int index;                          // Index of file
+    int fileIndex;			// Index of file
+    int fileID;				// internal sequence number
 
     psS64 imageId, sourceId;            // Image and source identifiers
@@ -160,4 +163,9 @@
     );
 
+/// Set strict checking when freeing file
+///
+/// If true (default), freeing a pmFPAfile involves asserting that the FITS filehandle has been closed.
+bool pmFPAfileFreeSetStrict(bool new    // New state
+    );
 
 
Index: branches/simmosaic_branches/psModules/src/camera/pmFPAfileDefine.c
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPAfileDefine.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPAfileDefine.c	(revision 27839)
@@ -57,5 +57,5 @@
         return NAN;
     }
-    int value = psMetadataItemParseF32(item); // Value of interst
+    float value = psMetadataItemParseF32(item); // Value of interst
     return value;
 }
@@ -72,5 +72,5 @@
         return NAN;
     }
-    int value = psMetadataItemParseF64(item); // Value of interst
+    double value = psMetadataItemParseF64(item); // Value of interst
     return value;
 }
@@ -91,5 +91,5 @@
     psMetadata *data = pmConfigFileRule(config, camera, name); // File rule
     if (!data) {
-        psError(PS_ERR_IO, true, "Can't find file rule %s!", name);
+        psError(psErrorCodeLast(), false, "Can't find file rule %s!", name);
         return NULL;
     }
@@ -105,5 +105,5 @@
     file->type = pmFPAfileTypeFromString(type);
     if (file->type == PM_FPA_FILE_NONE) {
-        psError(PS_ERR_IO, true, "FILE.TYPE is not defined for %s\n", name);
+        psError(PM_ERR_CONFIG, true, "FILE.TYPE is not defined for %s\n", name);
         psFree(file);
         return NULL;
@@ -115,5 +115,5 @@
     file->dataLevel = pmFPALevelFromName(psMetadataLookupStr (&status, data, "DATA.LEVEL"));
     if (file->dataLevel == PM_FPA_LEVEL_NONE) {
-        psError(PS_ERR_IO, true, "DATA.LEVEL is not set for %s\n", name);
+        psError(PM_ERR_CONFIG, true, "DATA.LEVEL is not set for %s\n", name);
         psFree(file);
         return NULL;
@@ -137,5 +137,5 @@
     if (!psMetadataAddPtr(config->files, PS_LIST_TAIL, name,
                           PS_DATA_UNKNOWN | PS_META_DUPLICATE_OK, "", file)) {
-        psError(PS_ERR_IO, false, "could not add %s to config files", name);
+        psError(PM_ERR_CONFIG, false, "could not add %s to config files", name);
         return NULL;
     }
@@ -167,11 +167,12 @@
         psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->system, "CAMERAS"); // Known cameras
         if (!mdok || !cameras) {
-            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the system configuration.\n");
+            psError(PM_ERR_CONFIG, true, "Unable to find CAMERAS in the system configuration.\n");
             return NULL;
         }
         camera = psMetadataLookupMetadata(&mdok, cameras, cameraName); // Camera configuration of interest
         if (!mdok || !camera) {
-            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find automatically generated "
-                    "camera configuration %s in system configuration.\n", cameraName);
+            psError(PM_ERR_CONFIG, true,
+                    "Unable to find automatically generated camera configuration %s in system configuration.",
+                    cameraName);
             return NULL;
         }
@@ -184,5 +185,5 @@
     psMetadata *filerule = pmConfigFileRule(config, camera, name); // File rule
     if (!filerule) {
-        psError(PS_ERR_IO, true, "Can't find file rule %s!", name);
+        psError(psErrorCodeLast(), false, "Can't find file rule %s!", name);
         return NULL;
     }
@@ -199,5 +200,5 @@
     file->type = pmFPAfileTypeFromString(type);
     if (file->type == PM_FPA_FILE_NONE) {
-        psError(PS_ERR_IO, true, "FILE.TYPE is not defined for %s\n", name);
+        psError(PM_ERR_CONFIG, true, "FILE.TYPE is not defined for %s\n", name);
         psFree(file);
         return NULL;
@@ -230,5 +231,5 @@
     psMetadata *format = psMetadataLookupMetadata(&status, formats, formatName); // Camera format to use
     if (!format) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find format %s for file %s.\n",
+        psError(PM_ERR_CONFIG, true, "Unable to find format %s for file %s.\n",
                 formatName, file->name);
         psFree(file);
@@ -287,5 +288,5 @@
                 options->stdevNum = parseOptionFloat(scheme, "STDEV.NUM", source); // Padding to edge
                 if (!isfinite(options->stdevNum)) {
-                    psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Bad value for STDEV.NUM for %s", source);
+                    psError(PM_ERR_CONFIG, true, "Bad value for STDEV.NUM for %s", source);
                     psFree(source);
                     psFree(file);
@@ -296,5 +297,5 @@
                 options->stdevBits = parseOptionInt(scheme, "STDEV.BITS", source, 0); // Bits for stdev
                 if (options->stdevBits <= 0) {
-                    psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Bad value for STDEV.BITS (%d) for %s",
+                    psError(PM_ERR_CONFIG, true, "Bad value for STDEV.BITS (%d) for %s",
                             options->stdevBits, source);
                     psFree(source);
@@ -310,4 +311,13 @@
                 psAbort("Should never get here.");
             }
+        }
+
+        psMetadataItem *fuzz = psMetadataLookup(scheme, "FUZZ"); // Quantisation fuzz?
+        if (fuzz) {
+            if (fuzz->type != PS_TYPE_BOOL) {
+                psWarning("FUZZ in compression scheme %s isn't boolean.", fitsType);
+                goto FITS_OPTIONS_DONE;
+            }
+            options->fuzz = fuzz->data.B;
         }
 
@@ -337,5 +347,5 @@
     file->fileLevel = pmFPAPHULevel(format);
     if (file->fileLevel == PM_FPA_LEVEL_NONE) {
-        psError(PS_ERR_IO, true, "Unable to determine file level for %s\n", name);
+        psError(PM_ERR_CONFIG, true, "Unable to determine file level for %s\n", name);
         psFree(file);
         return NULL;
@@ -344,5 +354,5 @@
     file->dataLevel = pmFPALevelFromName(psMetadataLookupStr(&status, filerule, "DATA.LEVEL"));
     if (file->dataLevel == PM_FPA_LEVEL_NONE) {
-        psError(PS_ERR_IO, true, "DATA.LEVEL is not set for %s\n", name);
+        psError(PM_ERR_CONFIG, true, "DATA.LEVEL is not set for %s\n", name);
         psFree(file);
         return NULL;
@@ -443,5 +453,5 @@
         psString realName = pmConfigConvertFilename(filenames->data[0], config, false, false);
         if (!realName) {
-            psError(PS_ERR_IO, false, "Failed to convert file name %s", (char *)filenames->data[0]);
+            psError(psErrorCodeLast(), false, "Failed to convert file name %s", (char *)filenames->data[0]);
             return NULL;
         }
@@ -451,5 +461,5 @@
         psFits *fits = psFitsOpen(realName, "r"); // FITS file
         if (!fits) {
-            psError(PS_ERR_IO, false, "Failed to open file %s", realName);
+            psError(psErrorCodeLast(), false, "Failed to open file %s", realName);
             psFree(realName);
             return NULL;
@@ -457,9 +467,13 @@
         phu = psFitsReadHeader (NULL, fits); // Primary header
         if (!phu) {
-            psError(PS_ERR_IO, false, "Failed to read file header %s", realName);
+            psError(psErrorCodeLast(), false, "Failed to read file header %s", realName);
             psFree(realName);
             return NULL;
         }
-        psFitsClose(fits);
+        if (!psFitsClose(fits)) {
+            psError(psErrorCodeLast(), false, "Failed to close file %s", realName);
+            psFree(realName);
+            return NULL;
+        }
 
         // Determine the current format from the header; determine camera if not specified already.
@@ -467,5 +481,5 @@
         format = pmConfigCameraFormatFromHeader(&camera, &cameraName, &formatName, config, phu, true);
         if (!format) {
-            psError(PS_ERR_IO, false, "Failed to determine camera format for %s", realName);
+            psError(psErrorCodeLast(), false, "Failed to determine camera format for %s", realName);
             psFree(camera);
             psFree(formatName);
@@ -477,5 +491,5 @@
         fileLevel = pmFPAPHULevel(format);
         if (fileLevel == PM_FPA_LEVEL_NONE) {
-            psError(PS_ERR_IO, true, "Unable to determine file level for %s", realName);
+            psError(PM_ERR_CONFIG, true, "Unable to determine file level for %s", realName);
             psFree(camera);
             psFree(formatName);
@@ -490,5 +504,5 @@
         psFree(camera);
         if (!fpa) {
-            psError(PS_ERR_IO, false, "Failed to construct FPA from %s", realName);
+            psError(psErrorCodeLast(), false, "Failed to construct FPA from %s", realName);
             psFree(formatName);
             psFree(realName);
@@ -504,5 +518,5 @@
     pmFPAfile *file = pmFPAfileDefineInput(config, fpa, cameraName, name); // File, to return
     if (!file) {
-        psError(PS_ERR_IO, false, "File %s not defined", name);
+        psError(psErrorCodeLast(), false, "File %s not defined", name);
         psFree(formatName);
         psFree(format);
@@ -531,17 +545,21 @@
             psString realName = pmConfigConvertFilename(filenames->data[i], config, false, false);
             if (!realName) {
-                psError(PS_ERR_IO, false, "Failed to convert file name %s", (char*)filenames->data[i]);
+                psError(psErrorCodeLast(), false, "Failed to convert file name %s", (char*)filenames->data[i]);
                 return NULL;
             }
             psFits *fits = psFitsOpen(realName, "r"); // FITS file
             if (!fits) {
-                psError(PS_ERR_IO, false, "Failed to open file %s", realName);
+                psError(psErrorCodeLast(), false, "Failed to open file %s", realName);
                 psFree(realName);
                 return NULL;
             }
             phu = psFitsReadHeader(NULL, fits);
-            psFitsClose(fits);
+            if (!psFitsClose(fits)) {
+                psError(psErrorCodeLast(), false, "Failed to close file %s", realName);
+                psFree(realName);
+                return NULL;
+            }
             if (!phu) {
-                psError(PS_ERR_IO, false, "Failed to read file header %s", realName);
+                psError(psErrorCodeLast(), false, "Failed to read file header %s", realName);
                 psFree(realName);
                 return NULL;
@@ -552,5 +570,5 @@
         if (i == 0 && file->type == PM_FPA_FILE_MASK) {
             if (!pmConfigMaskReadHeader(config, phu)) {
-                psError(PS_ERR_IO, false, "Error reading mask bits");
+                psError(psErrorCodeLast(), false, "Error reading mask bits");
                 psFree(phu);
                 return NULL;
@@ -562,10 +580,10 @@
                 bool valid = false;
                 if (!pmConfigValidateCameraFormat(&valid, format, phu)) {
-                    psError(PS_ERR_UNKNOWN, false, "Error in config scripts\n");
+                    psError(psErrorCodeLast(), false, "Error in config scripts\n");
                     psFree(phu);
                     return NULL;
                 }
                 if (!valid) {
-                    psError(PS_ERR_IO, false, "File %s is not from the required camera",
+                    psError(psErrorCodeLast(), false, "File %s is not from the required camera",
                             (char*)filenames->data[i]);
                     psFree(phu);
@@ -575,5 +593,5 @@
                 format = pmConfigCameraFormatFromHeader(NULL, NULL, NULL, config, phu, true);
                 if (!format) {
-                    psError(PS_ERR_IO, false, "Failed to determine camera format from %s",
+                    psError(psErrorCodeLast(), false, "Failed to determine camera format from %s",
                             (char*)filenames->data[i]);
                     psFree(phu);
@@ -601,5 +619,5 @@
         phu = NULL;
         if (!view) {
-            psError(PS_ERR_IO, false, "Unable to determine source for %s", name);
+            psError(PM_ERR_CONFIG, true, "Unable to determine source for %s", name);
             return NULL;
         }
@@ -633,5 +651,5 @@
     }
     if (filenames->n == 0) {
-        psError(PS_ERR_IO, false, "No files in array in %s in arguments", argname);
+        psError(PM_ERR_CONFIG, true, "No files in array in %s in arguments", argname);
         if (success) {
             *success = false;
@@ -667,5 +685,5 @@
     }
     if (filenames->n == 0) {
-        psError(PS_ERR_IO, false, "No files in array in %s in arguments", argname);
+        psError(PM_ERR_CONFIG, true, "No files in array in %s in arguments", argname);
         if (success) {
             *success = false;
@@ -700,5 +718,5 @@
     }
     if (filenames->n <= entry) {
-        psError(PS_ERR_IO, false, "Insufficient files (%ld) in array in %s in arguments",
+        psError(PM_ERR_CONFIG, true, "Insufficient files (%ld) in array in %s in arguments",
                 filenames->n, argname);
         if (success) {
@@ -760,5 +778,5 @@
     }
     if (bind && files->n != bind->n) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+        psError(PM_ERR_CONFIG, true,
                 "Length of filenames (%ld) and bind files (%ld) does not match.",
                 files->n, bind->n);
@@ -774,5 +792,5 @@
         files->data[i] = psMemIncrRefCounter(fpaFileDefineFromArray(config, bindFile, filename, dummy));
         if (!files->data[i]) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to define file %s %d", filename, i);
+            psError(psErrorCodeLast(), false, "Unable to define file %s %d", filename, i);
             psFree(dummy);
             psFree(files);
@@ -802,5 +820,5 @@
     // a camera config is needed (as source of file rule)
     if (config->camera == NULL) {
-        psError(PS_ERR_IO, true, "camera is not defined");
+        psError(PM_ERR_PROG, true, "camera is not defined");
         return NULL;
     }
@@ -809,5 +827,5 @@
     pmFPA *fpa = pmFPAConstruct(config->camera, config->cameraName);
     if (!fpa) {
-        psError(PS_ERR_IO, false, "Failed to construct FPA for %s", filename);
+        psError(psErrorCodeLast(), false, "Failed to construct FPA for %s", filename);
         return NULL;
     }
@@ -818,5 +836,5 @@
     psFree (fpa);
     if (!file) {
-        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        psError(psErrorCodeLast(), false, "file %s not defined\n", filename);
         return NULL;
     }
@@ -824,5 +842,5 @@
     // image names may not come from file->names
     if (!strcasecmp(file->filerule, "@FILES")) {
-        psError(PS_ERR_IO, true, "supplied filerule uses illegal value @FILES");
+        psError(PM_ERR_CONFIG, true, "supplied filerule uses illegal value @FILES");
         // XXX remove the file from config->files
         return NULL;
@@ -881,5 +899,5 @@
     // a camera config is needed (as source of file rule)
     if (config->camera == NULL) {
-        psError(PS_ERR_IO, true, "camera is not defined");
+        psError(PM_ERR_PROG, true, "camera is not defined");
         return NULL;
     }
@@ -895,5 +913,5 @@
         fpa = pmFPAConstruct(config->camera, config->cameraName);
         if (!fpa) {
-            psError(PS_ERR_IO, false, "Failed to construct FPA for %s", filename);
+            psError(psErrorCodeLast(), false, "Failed to construct FPA for %s", filename);
             return NULL;
         }
@@ -902,5 +920,5 @@
         file = pmFPAfileDefineInput (config, fpa, NULL, filename);
         if (!file) {
-            psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+            psError(psErrorCodeLast(), false, "file %s not defined\n", filename);
             psFree(fpa);
             return NULL;
@@ -934,5 +952,5 @@
     psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, "PPIMAGE");
     if (!status) {
-        psError(PS_ERR_UNEXPECTED_NULL, true, "PPIMAGE recipe not found.");
+        psError(PM_ERR_CONFIG, true, "PPIMAGE recipe not found.");
         psFree(options);
         psFree(fpa);
@@ -1012,5 +1030,5 @@
         pmDetrendSelectResults *results = pmDetrendSelect (options, config);
         if (!results) {
-            psError (PS_ERR_IO, false, "no matching detrend data");
+            psError (psErrorCodeLast(), false, "no matching detrend data");
             return NULL;
         }
@@ -1018,5 +1036,5 @@
         file->fileLevel = pmFPALevelFromName(results->level);
         if (file->fileLevel == PM_FPA_LEVEL_NONE) {
-            psError (PS_ERR_IO, false, "invalid file level for selected detrend data");
+            psError (PM_ERR_CONFIG, false, "invalid file level for selected detrend data");
             return NULL;
         }
@@ -1043,5 +1061,5 @@
     pmFPAfile *file = pmFPAfileDefineOutput (config, fpa, filename);
     if (!file) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        psError(psErrorCodeLast(), false, "file %s not defined\n", filename);
         return NULL;
     }
@@ -1062,5 +1080,5 @@
     pmFPAfile *file = pmFPAfileDefineOutputForFormat(config, NULL, filename, src->cameraName, src->formatName);
     if (!file) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        psError(psErrorCodeLast(), false, "file %s not defined\n", filename);
         return NULL;
     }
@@ -1084,9 +1102,9 @@
     pmFPAfile *file = pmFPAfileDefineOutput (config, NULL, filename);
     if (!file) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        psError(psErrorCodeLast(), false, "file %s not defined\n", filename);
         return NULL;
     }
     if (!file->camera) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s does not define a new camera\n", filename);
+        psError(PM_ERR_CONFIG, false, "file %s does not define a new camera\n", filename);
         return NULL;
     }
@@ -1127,5 +1145,5 @@
     }
     if (!file) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        psError(PM_ERR_CONFIG, true, "file %s not defined\n", filename);
         return NULL;
     }
@@ -1170,5 +1188,5 @@
     }
     if (!file) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        psError(PM_ERR_CONFIG, true, "file %s not defined\n", filename);
         return NULL;
     }
@@ -1177,5 +1195,5 @@
     if (src) {
         if (!pmConceptsCopyFPA(file->fpa, src, true, false)) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to copy concepts from source to new FPA");
+            psError(psErrorCodeLast(), false, "Unable to copy concepts from source to new FPA");
             return NULL;
         }
@@ -1221,5 +1239,5 @@
     }
     if (!file) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        psError(PM_ERR_CONFIG, true, "file %s not defined\n", filename);
         return NULL;
     }
@@ -1228,5 +1246,5 @@
     if (src) {
         if (!pmConceptsCopyFPA(file->fpa, src, false, false)) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to copy concepts from source to new FPA");
+            psError(psErrorCodeLast(), false, "Unable to copy concepts from source to new FPA");
             return NULL;
         }
@@ -1253,6 +1271,11 @@
     file->name = psStringCopy (name);
 
+    // free a previously existing readout
+    psFree(file->readout);
     file->readout = readout;
-    psMetadataAddPtr(files, PS_LIST_TAIL, name, PS_DATA_UNKNOWN, "", file);
+
+    // allow for multiple entries
+    // XXX handle replace vs multiple?
+    psMetadataAddPtr(files, PS_LIST_TAIL, name, PS_DATA_UNKNOWN | PS_META_DUPLICATE_OK, "", file);
     psFree(file);
     // we free this copy of file, but 'files' still has a copy
@@ -1274,5 +1297,5 @@
     }
     if (file == NULL) {
-        psError(PS_ERR_IO, true, "file %s is NULL", name);
+        psError(PM_ERR_CONFIG, true, "file %s is NULL", name);
         return false;
     }
@@ -1295,14 +1318,16 @@
                                 const char *name, // name of internal/external file
                                 const pmFPA *fpa, // use this fpa to generate
-                                const psImageBinning *binning) {
+                                const psImageBinning *binning,
+                                int index) {
   pmReadout *readout = NULL;
 
-  bool status = true;
-  pmFPAfile *file = psMetadataLookupPtr(&status, config->files, name);
+  pmFPAfile *file = pmFPAfileSelectSingle(config->files, name, index);
 
   // if the file does not exist, it is not being used as an I/O file: define an internal version
   if (file == NULL) {
-    readout = pmFPAfileDefineInternal (config->files, name, binning->nXruff, binning->nYruff, PS_TYPE_F32);
-    return readout;
+      // XXX currently, we do not guarantee that the defined file lands on entry 'index'
+      psAssert (binning, "internal files must be supplied a psImageBinning for the output images size"); 
+      readout = pmFPAfileDefineInternal (config->files, name, binning->nXruff, binning->nYruff, PS_TYPE_F32);
+      return readout;
   }
 
Index: branches/simmosaic_branches/psModules/src/camera/pmFPAfileDefine.h
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPAfileDefine.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPAfileDefine.h	(revision 27839)
@@ -172,5 +172,7 @@
                                 const char *name, // name of internal/external file
                                 const pmFPA *fpa, // use this fpa to generate
-                                const psImageBinning *binning);
+                                const psImageBinning *binning,
+				int index
+    );
 
 /// @}
Index: branches/simmosaic_branches/psModules/src/camera/pmFPAfileFitsIO.c
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPAfileFitsIO.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPAfileFitsIO.c	(revision 27839)
@@ -162,4 +162,5 @@
                   hdu->header = psMetadataAlloc();
               }
+
               pmConfigConformHeader(hdu->header, file->format);
 
@@ -178,9 +179,4 @@
           pmChip *chip = pmFPAviewThisChip(view, fpa); // Chip of interest, or NULL
           pmCell *cell = pmFPAviewThisCell(view, fpa); // Cell of interest, or NULL
-          if (!pmFPAUpdateNames(fpa, chip, cell, file->imageId, file->sourceId)) {
-              psError(PS_ERR_UNKNOWN, false, "Unable to update names in header.");
-              return false;
-          }
-
           if (cell) {
               if (!pmConceptsWriteCell(cell, true, config)) {
@@ -195,4 +191,9 @@
           } else if (!pmConceptsWriteFPA(fpa, true, config)) {
               psError(PS_ERR_IO, false, "Unable to write concepts for FPA.\n");
+              return false;
+          }
+
+          if (!pmFPAUpdateNames(fpa, chip, cell, file->imageId, file->sourceId)) {
+              psError(PS_ERR_UNKNOWN, false, "Unable to update names in header.");
               return false;
           }
Index: branches/simmosaic_branches/psModules/src/camera/pmFPAfileIO.c
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmFPAfileIO.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmFPAfileIO.c	(revision 27839)
@@ -43,4 +43,5 @@
 #include "pmFPAConstruct.h"
 #include "pmSubtractionIO.h"
+#include "pmPatternIO.h"
 #include "pmConcepts.h"
 #include "pmConfigRun.h"
@@ -66,5 +67,5 @@
 
         switch (place) {
-        case PM_FPA_BEFORE:
+          case PM_FPA_BEFORE:
             if (!pmFPAfileRead (file, view, config)) {
                 psError(PS_ERR_IO, false, "failed READ in FPA_BEFORE block for %s", file->name);
@@ -76,5 +77,5 @@
             }
             break;
-        case PM_FPA_AFTER:
+          case PM_FPA_AFTER:
             if (!pmFPAfileWrite (file, view, config)) {
                 psError(PS_ERR_IO, false, "failed WRITE in FPA_AFTER block for %s", file->name);
@@ -86,5 +87,5 @@
             }
             break;
-        default:
+          default:
             psAbort("You can't get here");
         }
@@ -202,4 +203,7 @@
         status = pmSubtractionReadKernels(view, file, config);
         break;
+      case PM_FPA_FILE_PATTERN:
+        status = pmPatternRead(view, file, config);
+        break;
       case PM_FPA_FILE_SX:
       case PM_FPA_FILE_RAW:
@@ -208,4 +212,5 @@
       case PM_FPA_FILE_CMF:
       case PM_FPA_FILE_WCS:
+      case PM_FPA_FILE_SRCTEXT:
         status = pmFPAviewReadObjects (view, file, config);
         break;
@@ -272,23 +277,25 @@
       case PM_FPA_FILE_VARIANCE:
       case PM_FPA_FILE_FRINGE:
-      case PM_FPA_FILE_DARK: {
-          // create FPA structure component based on view
-          psMetadata *format = file->format; // Camera format configuration
-          if (!format) {
-              format = config->format;
-          }
-
-          pmFPA *nameSource = file->src; // Source of FPA.OBS
-          if (!nameSource) {
-              nameSource = file->fpa;
-          }
-          bool mdok;                  // Status of MD lookup
-          const char *fpaObs = psMetadataLookupStr(&mdok, nameSource->concepts, "FPA.OBS"); // Obs. id
-
-          pmFPAAddSourceFromView(file->fpa, fpaObs, view, format);
-          psTrace ("psModules.camera", 5, "created fpa data elements for %s (%s) (%d:%d:%d)\n",
-                   file->name, file->name, view->chip, view->cell, view->readout);
-          break;
-      }
+      case PM_FPA_FILE_DARK:
+      case PM_FPA_FILE_PATTERN:
+        {
+            // create FPA structure component based on view
+            psMetadata *format = file->format; // Camera format configuration
+            if (!format) {
+                format = config->format;
+            }
+
+            pmFPA *nameSource = file->src; // Source of FPA.OBS
+            if (!nameSource) {
+                nameSource = file->fpa;
+            }
+            bool mdok;                  // Status of MD lookup
+            const char *fpaObs = psMetadataLookupStr(&mdok, nameSource->concepts, "FPA.OBS"); // Obs. id
+
+            pmFPAAddSourceFromView(file->fpa, fpaObs, view, format);
+            psTrace ("psModules.camera", 5, "created fpa data elements for %s (%s) (%d:%d:%d)\n",
+                     file->name, file->name, view->chip, view->cell, view->readout);
+            break;
+        }
       case PM_FPA_FILE_HEADER:
         psAbort ("Create not defined for HEADER");
@@ -461,4 +468,7 @@
         status = pmSubtractionWriteKernels(view, file, config);
         break;
+      case PM_FPA_FILE_PATTERN:
+        status = pmPatternWrite(view, file, config);
+        break;
       case PM_FPA_FILE_SX:
       case PM_FPA_FILE_RAW:
@@ -542,4 +552,5 @@
       case PM_FPA_FILE_DARK:
       case PM_FPA_FILE_SUBKERNEL:
+      case PM_FPA_FILE_PATTERN:
       case PM_FPA_FILE_CMF:
       case PM_FPA_FILE_WCS:
@@ -609,4 +620,5 @@
         break;
       case PM_FPA_FILE_SUBKERNEL:
+      case PM_FPA_FILE_PATTERN:
       case PM_FPA_FILE_SX:
       case PM_FPA_FILE_RAW:
@@ -770,4 +782,5 @@
       case PM_FPA_FILE_DARK:
       case PM_FPA_FILE_SUBKERNEL:
+      case PM_FPA_FILE_PATTERN:
       case PM_FPA_FILE_CMF:
       case PM_FPA_FILE_WCS:
@@ -961,4 +974,8 @@
       case PM_FPA_FILE_SUBKERNEL:
         status = pmSubtractionWritePHU(view, file, config);
+        break;
+      case PM_FPA_FILE_PATTERN:
+        status = pmPatternWritePHU(view, file, config);
+        break;
       case PM_FPA_FILE_CMF:
         status = pmSource_CMF_WritePHU (view, file, config);
Index: branches/simmosaic_branches/psModules/src/camera/pmHDUGenerate.c
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmHDUGenerate.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmHDUGenerate.c	(revision 27839)
@@ -611,4 +611,5 @@
             if (cells->n == 0) {
                 // Nothing to do
+		psFree (cells);
                 return true;
             }
@@ -660,4 +661,5 @@
             if (cells->n == 0) {
                 // Nothing to do
+		psFree (cells);
                 return true;
             }
@@ -707,8 +709,10 @@
             if (cells->n == 0) {
                 // Nothing to do
+		psFree(cells);
                 return true;
             }
-
-            return generateHDU(hdu, cells);
+            bool status = generateHDU(hdu, cells);
+	    psFree(cells);
+	    return status;
         }
     default:
Index: branches/simmosaic_branches/psModules/src/camera/pmReadoutFake.c
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmReadoutFake.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmReadoutFake.c	(revision 27839)
@@ -23,12 +23,16 @@
 #include "pmSourceUtils.h"
 #include "pmModelUtils.h"
+#include "pmSourceGroups.h"
 
 #include "pmReadoutFake.h"
 
-#define MODEL_TYPE "PS_MODEL_RGAUSS"    // Type of model to use
 #define MAX_AXIS_RATIO 20.0             // Maximum axis ratio for PSF model
-#define SOURCE_MASK (PM_SOURCE_MODE_DEFECT | PM_SOURCE_MODE_CR_LIMIT) // Mask to apply to input sources
 #define MODEL_MASK (PM_MODEL_STATUS_NONCONVERGE | PM_MODEL_STATUS_OFFIMAGE | \
                     PM_MODEL_STATUS_BADARGS | PM_MODEL_STATUS_LIMITS) // Mask to apply to models
+
+
+static bool threaded = false;           // Running threaded?
+
+
 
 
@@ -50,13 +54,205 @@
 }
 
-bool pmReadoutFakeFromSources(pmReadout *readout, int numCols, int numRows, const psArray *sources,
-                              const psVector *xOffset, const psVector *yOffset, const pmPSF *psf,
-                              float minFlux, int radius, bool circularise, bool normalisePeak)
+/// Generate fake sources on a readout
+static bool readoutFake(pmReadout *readout, // Readout of interest
+                        const pmSourceGroups *groups, // Source groups
+                        const psVector *x,        // x coordinates
+                        const psVector *y,        // y coordinates
+                        const psVector *mag,      // Magnitudes
+                        const psVector *xOffset,  // Offsets in x
+                        const psVector *yOffset,  // Offsets in y
+                        const pmPSF *psf,         // PSF
+                        float minFlux,            // Minimum flux
+                        float radius,             // Minimum radius
+                        bool circularise,         // Circularise PSF?
+                        bool normalisePeak,       // Normalise sources for peak?
+                        int groupIndex,           // Group index
+                        int cellIndex             // Cell index
+                        )
+{
+    psArray *cells = groups->groups->data[groupIndex]; // Cells in group
+    psVector *cellSources = cells->data[cellIndex];    // Sources in cell
+
+    for (int i = 0; i < cellSources->n; i++) {
+        int index = cellSources->data.S32[i];                       // Index for source of interest
+        float flux = powf(10.0, -0.4 * mag->data.F32[index]);       // Flux of source
+        float xSrc = x->data.F32[index], ySrc = y->data.F32[index]; // Coordinates of source
+
+        if (normalisePeak) {
+            // Normalise flux
+            pmModel *normModel = pmModelFromPSFforXY(psf, xSrc, ySrc, 1.0); // Model for normalisation
+            if (!normModel || (normModel->flags & MODEL_MASK)) {
+                psFree(normModel);
+                continue;
+            }
+            // check that all params are valid:
+            bool validParams = true;
+            for (int j = 0; validParams && (j < normModel->params->n); j++) {
+                switch (j) {
+                  case PM_PAR_SKY:
+                  case PM_PAR_I0:
+                  case PM_PAR_XPOS:
+                  case PM_PAR_YPOS:
+                    continue;
+                  default:
+                    if (!isfinite(normModel->params->data.F32[j])) {
+                        validParams = false;
+                    }
+                }
+            }
+            if (!validParams) {
+                psFree(normModel);
+                continue;
+            }
+            if (circularise && !circulariseModel(normModel)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to circularise PSF model.");
+                psFree(normModel);
+                return false;
+            }
+
+            flux /= normModel->modelFlux(normModel->params);
+            psFree(normModel);
+        }
+
+        pmModel *fakeModel = pmModelFromPSFforXY(psf, xSrc, ySrc, flux);
+        if (!fakeModel || (fakeModel->flags & MODEL_MASK)) {
+            psFree(fakeModel);
+            continue;
+        }
+        // check that all params are valid:
+        bool validParams = true;
+        for (int j = 0; validParams && (j < fakeModel->params->n); j++) {
+            switch (j) {
+              case PM_PAR_SKY:
+              case PM_PAR_I0:
+              case PM_PAR_XPOS:
+              case PM_PAR_YPOS:
+                continue;
+              default:
+                if (!isfinite(fakeModel->params->data.F32[j])) {
+                    validParams = false;
+                }
+            }
+        }
+        if (!validParams) {
+            psFree(fakeModel);
+            continue;
+        }
+        if (circularise && !circulariseModel(fakeModel)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to circularise PSF model.");
+            psFree(fakeModel);
+            return false;
+        }
+
+        psTrace("psModules.camera", 10, "Adding source at %f,%f with flux %f\n",
+                fakeModel->params->data.F32[PM_PAR_XPOS], fakeModel->params->data.F32[PM_PAR_YPOS],
+                fakeModel->params->data.F32[PM_PAR_I0]);
+
+        pmSource *fakeSource = pmSourceAlloc(); // Fake source to generate
+        fakeSource->peak = pmPeakAlloc(xSrc, ySrc, fakeModel->params->data.F32[PM_PAR_I0], PM_PEAK_LONE);
+        float fakeRadius = 1.0;         // Radius of fake source
+        if (isfinite(minFlux)) {
+            fakeRadius = PS_MAX(fakeRadius, fakeModel->modelRadius(fakeModel->params, minFlux));
+        }
+        if (radius > 0) {
+            fakeRadius = PS_MAX(fakeRadius, radius);
+        }
+
+        if (xOffset) {
+            if (!pmSourceDefinePixels(fakeSource, readout, xSrc + xOffset->data.S32[index],
+                                      ySrc + yOffset->data.S32[index], fakeRadius)) {
+                psErrorClear();
+                continue;
+            }
+            if (!pmModelAddWithOffset(fakeSource->pixels, NULL, fakeModel, PM_MODEL_OP_FULL, 0,
+                                      xOffset->data.S32[index], yOffset->data.S32[index])) {
+                psErrorClear();
+                continue;
+            }
+        } else {
+            if (!pmSourceDefinePixels(fakeSource, readout, xSrc, ySrc, fakeRadius)) {
+                psErrorClear();
+                continue;
+            }
+            if (!pmModelAdd(fakeSource->pixels, NULL, fakeModel, PM_MODEL_OP_FULL, 0)) {
+                psErrorClear();
+                continue;
+            }
+        }
+        psFree(fakeSource);
+        psFree(fakeModel);
+    }
+
+    return true;
+}
+
+/// Thread job for readoutFake()
+static bool readoutFakeThread(psThreadJob *job)
+{
+    PS_ASSERT_THREAD_JOB_NON_NULL(job, false);
+
+    psArray *args = job->args;          // Arguments
+
+    pmReadout *readout = args->data[0];     // Readout of interest
+    const pmSourceGroups *groups = args->data[1]; // Source groups
+    const psVector *x = args->data[2];        // x coordinates
+    const psVector *y = args->data[3];        // y coordinates
+    const psVector *mag = args->data[4];      // Magnitudes
+    const psVector *xOffset = args->data[5];  // Offsets in x
+    const psVector *yOffset = args->data[6];  // Offsets in y
+    const pmPSF *psf = args->data[7];         // PSF
+    float minFlux = PS_SCALAR_VALUE(args->data[8], F32); // Minimum flux
+    float radius = PS_SCALAR_VALUE(args->data[9], F32);  // Minimum radius
+    bool circularise = PS_SCALAR_VALUE(args->data[10], U8); // Circularise PSF?
+    bool normalisePeak = PS_SCALAR_VALUE(args->data[11], U8); // Normalise for peak?
+    int groupIndex = PS_SCALAR_VALUE(args->data[12], S32); // Group index
+    int cellIndex = PS_SCALAR_VALUE(args->data[13], S32);  // Cell index
+
+    return readoutFake(readout, groups, x, y, mag, xOffset, yOffset, psf, minFlux, radius, circularise,
+                       normalisePeak, groupIndex, cellIndex);
+}
+
+
+bool pmReadoutFakeThreads(bool new)
+{
+    bool old = threaded;                // Old status, to return
+
+    if (!old && new) {
+        threaded = true;
+
+        {
+            psThreadTask *task = psThreadTaskAlloc("PSMODULES_READOUT_FAKE", 14);
+            task->function = &readoutFakeThread;
+            psThreadTaskAdd(task);
+            psFree(task);
+        }
+
+    } else if (old && !new) {
+        threaded = false;
+        psThreadTaskRemove("PSMODULES_READOUT_FAKE");
+    }
+
+    return old;
+}
+
+
+bool pmReadoutFakeFromVectors(pmReadout *readout, int numCols, int numRows,
+                              const psVector *x, const psVector *y, const psVector *mag,
+                              const psVector *xOffset, const psVector *yOffset,
+                              const pmPSF *psf, float minFlux, int radius,
+                              bool circularise, bool normalisePeak)
 {
     PS_ASSERT_PTR_NON_NULL(readout, false);
     PS_ASSERT_INT_LARGER_THAN(numCols, 0, false);
     PS_ASSERT_INT_LARGER_THAN(numRows, 0, false);
-    PS_ASSERT_ARRAY_NON_NULL(sources, false);
-
+    PS_ASSERT_VECTOR_NON_NULL(x, false);
+    PS_ASSERT_VECTOR_TYPE(x, PS_TYPE_F32, false);
+    PS_ASSERT_VECTOR_NON_NULL(y, false);
+    PS_ASSERT_VECTOR_TYPE(y, PS_TYPE_F32, false);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(y, x, false);
+    PS_ASSERT_VECTOR_NON_NULL(mag, false);
+    PS_ASSERT_VECTOR_TYPE(mag, PS_TYPE_F32, false);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(mag, x, false);
+    long numSources = x->n;              // Number of sources
     if (xOffset || yOffset) {
         PS_ASSERT_VECTOR_NON_NULL(xOffset, false);
@@ -64,22 +260,102 @@
         PS_ASSERT_VECTORS_SIZE_EQUAL(xOffset, yOffset, false);
         PS_ASSERT_VECTOR_TYPE(xOffset, PS_TYPE_S32, false);
-        PS_ASSERT_VECTOR_TYPE_EQUAL(xOffset, yOffset, false);
-        if (xOffset->n != sources->n) {
+        PS_ASSERT_VECTOR_TYPE(yOffset, PS_TYPE_S32, false);
+        if (xOffset->n != numSources) {
             psError(PS_ERR_BAD_PARAMETER_SIZE, true,
                     "Number of offset vectors (%ld) and sources (%ld) doesn't match",
-                    xOffset->n, sources->n);
+                    xOffset->n, numSources);
             return false;
         }
     }
     PS_ASSERT_PTR_NON_NULL(psf, false);
-    if (radius > 0 && isfinite(minFlux) && minFlux > 0.0) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Cannot define both minimum flux and fixed radius.");
-        return false;
-    }
 
     readout->image = psImageRecycle(readout->image, numCols, numRows, PS_TYPE_F32);
     psImageInit(readout->image, 0);
 
+    int numThreads = threaded ? psThreadPoolSize() : 0; // Number of threads
+    pmSourceGroups *groups = pmSourceGroupsFromVectors(readout, x, y, numThreads); // Groups of sources
+    if (!groups) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate source groups");
+        return false;
+    }
+
+    if (threaded) {
+        for (int i = 0; i < groups->groups->n; i++) {
+            psArray *cells = groups->groups->data[i]; // Cell with sources
+            for (int j = 0; j < cells->n; j++) {
+                psThreadJob *job = psThreadJobAlloc("PSMODULES_READOUT_FAKE");
+                psArray *args = job->args;
+                psArrayAdd(args, 1, readout);
+                psArrayAdd(args, 1, groups);
+                // Casting away const to add to array
+                psArrayAdd(args, 1, (psVector*)x);
+                psArrayAdd(args, 1, (psVector*)y);
+                psArrayAdd(args, 1, (psVector*)mag);
+                psArrayAdd(args, 1, (psVector*)xOffset);
+                psArrayAdd(args, 1, (psVector*)yOffset);
+                psArrayAdd(args, 1, (pmPSF*)psf);
+                PS_ARRAY_ADD_SCALAR(args, minFlux, PS_TYPE_F32);
+                PS_ARRAY_ADD_SCALAR(args, radius, PS_TYPE_S32);
+                PS_ARRAY_ADD_SCALAR(args, circularise, PS_TYPE_U8);
+                PS_ARRAY_ADD_SCALAR(args, normalisePeak, PS_TYPE_U8);
+                PS_ARRAY_ADD_SCALAR(args, i, PS_TYPE_S32);
+                PS_ARRAY_ADD_SCALAR(args, j, PS_TYPE_S32);
+
+                if (!psThreadJobAddPending(job)) {
+                    psFree(job);
+                    psFree(groups);
+                    return false;
+                }
+                psFree(job);
+            }
+            if (!psThreadPoolWait(true)) {
+                psError(PS_ERR_UNKNOWN, false, "Error waiting for threads.");
+                psFree(groups);
+                return false;
+            }
+        }
+    } else if (!readoutFake(readout, groups, x, y, mag, xOffset, yOffset, psf, minFlux, radius, circularise,
+                            normalisePeak, 0, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate fake sources on readout");
+        psFree(groups);
+        return false;
+    }
+
+    psFree(groups);
+
+// Set a concept value
+#define CONCEPT_SET_S32(CONCEPTS, NAME, OLD, NEW) { \
+        psMetadataItem *item = psMetadataLookup(CONCEPTS, NAME); \
+        psAssert(item->type == PS_TYPE_S32, "Incorrect type: %x", item->type); \
+        if (item->data.S32 == OLD) { \
+            item->data.S32 = NEW; \
+        } \
+    }
+
+    if (readout->parent) {
+        CONCEPT_SET_S32(readout->parent->concepts, "CELL.XPARITY", 0, 1);
+        CONCEPT_SET_S32(readout->parent->concepts, "CELL.YPARITY", 0, 1);
+        CONCEPT_SET_S32(readout->parent->concepts, "CELL.XBIN", 0, 1);
+        CONCEPT_SET_S32(readout->parent->concepts, "CELL.YBIN", 0, 1);
+    }
+
+    return true;
+
+}
+
+
+bool pmReadoutFakeFromSources(pmReadout *readout, int numCols, int numRows, const psArray *sources,
+                              pmSourceMode sourceMask, const psVector *xOffset, const psVector *yOffset,
+                              const pmPSF *psf, float minFlux, int radius,
+                              bool circularise, bool normalisePeak)
+{
+    PS_ASSERT_ARRAY_NON_NULL(sources, false);
+
     int numSources = sources->n;          // Number of stars
+    psVector *x = psVectorAllocEmpty(numSources, PS_TYPE_F32);
+    psVector *y = psVectorAllocEmpty(numSources, PS_TYPE_F32);
+    psVector *mag = psVectorAllocEmpty(numSources, PS_TYPE_F32);
+
+    int numGood = 0;                    // Number of good sources
     for (int i = 0; i < numSources; i++) {
         pmSource *source = sources->data[i]; // Source of interest
@@ -87,5 +363,5 @@
             continue;
         }
-        if (source->mode & SOURCE_MASK) {
+        if (source->mode & sourceMask) {
             continue;
         }
@@ -93,77 +369,28 @@
             continue;
         }
-        float x, y;                     // Coordinates of source
+        float xSrc, ySrc;                     // Coordinates of source
         if (source->modelPSF) {
-            x = source->modelPSF->params->data.F32[PM_PAR_XPOS];
-            y = source->modelPSF->params->data.F32[PM_PAR_YPOS];
+            xSrc = source->modelPSF->params->data.F32[PM_PAR_XPOS];
+            ySrc = source->modelPSF->params->data.F32[PM_PAR_YPOS];
         } else {
-            x = source->peak->xf;
-            y = source->peak->yf;
-        }
-
-        float flux = powf(10.0, -0.4 * source->psfMag); // Flux of source
-
-        if (normalisePeak) {
-            // Normalise flux
-            pmModel *normModel = pmModelFromPSFforXY(psf, x, y, 1.0); // Model for normalisation
-            if (!normModel || (normModel->flags & MODEL_MASK)) {
-                psFree(normModel);
-                continue;
-            }
-            if (circularise && !circulariseModel(normModel)) {
-                psError(PS_ERR_UNKNOWN, false, "Unable to circularise PSF model.");
-                psFree(normModel);
-                return false;
-            }
-
-            flux /= normModel->modelFlux(normModel->params);
-            psFree(normModel);
-        }
-
-        pmModel *fakeModel = pmModelFromPSFforXY(psf, x, y, flux);
-        if (!fakeModel || (fakeModel->flags & MODEL_MASK)) {
-            psFree(fakeModel);
-            continue;
-        }
-        if (circularise && !circulariseModel(fakeModel)) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to circularise PSF model.");
-            psFree(fakeModel);
-            return false;
-        }
-
-        psTrace("psModules.camera", 10, "Adding source at %f,%f with flux %f\n",
-                fakeModel->params->data.F32[PM_PAR_XPOS], fakeModel->params->data.F32[PM_PAR_YPOS],
-                fakeModel->params->data.F32[PM_PAR_I0]);
-
-        pmSource *fakeSource = pmSourceAlloc(); // Fake source to generate
-        fakeSource->peak = pmPeakAlloc(x, y, fakeModel->params->data.F32[PM_PAR_I0], PM_PEAK_LONE);
-        float fakeRadius = radius > 0 ? radius :
-            PS_MAX(1.0, fakeModel->modelRadius(fakeModel->params, minFlux)); // Radius of fake source
-
-        if (xOffset) {
-            if (!pmSourceDefinePixels(fakeSource, readout, x + xOffset->data.S32[i],
-                                      y + yOffset->data.S32[i], fakeRadius)) {
-                psErrorClear();
-                continue;
-            }
-            if (!pmModelAddWithOffset(fakeSource->pixels, NULL, fakeModel, PM_MODEL_OP_FULL, 0,
-                                      xOffset->data.S32[i], yOffset->data.S32[i])) {
-                psErrorClear();
-                continue;
-            }
-        } else {
-            if (!pmSourceDefinePixels(fakeSource, readout, x, y, fakeRadius)) {
-                psErrorClear();
-                continue;
-            }
-            if (!pmModelAdd(fakeSource->pixels, NULL, fakeModel, PM_MODEL_OP_FULL, 0)) {
-                psErrorClear();
-                continue;
-            }
-        }
-        psFree(fakeSource);
-        psFree(fakeModel);
-    }
-
-    return true;
-}
+            xSrc = source->peak->xf;
+            ySrc = source->peak->yf;
+        }
+
+        x->data.F32[numGood] = xSrc;
+        y->data.F32[numGood] = ySrc;
+        mag->data.F32[numGood] = source->psfMag;
+        numGood++;
+    }
+    x->n = numGood;
+    y->n = numGood;
+    mag->n = numGood;
+
+    bool status = pmReadoutFakeFromVectors(readout, numCols, numRows, x, y, mag, xOffset, yOffset, psf,
+                                           minFlux, radius, circularise, normalisePeak);
+    psFree(x);
+    psFree(y);
+    psFree(mag);
+
+    return status;
+}
Index: branches/simmosaic_branches/psModules/src/camera/pmReadoutFake.h
===================================================================
--- branches/simmosaic_branches/psModules/src/camera/pmReadoutFake.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/camera/pmReadoutFake.h	(revision 27839)
@@ -11,9 +11,32 @@
 #include <pmTrend2D.h>
 #include <pmPSF.h>
+#include <pmSourceMasks.h>
+
+/// Set threading
+///
+/// Returns old threading state
+bool pmReadoutFakeThreads(
+    bool new                            // New threading state
+    );
+
+/// Generate a fake readout from vectors
+bool pmReadoutFakeFromVectors(pmReadout *readout, ///< Output readout
+                              int numCols, int numRows, ///< Dimension of image
+                              const psVector *x, const psVector *y, ///< Source coordinates
+                              const psVector *mag, ///< Source magnitudes
+                              const psVector *xOffset, ///< x offsets for sources (source -> img), or NULL
+                              const psVector *yOffset, ///< y offsets for sources (source -> img), or NULL
+                              const pmPSF *psf, ///< PSF for sources
+                              float minFlux, ///< Minimum flux to bother about; for setting source radius
+                              int radius, ///< Fixed radius for sources
+                              bool circularise, ///< Circularise PSF model?
+                              bool normalisePeak ///< Normalise the peak value?
+    );
 
 /// Generate a fake readout from an array of sources
-bool pmReadoutFakeFromSources(pmReadout *readout, ///< Output readout, or NULL
+bool pmReadoutFakeFromSources(pmReadout *readout, ///< Output readout
                               int numCols, int numRows, ///< Dimension of image
                               const psArray *sources, ///< Array of pmSource
+                              pmSourceMode sourceMask, ///< Mask for sources
                               const psVector *xOffset, ///< x offsets for sources (source -> img), or NULL
                               const psVector *yOffset, ///< y offsets for sources (source -> img), or NULL
Index: branches/simmosaic_branches/psModules/src/concepts/pmConcepts.c
===================================================================
--- branches/simmosaic_branches/psModules/src/concepts/pmConcepts.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/concepts/pmConcepts.c	(revision 27839)
@@ -249,4 +249,5 @@
 CONCEPT_REGISTER_FUNCTION(S32, Enum, -1); // For enums: set default to -1
 CONCEPT_REGISTER_FUNCTION(S32, S32, 0); // For values: set default to 0
+//CONCEPT_REGISTER_FUNCTION(Bool, Bool, NULL); // For values: set default to 0
 
 static void conceptRegisterTime(const char *name, /* Name of concept */ \
@@ -291,4 +292,5 @@
         conceptRegisterF32("FPA.FOCUS", "Telescope focus", NULL, NULL, NULL, false, PM_FPA_LEVEL_FPA);
         conceptRegisterF32("FPA.AIRMASS", "Airmass at boresight", NULL, NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        // XXX p_pmConceptParse_FPA_FILTER -> p_pmConceptParse_FPA_FILTERID (and Format as well)?
         conceptRegisterStr("FPA.FILTERID", "Filter used (parsed, abstract name)", p_pmConceptParse_FPA_FILTER, p_pmConceptFormat_FPA_FILTER, NULL, false, PM_FPA_LEVEL_FPA);
         conceptRegisterStr("FPA.FILTER", "Filter used (instrument name)", NULL, NULL, NULL, false, PM_FPA_LEVEL_FPA);
@@ -300,6 +302,9 @@
         conceptRegisterF64("FPA.LONGITUDE", "West longitude of observatory", p_pmConceptParse_FPA_Coords, p_pmConceptFormat_FPA_Coords, NULL, false, PM_FPA_LEVEL_FPA);
         conceptRegisterF64("FPA.LATITUDE", "Latitude of observatory", p_pmConceptParse_FPA_Coords, p_pmConceptFormat_FPA_Coords, NULL, false, PM_FPA_LEVEL_FPA);
-        conceptRegisterF32("FPA.ELEVATION", "Elevation of observatory (metres)", NULL, NULL, NULL, false, PM_FPA_LEVEL_FPA);
-        conceptRegisterStr("FPA.OBSTYPE", "Type of observation", NULL, NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.ELEVATION", "Elevation of observatory (meters)", NULL, NULL, NULL, false, PM_FPA_LEVEL_FPA);
+
+        // conceptRegisterStr("FPA.OBSTYPE", "Type of observation", NULL, NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterStr("FPA.OBSTYPE", "Type of observation", p_pmConceptParse_FPA_OBSTYPE, p_pmConceptFormat_FPA_OBSTYPE, NULL, false, PM_FPA_LEVEL_FPA);
+
         conceptRegisterStr("FPA.OBJECT", "Object of observation", NULL, NULL, NULL, false, PM_FPA_LEVEL_FPA);
         conceptRegisterF64("FPA.ALT", "Altitude of boresight", NULL, NULL, NULL, false, PM_FPA_LEVEL_FPA);
@@ -329,5 +334,7 @@
         conceptRegisterF32("FPA.TELTEMP.EXTRA", "Telescope Temperatures: extra", p_pmConceptParse_TELTEMPS, NULL, NULL, false, PM_FPA_LEVEL_FPA);
         conceptRegisterF32("FPA.PON.TIME", "Power On Time", NULL, NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterS32("FPA.BURNTOOL.APPLIED", "[T=applied] Burn streaks applied to image data", p_pmConceptParse_BTOOLAPP,p_pmConceptFormat_BTOOLAPP,NULL,false,PM_FPA_LEVEL_FPA);
         conceptRegisterF32("FPA.EXPOSURE", "Exposure time (sec)", NULL, NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.ZP", "Magnitude zero point", NULL, NULL, NULL, false, PM_FPA_LEVEL_FPA);
     }
     if (!conceptsChip) {
@@ -344,4 +351,5 @@
         conceptRegisterF32("CHIP.TEMP", "Temperature of chip", NULL, NULL, NULL, false, PM_FPA_LEVEL_CHIP);
         conceptRegisterStr("CHIP.ID", "Chip identifier", NULL, NULL, NULL, false, PM_FPA_LEVEL_CHIP);
+        conceptRegisterF32("CHIP.SEEING", "Seeing FWHM (pixels)", NULL, NULL, NULL, false, PM_FPA_LEVEL_CHIP);
     }
 
@@ -377,5 +385,4 @@
         conceptRegisterS32("CELL.XWINDOW", "Start of cell window (pixels)",p_pmConceptParse_Positions,p_pmConceptFormat_Positions, NULL, true, PM_FPA_LEVEL_CELL);
         conceptRegisterS32("CELL.YWINDOW", "Start of cell window (pixels)",p_pmConceptParse_Positions,p_pmConceptFormat_Positions, NULL, true, PM_FPA_LEVEL_CELL);
-        conceptRegisterF32("CELL.VARFACTOR", "Variance factor for conversion from large to small scales", NULL, NULL, NULL, true, PM_FPA_LEVEL_CELL);
 
         // CELL.TRIMSEC
@@ -485,27 +492,27 @@
         concept[length - 1] = '\0';
 
-	// special variants:
-	if (!strcmp(concept, "FPA.DATE")) {
-	  psTime *fpaTime = psMetadataLookupPtr(NULL, fpa->concepts, "FPA.TIME");
-	  if (!fpaTime) {
-	    psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Missing concept FPA.TIME needed for FPA.DATE");
-	    psFree(string);
-	    return NULL;
-	  }
-	  psString dateTimeString = psTimeToISO(fpaTime); // String representation
-	  psList *dateTime = psStringSplit(dateTimeString, "T", true);
-	  psFree(dateTimeString);
-	  psString dateString = psMemIncrRefCounter(psListGet(dateTime, PS_LIST_HEAD)); // The date string
-	  psFree (dateTime);
-
-	  if (!psStringSubstitute(&string, dateString, "{FPA.DATE}")) {
-	      psError(PS_ERR_UNKNOWN, false, "Unable to replace concept %s", concept);
-	      psFree(string); 
-	      psFree(dateString);
-	      return NULL; 
-	  }
-	  psFree (dateString);
-	  continue;
-	}
+        // special variants:
+        if (!strcmp(concept, "FPA.DATE")) {
+          psTime *fpaTime = psMetadataLookupPtr(NULL, fpa->concepts, "FPA.TIME");
+          if (!fpaTime) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Missing concept FPA.TIME needed for FPA.DATE");
+            psFree(string);
+            return NULL;
+          }
+          psString dateTimeString = psTimeToISO(fpaTime); // String representation
+          psList *dateTime = psStringSplit(dateTimeString, "T", true);
+          psFree(dateTimeString);
+          psString dateString = psMemIncrRefCounter(psListGet(dateTime, PS_LIST_HEAD)); // The date string
+          psFree (dateTime);
+
+          if (!psStringSubstitute(&string, dateString, "{FPA.DATE}")) {
+              psError(PS_ERR_UNKNOWN, false, "Unable to replace concept %s", concept);
+              psFree(string);
+              psFree(dateString);
+              return NULL;
+          }
+          psFree (dateString);
+          continue;
+        }
 
         psTrace("psModules.concepts", 7, "Interpolating concept %s", concept);
Index: branches/simmosaic_branches/psModules/src/concepts/pmConceptsAverage.c
===================================================================
--- branches/simmosaic_branches/psModules/src/concepts/pmConceptsAverage.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/concepts/pmConceptsAverage.c	(revision 27839)
@@ -36,4 +36,5 @@
 
     double time      = 0.0;             // Time of observation
+    double zp        = 0.0;             // Zero point
     psTimeType timeSys = 0;             // Time system
     char *filter     = NULL;            // Filter
@@ -63,4 +64,7 @@
         psTimeConvert(fpaTime, PS_TIME_TAI);
         time       += psTimeToMJD(fpaTime);
+
+        zp += psMetadataLookupF32(NULL, fpa->concepts, "FPA.ZP");
+
         if (num == 1) {
             timeSys = psMetadataLookupS32(NULL, fpa->concepts, "FPA.TIMESYS");
@@ -85,4 +89,5 @@
 
     time /= (double)num;
+    zp /= (double)num;
 
     MD_UPDATE(target->concepts, "FPA.TIMESYS", S32, timeSys);
@@ -92,4 +97,5 @@
     MD_UPDATE_STR(target->concepts, "FPA.INSTRUMENT", instrument);
     MD_UPDATE_STR(target->concepts, "FPA.DETECTOR", detector);
+    MD_UPDATE(target->concepts, "FPA.ZP", F32, zp);
 
     // FPA.TIME needs special care
@@ -278,4 +284,5 @@
 
     float temp = 0.0;                   // Temperature
+    float seeing = 0.0;                 // Seeing FWHM
     int x0 = 0, y0 = 0;                 // Offset
     int xParity = 0, yParity = 0;       // Parity
@@ -291,4 +298,5 @@
         }
         temp += psMetadataLookupF32(NULL, chip->concepts, "CHIP.TEMP");
+        seeing += psMetadataLookupF32(NULL, chip->concepts, "CHIP.SEEING");
         if (nChips == 0) {
             xSize = psMetadataLookupS32(NULL, chip->concepts, "CHIP.XSIZE");
@@ -335,6 +343,8 @@
 
     temp /= (float)nChips;
+    seeing /= (float)nChips;
 
     MD_UPDATE(target->concepts, "CHIP.TEMP", F32, temp);
+    MD_UPDATE(target->concepts, "CHIP.SEEING", F32, seeing);
     if (same) {
         MD_UPDATE(target->concepts, "CHIP.X0", S32, x0);
Index: branches/simmosaic_branches/psModules/src/concepts/pmConceptsRead.c
===================================================================
--- branches/simmosaic_branches/psModules/src/concepts/pmConceptsRead.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/concepts/pmConceptsRead.c	(revision 27839)
@@ -47,4 +47,6 @@
     case PS_DATA_F64:
         return psMetadataItemAllocF64(pattern->name, pattern->comment, psMetadataItemParseF64(concept));
+    case PS_DATA_BOOL:
+      return psMetadataItemAllocBool(pattern->name, pattern->comment, psMetadataItemParseBool(concept));
     default:
         psWarning("Concept %s (%s) is not of a standard type (%x)\n",
@@ -73,5 +75,4 @@
         return false;
     }
-
     psTrace ("psModules.concepts", 3, "parsing concept: %s\n", spec->blank->name);
     if (!strcmp (spec->blank->name, "CELL.XPARITY")) {
@@ -275,4 +276,5 @@
         psMetadataItem *headerItem = NULL; // The value of the concept from the header
 
+
         psTrace ("psModules.concepts", 3, "reading concept: %s\n", name);
         if (!strcmp (name, "CELL.XPARITY")) {
@@ -307,5 +309,4 @@
             }
         }
-
         if (!headerItem) {
             psMetadataItem *formatItem = psMetadataLookup(transSpec, name); // Item with keyword
@@ -328,5 +329,4 @@
             }
             psString keywords = formatItem->data.str; // The FITS keywords
-
             // In case there are multiple headers
             psList *keys = psStringSplit(keywords, " ,;", true); // List of keywords
Index: branches/simmosaic_branches/psModules/src/concepts/pmConceptsStandard.c
===================================================================
--- branches/simmosaic_branches/psModules/src/concepts/pmConceptsStandard.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/concepts/pmConceptsStandard.c	(revision 27839)
@@ -4,6 +4,7 @@
 
 #include <stdio.h>
+#include <ctype.h>			// for tolower()
 #include <string.h>
-#include <strings.h>            /* for strn?casecmp */
+#include <strings.h>			// for strn?casecmp 
 #include <assert.h>
 #include <pslib.h>
@@ -160,5 +161,4 @@
     assert(concept);
     assert(pattern);
-
     double value = NAN;
     switch (concept->type) {
@@ -318,4 +318,107 @@
             "Unable to find %s in FILTER.ID in camera configuration.\n", key);
     return NULL;
+}
+
+// FPA.OBSTYPE
+// convert concept->data.str to new value 
+psMetadataItem *p_pmConceptParse_FPA_OBSTYPE(const psMetadataItem *concept,
+                                            const psMetadataItem *pattern,
+                                            pmConceptSource source,
+                                            const psMetadata *cameraFormat,
+                                            const pmFPA *fpa,
+                                            const pmChip *chip,
+                                            const pmCell *cell)
+{
+    assert(concept);
+    assert(pattern);
+    assert(fpa);
+    assert(fpa->camera);
+
+    if (concept->type != PS_DATA_STRING) {
+        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Type for %s (%x) is not STR\n",
+                pattern->name, concept->type);
+        return NULL;
+    }
+    if (!concept->data.str || strlen(concept->data.str) == 0) {
+        return psMetadataItemAllocStr(pattern->name, pattern->comment, "");
+    }
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *table = psMetadataLookupMetadata(&mdok, fpa->camera, "OBSTYPE.TABLE");
+    if (!mdok || !table) {
+	// if the table is not defined, pass the supplied value unmodified
+        return psMetadataItemAllocStr(pattern->name, pattern->comment, concept->data.str);
+    }
+
+    // the metadata is in the format (external) STR (internal) 
+    // do a lookup to get the internal name
+    char *extname = psStringCopy (concept->data.str);
+    for (int i = 0; i < strlen(extname); i++) {
+	extname[i] = tolower(extname[i]);
+    }
+    char *name = psMetadataLookupStr (&mdok, table, extname);
+    psFree(extname);
+    if (!name) {
+	// if the entry is not defined, pass the supplied value unmodified
+        return psMetadataItemAllocStr(pattern->name, pattern->comment, concept->data.str);
+    }
+
+    return psMetadataItemAllocStr(pattern->name, pattern->comment, name);
+}
+
+// convert concept->data.str to new value 
+psMetadataItem *p_pmConceptFormat_FPA_OBSTYPE(const psMetadataItem *concept,
+                                             pmConceptSource source,
+                                             const psMetadata *cameraFormat,
+                                             const pmFPA *fpa,
+                                             const pmChip *chip,
+                                             const pmCell *cell)
+{
+    assert(concept);
+    assert(fpa);
+    assert(fpa->camera);
+
+    if (concept->type != PS_DATA_STRING) {
+        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Type for %s (%x) is not STR\n",
+                concept->name, concept->type);
+        return NULL;
+    }
+    if (!concept->data.str || strlen(concept->data.str) == 0) {
+        return psMetadataItemAllocStr(concept->name, concept->comment, "");
+    }
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *table = psMetadataLookupMetadata(&mdok, fpa->camera, "OBSTYPE.TABLE");
+    if (!mdok || !table) {
+	// if the table is not defined, pass the supplied value unmodified
+        return psMetadataItemAllocStr(concept->name, concept->comment, concept->data.str);
+    }
+
+    const char *key = concept->data.str;        // The name to look up
+    if (!key || strlen(key) == 0) {
+        return psMetadataItemAllocStr(concept->name, concept->comment, NULL);
+    }
+
+    // the metadata is in the format (internal) STR (external)
+    // do a reverse lookup to get the internal name
+    psMetadataIterator *iter = psMetadataIteratorAlloc(table, PS_LIST_HEAD, NULL); // Iterator for filters
+    psMetadataItem *item;               // Item from iteration
+    char *name = NULL;                  // The winning name
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (item->type != PS_DATA_STRING) {
+            psWarning("Type for %s (%x) in OBSTYPE.TABLE in camera configuration is not STR\n", item->name, item->type);
+            continue;
+        }
+        if (strcmp(item->data.str, key) == 0) {
+            name = item->name;
+            break;
+        }
+    }
+    psFree(iter);
+
+    if (!name) {
+	return psMetadataItemAllocStr(concept->name, concept->comment, key);
+    }
+    return psMetadataItemAllocStr(concept->name, concept->comment, name);
 }
 
@@ -429,8 +532,10 @@
         }
 
-        psString ra = psMetadataLookupStr(&mdok, formats, "FPA.RA"); // Format for RA
-        psString dec = psMetadataLookupStr(&mdok, formats, "FPA.DEC"); // Format for Dec
-        if (ra && strcasecmp(ra, "HOURS") == 0 && dec && strcasecmp(dec, "DEGREES") == 0) {
-            sexagesimal = true;
+        if (strcmp(concept->name, "FPA.RA") == 0 || strcmp(concept->name, "FPA.DEC") == 0) {
+            psString ra = psMetadataLookupStr(&mdok, formats, "FPA.RA"); // Format for RA
+            psString dec = psMetadataLookupStr(&mdok, formats, "FPA.DEC"); // Format for Dec
+            if (ra && strcasecmp(ra, "HOURS") == 0 && dec && strcasecmp(dec, "DEGREES") == 0) {
+                sexagesimal = true;
+            }
         }
     } else {
@@ -450,9 +555,8 @@
         small = 3600.0 * coords;
         small = (float)((int)(small * 1000.0)) / 1000.0;
-        if (negative) {
-            big *= -1;
-        }
         psString coordString = NULL;        // String with the coordinates in sexagesimal format
-        psStringAppend(&coordString, "%d:%02d:%06.3f", big, medium, small);
+        psStringAppend(&coordString, "%s%02d:%02d:%06.3f",
+                       negative ? "-" : (strcmp(concept->name, "FPA.DEC") == 0 ? "+" : ""),
+                       big, medium, small);
         coordItem = psMetadataItemAllocStr(concept->name, concept->comment, coordString);
         psFree(coordString);
@@ -631,4 +735,74 @@
     return psMetadataItemAllocS32(pattern->name, pattern->comment, binning);
 }
+
+// BTOOLAPP
+psMetadataItem *p_pmConceptParse_BTOOLAPP(const psMetadataItem *concept,
+					  const psMetadataItem *pattern,
+					  pmConceptSource source,
+					  const psMetadata *cameraFormat,
+					  const pmFPA *fpa,
+					  const pmChip *chip,
+					  const pmCell *cell)
+{
+  assert(concept);
+  assert(pattern);
+
+  int bt_status = 0;
+
+  if (concept->type != PS_DATA_BOOL) {
+    psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Type for %s (%x) is not BOOL\n",
+	    concept->name, concept->type);
+    if (concept->type != PS_DATA_S32) {
+      psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Wasn't the type I'd guessed either.\n");
+      return NULL;
+    }
+    psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Looks like an S32 value? (%d)\n",
+	    concept->data.S32);
+
+    if (concept->data.S32 == 0) {
+      bt_status = 1;
+    }
+    else if (concept->data.S32 == 1) {
+      bt_status = -2;
+    }
+  }
+
+  if (concept->data.B == true) {
+    bt_status = -2;
+  }
+  else if (concept->data.B == false) {
+    bt_status = 1 ;
+  }
+  
+  return psMetadataItemAllocS32(concept->name, concept->comment, bt_status);
+}  
+psMetadataItem *p_pmConceptFormat_BTOOLAPP(const psMetadataItem *concept,
+					   pmConceptSource source,
+					   const psMetadata *cameraFormat,
+					   const pmFPA *fpa,
+					   const pmChip *chip,
+					   const pmCell *cell)
+{
+  assert(concept);
+
+  if (concept->type != PS_DATA_S32) {
+    return NULL;
+  }
+
+  if (concept->data.S32 == 0) {
+    return NULL;
+  }
+  else if (concept->data.S32 == -2) {
+    return psMetadataItemAllocBool(concept->name,concept->comment,true);
+  }
+  else if (concept->data.S32 == 1) {
+    return psMetadataItemAllocBool(concept->name,concept->comment,false);
+  }
+  else {
+    return NULL;
+  }
+
+}  
+
 
 // Get the current value of a concept
Index: branches/simmosaic_branches/psModules/src/concepts/pmConceptsStandard.h
===================================================================
--- branches/simmosaic_branches/psModules/src/concepts/pmConceptsStandard.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/concepts/pmConceptsStandard.h	(revision 27839)
@@ -72,4 +72,25 @@
     );
 
+/// Parse the FPA.OBSTYPE concept to apply a lookup table
+psMetadataItem *p_pmConceptParse_FPA_OBSTYPE(
+    const psMetadataItem *concept, ///< Concept to parse
+    const psMetadataItem *pattern, ///< Pattern to use in parsing
+    pmConceptSource source, ///< Source for concept
+    const psMetadata *cameraFormat, ///< Camera format definition
+    const pmFPA *fpa, ///< FPA for concept, or NULL
+    const pmChip *chip, ///< Chip for concept, or NULL
+    const pmCell *cell ///< Cell for concept, or NULL
+    );
+
+/// Format the FPA.OBSTYPE concept to (reverse-)apply a lookup table
+psMetadataItem *p_pmConceptFormat_FPA_OBSTYPE(
+    const psMetadataItem *concept, ///< Concept to format
+    pmConceptSource source, ///< Source for concept
+    const psMetadata *cameraFormat, ///< Camera format definition
+    const pmFPA *fpa, ///< FPA for concept, or NULL
+    const pmChip *chip, ///< Chip for concept, or NULL
+    const pmCell *cell ///< Cell for concept, or NULL
+    );
+
 /// Parse the coordinates concepts: FPA.RA and FPA.DEC
 psMetadataItem *p_pmConceptParse_FPA_Coords(
@@ -114,4 +135,24 @@
     const pmCell *cell ///< Cell for concept, or NULL
     );
+
+/// Format for the BTOOLAPP conceptn
+psMetadataItem *p_pmConceptParse_BTOOLAPP(
+    const psMetadataItem *concept, ///< Concept to format
+    const psMetadataItem *pattern,
+    pmConceptSource source, ///< Source for concept
+    const psMetadata *cameraFormat, ///< Camera format definition
+    const pmFPA *fpa, ///< FPA for concept, or NULL
+    const pmChip *chip, ///< Chip for concept, or NULL
+    const pmCell *cell ///< Cell for concept, or NULL
+    );
+psMetadataItem *p_pmConceptFormat_BTOOLAPP(
+    const psMetadataItem *concept, ///< Concept to format
+    pmConceptSource source, ///< Source for concept
+    const psMetadata *cameraFormat, ///< Camera format definition
+    const pmFPA *fpa, ///< FPA for concept, or NULL
+    const pmChip *chip, ///< Chip for concept, or NULL
+    const pmCell *cell ///< Cell for concept, or NULL
+    );
+
 
 /// Parse the cell binning concepts: CELL.XBIN, CELL.YBIN
Index: branches/simmosaic_branches/psModules/src/concepts/pmConceptsWrite.c
===================================================================
--- branches/simmosaic_branches/psModules/src/concepts/pmConceptsWrite.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/concepts/pmConceptsWrite.c	(revision 27839)
@@ -157,31 +157,35 @@
     }
     switch (item->type) {
-    case PS_DATA_STRING:
+      case PS_DATA_BOOL:
+        psTrace("psModules.concepts", 9, "Writing header %s: %d\n", keyword, item->data.B);
+        return psMetadataAddBool(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                 item->data.B);
+      case PS_DATA_STRING:
         psTrace("psModules.concepts", 9, "Writing header %s: %s\n", keyword, item->data.str);
         return psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
                                 item->data.V);
-    case PS_DATA_S32:
+      case PS_DATA_S32:
         psTrace("psModules.concepts", 9, "Writing header %s: %d\n", keyword, item->data.S32);
         return psMetadataAddS32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
                                 item->data.S32);
-    case PS_DATA_F32:
+      case PS_DATA_F32:
         psTrace("psModules.concepts", 9, "Writing header %s: %f\n", keyword, item->data.F32);
         return psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
                                 item->data.F32);
-    case PS_DATA_F64:
+      case PS_DATA_F64:
         psTrace("psModules.concepts", 9, "Writing header %s: %f\n", keyword, item->data.F64);
         return psMetadataAddF64(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
                                 item->data.F64);
-    case PS_DATA_REGION: {
-            psString region = psRegionToString(*(psRegion*)item->data.V);
-            psTrace("psModules.concepts", 9, "Writing header %s: %s\n", keyword, region);
-            bool result = psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
-                                           region);
-            psFree(region);
-            return result;
-        }
-    default:
-      psWarning("Type of %s is not suitable for a FITS header --- not added.\n",
-                 item->name);
+      case PS_DATA_REGION: {
+          psString region = psRegionToString(*(psRegion*)item->data.V);
+          psTrace("psModules.concepts", 9, "Writing header %s: %s\n", keyword, region);
+          bool result = psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                         region);
+          psFree(region);
+          return result;
+      }
+      default:
+        psWarning("Type of %s is not suitable for a FITS header --- not added.\n",
+                  item->name);
         return false;
     }
Index: branches/simmosaic_branches/psModules/src/config/Makefile.am
===================================================================
--- branches/simmosaic_branches/psModules/src/config/Makefile.am	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/config/Makefile.am	(revision 27839)
@@ -32,4 +32,5 @@
 	pmConfigDump.c \
 	pmConfigRun.c \
+	pmConfigRecipeValue.c \
 	pmVersion.c \
 	pmErrorCodes.c
@@ -43,4 +44,5 @@
 	pmConfigDump.h \
 	pmConfigRun.h \
+	pmConfigRecipeValue.h \
 	pmVersion.h \
 	pmErrorCodes.h
Index: branches/simmosaic_branches/psModules/src/config/pmConfig.c
===================================================================
--- branches/simmosaic_branches/psModules/src/config/pmConfig.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/config/pmConfig.c	(revision 27839)
@@ -43,8 +43,12 @@
 #define DEFAULT_TRACE STDERR_FILENO     // Default file descriptor for trace messages
 
+#define CHECK_FILE_RETRY 5              // Number of retries when checking a file
+#define CHECK_FILE_WAIT 250000          // Wait between retries (usec) when checking a file
+
 static bool readCameraConfig = true;    // Read the camera config on startup (with pmConfigRead)?
 static psArray *configPath = NULL;      // Search path for configuration files
 
 static bool checkPath(const char *filename, bool create, bool trunc);
+static psString resolveConfigFile(const char *name);
 
 bool pmConfigReadParamsSet(bool newReadCameraConfig)
@@ -174,5 +178,5 @@
         char *envName = envStart + 1;   // Start of the environment variable name
         if (envName[0] == '\0') {
-            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Path %s contains a bad environment variable.\n", dir);
+            psError(PM_ERR_CONFIG, true, "Path %s contains a bad environment variable.\n", dir);
             return NULL;
         }
@@ -180,5 +184,5 @@
             envName++;
             if (envName[0] == '\0') {
-                psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                psError(PM_ERR_CONFIG, true,
                         "Path %s contains a bad environment variable.\n", dir);
                 return NULL;
@@ -272,5 +276,5 @@
 
     if (configPath == NULL) {
-        psError(PS_ERR_IO, true, "Cannot find %s configuration file (%s) in path\n", description, name);
+        psError(PM_ERR_CONFIG, true, "Cannot find %s configuration file (%s) in path\n", description, name);
         return false;
     }
@@ -296,5 +300,5 @@
     }
 
-    psError(PS_ERR_IO, true, "Cannot find %s configuration file %s in path\n", description, name);
+    psError(PM_ERR_CONFIG, true, "Cannot find %s configuration file %s in path\n", description, name);
     return false;
 
@@ -302,5 +306,5 @@
     *config = psMetadataConfigRead(NULL, &numBadLines, realName, true);
     if (numBadLines > 0) {
-        psError(PS_ERR_IO, false, "%d bad lines in %s configuration file (%s)",
+        psError(PM_ERR_CONFIG, false, "%d bad lines in %s configuration file (%s)",
                 numBadLines, description, realName);
         psFree (realName);
@@ -309,5 +313,5 @@
     }
     if (!*config) {
-        psError(PS_ERR_IO, true, "Unable to read %s configuration from %s",
+        psError(PM_ERR_CONFIG, true, "Unable to read %s configuration from %s",
                 description, realName);
         psFree (realName);
@@ -328,5 +332,5 @@
     }
     if (item->type != PS_DATA_STRING) {
-        psTrace("config", 2, "Element %s in %s metadata is not of type STR.\n",
+        psError(PM_ERR_CONFIG, true, "Element %s in %s metadata is not of type STR.\n",
                 item->name, description);
         return false;
@@ -336,5 +340,5 @@
     psMetadata *new = NULL;         // New metadata
     if (!pmConfigFileRead(&new, item->data.str, item->name)) {
-        psError(PM_ERR_CONFIG, false, "Trouble reading reading %s %s.\n",
+        psError(psErrorCodeLast(), false, "Trouble reading reading %s %s.\n",
                 description, item->name);
         psFree(new);
@@ -361,5 +365,5 @@
     while ((item = psMetadataGetAndIncrement(iter))) {
         if (!pmConfigFileIngest(item, description)) {
-            psError(PM_ERR_CONFIG, false, "Unable to read %s %s.", description, item->name);
+            psError(psErrorCodeLast(), false, "Unable to read %s %s.", description, item->name);
             psFree(iter);
             return false;
@@ -382,9 +386,9 @@
     psMetadata *formats = psMetadataLookupMetadata(&mdok, camera, "FORMATS"); // Formats
     if (!mdok || !formats) {
-        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find FORMATS in camera configuration %s.\n", name);
+        psError(PM_ERR_CONFIG, true, "Unable to find FORMATS in camera configuration %s.\n", name);
         return false;
     }
     if (!metadataReadFiles(formats, "camera format")) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to read formats within camera configuration %s.\n", name);
+        psError(psErrorCodeLast(), false, "Unable to read formats within camera configuration %s.\n", name);
         return false;
     }
@@ -447,5 +451,5 @@
             psWarning("-ipprc command-line switch provided without the required filename --- ignored.\n");
         } else {
-            configFile = psStringCopy(argv[argNum]);
+            configFile = resolveConfigFile(argv[argNum]);
             psArgumentRemove(argNum, argc, argv);
         }
@@ -486,10 +490,10 @@
     psMetadataItem *siteItem = psMetadataLookup(config->user, "SITE");
     if (!siteItem) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find SITE in user configuration.");
+        psError(PM_ERR_CONFIG, true, "Unable to find SITE in user configuration.");
         psFree(config);
         return NULL;
     }
     if (!pmConfigFileIngest(siteItem, "site configuration")) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to read site configuration");
+        psError(psErrorCodeLast(), false, "Unable to read site configuration");
         psFree(config);
         return NULL;
@@ -500,10 +504,10 @@
     psMetadataItem *systemItem = psMetadataLookup(config->user, "SYSTEM");
     if (!systemItem) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find SYSTEM in user configuration.");
+        psError(PM_ERR_CONFIG, true, "Unable to find SYSTEM in user configuration.");
         psFree(config);
         return NULL;
     }
     if (!pmConfigFileIngest(systemItem, "system configuration")) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to read system configuration");
+        psError(psErrorCodeLast(), false, "Unable to read system configuration");
         psFree(config);
         return NULL;
@@ -551,9 +555,10 @@
             psString resolved = pmConfigConvertFilename(logDest, config, true, false); // Resolved filename
             if (!resolved || strlen(resolved) == 0) {
-                psWarning("Unable to resolve log destination: %s --- ignored", logDest);
-            } else {
-                pmConfigRunFilenameAddWrite(config, "LOG", logDest);
-                config->logFD = psMessageDestination(resolved);
-            }
+                psError(psErrorCodeLast(), false, "Unable to resolve log destination: %s", logDest);
+                psFree(logDest);
+                return NULL;
+            }
+            pmConfigRunFilenameAddWrite(config, "LOG", logDest);
+            config->logFD = psMessageDestination(resolved);
             psFree(resolved);
             psFree(logDest);
@@ -610,9 +615,10 @@
             psString resolved = pmConfigConvertFilename(traceDest, config, true, false); // Resolved filename
             if (!resolved || strlen(resolved) == 0) {
-                psWarning("Unable to resolve trace destination: %s --- ignored", traceDest);
-            } else {
-                pmConfigRunFilenameAddWrite(config, "TRACE", traceDest);
-                config->traceFD = psMessageDestination(resolved);
-            }
+                psError(psErrorCodeLast(), false, "Unable to resolve trace destination: %s", traceDest);
+                psFree(traceDest);
+                return NULL;
+            }
+            pmConfigRunFilenameAddWrite(config, "TRACE", traceDest);
+            config->traceFD = psMessageDestination(resolved);
             psFree(resolved);
             psFree(traceDest);
@@ -659,5 +665,6 @@
                 seed = strtoll(argv[argNum], &end, 0);
                 if (strlen(end) > 0) {
-                    psError(PS_ERR_IO, true, "Unable to read random number generator seed: %s", argv[argNum]);
+                    psError(PM_ERR_CONFIG, true, "Unable to read random number generator seed: %s",
+                            argv[argNum]);
                     psFree(config);
                     return NULL;
@@ -684,5 +691,5 @@
             psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->system, "CAMERAS");
             if (!cameras) {
-                psError(PS_ERR_IO, false, "Unable to find CAMERAS in site configuration.\n");
+                psError(PM_ERR_CONFIG, false, "Unable to find CAMERAS in site configuration.\n");
                 psFree(config);
                 return NULL;
@@ -692,5 +699,5 @@
             char *cameraFile = psMetadataLookupStr(&mdok, cameras, cameraName); // The filename
             if (!cameraFile) {
-                psError(PS_ERR_IO, false, "%s is not listed in the site CAMERAS list\n", cameraName);
+                psError(PM_ERR_CONFIG, false, "%s is not listed in the site CAMERAS list\n", cameraName);
                 psFree(config);
                 return NULL;
@@ -699,5 +706,5 @@
             // load this camera's configuration informatoin
             if (!pmConfigFileRead(&config->camera, cameraFile, "camera")) {
-                psError(PM_ERR_CONFIG, false, "Problem reading %s", cameraName);
+                psError(psErrorCodeLast(), false, "Problem reading %s", cameraName);
                 psFree(config);
                 return NULL;
@@ -710,5 +717,5 @@
             // Read in the formats
             if (!cameraReadFormats(config->camera, cameraFile)) {
-                psError(PS_ERR_UNKNOWN, false, "Unable to read formats within camera configuration %s.\n",
+                psError(psErrorCodeLast(), false, "Unable to read formats within camera configuration %s.\n",
                         cameraFile);
                 psFree(config);
@@ -718,5 +725,5 @@
             // Read in any camera-specific calibrations
             if (!cameraReadCalibrations(config->camera, cameraName)) {
-                psError(PS_ERR_UNKNOWN, false,
+                psError(psErrorCodeLast(), false,
                         "Unable to read calibrations within camera configuration %s.\n",
                         cameraName);
@@ -729,5 +736,5 @@
 
             if (!pmConfigCameraSkycellVersion(config->system, cameraName)) {
-                psError(PS_ERR_UNKNOWN, false,
+                psError(psErrorCodeLast(), false,
                         "Unable to generate skycell versions of specified camera %s.\n",
                         cameraName);
@@ -737,5 +744,5 @@
 
             if (!pmConfigCameraMosaickedVersions(config->system, cameraName)) {
-                psError(PS_ERR_UNKNOWN, false,
+                psError(psErrorCodeLast(), false,
                         "Unable to generate mosaicked versions of specified camera %s.\n",
                         cameraName);
@@ -751,10 +758,10 @@
         psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->system, "CAMERAS"); // List of cameras
         if (!mdok || !cameras) {
-            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the system configuration.\n");
+            psError(PM_ERR_CONFIG, true, "Unable to find CAMERAS in the system configuration.\n");
             return false;
         }
 
         if (!metadataReadFiles(cameras, "camera configuration")) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to read cameras within system configuration.\n");
+            psError(psErrorCodeLast(), false, "Unable to read cameras within system configuration.\n");
             psFree(config);
             return NULL;
@@ -784,10 +791,10 @@
 
         if (!pmConfigCameraSkycellVersionsAll(config->system)) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to generate skycell versions of cameras.\n");
+            psError(psErrorCodeLast(), false, "Unable to generate skycell versions of cameras.\n");
             psFree(config);
             return NULL;
         }
         if (!pmConfigCameraMosaickedVersionsAll(config->system)) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to generate mosaicked versions of cameras.\n");
+            psError(psErrorCodeLast(), false, "Unable to generate mosaicked versions of cameras.\n");
             psFree(config);
             return NULL;
@@ -797,5 +804,5 @@
     // Load the recipes from the camera file, if appropriate
     if(!pmConfigReadRecipes(config, PM_RECIPE_SOURCE_SYSTEM | PM_RECIPE_SOURCE_CAMERA)) {
-        psError(PS_ERR_IO, false, "Failed to read recipes from camera file");
+        psError(psErrorCodeLast(), false, "Failed to read recipes from camera file");
         psFree(config);
         return NULL;
@@ -812,5 +819,5 @@
 
     if (!pmConfigReadRecipes(config, PM_RECIPE_SOURCE_CL)) {
-        psError(PS_ERR_IO, false, "Failed to read recipes from command-line");
+        psError(psErrorCodeLast(), false, "Failed to read recipes from command-line");
         psFree(config);
         return NULL;
@@ -821,5 +828,5 @@
         psArgumentRemove(argNum, argc, argv);
         if (argNum + 1 >= *argc) {
-            psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+            psError(PM_ERR_CONFIG, true,
                     "Filerule switch (-F) provided without old and new filerule.");
             psFree(config);
@@ -834,5 +841,5 @@
         psMetadata *cameras = psMetadataLookupMetadata(NULL, config->system, "CAMERAS"); // List of cameras
         if (!cameras) {
-            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find CAMERAS in the site configuration.\n");
+            psError(PM_ERR_CONFIG, false, "Unable to find CAMERAS in the site configuration.\n");
             return false;
         }
@@ -995,5 +1002,5 @@
     psMetadata *rule = psMetadataLookupMetadata(&mdStatus, cameraFormat, "RULE");
     if (! mdStatus || ! rule) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to read rule for camera.");
+        psError(PM_ERR_CONFIG, false, "Unable to read rule for camera.");
         *valid = false;
         return false;
@@ -1004,5 +1011,5 @@
     psArray *keys = psListToArray (keyList);
     if (! keys) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to read rule for camera.");
+        psError(PM_ERR_CONFIG, false, "Unable to read rule for camera.");
         *valid = false;
         return false;
@@ -1053,5 +1060,5 @@
         }
 
-        psError(PS_ERR_UNKNOWN, false, "Invalid type for RULE %s.", ruleItem->name);
+        psError(PM_ERR_CONFIG, false, "Invalid type for RULE %s.", ruleItem->name);
         *valid = false;
         psFree (keyList);
@@ -1088,5 +1095,5 @@
     psMetadata *formats = psMetadataLookupMetadata(&mdok, camera, "FORMATS"); // List of formats
     if (!mdok || !formats) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to find list of FORMATS in camera %s", cameraName);
+        psError(PM_ERR_CONFIG, false, "Unable to find list of FORMATS in camera %s", cameraName);
         *status = false;
         return false;
@@ -1094,5 +1101,5 @@
 
     if (!metadataReadFiles(formats, "camera format")) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to read cameras formats within camera configuration.\n");
+        psError(psErrorCodeLast(), false, "Unable to read cameras formats within camera configuration.\n");
         *status = false;
         return false;
@@ -1110,5 +1117,5 @@
         bool valid = false;
         if (!pmConfigValidateCameraFormat(&valid, testFormat, header)) {
-            psError (PS_ERR_UNKNOWN, false, "Error in config scripts for camera %s, format %s\n",
+            psError (psErrorCodeLast(), false, "Error in config scripts for camera %s, format %s\n",
                      cameraName, formatsItem->name);
             *status = false;
@@ -1156,10 +1163,10 @@
         psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->system, "CAMERAS");
         if (! mdok || !cameras) {
-            psError(PS_ERR_IO, true, "Unable to find CAMERAS in the configuration.");
+            psError(PM_ERR_CONFIG, true, "Unable to find CAMERAS in the configuration.");
             return NULL;
         }
 
         if (!metadataReadFiles(cameras, "camera configuration")) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to read cameras within site configuration.\n");
+            psError(psErrorCodeLast(), false, "Unable to read cameras within site configuration.\n");
             return NULL;
         }
@@ -1190,5 +1197,6 @@
             } else {
                 if (!status) {
-                    psError(PS_ERR_IO, false, "Error reading camera config data for %s", camerasItem->name);
+                    psError(psErrorCodeLast(), false, "Error reading camera config data for %s",
+                            camerasItem->name);
                     return NULL;
                 }
@@ -1199,5 +1207,5 @@
         // Done looking at all cameras
         if (!config->camera) {
-            psError(PS_ERR_IO, false, "Unable to find a camera that matches input FITS header!");
+            psError(PM_ERR_CONFIG, true, "Unable to find a camera that matches input FITS header!");
             return NULL;
         }
@@ -1205,5 +1213,6 @@
         // Now we have the camera, we can read the recipes
         if (readRecipes && !pmConfigReadRecipes(config, PM_RECIPE_SOURCE_CAMERA | PM_RECIPE_SOURCE_CL)) {
-            psError(PS_ERR_IO, false, "Error reading recipes from camera config for %s", config->cameraName);
+            psError(psErrorCodeLast(), false, "Error reading recipes from camera config for %s",
+                    config->cameraName);
             return NULL;
         }
@@ -1281,6 +1290,7 @@
 
     if (!found) {
-        psError(PS_ERR_IO, true, "Unable to find a format with the specified camera (%s) that matches the "
-                "given header.\n", baseName);
+        psError(PM_ERR_CONFIG, true,
+                "Unable to find a format with the specified camera (%s) that matches the given header.",
+                baseName);
         psFree (baseName);
         return NULL;
@@ -1313,5 +1323,5 @@
     psMetadata *cameras = psMetadataLookupMetadata(NULL, config->system, "CAMERAS");
     if (!cameras) {
-        psError(PS_ERR_IO, true, "Unable to find CAMERAS in the configuration.");
+        psError(PM_ERR_CONFIG, true, "Unable to find CAMERAS in the configuration.");
         return NULL;
     }
@@ -1319,5 +1329,5 @@
     psMetadataItem *item = psMetadataLookup(cameras, cameraName); // Item with camera of interest
     if (!pmConfigFileIngest(item, "camera configuration")) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to ingest camera configuration.");
+        psError(psErrorCodeLast(), false, "Unable to ingest camera configuration.");
         return NULL;
     }
@@ -1335,5 +1345,5 @@
         item = psMetadataLookup(config->site, name);
         if (!item) {
-            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+            psError(PM_ERR_CONFIG, true,
                     "Unable to find %s in user or site configuration", name);
             return NULL;
@@ -1341,5 +1351,5 @@
     }
     if (item->type != type) {
-        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+        psError(PM_ERR_CONFIG, true,
                 "Type of %s (%x) in user/site configuration does not match expected (%x)",
                 name, item->type, type);
@@ -1358,5 +1368,5 @@
 #ifndef HAVE_PSDB
 
-    psError(PS_ERR_UNKNOWN, false,
+    psError(PM_ERR_PROG, false,
             "Cannot configure database: psModules was compiled without database support.");
     return NULL;
@@ -1407,5 +1417,5 @@
     psMetadata *rules = psMetadataLookupMetadata(&mdok, format, "RULE"); // How to identify this format
     if (!mdok || !rules) {
-        psError(PS_ERR_IO, true, "Unable to find RULE in camera format.\n");
+        psError(PM_ERR_CONFIG, true, "Unable to find RULE in camera format.\n");
         return false;
     }
@@ -1415,5 +1425,5 @@
     while ((rulesItem = psMetadataGetAndIncrement(rulesIter))) {
         if (!PS_DATA_IS_PRIMITIVE(rulesItem->type) && rulesItem->type != PS_DATA_STRING) {
-            psError(PS_ERR_UNKNOWN, false, "Invalid type for RULE %s.", rulesItem->name);
+            psError(PM_ERR_CONFIG, false, "Invalid type for RULE %s.", rulesItem->name);
             return false;
         }
@@ -1447,5 +1457,5 @@
           case PS_METADATA_ITEM_COMPARE_OP_NE:
             // It's not at all obvious what the value should be, so return an error.
-            psError(PS_ERR_IO, true,
+            psError(PM_ERR_CONFIG, true,
                     "RULE %s (defined by an OPeration) is not present or not consistent in output header",
                     rulesItem->name);
@@ -1530,5 +1540,5 @@
               default:
                 // rigid format, no comments allowed?
-                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to parse file list: spaces detected.");
+                psError(PM_ERR_CONFIG, true, "Unable to parse file list: spaces detected.");
                 psFree(input);
                 fclose(f);
@@ -1563,5 +1573,5 @@
     if (!files) {
         psAbort("error parsing argument list");
-        psError(PS_ERR_IO, false, "error parsing argument list");
+        psError(psErrorCodeLast(), false, "error parsing argument list");
         psFree (files);
         return false;
@@ -1590,6 +1600,7 @@
 
         char *point = newName + strlen("file:");
-        while (*point == '/')
+        while (*point == '/') {
             point ++;
+        }
         char *tmpName = NULL;
         psStringAppend (&tmpName, "/%s", point);
@@ -1599,5 +1610,5 @@
         if (!checkPath(newName, create, trunc)) {
             // let checkPath()'s psError() call float up
-            psError(PS_ERR_UNKNOWN, false, "error from checkPath for file:// (%s)", newName);
+            psError(psErrorCodeLast(), false, "error from checkPath for file:// (%s)", newName);
             psFree (newName);
             return NULL;
@@ -1617,5 +1628,5 @@
         psMetadata *datapath = psMetadataLookupPtr (NULL, config->site, "DATAPATH");
         if (datapath == NULL) {
-            psError(PS_ERR_UNKNOWN, true, "DATAPATH is not defined in config.site");
+            psError(PM_ERR_CONFIG, true, "DATAPATH is not defined in config.site");
             psFree (newName);
             return NULL;
@@ -1625,5 +1636,5 @@
         char *mark = strchr (point, '/');
         if (mark == NULL) {
-            psError(PS_ERR_UNKNOWN, true, "syntax error in PATH-style name %s", newName);
+            psError(PM_ERR_CONFIG, true, "syntax error in PATH-style name %s", newName);
             psFree (newName);
             return false;
@@ -1633,5 +1644,5 @@
         char *realpath = psMetadataLookupStr (NULL, datapath, path);
         if (realpath == NULL) {
-            psError(PS_ERR_UNKNOWN, true,
+            psError(PM_ERR_CONFIG, true,
                     "path (%s) not defined in config.site:DATAPATH for PATH-style name %s",
                     path, newName);
@@ -1649,5 +1660,5 @@
         if (!checkPath(newName, create, trunc)) {
             // let checkPath()'s psError() call float up
-            psError(PS_ERR_UNKNOWN, false, "error from checkPath for path:// (%s)", newName);
+            psError(psErrorCodeLast(), false, "error from checkPath for path:// (%s)", newName);
             psFree (newName);
             return NULL;
@@ -1711,10 +1722,9 @@
         nebServerFree(server);
 
-        if (trunc) {
-            if(truncate(path, 0) != 0) {
-                psError(PS_ERR_IO, true, "Failed to truncate Nebulous file %s (real name %s)\n",
-                        filename, path);
-                return NULL;
-            }
+        // Check to ensure it's there.  Will create the file if Nebulous failed to do so.
+        if (!checkPath(path, create, trunc)) {
+            psError(psErrorCodeLast(), false, "Cannot find file %s", path);
+            psFree(path);
+            return NULL;
         }
 
@@ -1741,5 +1751,5 @@
     psMetadataItem *item = psMetadataLookup(camera, "FILERULES"); // Item with the file rule of interest
     if (!item) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find FILERULES in the camera configuration.");
+        psError(PM_ERR_CONFIG, false, "Unable to find FILERULES in the camera configuration.");
         return NULL;
     }
@@ -1774,5 +1784,5 @@
     psMetadataItem *item = psMetadataLookup(camera, "FITSTYPES"); // Item with the file rule of interest
     if (!item) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find FITSTYPES in the camera configuration.");
+        psError(PM_ERR_CONFIG, false, "Unable to find FITSTYPES in the camera configuration.");
         return NULL;
     }
@@ -1801,5 +1811,5 @@
 
     // re-try access up to 5 times (1.25sec) to reduce NFS lurches
-    for (int i = 0; i < 5; i++) {
+    for (int i = 0; i < CHECK_FILE_RETRY; i++) {
         if (access(filename, R_OK) == 0) {
             // file already exists
@@ -1826,5 +1836,5 @@
             return true;
         }
-        usleep (250000);
+        usleep(CHECK_FILE_WAIT);
     }
 
@@ -1834,2 +1844,41 @@
     return false;
 }
+
+static psString resolveConfigFile(const char *nameArg)
+{
+    // if config file name is nebulous path resolve it
+    // otherwise just return a copy of the argument
+    if (strncasecmp(nameArg, "neb://", strlen("neb://"))) {
+        return psStringCopy(nameArg);
+    }
+
+#ifdef HAVE_NEBCLIENT
+    char *neb_server = getenv("NEB_SERVER");
+
+    // if env isn't set, check the config system
+    if (!neb_server) {
+        psError(PM_ERR_CONFIG, true, "NEB_SERVER environment variable must be set in order to resolve config file.");
+            return NULL;
+    }
+
+    nebServer *server = nebServerAlloc(neb_server);
+    if (!server) {
+        psError(PM_ERR_SYS, true, "failed to create a nebServer object.");
+        return NULL;
+    }
+
+    char *nebfile = nebFind(server, nameArg);
+    nebServerFree(server);
+    if (!nebfile) {
+        // object does not exist
+        psError(PM_ERR_SYS, true, "failed to resolve nebulous path: %s.", nameArg);
+        return NULL;
+    }
+    // XXX: do I need to free nebfile?
+
+    return psStringCopy(nebfile);
+#else
+    psError(PM_ERR_PROG, true, "psModules was compiled without nebulous support.");
+    return NULL;
+#endif // ifdef HAVE_NEBCLIENT
+}
Index: branches/simmosaic_branches/psModules/src/config/pmConfigCamera.c
===================================================================
--- branches/simmosaic_branches/psModules/src/config/pmConfigCamera.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/config/pmConfigCamera.c	(revision 27839)
@@ -149,5 +149,5 @@
     camerasIter = psMetadataIteratorAlloc(new, PS_LIST_HEAD, NULL); // Iterator
     while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
-        psMetadataAddItem(cameras, camerasItem, PS_LIST_HEAD, 0);
+        psMetadataAddItem(cameras, camerasItem, PS_LIST_HEAD, PS_META_REPLACE);
     }
     psFree(camerasIter);
@@ -210,7 +210,12 @@
 
     // See if the new one is already there
-    psString newName = NULL;       // Name of skycelled camera
-    psStringAppend(&newName, "_%s-SKYCELL", name);
-    if (psMetadataLookup(oldCameras, newName)) {
+    psString newName = pmConfigCameraSkycellName(name); // Name of skycelled camera
+    bool mdok;                       // Status of MD lookup
+    psMetadata *oldCam = psMetadataLookupMetadata(&mdok, oldCameras, newName); // Existing camera configuration
+    if (mdok && oldCam) {
+        // Ensure new camera goes to the head of the metadata, so that it will be recognised first
+        // The old camera doesn't contain the PSMOSAIC header, so it will match anything!
+        psTrace("psModules.config", 6, "Camera configuration for %s exists, so moving to the front.", newName);
+        psMetadataAddMetadata(newCameras, PS_LIST_HEAD, newName, PS_META_REPLACE, NULL, oldCam);
         psFree(newName);
         return true;
@@ -366,4 +371,5 @@
     // New camera MUST go to the head of the metadata, so that it will be recognised first
     // The old camera doesn't contain the PSCAMERA and PSFORMAT headers, so it will match anything!
+    psTrace("psModules.config", 6, "Generated new camera configuration for %s.", newName);
     psMetadataAddMetadata(newCameras, PS_LIST_HEAD, newName, PS_META_REPLACE,
                           "Automatically generated", new);
@@ -436,5 +442,5 @@
     camerasIter = psMetadataIteratorAlloc(new, PS_LIST_HEAD, NULL); // Iterator
     while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
-        psMetadataAddItem(cameras, camerasItem, PS_LIST_HEAD, 0);
+        psMetadataAddItem(cameras, camerasItem, PS_LIST_HEAD, PS_META_REPLACE);
     }
     psFree(camerasIter);
@@ -469,7 +475,13 @@
 
     // See if the new one is already there
-    psString newName = NULL;       // Name of mosaicked camera
-    psStringAppend(&newName, "_%s-%s", name, mosaicLevel == PM_FPA_LEVEL_CHIP ? "CHIP" : "FPA");
-    if (psMetadataLookup(oldCameras, newName)) {
+    psString newName = mosaicLevel == PM_FPA_LEVEL_CHIP ? pmConfigCameraChipName(name) :
+        pmConfigCameraFPAName(name); // Name of mosaicked camera
+    bool mdok;                       // Status of MD lookup
+    psMetadata *oldCam = psMetadataLookupMetadata(&mdok, oldCameras, newName); // Existing camera configuration
+    if (mdok && oldCam) {
+        // Ensure new camera goes to the head of the metadata, so that it will be recognised first
+        // The old camera doesn't contain the PSMOSAIC header, so it will match anything!
+        psTrace("psModules.config", 6, "Camera configuration for %s exists, so moving to the front.", newName);
+        psMetadataAddMetadata(newCameras, PS_LIST_HEAD, newName, PS_META_REPLACE, NULL, oldCam);
         psFree(newName);
         return true;
@@ -477,5 +489,4 @@
 
     psMetadata *new = psMetadataCopy(NULL, camera); // Copy of the camera description
-    bool mdok;                          // Status of MD lookups
 
     // ** Fix up the contents of the FPA description to match the mosaicked camera **
@@ -849,4 +860,5 @@
     // New camera MUST go to the head of the metadata, so that it will be recognised first
     // The old camera doesn't contain the PSMOSAIC header, so it will match anything!
+    psTrace("psModules.config", 6, "Generated new camera configuration for %s.", newName);
     psMetadataAddMetadata(newCameras, PS_LIST_HEAD, newName, PS_META_REPLACE,
                           "Automatically generated", new);
Index: branches/simmosaic_branches/psModules/src/config/pmConfigDump.c
===================================================================
--- branches/simmosaic_branches/psModules/src/config/pmConfigDump.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/config/pmConfigDump.c	(revision 27839)
@@ -14,4 +14,5 @@
 #include "pmFPAfile.h"
 #include "pmConfigCamera.h"
+#include "pmErrorCodes.h"
 
 #include "pmConfigDump.h"
@@ -55,5 +56,5 @@
 
     if (!configCull(config->recipes, keep)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to cull system recipes.");
+        psError(psErrorCodeLast(), false, "Unable to cull system recipes.");
         psFree(keep);
         return false;
@@ -64,5 +65,5 @@
     psMetadata *cameras = psMetadataLookupMetadata(NULL, config->system, "CAMERAS"); // Known cameras
     if (!cameras) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find CAMERAS in the system configuration.\n");
+        psError(PM_ERR_CONFIG, false, "Unable to find CAMERAS in the system configuration.\n");
         return false;
     }
@@ -80,5 +81,5 @@
         }
         if (!configCull(recipes, keep)) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to cull recipes for camera %s", item->name);
+            psError(psErrorCodeLast(), false, "Unable to cull recipes for camera %s", item->name);
             psFree(iter);
             psFree(keep);
@@ -98,5 +99,5 @@
       psMetadata *cameras = psMetadataLookupMetadata(NULL, config->system, "CAMERAS"); // Known cameras
       if (!cameras) {
-          psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find CAMERAS in the system configuration.\n");
+          psError(PM_ERR_CONFIG, false, "Unable to find CAMERAS in the system configuration.\n");
           return NULL;
       }
@@ -144,7 +145,11 @@
 
     psString resolved = pmConfigConvertFilename(filename, config, true, false); // Resolved filename
+    if (!resolved) {
+        psError(psErrorCodeLast(), false, "Unable to create file for configuration dump: %s", filename);
+        return false;
+    }
 
     if (!psMetadataConfigWrite(config->user, resolved)) {
-        psError(PS_ERR_IO, false, "Unable to dump configuration to %s", filename);
+        psError(psErrorCodeLast(), false, "Unable to dump configuration to %s", filename);
         psFree(resolved);
         return false;
Index: branches/simmosaic_branches/psModules/src/config/pmConfigMask.c
===================================================================
--- branches/simmosaic_branches/psModules/src/config/pmConfigMask.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/config/pmConfigMask.c	(revision 27839)
@@ -16,4 +16,5 @@
     { "BLANK",     "DETECTOR", 0x01, true  }, // Pixel doesn't contain valid data
     { "CTE",       "DETECTOR", 0x01, true  }, // Pixel has poor CTE
+    { "BURNTOOL",  NULL,       0x04, false }, // Pixel has been touched by burntool
     // Invalid signal ranges
     { "SAT",       NULL,       0x02, true  }, // Pixel is saturated or non-linear
@@ -22,8 +23,8 @@
     // Non-astronomical structures
     { "CR",        NULL,       0x08, true  }, // Pixel contains a cosmic ray
-    { "SPIKE",     NULL,       0x08, true  }, // Pixel contains a diffraction spike
-    { "GHOST",     NULL,       0x08, true  }, // Pixel contains an optical ghost
-    { "STREAK",    NULL,       0x08, true  }, // Pixel contains streak data
-    { "STARCORE",  NULL,       0x08, true  }, // Pixel contains a bright star core
+    { "SPIKE",     NULL,       0x08, false  }, // Pixel contains a diffraction spike
+    { "GHOST",     NULL,       0x08, false  }, // Pixel contains an optical ghost
+    { "STREAK",    NULL,       0x08, false  }, // Pixel contains streak data
+    { "STARCORE",  NULL,       0x08, false  }, // Pixel contains a bright star core
     // Effects of convolution and interpolation
     { "CONV.BAD",  NULL,       0x02, true  }, // Pixel is bad after convolution with a bad pixel
Index: branches/simmosaic_branches/psModules/src/config/pmConfigRecipeValue.c
===================================================================
--- branches/simmosaic_branches/psModules/src/config/pmConfigRecipeValue.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/config/pmConfigRecipeValue.c	(revision 27839)
@@ -0,0 +1,84 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+
+
+psMetadataItem *pmConfigRecipeValueByView(const pmConfig *config, // Configuration
+                                          const char *recipeName, // Name of recipe
+                                          const char *valueName,  // Name of value in recipe
+                                          const pmFPA *fpa,       // FPA of interest
+                                          const pmFPAview *view   // View to component of interest
+    )
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_METADATA_NON_NULL(config->recipes, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(recipeName, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(valueName, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, recipeName); // Recipe of interest
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find recipe %s", recipeName);
+        return NULL;
+    }
+
+    psMetadataItem *fpaItem = psMetadataLookup(recipe, valueName); // Value for FPA or menu of chips
+    if (!fpaItem) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find value %s in recipe %s", valueName, recipeName);
+        return NULL;
+    }
+    if (view->chip == -1 && view->cell == -1) {
+        // Default value only
+        return fpaItem;
+    }
+    if (fpaItem->type != PS_DATA_METADATA) {
+        // Single value appropriate for all
+        return fpaItem;
+    }
+
+    psMetadata *menu = fpaItem->data.md;   // Menu with values by chip
+
+    pmChip *chip = pmFPAviewThisChip(view, fpa);                                   // Chip of interest
+    const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+
+    psMetadataItem *chipItem = psMetadataLookup(menu, chipName); // Value for chip of menu of cells
+    if (!chipItem) {
+        psError(PS_ERR_UNEXPECTED_NULL, true,
+                "Chip %s does not have a menu entry in %s within recipe %s.",
+                  chipName, valueName, recipeName);
+        return NULL;
+    }
+    if (view->cell == -1) {
+        // Default value for chip
+        return chipItem;
+    }
+    if (chipItem->type != PS_DATA_METADATA) {
+        // Single value appropriate for all
+        return chipItem;
+    }
+
+    menu = chipItem->data.md;   // Menu with values by chip
+
+    pmCell *cell = pmFPAviewThisCell(view, fpa);                                   // Cell of interest
+    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+
+    psMetadataItem *cellItem = psMetadataLookup(menu, cellName); // Value for cell of menu of cells
+    if (!cellItem) {
+        psError(PS_ERR_UNEXPECTED_NULL, true,
+                "Chip %s, cell %s does not have a menu entry in %s within recipe %s --- using default.",
+                chipName, cellName, valueName, recipeName);
+        return NULL;
+    }
+
+    return cellItem;
+}
Index: branches/simmosaic_branches/psModules/src/config/pmConfigRecipeValue.h
===================================================================
--- branches/simmosaic_branches/psModules/src/config/pmConfigRecipeValue.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/config/pmConfigRecipeValue.h	(revision 27839)
@@ -0,0 +1,18 @@
+#ifndef PM_CONFIG_RECIPE_VALUE_H
+#define PM_CONFIG_RECIPE_VALUE_H
+
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+
+/// Return a recipe value according to the provided view (i.e., chip- and/or cell-dependent)
+psMetadataItem *pmConfigRecipeValueByView(const pmConfig *config, // Configuration
+                                          const char *recipeName, // Name of recipe
+                                          const char *valueName,  // Name of value in recipe
+                                          const pmFPA *fpa,       // FPA of interest
+                                          const pmFPAview *view   // View to component of interest
+    );
+
+#endif
Index: branches/simmosaic_branches/psModules/src/config/pmConfigRun.c
===================================================================
--- branches/simmosaic_branches/psModules/src/config/pmConfigRun.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/config/pmConfigRun.c	(revision 27839)
@@ -160,5 +160,5 @@
     psArray *files = configRunFileGet(config, name, "FILES.INPUT"); // Files from RUN metadata
     if (!files) {
-        configRunFileGet(config, name, "FILES.OUTPUT");
+        files = configRunFileGet(config, name, "FILES.OUTPUT");
     }
 
Index: branches/simmosaic_branches/psModules/src/detrend/Makefile.am
===================================================================
--- branches/simmosaic_branches/psModules/src/detrend/Makefile.am	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/detrend/Makefile.am	(revision 27839)
@@ -16,5 +16,7 @@
 	pmShifts.c \
 	pmDark.c \
-	pmRemnance.c
+	pmRemnance.c \
+	pmPattern.c \
+	pmPatternIO.c
 
 #	pmSkySubtract.c
@@ -33,5 +35,7 @@
 	pmShifts.h \
 	pmDark.h \
-	pmRemnance.h
+	pmRemnance.h \
+	pmPattern.h \
+	pmPatternIO.h
 
 #	pmSkySubtract.h
Index: branches/simmosaic_branches/psModules/src/detrend/pmDark.c
===================================================================
--- branches/simmosaic_branches/psModules/src/detrend/pmDark.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/detrend/pmDark.c	(revision 27839)
@@ -38,10 +38,10 @@
     if (!item) {
         pmChip *chip = cell->parent; // Parent chip
-	psAssert(chip, "cell is missing chip \n");
+        psAssert(chip, "cell is missing chip \n");
 
         item = psMetadataLookup(chip->concepts, name);
         if (!item) {
             pmFPA *fpa = chip->parent; // Parent FPA
-	    psAssert(fpa, "chip is missing fpa \n");
+            psAssert(fpa, "chip is missing fpa \n");
 
             item = psMetadataLookup(fpa->concepts, name);
@@ -68,5 +68,5 @@
 
     psArray *words = psStringSplitArray(rule, " ", false);
-    
+
     // we should have a rule of the form (concept) OP (concept) OP (concept) ...
     // for now, the only allowed OP is * (eventually, we can steal code from opihi for a better
@@ -74,31 +74,31 @@
 
     if (words->n % 2 == 0) {
-	psError(PM_ERR_CONFIG, true, "syntax error in DARK.ORDINATE %s rule %s\n", name, rule);
-	psFree(words);
-	return false;
+        psError(PM_ERR_CONFIG, true, "syntax error in DARK.ORDINATE %s rule %s\n", name, rule);
+        psFree(words);
+        return false;
     }
 
     for (int i = 1; i < words->n; i+=2) {
-	if (strcmp((char *)words->data[i], "*")) {
-	    psError(PM_ERR_CONFIG, true, "syntax error in DARK.ORDINATE %s rule %s\n", name, rule);
-	    psFree(words);
-	    return false;
-	}
-    }
-    
+        if (strcmp((char *)words->data[i], "*")) {
+            psError(PM_ERR_CONFIG, true, "syntax error in DARK.ORDINATE %s rule %s\n", name, rule);
+            psFree(words);
+            return false;
+        }
+    }
+
     if (!ordinateParseConcept(value, readout, words->data[0])) {
-	psError(PM_ERR_CONFIG, false, "syntax error in DARK.ORDINATE %s rule %s\n", name, rule);
-	psFree(words);
-	return false;
+        psError(PM_ERR_CONFIG, false, "syntax error in DARK.ORDINATE %s rule %s\n", name, rule);
+        psFree(words);
+        return false;
     }
 
     double value2 = 0.0;
     for (int i = 2; i < words->n; i+=2) {
-	if (!ordinateParseConcept(&value2, readout, words->data[i])) {
-	    psError(PM_ERR_CONFIG, false, "syntax error in DARK.ORDINATE %s rule %s\n", name, rule);
-	    psFree(words);
-	    return false;
-	}
-	*value *= value2;
+        if (!ordinateParseConcept(&value2, readout, words->data[i])) {
+            psError(PM_ERR_CONFIG, false, "syntax error in DARK.ORDINATE %s rule %s\n", name, rule);
+            psFree(words);
+            return false;
+        }
+        *value *= value2;
     }
     psFree(words);
@@ -123,13 +123,13 @@
 
     if (rule) {
-	if (!ordinateParseRule(value, readout, name, rule)) {
-	    psError(PM_ERR_CONFIG, false, "trouble parsing rule %s for DARK.ORDINATE %s", rule, name);
-	    return false;
-	}
+        if (!ordinateParseRule(value, readout, name, rule)) {
+            psError(PM_ERR_CONFIG, false, "trouble parsing rule %s for DARK.ORDINATE %s", rule, name);
+            return false;
+        }
     } else {
-	if (!ordinateParseConcept(value, readout, name)) {
-	    psError(PM_ERR_CONFIG, false, "trouble parsing rule %s for DARK.ORDINATE %s", rule, name);
-	    return false;
-	}
+        if (!ordinateParseConcept(value, readout, name)) {
+            psError(PM_ERR_CONFIG, false, "trouble parsing rule %s for DARK.ORDINATE %s", rule, name);
+            return false;
+        }
     }
 
@@ -188,8 +188,8 @@
         double normValue;            // Normalisation value
         if (!ordinateLookup(&normValue, &inRange, normConcept, NULL, false, NAN, NAN, readout)) {
-	    psError(PM_ERR_CONFIG, false, "problem finding concept %s for DARK.NORM", normConcept);
-	    return false;
-	}
-	if (!isfinite(normValue)) {
+            psError(PM_ERR_CONFIG, false, "problem finding concept %s for DARK.NORM", normConcept);
+            return false;
+        }
+        if (!isfinite(normValue)) {
             psWarning("Unable to find acceptable value of %s for readout %d", normConcept, i);
             roMask->data.PS_TYPE_VECTOR_MASK_DATA[i] = 0xff;
@@ -231,9 +231,9 @@
             double value = NAN;          // Value of ordinate
             if (!ordinateLookup(&value, &inRange, ord->name, ord->rule, ord->scale, ord->min, ord->max, readout)) {
-		psError(PM_ERR_CONFIG, false, "problem finding rule for DARK.ORDINATE %s", ord->name);
-		return false;
-	    }
-	    if (!isfinite(value)) {
-		psWarning("Unable to find acceptable value of DARK.ORDINATE %s for readout %d", ord->name, i);
+                psError(PM_ERR_CONFIG, false, "problem finding rule for DARK.ORDINATE %s", ord->name);
+                return false;
+            }
+            if (!isfinite(value)) {
+                psWarning("Unable to find acceptable value of DARK.ORDINATE %s for readout %d", ord->name, i);
                 roMask->data.PS_TYPE_VECTOR_MASK_DATA[j] = 0xff;
                 val->data.F32[i] = NAN;
@@ -242,5 +242,5 @@
             }
             if (!inRange) {
-		psWarning("Value of DARK.ORDINATE %s for readout %d is out of range", ord->name, i);
+                psWarning("Value of DARK.ORDINATE %s for readout %d is out of range", ord->name, i);
                 roMask->data.PS_TYPE_VECTOR_MASK_DATA[j] = 0xff;
                 val->data.F32[i] = NAN;
@@ -349,9 +349,4 @@
     PS_ASSERT_INT_NONNEGATIVE(iter, false);
     PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, false);
-
-    pthread_t id = pthread_self();
-    char name[64];
-    sprintf (name, "%x", (unsigned int) id);
-    psTimerStart (name);
 
     bool mdok = false;
@@ -433,6 +428,6 @@
             }
 
-	    pmDarkVisualPixelFit(pixels, mask);
-	    pmDarkVisualPixelModel(poly, values);
+            pmDarkVisualPixelFit(pixels, mask);
+            pmDarkVisualPixelModel(poly, values);
 
             for (int k = 0; k < poly->coeff->n; k++) {
@@ -537,9 +532,9 @@
             return false;
         }
-	if (!isfinite(value)) {
+        if (!isfinite(value)) {
             psError(PS_ERR_UNKNOWN, true, "Value for DARK.ORDINATE %s is NAN", ord->name);
             psFree(values);
             return false;
-	}
+        }
         values->data.F32[i] = value;
     }
@@ -792,10 +787,10 @@
             psMetadataAddStr(row, PS_LIST_TAIL, PM_DARK_FITS_NAME, 0, "DARK.ORDINATE name", ord->name);
 
-	    // XXX write a dummy value if ord->rule == NULL? (eg, NONE)
-	    if (ord->rule) {
-		psMetadataAddStr(row, PS_LIST_TAIL, PM_DARK_FITS_RULE, 0, "DARK.ORDINATE rule", ord->rule);
-	    } else {
-		psMetadataAddStr(row, PS_LIST_TAIL, PM_DARK_FITS_RULE, 0, "DARK.ORDINATE rule", "NONE");
-	    }
+            // XXX write a dummy value if ord->rule == NULL? (eg, NONE)
+            if (ord->rule) {
+                psMetadataAddStr(row, PS_LIST_TAIL, PM_DARK_FITS_RULE, 0, "DARK.ORDINATE rule", ord->rule);
+            } else {
+                psMetadataAddStr(row, PS_LIST_TAIL, PM_DARK_FITS_RULE, 0, "DARK.ORDINATE rule", "NONE");
+            }
 
             psMetadataAddS32(row, PS_LIST_TAIL, PM_DARK_FITS_ORDER, 0, "Polynomial order", ord->order);
@@ -975,12 +970,12 @@
               ord->max = psMetadataLookupF32(NULL, row, PM_DARK_FITS_MAX);
 
-	      // load the ordinate rule; it is not an error if this field is missing or NULL
-	      // a NULL rule means 'use the name as the single concept'
+              // load the ordinate rule; it is not an error if this field is missing or NULL
+              // a NULL rule means 'use the name as the single concept'
               const char *rule = psMetadataLookupStr(&mdok, row, PM_DARK_FITS_RULE);
-	      if (!strcasecmp(rule, "NONE")) {
-		  ord->rule = NULL;
-	      } else {
-		  ord->rule = psStringCopy(rule);
-	      }
+              if (!rule || !strcasecmp(rule, "NONE")) {
+                  ord->rule = NULL;
+              } else {
+                  ord->rule = psStringCopy(rule);
+              }
               ordinates->data[i] = ord;
           }
@@ -1009,5 +1004,5 @@
 // this init function only gets the ordinates for the first readout...
 bool pmDarkVisualInit(psArray *values) {
-    
+
     if (!pmVisualIsVisual()) return true;
 
@@ -1018,22 +1013,22 @@
     int nOrders = 0;
     for (int i = 0; i < values->n; i++) {
-	psVector *vect = values->data[i];
-	if (!nOrders) {
-	    nOrders = vect->n;
-	} else {
-	    psAssert (nOrders == vect->n, "mismatch in order vector lengths");
-	}
+        psVector *vect = values->data[i];
+        if (!nOrders) {
+            nOrders = vect->n;
+        } else {
+            psAssert (nOrders == vect->n, "mismatch in order vector lengths");
+        }
     }
     xVectors = psArrayAlloc(nOrders);
     for (int i = 0; i < nOrders; i++) {
-	xVectors->data[i] = psVectorAlloc(values->n, PS_TYPE_F32);
+        xVectors->data[i] = psVectorAlloc(values->n, PS_TYPE_F32);
     }
 
     for (int i = 0; i < values->n; i++) {
-	psVector *vect = values->data[i];
-	for (int j = 0; j < vect->n; j++) {
-	    psVector *xVec = xVectors->data[j];
-	    xVec->data.F32[i] = vect->data.F32[j];
-	}
+        psVector *vect = values->data[i];
+        for (int j = 0; j < vect->n; j++) {
+            psVector *xVec = xVectors->data[j];
+            xVec->data.F32[i] = vect->data.F32[j];
+        }
     }
 
@@ -1041,8 +1036,8 @@
 
     kapa = psAlloc(nKapa*sizeof(int));
-    
+
     for (int i = 0; i < nKapa; i++) {
-	kapa[i] = -1;
-	pmVisualInitWindow(&kapa[i], "ppmerge");
+        kapa[i] = -1;
+        pmVisualInitWindow(&kapa[i], "ppmerge");
     }
     return true;
@@ -1063,33 +1058,33 @@
 
     for (int i = 0; i < xVectors->n; i++) {
-	psVector *x = xVectors->data[i];
-
-	// generate vectors of the unmasked values
-	int nSub = 0;
-	for (int j = 0; j < pixels->n; j++) {
-	    if (mask && mask->data.PS_TYPE_VECTOR_MASK_DATA[j]) continue;
-	    xSub->data.F32[nSub] = x->data.F32[j];
-	    ySub->data.F32[nSub] = pixels->data.F32[j];
-	    nSub ++;
-	}
-	xSub->n = ySub->n = nSub;
-	
-	// plot the unmasked values
-	pmVisualScaleGraphdata (&graphdata, xSub, ySub, false);
-	KapaSetGraphData(kapa[i], &graphdata);
-	KapaSetLimits(kapa[i], &graphdata);
-	KapaClearPlots (kapa[i]);
-
-	KapaSetFont (kapa[i], "courier", 14);
-	KapaBox (kapa[i], &graphdata);
-	KapaSendLabel (kapa[i], "ordinate", KAPA_LABEL_XM);
-	KapaSendLabel (kapa[i], "pixel values", KAPA_LABEL_YM);
-
-	graphdata.color = KapaColorByName("black");
-	graphdata.style = 2;
-	graphdata.ptype = 2;
-	KapaPrepPlot  (kapa[i], xSub->n, &graphdata);
-	KapaPlotVector(kapa[i], xSub->n, xSub->data.F32, "x");
-	KapaPlotVector(kapa[i], xSub->n, ySub->data.F32, "y");
+        psVector *x = xVectors->data[i];
+
+        // generate vectors of the unmasked values
+        int nSub = 0;
+        for (int j = 0; j < pixels->n; j++) {
+            if (mask && mask->data.PS_TYPE_VECTOR_MASK_DATA[j]) continue;
+            xSub->data.F32[nSub] = x->data.F32[j];
+            ySub->data.F32[nSub] = pixels->data.F32[j];
+            nSub ++;
+        }
+        xSub->n = ySub->n = nSub;
+
+        // plot the unmasked values
+        pmVisualScaleGraphdata (&graphdata, xSub, ySub, false);
+        KapaSetGraphData(kapa[i], &graphdata);
+        KapaSetLimits(kapa[i], &graphdata);
+        KapaClearPlots (kapa[i]);
+
+        KapaSetFont (kapa[i], "courier", 14);
+        KapaBox (kapa[i], &graphdata);
+        KapaSendLabel (kapa[i], "ordinate", KAPA_LABEL_XM);
+        KapaSendLabel (kapa[i], "pixel values", KAPA_LABEL_YM);
+
+        graphdata.color = KapaColorByName("black");
+        graphdata.style = 2;
+        graphdata.ptype = 2;
+        KapaPrepPlot  (kapa[i], xSub->n, &graphdata);
+        KapaPlotVector(kapa[i], xSub->n, xSub->data.F32, "x");
+        KapaPlotVector(kapa[i], xSub->n, ySub->data.F32, "y");
     }
     pmVisualAskUser (&plotFlag);
@@ -1110,18 +1105,18 @@
 
     for (int i = 0; i < values->n; i++) {
-	psVector *coord = values->data[i];
-	yFit->data.F32[i] = psPolynomialMDEval (poly, coord);
+        psVector *coord = values->data[i];
+        yFit->data.F32[i] = psPolynomialMDEval (poly, coord);
     }
 
     for (int i = 0; i < xVectors->n; i++) {
-	psVector *xFit = xVectors->data[i];
-
-	KapaGetGraphData(kapa[i], &graphdata);
-	graphdata.color = KapaColorByName("red");
-	graphdata.style = 2;
-	graphdata.ptype = 7;
-	KapaPrepPlot  (kapa[i], xFit->n, &graphdata);
-	KapaPlotVector(kapa[i], xFit->n, xFit->data.F32, "x");
-	KapaPlotVector(kapa[i], xFit->n, yFit->data.F32, "y");
+        psVector *xFit = xVectors->data[i];
+
+        KapaGetGraphData(kapa[i], &graphdata);
+        graphdata.color = KapaColorByName("red");
+        graphdata.style = 2;
+        graphdata.ptype = 7;
+        KapaPrepPlot  (kapa[i], xFit->n, &graphdata);
+        KapaPlotVector(kapa[i], xFit->n, xFit->data.F32, "x");
+        KapaPlotVector(kapa[i], xFit->n, yFit->data.F32, "y");
     }
     pmVisualAskUser (&plotFlag);
@@ -1132,5 +1127,5 @@
 
     for (int i = 0; i < nKapa; i++) {
-	KapaClose(kapa[i]);
+        KapaClose(kapa[i]);
     }
     psFree (kapa);
Index: branches/simmosaic_branches/psModules/src/detrend/pmFringeStats.c
===================================================================
--- branches/simmosaic_branches/psModules/src/detrend/pmFringeStats.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/detrend/pmFringeStats.c	(revision 27839)
@@ -11,4 +11,9 @@
 #include "pmFPA.h"
 #include "pmFringeStats.h"
+
+#include "psPolynomialMD.h"
+#include "psMinimizePolyFit.h"
+#include "psVector.h"
+
 
 // Future optimisations for speed:
@@ -331,6 +336,6 @@
         dfPt[i] = 1.0 / medianSd->sampleStdev;
 
-        psTrace("psModules.detrend", 7, "[%d:%d,%d:%d]: %f %f\n", (int)region.x0, (int)region.x1,
-                (int)region.y0, (int)region.y1, fPt[i], dfPt[i]);
+        psTrace("psModules.detrend", 7, "[%d:%d,%d:%d]: %f %f : %s\n", (int)region.x0, (int)region.x1,
+                (int)region.y0, (int)region.y1, fPt[i], dfPt[i], readout->parent->hdu->extname);
     }
     psFree(sky);
@@ -840,5 +845,5 @@
             }
         }
-        B->data.F64[i] = vector;
+	B->data.F64[i] = vector;
     }
 
@@ -855,5 +860,7 @@
     // Solve the least-squares equation
     if (!psMatrixGJSolve(A, B)) {
-        psError(PS_ERR_UNKNOWN, false, "Could not solve linear equations.  Returning NULL.\n");
+        psLogMsg("psModules.detrend", PS_LOG_INFO, "Could not solve linear equations.  Returning NULL.\n");
+	psFree(A);
+	psFree(B);
         return false;
     }
@@ -964,4 +971,5 @@
             }
         }
+	
     }
 
@@ -986,7 +994,46 @@
                 psTrace("psModules.detrend", 9, "Masking region %d because not finite in fringe %d.\n", j, i);
             }
-        }
-    }
-
+	    else if (fabs(fringe->f->data.F32[j]) > 0.1) {
+	      mask->data.PS_TYPE_VECTOR_MASK_DATA[j] = 1;
+	      psTrace("psModules.detrend", 9, "Masking region %d because too large fringe %d.\n", j, i);
+	    }
+	    // Mask bad points in the science data as well.
+	    if ((i == 0) && (!isfinite(science->f->data.F32[j]))) {
+                mask->data.PS_TYPE_VECTOR_MASK_DATA[j] = 1;
+                psTrace("psModules.detrend", 9, "Masking region %d because not finite in science fringe %d.\n", j, i);
+	    }	      
+	    psTrace("psModules.detrend", 7, "F %f %f %f %d\n",
+		    fringe->f->data.F32[j], science->f->data.F32[j],
+		    1 / science->df->data.F32[j],(int) mask->data.PS_TYPE_VECTOR_MASK_DATA[j]);
+        }
+    }
+    
+/*     // Begin switch from old outlier removal and fitting code. */
+
+    psPolynomial1D *poly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1);
+
+    pmFringeStats *fringe = fringes->data[0];
+    psVector *errors = psVectorAlloc(science->df->n,PS_TYPE_F32);
+    for (int j = 0; j < errors->n; j++) {
+      errors->data.F32[j] = 1 / science->df->data.F32[j];
+    }
+    psVectorFitPolynomial1D(poly,mask,0xff,science->f,errors,fringe->f);
+
+    for (int i = 0; i <= poly->nX; i++) {
+      scale->coeff->data.F32[i] = poly->coeff[i];
+      psTrace("psModules.detrend",7,"COEFFS: %d %g %g %g\n",i,scale->coeff->data.F32[i],poly->coeff[i],poly->coeffErr[i]);
+    }
+
+    psFree(poly);
+    //    psFree(fringe);
+    psFree(errors);
+
+    psFree(median);
+    psFree(diff);
+    return scale;
+    // End switch from old code.
+    
+
+    
 # if (0)
     // Write fringe data to file for a test
@@ -1017,5 +1064,7 @@
 	}
         scale->coeff->data.F32[0] -= median->sampleMedian;
-        scale->coeff->data.F32[i] = 0.0;
+	if (i != 0) {
+	  scale->coeff->data.F32[i] = 0.0;
+	}
     }
     psFree(median);
@@ -1049,4 +1098,5 @@
 
     return scale;
+    //# endif
 }
 
Index: branches/simmosaic_branches/psModules/src/detrend/pmMaskBadPixels.c
===================================================================
--- branches/simmosaic_branches/psModules/src/detrend/pmMaskBadPixels.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/detrend/pmMaskBadPixels.c	(revision 27839)
@@ -266,4 +266,5 @@
             }
         }
+	psFree(histo);
 
         // Since the mode is most likely zero, we add one to get something realistic.  Then "thresh" is
Index: branches/simmosaic_branches/psModules/src/detrend/pmPattern.c
===================================================================
--- branches/simmosaic_branches/psModules/src/detrend/pmPattern.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/detrend/pmPattern.c	(revision 27839)
@@ -0,0 +1,383 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmFPA.h"
+
+#include "pmPattern.h"
+
+
+// Mask a row as bad
+static void patternMaskRow(pmReadout *ro, // Readout to mask
+                           int y,       // Row to mask
+                           psImageMaskType bad // Mask value to give
+                           )
+{
+    psImage *image = ro->image;         // Image to mask
+    psAssert(image, "Require image");
+    psAssert(y < image->numRows, "Row not in image");
+
+    int numCols = image->numCols;       // Number of columns
+    for (int x = 0; x < numCols; x++) {
+        image->data.F32[y][x] = NAN;
+    }
+    if (ro->mask) {
+        psImage *mask = ro->mask;       // Mask image to mask
+        for (int x = 0; x < numCols; x++) {
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= bad;
+        }
+    }
+    return;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Measurement and application
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmPatternRow(pmReadout *ro, int order, int iter, float rej, float thresh,
+                  psStatsOptions clipMean, psStatsOptions clipStdev,
+                  psImageMaskType maskVal, psImageMaskType maskBad)
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PM_ASSERT_READOUT_IMAGE(ro, false);
+    PS_ASSERT_INT_NONNEGATIVE(order, false);
+    PS_ASSERT_INT_NONNEGATIVE(iter, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(thresh, 0.0, false);
+
+    psImage *image = ro->image;         // Image to correct
+    psImage *mask = ro->mask;           // Mask for image
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+
+    psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS); // Random number generator
+    if (!psImageBackground(stats, NULL, ro->image, ro->mask, maskVal, rng)) {
+        psWarning("Unable to calculate statistics on readout; masking entire readout.");
+        psErrorClear();
+        psFree(stats);
+        psFree(rng);
+        psImageInit(image, NAN);
+        if (mask) {
+            psBinaryOp(mask, mask, "|", psScalarAlloc(maskBad, PS_TYPE_IMAGE_MASK));
+        }
+        if (ro->variance) {
+            psImageInit(image, NAN);
+        }
+        return true;
+    }
+    float lower = stats->robustMedian - thresh * stats->robustStdev; // Lower bound for data
+    float upper = stats->robustMedian + thresh * stats->robustStdev; // Upper bound for data
+    float background = stats->robustMedian;
+    psFree(stats);
+    psFree(rng);
+
+    // Indices are distributed [-1:1)
+    psVector *indices = psVectorAlloc(numCols, PS_TYPE_F32); // Indices for fitting
+    float norm = 2.0 / (float)numCols;  // Normalisation for indices
+    for (int x = 0; x < numCols; x++) {
+        indices->data.F32[x] = x * norm - 1.0;
+    }
+
+    psStats *clip = psStatsAlloc(clipMean | clipStdev); // Clipping statistics
+    clip->clipIter = iter;
+    clip->clipSigma = rej;
+    psVector *clipMask = psVectorAlloc(numCols, PS_TYPE_VECTOR_MASK); // Mask for clipping
+    psPolynomial1D *poly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, order); // Polynomial to fit
+    psVector *data = psVectorAlloc(numCols, PS_TYPE_F32); // Data to fit
+
+    psImage *corr = psImageAlloc(order + 1, numRows, PS_TYPE_F64); // Corrections applied
+    psImageInit(corr, NAN);
+
+    for (int y = 0; y < numRows; y++) {
+        psVectorInit(clipMask, 0);
+        data = psImageRow(data, image, y);
+        int num = 0;                    // Number of good pixels
+        for (int x = 0; x < numCols; x++) {
+            if ((mask && mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskVal) ||
+                data->data.F32[x] < lower || data->data.F32[x] > upper) {
+                clipMask->data.PS_TYPE_VECTOR_MASK_DATA[x] = 0xFF;
+            } else {
+                clipMask->data.PS_TYPE_VECTOR_MASK_DATA[x] = 0;
+                num++;
+            }
+        }
+        if (num < order + 1) {
+            // Not enough points to fit
+            patternMaskRow(ro, y, maskBad);
+            continue;
+        }
+        if (!psVectorClipFitPolynomial1D(poly, clip, clipMask, 0xFF, data, NULL, indices)) {
+            psWarning("Unable to fit polynomial to row %d", y);
+            psErrorClear();
+            patternMaskRow(ro, y, maskBad);
+            continue;
+        }
+
+        poly->coeff[0] -= background;
+        memcpy(corr->data.F64[y], poly->coeff, (order + 1) * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
+        psVector *solution = psPolynomial1DEvalVector(poly, indices); // Solution vector
+        if (!solution) {
+            psWarning("Unable to evaluate polynomial for row %d", y);
+            psErrorClear();
+            patternMaskRow(ro, y, maskBad);
+            continue;
+        }
+
+        for (int x = 0; x < numCols; x++) {
+            image->data.F32[y][x] -= solution->data.F32[x];
+        }
+        psFree(solution);
+    }
+
+    psMetadataAddImage(ro->analysis, PS_LIST_TAIL, PM_PATTERN_ROW_CORRECTION, PS_META_REPLACE,
+                       "Pattern row correction", corr);
+    psFree(corr);
+
+    psFree(indices);
+    psFree(clip);
+    psFree(clipMask);
+    psFree(poly);
+    psFree(data);
+
+    return true;
+}
+
+bool pmPatternRowApply(pmReadout *ro, psImageMaskType maskBad)
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PM_ASSERT_READOUT_IMAGE(ro, false);
+
+    bool mdok;                          // Status of MD lookup
+    psImage *corr = psMetadataLookupPtr(&mdok, ro->analysis, PM_PATTERN_ROW_CORRECTION); // Correction
+    if (!mdok) {
+        // No correction to apply
+        return true;
+    }
+
+    psImage *image = ro->image; // Image of interest
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+
+    if (corr->numRows != numRows) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Number of rows of image (%d) does not match number of rows of pattern correction (%d)\n",
+                numRows, corr->numRows);
+        return false;
+    }
+
+    int order = corr->numCols - 1;                                        // Polynomial order
+    psPolynomial1D *poly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, order); // Polynomial to apply
+    psVector *indices = psVectorAlloc(numCols, PS_TYPE_F32); // Indices for polynomial
+    float norm = 2.0 / (float)numCols;  // Normalisation for indices
+    for (int x = 0; x < numCols; x++) {
+        indices->data.F32[x] = x * norm - 1.0;
+    }
+
+    for (int y = 0; y < numRows; y++) {
+        memcpy(poly->coeff, corr->data.F64[y], (order + 1) * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
+        psVector *solution = psPolynomial1DEvalVector(poly, indices); // Solution vector
+        if (!solution) {
+            psWarning("Unable to evaluate polynomial for row %d", y);
+            psErrorClear();
+            patternMaskRow(ro, y, maskBad);
+            continue;
+        }
+
+        for (int x = 0; x < numCols; x++) {
+            image->data.F32[y][x] -= solution->data.F32[x];
+        }
+        psFree(solution);
+    }
+
+    psFree(poly);
+    psFree(indices);
+
+    return true;
+}
+
+
+bool pmPatternCell(pmChip *chip, const psVector *tweak, psStatsOptions bgStat, psStatsOptions cellStat,
+                   psImageMaskType maskVal, psImageMaskType maskBad)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_VECTOR_NON_NULL(tweak, false);
+    PS_ASSERT_VECTOR_SIZE(tweak, chip->cells->n, false);
+    PS_ASSERT_VECTOR_TYPE(tweak, PS_TYPE_U8, false);
+
+    int numCells = tweak->n;            // Number of cells
+
+    psVector *mean = psVectorAlloc(numCells, PS_TYPE_F32); // Mean for each cell
+    psVector *meanMask = psVectorAlloc(numCells, PS_TYPE_VECTOR_MASK); // Mask for means
+    psVectorInit(mean, NAN);
+    psVectorInit(meanMask, 0);
+
+    // Mask bits
+    enum {
+        PM_PATTERN_IGNORE = 0x01,       // Ignore this cell
+        PM_PATTERN_TWEAK  = 0x02,       // Tweak this cell
+        PM_PATTERN_ERROR  = 0x04,       // Error in calculating background
+        PM_PATTERN_ALL    = 0xFF,       // All causes
+    };
+
+    // Count number of cells to tweak
+    int numTweak = 0;                   // Number of cells to tweak
+    int numIgnore = 0;                  // Number of cells to ignore
+    for (int i = 0; i < numCells; i++) {
+        pmCell *cell = chip->cells->data[i]; // Cell of interest
+        if (!cell || !cell->data_exists || !cell->process ||
+            cell->readouts->n == 0 || cell->readouts->n > 1 || !cell->readouts->data[0]) {
+            numIgnore++;
+            meanMask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PM_PATTERN_IGNORE;
+            continue;
+        }
+        if (tweak->data.U8[i]) {
+            meanMask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PM_PATTERN_TWEAK;
+            numTweak++;
+        }
+    }
+    if (numTweak == 0) {
+        // Nothing to do
+        psFree(mean);
+        psFree(meanMask);
+        return true;
+    }
+    if (numTweak >= numCells - numIgnore) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Cannot pattern-correct all cells within a chip.");
+        psFree(mean);
+        psFree(meanMask);
+        return false;
+    }
+
+    // Measure mean of each cell
+    // This is not really the perfect thing to do, which would be to take a common mean for the set of cells
+    // which aren't being tweaked (because some cells will be heavily masked, so shouldn't be weighted the
+    // same as pure cells), but it's simple and fast.
+    psStats *bgStats = psStatsAlloc(bgStat); // Statistics on background
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS); // Random number generator
+    for (int i = 0; i < numCells; i++) {
+        if (meanMask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PM_PATTERN_IGNORE) {
+            continue;
+        }
+        pmCell *cell = chip->cells->data[i]; // Cell of interest
+        pmReadout *ro = cell->readouts->data[0]; // Readout of interest
+
+        psStatsInit(bgStats);
+#if 1
+        if (!psImageBackground(bgStats, NULL, ro->image, ro->mask, maskVal, rng)) {
+            psWarning("Unable to measure background for cell %d\n", i);
+            psErrorClear();
+            meanMask->data.PS_TYPE_VECTOR_MASK_DATA[i] |= PM_PATTERN_ERROR;
+            continue;
+        }
+#else
+        if (!psImageStats(bgStats, ro->image, ro->mask, maskVal)) {
+            psWarning("Unable to measure background for cell %d\n", i);
+            psErrorClear();
+            meanMask->data.PS_TYPE_VECTOR_MASK_DATA[i] |= PM_PATTERN_ERROR;
+            continue;
+        }
+#endif
+        mean->data.F32[i] = psStatsGetValue(bgStats, bgStat);
+        if (!isfinite(mean->data.F32[i])) {
+            psWarning("Non-finite background for cell %d\n", i);
+            psErrorClear();
+            meanMask->data.PS_TYPE_VECTOR_MASK_DATA[i] |= PM_PATTERN_ERROR;
+            continue;
+        }
+    }
+    psFree(bgStats);
+    psFree(rng);
+
+    psStats *cellStats = psStatsAlloc(cellStat); // Statistics on cells
+    if (!psVectorStats(cellStats, mean, NULL, meanMask, PM_PATTERN_ALL)) {
+        // an error in psVectorStats implies a programming error
+        psError(PS_ERR_UNKNOWN, false, "Unable to calculate mean cell background.");
+        psFree(mean);
+        psFree(meanMask);
+        psFree(cellStats);
+        return false;
+    }
+
+    float background = psStatsGetValue(cellStats, cellStat); // Background value for chip
+    psFree(cellStats);
+    if (!isfinite(background)) {
+        // this can happen if all data in the image is bad -- in this case, do not correct, but
+        // do not treat this as an error (other functions are responsible for check data validity)
+        psLogMsg("psModules.detrend", PS_LOG_DETAIL, "Non-finite mean cell background -- skipping correction (data probabaly bad).");
+        psFree(mean);
+        psFree(meanMask);
+        return true;
+    }
+
+    psLogMsg("psModules.detrend", PS_LOG_DETAIL, "Mean chip background is %f", background);
+
+    for (int i = 0; i < numCells; i++) {
+        if (meanMask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PM_PATTERN_IGNORE) {
+            continue;
+        }
+        if (!(meanMask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PM_PATTERN_TWEAK)) {
+            continue;
+        }
+        pmCell *cell = chip->cells->data[i]; // Cell of interest
+        pmReadout *ro = cell->readouts->data[0]; // Readout of interest
+        if (meanMask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PM_PATTERN_ERROR) {
+            psImageInit(ro->image, NAN);
+            psBinaryOp(ro->mask, ro->mask, "|", psScalarAlloc(maskBad, PS_TYPE_IMAGE_MASK));
+            psMetadataAddF32(ro->analysis, PS_LIST_TAIL, PM_PATTERN_CELL_CORRECTION, PS_META_REPLACE,
+                             "Pattern cell correction solution", NAN);
+            continue;
+        }
+        float correction = background - mean->data.F32[i]; // Correction to apply
+        const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+        psLogMsg("psModules.detrend", PS_LOG_DETAIL, "Correcting background of cell %s by %f",
+                 cellName, correction);
+        psBinaryOp(ro->image, ro->image, "+", psScalarAlloc(correction, PS_TYPE_F32));
+        psMetadataAddF32(ro->analysis, PS_LIST_TAIL, PM_PATTERN_CELL_CORRECTION, PS_META_REPLACE,
+                         "Pattern cell correction solution", correction);
+    }
+
+    psFree(mean);
+    psFree(meanMask);
+
+    return true;
+}
+
+bool pmPatternCellApply(pmReadout *ro, psImageMaskType maskBad)
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PM_ASSERT_READOUT_IMAGE(ro, false);
+
+    bool mdok;                          // Status of MD lookup
+    float corr = psMetadataLookupF32(&mdok, ro->analysis, PM_PATTERN_CELL_CORRECTION); // Correction to apply
+    if (!mdok) {
+        // No correction to apply
+        return true;
+    }
+
+    psImage *image = ro->image, *mask = ro->mask; // Image and mask of interest
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+
+    if (!isfinite(corr)) {
+        for (int y = 0; y < numRows; y++) {
+            for (int x = 0; x < numCols; x++) {
+                image->data.F32[y][x] = NAN;
+            }
+        }
+        if (mask) {
+            for (int y = 0; y < numRows; y++) {
+                for (int x = 0; x < numCols; x++) {
+                    mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= maskBad;
+                }
+            }
+        }
+    } else {
+        for (int y = 0; y < numRows; y++) {
+            for (int x = 0; x < numCols; x++) {
+                image->data.F32[y][x] += corr;
+            }
+        }
+    }
+
+    return true;
+}
+
+
Index: branches/simmosaic_branches/psModules/src/detrend/pmPattern.h
===================================================================
--- branches/simmosaic_branches/psModules/src/detrend/pmPattern.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/detrend/pmPattern.h	(revision 27839)
@@ -0,0 +1,61 @@
+/* @file pmPattern.h
+ * @brief Fit and remove pattern noise
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.16 $
+ * @date $Date: 2009-02-12 19:25:52 $
+ * Copyright 2004-2009 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_PATTERN_H
+#define PM_PATTERN_H
+
+#include <pslib.h>
+#include <pmFPA.h>
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+#define PM_PATTERN_ROW_CORRECTION "PATTERN.ROW.CORRECTION" // Pattern row correction on analysis metadata
+#define PM_PATTERN_CELL_CORRECTION "PATTERN.CELL.CORRECTION" // Pattern cell correction on analysis metadata
+
+/// Fit and remove pattern noise over rows
+bool pmPatternRow(
+    pmReadout *ro,                      ///< Readout to correct
+    int order,                          ///< Polynomial order
+    int iter,                           ///< Number of clipping iterations
+    float rej,                          ///< Rejection threshold for clipping
+    float thresh,                       ///< Threshold for ignoring pixels
+    psStatsOptions clipMean,            ///< Statistic to use for mean
+    psStatsOptions clipStdev,           ///< Statistic to use for standard deviation
+    psImageMaskType maskVal,            ///< Mask value to use
+    psImageMaskType maskBad             ///< Mask value to give bad pixels
+    );
+
+/// Apply previously measured row pattern correction
+bool pmPatternRowApply(pmReadout *ro,   ///< Readout to correct
+                       psImageMaskType maskBad ///< Mask value to give bad pixels
+                       );
+
+/// Fix the background on cells known to be troublesome
+bool pmPatternCell(
+    pmChip *chip,                       ///< Chip to correct
+    const psVector *tweak,              ///< U8 vector indicating whether to tweak the corresponding cell
+    psStatsOptions bgStat,              ///< Statistic to use for background measurement
+    psStatsOptions cellStat,            ///< Statistic to use for combination of cell background measurements
+    psImageMaskType maskVal,            ///< Mask value to use
+    psImageMaskType maskBad             ///< Mask value to give bad pixels
+    );
+
+/// Apply previously measured cell pattern correction
+bool pmPatternCellApply(pmReadout *ro,          ///< Readout to correct
+                        psImageMaskType maskBad ///< Mask value to give bad pixels
+                        );
+
+
+/// @}
+#endif
+
+
+
Index: branches/simmosaic_branches/psModules/src/detrend/pmPatternIO.c
===================================================================
--- branches/simmosaic_branches/psModules/src/detrend/pmPatternIO.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/detrend/pmPatternIO.c	(revision 27839)
@@ -0,0 +1,453 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <string.h>
+
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPAfileFitsIO.h"
+#include "pmFPAHeader.h"
+#include "pmConceptsRead.h"
+#include "pmConceptsWrite.h"
+
+#include "pmPattern.h"
+#include "pmPatternIO.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmReadoutWritePattern(pmReadout *ro, psFits *fits)
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    bool gotRow, gotCell;
+    psImage *rowCorr = psMetadataLookupPtr(&gotRow, ro->analysis, PM_PATTERN_ROW_CORRECTION); // Row correction
+    float cellCorr = psMetadataLookupF32(&gotCell, ro->analysis, PM_PATTERN_CELL_CORRECTION); // Cell corr.
+
+    pmCell *cell = ro->parent;          // Cell of interest
+    if (!cell) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "No cell associated with readout.");
+        return false;
+    }
+    pmChip *chip = cell->parent;    // Chip of interest
+    pmFPA *fpa = chip->parent;      // FPA of interest
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // HDU for readout
+    if (!hdu) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "No HDU associated with readout.");
+        return false;
+    }
+
+    psMetadata *header = psMetadataCopy(NULL, hdu->header); // Header for output
+
+    if (gotCell) {
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_PATTERN_CELL_CORRECTION, PS_META_REPLACE,
+                         "Pattern cell correction value", cellCorr);
+    }
+
+    if (gotRow) {
+        if (!psFitsWriteImage(fits, header, rowCorr, 0, hdu->extname)) {
+            psError(PS_ERR_IO, false, "Unable to write pattern row correction.");
+            psFree(header);
+            return false;
+        }
+    } else {
+        if (!psFitsWriteBlank(fits, header, hdu->extname)) {
+            psError(PS_ERR_IO, false, "Unable to write pattern cell correction.");
+            psFree(header);
+            return false;
+        }
+    }
+
+    psFree(header);
+    return true;
+}
+
+static bool pmCellWritePattern(pmCell *cell, const pmFPAview *view,
+                               pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(cell->readouts, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    if (!pmConceptsWriteCell(cell, true, config)) {
+        psError(PS_ERR_IO, false, "Unable to write concepts for cell.");
+        return false;
+    }
+
+    // Only do the FIRST readout --- don't want to write lots of headers
+    pmReadout *readout = cell->readouts->data[0];
+    if (!pmReadoutWritePattern(readout, file->fits)) {
+        psError(PS_ERR_IO, false, "Failed to write readout");
+        return false;
+    }
+    return true;
+}
+
+static bool pmChipWritePattern(pmChip *chip, const pmFPAview *view,
+                               pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(chip->cells, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    if (!pmConceptsWriteChip(chip, true, true, config)) {
+        psError(PS_ERR_IO, false, "Unable to write concepts for chip.\n");
+        return false;
+    }
+
+    pmFPAview *thisView = pmFPAviewAlloc(view->nRows); // Copy of input view
+    *thisView = *view;
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        thisView->cell = i;
+        if (!pmCellWritePattern(cell, thisView, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth cell", i);
+            psFree(thisView);
+            return false;
+        }
+    }
+    psFree(thisView);
+    return true;
+}
+
+static bool pmFPAWritePattern(pmFPA *fpa, const pmFPAview *view,
+                              pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fpa->chips, false);
+
+    if (!pmConceptsWriteFPA(fpa, true, config)) {
+        psError(PS_ERR_IO, false, "Unable to write concepts for FPA.\n");
+        return false;
+    }
+
+    pmFPAview *thisView = pmFPAviewAlloc(view->nRows); // Copy of input view
+    *thisView = *view;
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        thisView->chip = i;
+        if (!pmChipWritePattern(chip, thisView, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth chip", i);
+            psFree(thisView);
+            return false;
+        }
+    }
+    psFree(thisView);
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+bool pmReadoutReadPattern(pmReadout *ro, psFits *fits)
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    pmCell *cell = ro->parent;          // Cell of interest
+    if (!cell) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "No cell associated with readout.");
+        return false;
+    }
+    pmChip *chip = cell->parent;    // Chip of interest
+    pmFPA *fpa = chip->parent;      // FPA of interest
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // HDU for readout
+    if (!hdu) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "No HDU associated with readout.");
+        return false;
+    }
+
+    if (!psFitsMoveExtName(fits, hdu->extname)) {
+        psError(PS_ERR_IO, false, "Unable to move to pattern correction.");
+        return false;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, fits); // Header
+    if (!header) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to read header for pattern correction.");
+        return false;
+    }
+
+    bool data = true;                  // Did we find any data?
+
+    psMetadataItem *cellCorr = psMetadataLookup(header, PM_PATTERN_CELL_CORRECTION); // Cell pattern correction
+    if (cellCorr) {
+        psMetadataAddItem(ro->analysis, cellCorr, PS_LIST_TAIL, PS_META_REPLACE);
+        data = true;
+    }
+
+    int naxis = psMetadataLookupS32(NULL, header, "NAXIS"); // Number of axes
+    if (naxis > 0) {
+        psImage *rowCorr = psFitsReadImage(fits, psRegionSet(0, 0, 0, 0), 0); // Row pattern correction
+        psMetadataAddImage(ro->analysis, PS_LIST_TAIL, PM_PATTERN_ROW_CORRECTION, PS_META_REPLACE,
+                           "Pattern row correction", rowCorr);
+        psFree(rowCorr);
+        data = true;
+    }
+
+    if (data) {
+        ro->data_exists = true;
+        ro->parent->data_exists = true;
+        ro->parent->parent->data_exists = true;
+    }
+
+    psFree(header);
+
+    return true;
+}
+
+static bool pmCellReadPattern(pmCell *cell, const pmFPAview *view,
+                              pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(cell->readouts, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc(view->nRows); // Copy of input view
+    *thisView = *view;
+
+    // Create a readout if none exists
+    if (!cell->readouts || cell->readouts->n == 0) {
+        pmReadout *readout = pmReadoutAlloc(cell); // New readout
+        psFree(readout);                // Drop reference
+    }
+
+    cell->data_exists = false;
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        thisView->readout = i;
+        if (!pmReadoutReadPattern(readout, file->fits)) {
+            psError(PS_ERR_IO, false, "Unable to read pattern correction.");
+            return false;
+        }
+    }
+    psFree(thisView);
+
+    if (!pmCellReadHeader(cell, file->fits, config)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.");
+        return false;
+    }
+    // load in the concept information for this cell
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        psErrorClear();
+        psWarning("Difficulty reading concepts for cell; attempting to proceed.");
+    }
+
+    return true;
+}
+
+static bool pmChipReadPattern(pmChip *chip, const pmFPAview *view,
+                              pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(chip->cells, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc(view->nRows); // Copy of input view
+    *thisView = *view;
+
+    chip->data_exists = false;
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        thisView->cell = i;
+        pmCellReadPattern(cell, thisView, file, config);
+        if (!cell->data_exists) {
+            continue;
+        }
+        chip->data_exists = true;
+    }
+    psFree(thisView);
+
+    if (!pmChipReadHeader(chip, file->fits, config)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.");
+        return false;
+    }
+    if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, true, NULL)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for chip.\n");
+        return false;
+    }
+
+    return true;
+}
+
+static bool pmFPAReadPattern(pmFPA *fpa, const pmFPAview *view,
+                             pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa->chips, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc(view->nRows); // Copy of input view
+    *thisView = *view;
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        thisView->chip = i;
+        pmChipReadPattern(chip, thisView, file, config);
+    }
+    psFree(thisView);
+
+    if (!pmFPAReadHeader(fpa, file->fits, config)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.");
+        return false;
+    }
+    if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for fpa.\n");
+        return false;
+    }
+
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmPatternWrite(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    pmFPA *fpa = pmFPAfileSuitableFPA(file, view, config, false); // Suitable FPA for writing
+
+    if (view->chip == -1) {
+        if (!pmFPAWritePattern(fpa, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write pattern correction from fpa");
+            psFree(fpa);
+            return false;
+        }
+        psFree(fpa);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing chip == %d (>= chips->n == %ld)", view->chip, fpa->chips->n);
+        psFree(fpa);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        if (!pmChipWritePattern(chip, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write pattern correction from chip");
+            psFree(fpa);
+            return false;
+        }
+        psFree(fpa);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing cell == %d (>= cells->n == %ld)",
+                view->cell, chip->cells->n);
+        psFree(fpa);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        if (!pmCellWritePattern(cell, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write pattern correction from cell");
+            psFree(fpa);
+            return false;
+        }
+        psFree(fpa);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing readout == %d (>= readouts->n == %ld)",
+                view->readout, cell->readouts->n);
+        psFree(fpa);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    if (!pmReadoutWritePattern(readout, file->fits)) {
+        psError(PS_ERR_IO, false, "Failed to write pattern correction from readout %d", view->readout);
+        psFree(fpa);
+        return false;
+    }
+
+    psFree(fpa);
+    return true;
+}
+
+bool pmPatternWritePHU(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    if (file->wrote_phu) {
+        return true;
+    }
+
+    // find the FPA phu
+    pmFPA *fpa = pmFPAfileSuitableFPA(file, view, config, false); // Suitable FPA for writing
+    pmHDU *phu = psMemIncrRefCounter(pmFPAviewThisPHU(view, fpa));
+    psFree(fpa);
+
+    // if there is no PHU, this is a single header+image (extension-less) file. This could be the case for an
+    // input SPLIT set of files being written out as a MEF.  if there is a PHU, write it out as a 'blank'
+    psMetadata *outhead = psMetadataAlloc();
+    if (phu) {
+        psMetadataCopy (outhead, phu->header);
+    }
+    psFree(phu);
+
+    pmConfigConformHeader(outhead, file->format);
+
+    psFitsWriteBlank(file->fits, outhead, "");
+    file->wrote_phu = true;
+
+    psTrace("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
+    psFree(outhead);
+
+    return true;
+}
+
+bool pmPatternRead(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAReadPattern(fpa, view, file, config);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipReadPattern(chip, view, file, config);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellReadPattern(cell, view, file, config);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    return pmReadoutReadPattern(readout, file->fits);
+}
Index: branches/simmosaic_branches/psModules/src/detrend/pmPatternIO.h
===================================================================
--- branches/simmosaic_branches/psModules/src/detrend/pmPatternIO.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/detrend/pmPatternIO.h	(revision 27839)
@@ -0,0 +1,37 @@
+#ifndef PM_PATTERN_IO_H
+#define PM_PATTERN_IO_H
+
+#include <pslib.h>
+
+#include <pmHDU.h>
+#include <pmFPA.h>
+
+/// Write pattern correction within a readout to a FITS file
+bool pmReadoutWritePattern(
+    pmReadout *readout,                 ///< Readout for which to write pattern correction (in analysis MD)
+    psFits *fits                        ///< FITS file to which to write
+    );
+
+
+bool pmReadoutReadPattern(
+    pmReadout *readout,                 ///< Readout for which to read pattern correction (into analysis MD)
+    psFits *fits                        ///< FITS file to which to write
+    );
+
+
+bool pmPatternRead(const pmFPAview *view, ///< View into which to read
+                   pmFPAfile *file, ///< File from which to read
+                   pmConfig *config ///< Configuration
+    );
+
+bool pmPatternWrite(const pmFPAview *view, ///< View from which to write
+                    pmFPAfile *file, ///< File to which to write
+                    pmConfig *config ///< Configuration
+    );
+
+bool pmPatternWritePHU(const pmFPAview *view, // View to PHU
+                       pmFPAfile *file, ///< File to which to write
+                       pmConfig *config ///< Configuration
+    );
+
+#endif
Index: branches/simmosaic_branches/psModules/src/detrend/pmShutterCorrection.c
===================================================================
--- branches/simmosaic_branches/psModules/src/detrend/pmShutterCorrection.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/detrend/pmShutterCorrection.c	(revision 27839)
@@ -64,7 +64,4 @@
                                         // the code (see pmShutterCorrectionDataAlloc).
 
-//int corrRefCount = 1;
-//int corrFitCount = 1;
-//int corrGuessCount = 1;
 
 static void pmShutterCorrectionFree(pmShutterCorrection *pars)
@@ -91,119 +88,7 @@
 pmShutterCorrection *pmShutterCorrectionGuess(const psVector *exptime, const psVector *counts)
 {
-//printf("pmShutterCorrectGuess count: %d\n", corrGuessCount++);
     // NOTE: vectors must be sorted on input.  It is expensive to sort or check this here, but
     // it is easy to arrange by sorting the images before generating these vectors.
 
-    PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
-    PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
-    PS_ASSERT_VECTOR_TYPE(exptime, PS_TYPE_F32, NULL);
-    PS_ASSERT_VECTOR_TYPE(counts, PS_TYPE_F32, NULL);
-    PS_ASSERT_VECTORS_SIZE_EQUAL(exptime, counts, NULL);
-    if (exptime->n <= 2) {
-        printf("Require more than 2 exposures to guess shutter correction.\n");
-        return NULL;
-    }
-
-    long N = exptime->n;                // Number of exposures
-
-    // use interpolation to guess shutter correction parameters given a set of exposures times and normalized
-    // counts (divided by the reference counts for each image)
-
-    pmShutterCorrection *corr = pmShutterCorrectionAlloc(); // Shutter correction, to be returned
-    psPolynomial1D *line = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1); // Straight line, for extrapolation
-
-    // choose the highest exptime point as the guess for the scale:
-    // XXX we could examine the top 2 or 3 values and decide if we
-    // extended exptime enough or median clip.
-    corr->scale = counts->data.F32[N-1];
-
-    // fit a line to the lowest three points and extrapolate to 0.0
-    psVector *tmpX = psVectorAlloc(2, PS_TYPE_F32);
-    psVector *tmpY = psVectorAlloc(2, PS_TYPE_F32);
-
-    long index;
-
-    // Iterate only
-    for (index = 0; !isfinite(exptime->data.F32[index]) && index < N - 1; index++);
-
-    if (index == N - 1) {
-        printf("Not enough good values to guess shutter correction.\n");
-        goto GUESS_ERROR;
-    }
-    tmpX->data.F32[0] = exptime->data.F32[index];
-    tmpY->data.F32[0] = counts->data.F32[index];
-
-    for (index++;
-            (!isfinite(exptime->data.F32[index]) || exptime->data.F32[index] == exptime->data.F32[0]) &&
-            index < N; index++)
-        ; // Iterate only
-    if (index == N) {
-        printf("Exposure times are all identical --- cannot guess shutter correction.\n");
-        goto GUESS_ERROR;
-    }
-    tmpY->data.F32[1] = counts->data.F32[index];
-    tmpX->data.F32[1] = exptime->data.F32[index];
-
-    // fit a line and extrapolate the fit to 0.0
-    if (!psVectorFitPolynomial1D(line, NULL, 0, tmpY, NULL, tmpX)) {
-        printf("Unable to fit for the time offset.\n");
-        goto GUESS_ERROR;
-    }
-    float ratio = psPolynomial1DEval(line, 0.0) / corr->scale;
-
-    // XXX we need a sanity check:
-    // if the mean value of the three points is higher than corr->scale,
-    // then the slope should be negative.
-    // if the mean value of the three points is lower than corr->scale,
-    // then the slope should be positive.
-
-    // find two points bracketing the value counts = A (1 + dTk/dTo) / 2 = corr->scale (1 + ratio) / 2
-    float value = corr->scale * (1 + ratio) / 2.0;
-
-    int Np;                             // Index of the value above (positive side)
-    if (ratio < 1.0) {
-        Np = psVectorBracket(counts, value, true);
-    } else {
-        Np = psVectorBracketDescend(counts, value, true);
-    }
-    int Nm = (Np == 0) ? 1 : Np - 1;    // Index of the value below (negative side)
-
-    tmpX->data.F32[0] = counts->data.F32[Nm];
-    tmpX->data.F32[1] = counts->data.F32[Np];
-    tmpY->data.F32[0] = exptime->data.F32[Nm];
-    tmpY->data.F32[1] = exptime->data.F32[Np];
-
-    // fit a line and extrapolate the fit to counts = A (1 + dTk/dTo) : exptime = dTo
-    if (!psVectorFitPolynomial1D (line, NULL, 0, tmpY, NULL, tmpX)) {
-        printf("Unable to fit for the reference offset.\n");
-        printf("tmpX0: %f, tmpX1: %f, tmpY0: %f, tmpY1: %f\n", tmpX->data.F32[0], tmpX->data.F32[1], tmpY->data.F32[0], tmpY->data.F32[1]);
-        goto GUESS_ERROR;
-    }
-    corr->offref = psPolynomial1DEval(line, value);
-    corr->offset = ratio * corr->offref;
-printf("guess scale, offset, offref: %f, %f, %f\n", corr->scale, corr->offset, corr->offref);
-    psFree(line);
-    psFree(tmpX);
-    psFree(tmpY);
-
-    return corr;
-
-GUESS_ERROR:
-    psFree(tmpX);
-    psFree(tmpY);
-    psFree(line);
-//    psFree(corr);
-//    return NULL;
-    corr->scale = 0.5;
-    corr->offset = 0.5;
-    corr->offref = 0.5;
-    return corr;
-}
-
-// linear fit to the counts and exptime, given a value for offref
-pmShutterCorrection *pmShutterCorrectionLinFit(const psVector *exptime, const psVector *counts,
-                                               const psVector *cntError, const psVector *mask, float offref,
-                                               int nIter, float rej)
-{
     PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
     PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
@@ -216,81 +101,103 @@
         return NULL;
     }
-    if (cntError) {
-        PS_ASSERT_VECTOR_TYPE(cntError, PS_TYPE_F32, NULL);
-        PS_ASSERT_VECTORS_SIZE_EQUAL(counts, cntError, NULL);
-    }
-    PS_ASSERT_FLOAT_LARGER_THAN(offref, 0.0, NULL);
-
-    // this step is identical for all pixels: do it once and save?
-    psVector *x = psVectorAlloc(exptime->n, PS_TYPE_F32);
-    psVector *y = psVectorAlloc(exptime->n, PS_TYPE_F32);
-
-    for (long i = 0; i < exptime->n; i++) {
-        // Should be safe (if expensive) to stick NaNs in --- the fitter deals with them
-        float value = 1.0 / (exptime->data.F32[i] + offref);
-        x->data.F32[i] = exptime->data.F32[i] * value;
-        y->data.F32[i] = value;
-    }
-
-    psPolynomial2D *line = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, 1, 1);
-
-    // mask out the terms we will not fit
-    line->coeffMask[0][0] = PS_POLY_MASK_SET;
-    line->coeffMask[1][1] = PS_POLY_MASK_SET;
-    line->coeff[0][0] = 0;
-    line->coeff[1][1] = 0;
-
-    // the stats structure determines how the clipping statistic is measured
-    // too few points to use the robust analysis method
-    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
-    stats->clipSigma = rej;
-    stats->clipIter = nIter;
-
-    if (!psVectorClipFitPolynomial2D(line, stats, mask, 0xff, counts, cntError, x, y)) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit shutter correction.\n");
-        psFree(stats);
-        psFree(x);
-        psFree(y);
-        psFree(line);
-        return NULL;
-    }
-
-    pmShutterCorrection *corr = pmShutterCorrectionAlloc();
-    corr->offref = offref;
-    corr->scale  = line->coeff[1][0];
-    corr->offset = line->coeff[0][1] / line->coeff[1][0];
-    corr->num = stats->clippedNvalues;
-    corr->stdev = stats->clippedStdev;
-
-    psFree(stats);
-    psFree(x);
-    psFree(y);
+
+    long N = exptime->n;                // Number of exposures
+
+    // use interpolation to guess shutter correction parameters given a set of exposures times and normalized
+    // counts (divided by the reference counts for each image)
+
+    pmShutterCorrection *corr = pmShutterCorrectionAlloc(); // Shutter correction, to be returned
+    psPolynomial1D *line = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1); // Straight line, for extrapolation
+
+    // choose the highest exptime point as the guess for the scale:
+    // XXX we could examine the top 2 or 3 values and decide if we
+    // extended exptime enough or median clip.
+    corr->scale = counts->data.F32[N-1];
+
+    // fit a line to the lowest three points and extrapolate to 0.0
+    psVector *tmpX = psVectorAlloc(2, PS_TYPE_F32);
+    psVector *tmpY = psVectorAlloc(2, PS_TYPE_F32);
+
+    long index;
+
+    // Iterate only
+    for (index = 0; !isfinite(exptime->data.F32[index]) && index < N - 1; index++);
+
+    if (index == N - 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Not enough good values to guess shutter correction.\n");
+        goto GUESS_ERROR;
+    }
+    tmpX->data.F32[0] = exptime->data.F32[index];
+    tmpY->data.F32[0] = counts->data.F32[index];
+
+    for (index++;
+            (!isfinite(exptime->data.F32[index]) || exptime->data.F32[index] == exptime->data.F32[0]) &&
+            index < N; index++)
+        ; // Iterate only
+    if (index == N) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Exposure times are all identical --- cannot guess shutter correction.\n");
+        goto GUESS_ERROR;
+    }
+    tmpY->data.F32[1] = counts->data.F32[index];
+    tmpX->data.F32[1] = exptime->data.F32[index];
+
+    // fit a line and extrapolate the fit to 0.0
+    if (!psVectorFitPolynomial1D(line, NULL, 0, tmpY, NULL, tmpX)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit for the time offset.\n");
+        goto GUESS_ERROR;
+    }
+    float ratio = psPolynomial1DEval(line, 0.0) / corr->scale;
+
+    // XXX we need a sanity check:
+    // if the mean value of the three points is higher than corr->scale,
+    // then the slope should be negative.
+    // if the mean value of the three points is lower than corr->scale,
+    // then the slope should be positive.
+
+    // find two points bracketing the value counts = A (1 + dTk/dTo) / 2 = corr->scale (1 + ratio) / 2
+    float value = corr->scale * (1 + ratio) / 2.0;
+
+    int Np;                             // Index of the value above (positive side)
+    if (ratio < 1.0) {
+        Np = psVectorBracket(counts, value, true);
+    } else {
+        Np = psVectorBracketDescend(counts, value, true);
+    }
+    int Nm = (Np == 0) ? 1 : Np - 1;    // Index of the value below (negative side)
+
+    tmpX->data.F32[0] = counts->data.F32[Nm];
+    tmpX->data.F32[1] = counts->data.F32[Np];
+    tmpY->data.F32[0] = exptime->data.F32[Nm];
+    tmpY->data.F32[1] = exptime->data.F32[Np];
+
+    // fit a line and extrapolate the fit to counts = A (1 + dTk/dTo) : exptime = dTo
+    if (!psVectorFitPolynomial1D (line, NULL, 0, tmpY, NULL, tmpX)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit for the reference offset.\n");
+        goto GUESS_ERROR;
+    }
+    corr->offref = psPolynomial1DEval(line, value);
+    corr->offset = ratio * corr->offref;
+
     psFree(line);
+    psFree(tmpX);
+    psFree(tmpY);
 
     return corr;
-}
-
-static psF32 pmShutterCorrectionModel(psVector *deriv, const psVector *params, const psVector *x)
-{
-    // This is in a tight loop, so we won't assert here.
-
-    psF32 A = params->data.F32[0];
-    psF32 p = x->data.F32[0] + params->data.F32[1];
-    psF32 q = 1.0 / (x->data.F32[0] + params->data.F32[2]);
-    psF32 f = A * p * q;
-
-    if (deriv) {
-        deriv->data.F32[0] = p * q;
-        deriv->data.F32[1] = A * q;
-        deriv->data.F32[2] = - f * q;
-    }
-    return f;
-}
-
-// non-linear fit to the counts and exptime, given a guess for the three parameters
-pmShutterCorrection *pmShutterCorrectionFullFit(const psVector *exptime, const psVector *counts,
-                                                const psVector *cntError, const pmShutterCorrection *guess)
-{
-//printf("pmShutterCorrectionFullFit count: %d\n", corrFitCount++);
+
+GUESS_ERROR:
+    psFree(tmpX);
+    psFree(tmpY);
+    psFree(line);
+    psFree(corr);
+    return NULL;
+}
+
+// linear fit to the counts and exptime, given a value for offref
+pmShutterCorrection *pmShutterCorrectionLinFit(const psVector *exptime, const psVector *counts,
+                                               const psVector *cntError, const psVector *mask, float offref,
+                                               int nIter, float rej)
+{
     PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
     PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
@@ -307,4 +214,90 @@
         PS_ASSERT_VECTORS_SIZE_EQUAL(counts, cntError, NULL);
     }
+    PS_ASSERT_FLOAT_LARGER_THAN(offref, 0.0, NULL);
+
+    // this step is identical for all pixels: do it once and save?
+    psVector *x = psVectorAlloc(exptime->n, PS_TYPE_F32);
+    psVector *y = psVectorAlloc(exptime->n, PS_TYPE_F32);
+
+    for (long i = 0; i < exptime->n; i++) {
+        // Should be safe (if expensive) to stick NaNs in --- the fitter deals with them
+        float value = 1.0 / (exptime->data.F32[i] + offref);
+        x->data.F32[i] = exptime->data.F32[i] * value;
+        y->data.F32[i] = value;
+    }
+
+    psPolynomial2D *line = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, 1, 1);
+
+    // mask out the terms we will not fit
+    line->coeffMask[0][0] = PS_POLY_MASK_SET;
+    line->coeffMask[1][1] = PS_POLY_MASK_SET;
+    line->coeff[0][0] = 0;
+    line->coeff[1][1] = 0;
+
+    // the stats structure determines how the clipping statistic is measured
+    // too few points to use the robust analysis method
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    stats->clipSigma = rej;
+    stats->clipIter = nIter;
+
+    if (!psVectorClipFitPolynomial2D(line, stats, mask, 0xff, counts, cntError, x, y)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit shutter correction.\n");
+        psFree(stats);
+        psFree(x);
+        psFree(y);
+        psFree(line);
+        return NULL;
+    }
+
+    pmShutterCorrection *corr = pmShutterCorrectionAlloc();
+    corr->offref = offref;
+    corr->scale  = line->coeff[1][0];
+    corr->offset = line->coeff[0][1] / line->coeff[1][0];
+    corr->num = stats->clippedNvalues;
+    corr->stdev = stats->clippedStdev;
+
+    psFree(stats);
+    psFree(x);
+    psFree(y);
+    psFree(line);
+
+    return corr;
+}
+
+static psF32 pmShutterCorrectionModel(psVector *deriv, const psVector *params, const psVector *x)
+{
+    // This is in a tight loop, so we won't assert here.
+
+    psF32 A = params->data.F32[0];
+    psF32 p = x->data.F32[0] + params->data.F32[1];
+    psF32 q = 1.0 / (x->data.F32[0] + params->data.F32[2]);
+    psF32 f = A * p * q;
+
+    if (deriv) {
+        deriv->data.F32[0] = p * q;
+        deriv->data.F32[1] = A * q;
+        deriv->data.F32[2] = - f * q;
+    }
+    return f;
+}
+
+// non-linear fit to the counts and exptime, given a guess for the three parameters
+pmShutterCorrection *pmShutterCorrectionFullFit(const psVector *exptime, const psVector *counts,
+                                                const psVector *cntError, const pmShutterCorrection *guess)
+{
+    PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
+    PS_ASSERT_VECTOR_TYPE(exptime, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(counts, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(exptime, counts, NULL);
+    if (exptime->n <= 2) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Require more than 2 exposures to guess shutter correction.\n");
+        return NULL;
+    }
+    if (cntError) {
+        PS_ASSERT_VECTOR_TYPE(cntError, PS_TYPE_F32, NULL);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(counts, cntError, NULL);
+    }
     PS_ASSERT_PTR_NON_NULL(guess, NULL);
 
@@ -351,7 +344,4 @@
     psVector *resid = psVectorAlloc (exptime->n, PS_TYPE_F32);
     for (int i = 0; i < exptime->n; i++) {
-float divTest = exptime->data.F32[i] + corr->offref;
-if(divTest == 0) {printf("division by 0\n");}
-
         float fitCounts = corr->scale * (exptime->data.F32[i] + corr->offset) / (exptime->data.F32[i] + corr->offref);
         resid->data.F32[i] = counts->data.F32[i] - fitCounts;
@@ -373,5 +363,4 @@
     if (rawStats->sampleStdev / resStats->sampleStdev < 1.5) corr->valid = false;
     if (isnan(rawStats->sampleStdev) || isnan(resStats->sampleStdev)) corr->valid = false;
-printf("corr scale, offset, offref: %f, %f, %f\n", corr->scale, corr->offset, corr->offref);
 
     psFree (rawStats);
@@ -994,5 +983,4 @@
 float pmShutterCorrectionReference(pmShutterCorrectionData *data)
 {
-//printf("pmShutterCorrectionReference: count %d\n", corrRefCount++);
     PS_ASSERT_PTR_NON_NULL(data, NAN);
     PS_ASSERT_INT_POSITIVE(data->num, NAN);
@@ -1025,15 +1013,17 @@
         pmShutterCorrection *guess = pmShutterCorrectionGuess(newtimes, newcounts); // Guess at correction
         psTrace("psModules.detrend", 5, "Shutter correction guess: scale: %f, offset: %f, offref: %f\n", guess->scale, guess->offset, guess->offref);
-//printf("Reference guess scale, offset, offref: %f, %f, %f\n", guess->scale, guess->offset, guess->offref);
 
         pmShutterCorrection *corr = pmShutterCorrectionFullFit(newtimes, newcounts, newerrors, guess); // The actual correction
-        psTrace("psModules.detrend", 5, "Shutter correction fit: scale: %f, offset: %f, offref: %f\n", corr->scale, corr->offset, corr->offref);
-
-//printf("Reference corr scale, offset, offref: %f, %f, %f\n", corr->scale, corr->offset, corr->offref);
-        if (corr && isfinite(corr->offref) && corr->valid) {
-            psTrace("psModules.detrend", 5, "Sample reference value: %f\n", corr->offref);
-            meanRef += corr->offref;
-            numGood++;
-        }
+
+        if (corr) {
+	    psTrace("psModules.detrend", 5, "Shutter correction fit: scale: %f, offset: %f, offref: %f\n", corr->scale, corr->offset, corr->offref);
+	    if (isfinite(corr->offref) && corr->valid) {
+		psTrace("psModules.detrend", 5, "Sample reference value: %f\n", corr->offref);
+		meanRef += corr->offref;
+		numGood++;
+	    }
+        } else {
+	    psTrace("psModules.detrend", 5, "failed Shutter correction fit\n");
+	}
 
         psFree(corr);
@@ -1146,5 +1136,5 @@
     psImage *patternImage = pattern->image; // Illumination pattern
 
-    int num = data->num;                // NmaxInputRowsmaxInputRowsumber of images
+    int num = data->num;                // Number of images
     psVector *counts = psVectorAlloc(num, PS_TYPE_F32); // Counts in each image
     psVector *errors = psVectorAlloc(num, PS_TYPE_F32); // Counts in each image
Index: branches/simmosaic_branches/psModules/src/extras/Makefile.am
===================================================================
--- branches/simmosaic_branches/psModules/src/extras/Makefile.am	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/extras/Makefile.am	(revision 27839)
@@ -8,5 +8,6 @@
 	psIOBuffer.c \
 	pmKapaPlots.c \
-	pmVisual.c
+	pmVisual.c \
+	ippStages.c
 
 pkginclude_HEADERS = \
@@ -15,5 +16,7 @@
 	psIOBuffer.h \
 	pmKapaPlots.h \
-	pmVisual.h
+	pmVisual.h \
+	ippDiffMode.h \
+	ippStages.h
 
 CLEANFILES = *~
Index: branches/simmosaic_branches/psModules/src/extras/ippDiffMode.h
===================================================================
--- branches/simmosaic_branches/psModules/src/extras/ippDiffMode.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/extras/ippDiffMode.h	(revision 27839)
@@ -0,0 +1,20 @@
+/* @file ippDiffMode.h
+ * @brief some macro defintions for the stages of the pipeline
+ * @author Bill Sweeney, IfA
+ *
+ * Copyright 2010 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef IPP_DIFF_MODE_H
+#define IPP_DIFF_MODE_H
+
+typedef enum {
+    IPP_DIFF_MODE_UNDEFINED   = 0,
+    IPP_DIFF_MODE_WARP_WARP   = 1,
+    IPP_DIFF_MODE_WARP_STACK  = 2,
+    IPP_DIFF_MODE_STACK_WARP  = 3,
+    IPP_DIFF_MODE_STACK_STACK = 4
+} ippDiffMode;
+
+
+#endif
Index: branches/simmosaic_branches/psModules/src/extras/ippStages.c
===================================================================
--- branches/simmosaic_branches/psModules/src/extras/ippStages.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/extras/ippStages.c	(revision 27839)
@@ -0,0 +1,31 @@
+/** Functions that describe the various stages of the IPP
+ *  @author Bill Sweeney, IfA
+ **/
+
+#include <pslib.h>
+#include "ippStages.h"
+#include <string.h>
+
+ippStage ippStringToStage(const psString stageString)
+{
+    PS_ASSERT_PTR_NON_NULL(stageString, IPP_STAGE_NONE);
+
+    if (!strcmp(stageString, "raw")) {
+        return IPP_STAGE_RAW;
+    } else if (!strcmp(stageString, "chip")) {
+        return IPP_STAGE_CHIP;
+    } else if (!strcmp(stageString, "camera")) {
+        return IPP_STAGE_CAMERA;
+    } else if (!strcmp(stageString, "warp")) {
+        return IPP_STAGE_WARP;
+    } else if (!strcmp(stageString, "fake")) {
+        return IPP_STAGE_FAKE;
+    } else if (!strcmp(stageString, "diff")) {
+        return IPP_STAGE_DIFF;
+    } else if (!strcmp(stageString, "stack")) {
+        return IPP_STAGE_STACK;
+    } else {
+        psError(PS_ERR_PROGRAMMING, true, "%s is not a valid IPP stage", stageString);
+        return IPP_STAGE_NONE;
+    }
+}
Index: branches/simmosaic_branches/psModules/src/extras/ippStages.h
===================================================================
--- branches/simmosaic_branches/psModules/src/extras/ippStages.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/extras/ippStages.h	(revision 27839)
@@ -0,0 +1,27 @@
+/* @file ippStages.h
+ * @brief some macro defintions for the stages of the pipeline
+ * @author Bill Sweeney, IfA
+ *
+ * Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef IPP_STAGES_H
+#define IPP_STAGES_H
+
+typedef enum {
+    IPP_STAGE_NONE = -1,
+    IPP_STAGE_RAW = 0,
+    IPP_STAGE_CHIP,
+    IPP_STAGE_CAMERA,
+    IPP_STAGE_FAKE,
+    IPP_STAGE_WARP,
+    IPP_STAGE_DIFF,
+    IPP_STAGE_STACK,
+} ippStage;
+
+/** return the ippStage represented by a string
+ * @return the corresponding value or IPP_STAGE_NONE if invalid
+ */
+ippStage ippStringToStage(const psString stageString);
+
+#endif
Index: branches/simmosaic_branches/psModules/src/extras/pmVisual.c
===================================================================
--- branches/simmosaic_branches/psModules/src/extras/pmVisual.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/extras/pmVisual.c	(revision 27839)
@@ -22,4 +22,7 @@
 #include "pmSubtractionStamps.h"
 #include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmPSFtry.h"
+#include "pmSource.h"
 #include "pmFPAExtent.h"
 
@@ -86,9 +89,13 @@
 {
     char key[10];
-    fprintf (stdout, "[c]ontinue? [s]kip the rest of these plots? [a]bort all visual plots?");
+    if (plotFlag) {
+	fprintf (stdout, "[c]ontinue? [s]kip the rest of these plots? [a]bort all visual plots? (c) ");
+    } else {
+	fprintf (stdout, "[c]ontinue? [a]bort all visual plots? (c) ");
+    }
     if (!fgets(key, 8, stdin)) {
         psWarning("Unable to read option");
     }
-    if (key[0] == 's') {
+    if (plotFlag && (key[0] == 's')) {
         *plotFlag = false;
     }
Index: branches/simmosaic_branches/psModules/src/imcombine/Makefile.am
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/Makefile.am	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/Makefile.am	(revision 27839)
@@ -13,4 +13,6 @@
 	pmSubtractionIO.c	\
 	pmSubtractionKernels.c	\
+	pmSubtractionHermitian.c	\
+	pmSubtractionDeconvolve.c	\
 	pmSubtractionMask.c	\
 	pmSubtractionMatch.c	\
@@ -32,4 +34,6 @@
 	pmSubtractionIO.h	\
 	pmSubtractionKernels.h	\
+	pmSubtractionHermitian.h	\
+	pmSubtractionDeconvolve.h	\
 	pmSubtractionMask.h	\
 	pmSubtractionMatch.h	\
Index: branches/simmosaic_branches/psModules/src/imcombine/pmImageCombine.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmImageCombine.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmImageCombine.c	(revision 27839)
@@ -118,6 +118,6 @@
             psImage *mask = masks->data[i]; // Mask of interest
             pixelMasks->data.PS_TYPE_VECTOR_MASK_DATA[i] = (mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskVal);
-        }        
-	// Set the pixel error data, if necessary
+        }
+        // Set the pixel error data, if necessary
         if (errors) {
             psImage *error = errors->data[i]; // Error image of interest
@@ -132,8 +132,8 @@
         // Combine all the pixels, using the specified stat.
         if (!psVectorStats(stats, pixelData, pixelErrors, pixelMasks, 0xff)) {
-	    psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
-	    return false;
-	}
-	if (isnan(stats->sampleMean)) {
+            psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
+            return false;
+        }
+        if (isnan(stats->sampleMean)) {
             combine->data.F32[y][x] = NAN;
             psFree(buffer);
@@ -141,5 +141,5 @@
         }
         float combinedPixel = stats->sampleMean; // Value of the combination
-	
+
         if (iter == 0) {
             // Save the value produced with no rejection, since it may be useful later
@@ -369,5 +369,5 @@
     psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
     if (!psVectorStats(stats, pixels, NULL, mask, 1)) {
-	psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
+        psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
     }
     float median = stats->sampleMedian;
@@ -640,6 +640,6 @@
                                               (psPlaneTransform * )outToIn->data[otherImg],
                                               outCoords);
-                        psS32 xPix = (int)(inCoords->x + 0.5);
-                        psS32 yPix = (int)(inCoords->y + 0.5);
+                        psS32 xPix = (int)(inCoords->x - 0.5);
+                        psS32 yPix = (int)(inCoords->y - 0.5);
                         if ((xPix >= 0) && (xPix <= ((psImage*)(images->data[otherImg]))->numCols - 1) &&
                                 (yPix >= 0) && (yPix <= ((psImage*)(images->data[otherImg]))->numRows - 1)) {
Index: branches/simmosaic_branches/psModules/src/imcombine/pmPSFEnvelope.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmPSFEnvelope.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmPSFEnvelope.c	(revision 27839)
@@ -34,4 +34,5 @@
 
 // #define TESTING                         // Enable test output
+// #define PEAK_NORM                       // Normalise peaks?
 #define PEAK_FLUX 1.0e4                 // Peak flux for each source
 #define SKY_VALUE 0.0e0                 // Sky value for fake image
@@ -64,5 +65,6 @@
                      int radius,        // Radius of each PSF
                      const char *modelName,// Name of PSF model to use
-                     int xOrder, int yOrder // Order for PSF variation fit
+                     int xOrder, int yOrder, // Order for PSF variation fit
+                     psImageMaskType maskVal
                      )
 {
@@ -122,8 +124,68 @@
             continue;
         }
+
+        if (psTraceGetLevel("psModules.imcombine") >= 1) {
+            psString string = NULL;     // String with values
+            psStringAppend(&string, "PSF %d: ", i);
+            float x = numCols / 2.0, y = numRows / 2.0; // Coordinates of interest
+            for (int j = 4; j < psf->params->n; j++) {
+                pmTrend2D *trend = psf->params->data[j]; // Trend of interest
+                double val = pmTrend2DEval(trend, x, y);
+                double err;
+                switch (trend->mode) {
+                  case PM_TREND_POLY_ORD:
+                  case PM_TREND_POLY_CHEB:
+                    err = NAN;
+                    break;
+                  case PM_TREND_MAP:
+                    err = psImageUnbinPixel(x, y, trend->map->error, trend->map->binning);
+                    break;
+                  default:
+                    psAbort("Unsupported mode: %x", trend->mode);
+                }
+                psStringAppend(&string, "%lf %lf   ", val, err);
+            }
+            psTrace("psModules.imcombine", 1, "%s\n", string);
+            psFree(string);
+        }
+
+        // Test PSF
+        {
+            bool goodPSF = false;       // Is there a PSF that we can use?
+            int xNum = PS_MAX(psf->trendNx, 1), yNum = PS_MAX(psf->trendNy, 1); // Number of positions to check
+            for (int j = 0; j < yNum && !goodPSF; j++) {
+                float y = ((float)j + 0.5) / (float)yNum * numRows; // Position on image
+                for (int i = 0; i < xNum && !goodPSF; i++) {
+                    float x = ((float)i + 0.5) / (float)xNum * numCols; // Position on image
+                    pmModelClassSetLimits(PM_MODEL_LIMITS_IGNORE);
+                    pmModel *model = pmModelFromPSFforXY(psf, x, y, PEAK_FLUX); // Test model
+                    if (!model) {
+                        continue;
+                    }
+                    model->modelSetLimits(PM_MODEL_LIMITS_MODERATE);
+                    bool limits = true; // Model within limits?
+                    for (int j = 0; j < model->params->n && limits; j++) {
+                        if (!model->modelLimits(PS_MINIMIZE_PARAM_MIN, j, model->params->data.F32, NULL) ||
+                            !model->modelLimits(PS_MINIMIZE_PARAM_MAX, j, model->params->data.F32, NULL)) {
+                            limits = false;
+                        }
+                    }
+                    psFree(model);
+                    if (limits) {
+                        goodPSF = true;
+                    }
+                }
+            }
+            if (!goodPSF) {
+                psWarning("PSF %d is completely bad --- not including in envelope calculation.", i);
+                continue;
+            }
+        }
+
         pmResiduals *resid = psf->residuals;// PSF residuals
         psf->residuals = NULL;
-        if (!pmReadoutFakeFromSources(fakeRO, fakeSize, fakeSize, fakes, xOffset, yOffset, psf,
-                                      NAN, radius, true, true)) {
+        pmModelClassSetLimits(PM_MODEL_LIMITS_MODERATE);
+        if (!pmReadoutFakeFromSources(fakeRO, fakeSize, fakeSize, fakes, 0, xOffset, yOffset, psf,
+                                      NAN, radius, true, false)) {
             psError(PS_ERR_UNKNOWN, false, "Unable to generate fake readout.");
             psFree(envelope);
@@ -144,13 +206,28 @@
             float y = source->peak->yf + yOffset->data.S32[j]; // y coordinate of source
 
-            double flux = fakeRO->image->data.F32[(int)y][(int)x];
+#ifdef PEAK_NORM
+            // Perhaps I'm being paranoid, but specify a range to check
+            int uMax = PS_MIN(x + radius, numCols - 1), uMin = PS_MAX(x - radius, 0);
+            int vMax = PS_MIN(y + radius, numRows - 1), vMin = PS_MAX(y - radius, 0);
+
+            double flux = -INFINITY;    // Peak flux
+            for (int v = vMin; v <= vMax; v++) {
+                for (int u = uMin; u <= uMax; u++) {
+                    if (fakeRO->image->data.F32[v][u] > flux) {
+                        flux = fakeRO->image->data.F32[v][u];
+                    }
+                }
+            }
             if (!isfinite(flux) || flux < 0) {
                 continue;
             }
             float norm = PEAK_FLUX / flux; // Normalisation for source
+#endif
             psRegion region = psRegionSet(x - radius, x + radius, y - radius, y + radius); // PSF region
             psImage *subImage = psImageSubset(fakeRO->image, region); // Subimage of fake PSF
             psImage *subEnv = psImageSubset(envelope, region); // Subimage of envelope
+#ifdef PEAK_NORM
             psBinaryOp(subImage, subImage, "*", psScalarAlloc(norm, PS_TYPE_F32));
+#endif
             psBinaryOp(subEnv, subEnv, "MAX", subImage);
             psFree(subImage);
@@ -163,4 +240,5 @@
             }
             float srcRadius = model->modelRadius(model->params, PS_SQR(VARIANCE_VAL)); // Radius for source
+            psFree(model);
             if (srcRadius == 0) {
                 continue;
@@ -298,6 +376,7 @@
         }
 
-	// measure the source moments: tophat windowing, no pixel S/N cutoff
-        if (!pmSourceMoments(source, maxRadius, 0.0, 1.0)) {
+        // measure the source moments: tophat windowing, no pixel S/N cutoff
+        // XXX probably should be passing the maskVal to this function so we can pass it along here...
+        if (!pmSourceMoments(source, maxRadius, 0.0, 1.0, maskVal)) {
             // Can't do anything about it; limp along as best we can
             psErrorClear();
@@ -320,5 +399,6 @@
     options->poissonErrorsParams = true;
     options->stats = psStatsAlloc(PSF_STATS);
-    options->radius = maxRadius;
+    options->fitRadius = maxRadius;
+    options->apRadius = maxRadius; // XXX need to decide if aperture mags need a different radius
     options->psfTrendMode = PM_TREND_MAP;
     options->psfTrendNx = xOrder;
@@ -331,4 +411,5 @@
 
     pmSourceFitModelInit(SOURCE_FIT_ITERATIONS, 0.01, VARIANCE_VAL, true);
+    pmModelClassSetLimits(PM_MODEL_LIMITS_STRICT); // Important for getting a good stack target PSF
 
     pmPSFtry *try = pmPSFtryModel(fakes, modelName, options, 0, 0xff);
@@ -343,4 +424,29 @@
     pmPSF *psf = psMemIncrRefCounter(try->psf); // Output PSF
     psFree(try);
+
+    if (psTraceGetLevel("psModules.imcombine") >= 1) {
+        psString string = NULL;     // String with values
+        psStringAppend(&string, "Envelope PSF: ");
+        float x = numCols / 2.0, y = numRows / 2.0; // Coordinates of interest
+        for (int j = 4; j < psf->params->n; j++) {
+            pmTrend2D *trend = psf->params->data[j]; // Trend of interest
+            double val = pmTrend2DEval(trend, x, y);
+            double err;
+            switch (trend->mode) {
+              case PM_TREND_POLY_ORD:
+              case PM_TREND_POLY_CHEB:
+                err = NAN;
+                break;
+              case PM_TREND_MAP:
+                err = psImageUnbinPixel(x, y, trend->map->error, trend->map->binning);
+                break;
+              default:
+                psAbort("Unsupported mode: %x", trend->mode);
+            }
+            psStringAppend(&string, "%lf %lf   ", val, err);
+        }
+        psTrace("psModules.imcombine", 1, "%s\n", string);
+        psFree(string);
+    }
 
 #ifdef TESTING
@@ -357,5 +463,5 @@
 
         pmReadout *generated = pmReadoutAlloc(NULL); // Generated image
-        pmReadoutFakeFromSources(generated, numCols, numRows, fakes, NULL, NULL, psf, NAN, radius,
+        pmReadoutFakeFromSources(generated, numCols, numRows, fakes, 0, NULL, NULL, psf, NAN, radius,
                                  false, true);
         {
Index: branches/simmosaic_branches/psModules/src/imcombine/pmPSFEnvelope.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmPSFEnvelope.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmPSFEnvelope.h	(revision 27839)
@@ -17,5 +17,6 @@
                      int radius,        // Radius of each PSF
                      const char *modelName, // Name of PSF model to use
-                     int xOrder, int yOrder // Order for PSF variation
+                     int xOrder, int yOrder, // Order for PSF variation
+		     psImageMaskType maskVal
     );
 
Index: branches/simmosaic_branches/psModules/src/imcombine/pmReadoutCombine.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmReadoutCombine.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmReadoutCombine.c	(revision 27839)
@@ -281,4 +281,7 @@
     #endif
 
+    // set up windows for visualization (if selected)
+    pmReadoutCombineVisualInit();
+
     // Dereference output products
     psF32 **outputImage  = output->image->data.F32; // Output image
@@ -397,4 +400,8 @@
 	    outputImage[yOut][xOut] = psStatsGetValue(stats, params->combine);
 
+	    if (!isfinite(outputImage[yOut][xOut])) {
+		pmReadoutCombineVisualPixels(pixels, mask, outputImage[yOut][xOut]);
+	    }
+
 	    if (isnan(outputImage[yOut][xOut])) {
                 outputImage[yOut][xOut] = NAN;
@@ -434,2 +441,106 @@
 }
 
+#if (HAVE_KAPA)
+#include <kapa.h>
+#include "pmKapaPlots.h"
+#include "pmVisual.h"
+
+static int kapa = -1;
+static bool plotFlag = true;
+
+// this init function only gets the ordinates for the first readout...
+bool pmReadoutCombineVisualInit(void) {
+    
+    if (!pmVisualIsVisual()) return true;
+
+    // skip if we have already opened the windows (or if none are requested...)
+    if (kapa != -1) return true;
+
+    pmVisualInitWindow(&kapa, "ppmerge");
+    return true;
+}
+
+bool pmReadoutCombineVisualPixels(psVector *pixels, psVector *mask, float mean) {
+
+    Graphdata graphdata;
+    float xline[2], yline[2];
+    
+    if (!pmVisualIsVisual()) return true;
+
+    if (!plotFlag) return true;
+
+    KapaInitGraph(&graphdata);
+
+    psVector *xAll = psVectorAlloc(pixels->n, PS_TYPE_F32);
+    psVector *xSub = psVectorAlloc(pixels->n, PS_TYPE_F32);
+    psVector *ySub = psVectorAlloc(pixels->n, PS_TYPE_F32);
+
+    // generate vectors of the unmasked values
+    int nSub = 0;
+    for (int j = 0; j < pixels->n; j++) {
+	xAll->data.F32[j] = j;
+	if (mask && mask->data.PS_TYPE_VECTOR_MASK_DATA[j]) continue;
+	xSub->data.F32[nSub] = j;
+	ySub->data.F32[nSub] = pixels->data.F32[j];
+	nSub ++;
+    }
+    xSub->n = ySub->n = nSub;
+    xAll->n = pixels->n;
+	
+    xline[0] = 0;
+    xline[1] = pixels->n;
+    yline[0] = mean;
+    yline[1] = mean;
+
+    // plot the unmasked values
+    pmVisualScaleGraphdata (&graphdata, xAll, pixels, false);
+    KapaSetGraphData(kapa, &graphdata);
+    KapaSetLimits(kapa, &graphdata);
+    KapaClearPlots (kapa);
+
+    KapaSetFont (kapa, "courier", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "ordinate", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "pixel values", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName("black");
+    graphdata.style = 2;
+    graphdata.ptype = 2;
+    KapaPrepPlot  (kapa, xSub->n, &graphdata);
+    KapaPlotVector(kapa, xSub->n, xSub->data.F32, "x");
+    KapaPlotVector(kapa, xSub->n, ySub->data.F32, "y");
+
+    graphdata.color = KapaColorByName("red");
+    graphdata.style = 2;
+    graphdata.ptype = 7;
+    KapaPrepPlot  (kapa, xAll->n, &graphdata);
+    KapaPlotVector(kapa, xAll->n, xAll->data.F32, "x");
+    KapaPlotVector(kapa, xAll->n, pixels->data.F32, "y");
+
+    graphdata.color = KapaColorByName("blue");
+    graphdata.style = 0;
+    graphdata.ptype = 7;
+    KapaPrepPlot  (kapa, 2, &graphdata);
+    KapaPlotVector(kapa, 2, xline, "x");
+    KapaPlotVector(kapa, 2, yline, "y");
+
+    pmVisualAskUser (&plotFlag);
+    return true;
+}
+
+bool pmReadoutCombineVisualCleanup(void) {
+
+    if (!pmVisualIsVisual()) return true;
+    if (kapa == -1) return true;
+
+    KapaClose(kapa);
+    return true;
+}
+
+# else
+
+bool pmReadoutCombineVisualInit(void) { return true; }
+bool pmReadoutCombineVisualPixels(psVector *pixels, psVector *mask, float mean) { return true; }
+bool pmReadoutCombineVisualCleanup(void) { return true; }
+
+# endif
Index: branches/simmosaic_branches/psModules/src/imcombine/pmReadoutCombine.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmReadoutCombine.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmReadoutCombine.h	(revision 27839)
@@ -47,4 +47,8 @@
                      );
 
+bool pmReadoutCombineVisualInit(void);
+bool pmReadoutCombineVisualPixels(psVector *pixels, psVector *mask, float mean);
+bool pmReadoutCombineVisualCleanup(void);
+
 /// @}
 #endif
Index: branches/simmosaic_branches/psModules/src/imcombine/pmStack.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmStack.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmStack.c	(revision 27839)
@@ -30,10 +30,12 @@
 #define PIXEL_LIST_BUFFER 100           // Number of entries to add to pixel list at a time
 #define PIXEL_MAP_BUFFER 2              // Number of entries to add to pixel map at a time
-#define ADD_VARIANCE                    // Allow additional variance (besides variance factor)?
+//#define ADD_VARIANCE                    // Allow additional variance (besides variance factor)?
 #define NUM_DIRECT_STDEV 5              // For less than this number of values, measure stdev directly
 
+
 //#define TESTING                         // Enable test output
-//#define TEST_X 1085                     // x coordinate to examine
-//#define TEST_Y 3371                     // y coordinate to examine
+//#define TEST_X 843-1                     // x coordinate to examine
+//#define TEST_Y 813-1                     // y coordinate to examine
+//#define TEST_RADIUS 0                    // Radius to examine
 
 
@@ -42,9 +44,10 @@
 typedef struct {
     psVector *pixels;                   // Pixel values
-    psVector *masks;                    // Pixel masks
     psVector *variances;                // Pixel variances
     psVector *weights;                  // Pixel weightings
+    psVector *exps;                     // Pixel exposures
     psVector *sources;                  // Pixel sources (which image did they come from?)
     psVector *limits;                   // Rejection limits
+    psVector *suspects;                 // Pixel is suspect?
     psVector *sort;                     // Buffer for sorting (to get a robust estimator of the standard dev)
 } combineBuffer;
@@ -53,9 +56,10 @@
 {
     psFree(buffer->pixels);
-    psFree(buffer->masks);
     psFree(buffer->variances);
     psFree(buffer->weights);
+    psFree(buffer->exps);
     psFree(buffer->sources);
     psFree(buffer->limits);
+    psFree(buffer->suspects);
     psFree(buffer->sort);
     return;
@@ -69,9 +73,10 @@
 
     buffer->pixels = psVectorAlloc(numImages, PS_TYPE_F32);
-    buffer->masks = psVectorAlloc(numImages, PS_TYPE_VECTOR_MASK);
     buffer->variances = psVectorAlloc(numImages, PS_TYPE_F32);
     buffer->weights = psVectorAlloc(numImages, PS_TYPE_F32);
+    buffer->exps = psVectorAlloc(numImages, PS_TYPE_F32);
     buffer->sources = psVectorAlloc(numImages, PS_TYPE_U16);
     buffer->limits = psVectorAlloc(numImages, PS_TYPE_F32);
+    buffer->suspects = psVectorAlloc(numImages, PS_TYPE_U8);
     buffer->sort = psVectorAlloc(numImages, PS_TYPE_F32);
     return buffer;
@@ -93,6 +98,9 @@
 static bool combinationMeanVariance(float *mean, // Mean value, to return
                                     float *var, // Variance value, to return
+                                    float *exp, // Exposure time, to return
+                                    float *expWeight,          // Weighted exposure time, to return
                                     const psVector *values, // Values to combine
                                     const psVector *variances, // Pixel variances to combine
+                                    const psVector *exps,      // Exposure times to combine
                                     const psVector *weights // Weights to apply
                                     )
@@ -119,4 +127,7 @@
     float sumVarianceWeight = 0.0;     // Sum of the pixel variances multiplied by the global weights
     float sumWeight = 0.0;              // Sum of the image weights
+    float sumExp = 0.0;                 // Sum of the exposure time
+    float sumExpWeight = 0.0;           // Sum of the exposure time multiplied by the global weights
+    int numGood = 0;                    // Number of good exposures
     for (int i = 0; i < values->n; i++) {
         sumValueWeight += values->data.F32[i] * weights->data.F32[i];
@@ -125,4 +136,9 @@
             sumVarianceWeight += variances->data.F32[i] * PS_SQR(weights->data.F32[i]);
         }
+        if (exps) {
+            sumExp += exps->data.F32[i];
+            sumExpWeight += exps->data.F32[i] * weights->data.F32[i];
+            numGood++;
+        }
     }
 
@@ -134,4 +150,10 @@
     if (var) {
         *var = sumVarianceWeight / PS_SQR(sumWeight);
+    }
+    if (exp) {
+        *exp = sumExp;
+    }
+    if (expWeight) {
+        *expWeight = sumExpWeight;
     }
     return true;
@@ -143,23 +165,16 @@
                                    float *stdev, // Standard deviation value, to return
                                    const psVector *values, // Values to combine
-                                   const psVector *masks, // Mask to apply
                                    psVector *sortBuffer // Buffer for sorting
                                    )
 {
     assert(values);
-    assert(!masks || values->n == masks->n);
     assert(values->type.type == PS_TYPE_F32);
-    assert(!masks || masks->type.type == PS_TYPE_VECTOR_MASK);
     assert(sortBuffer && sortBuffer->nalloc >= values->n && sortBuffer->type.type == PS_TYPE_F32);
 
-    // Need to filter out clipped values
-    int num = 0;            // Number of valid values
-    for (int i = 0; i < values->n; i++) {
-        if (!masks || !masks->data.PS_TYPE_VECTOR_MASK_DATA[i]) {
-            sortBuffer->data.F32[num++] = values->data.F32[i];
-        }
-    }
-    sortBuffer->n = num;
-    if (!psVectorSortInPlace(sortBuffer)) {
+    int num = values->n;                // Number of values
+    sortBuffer = psVectorSortIndex(sortBuffer, values);
+    if (!sortBuffer) {
+        *median = NAN;
+        *stdev = NAN;
         return false;
     }
@@ -167,14 +182,15 @@
     if (num == 3) {
         // Attempt to measure standard deviation with only three values (and one of those possibly corrupted)
-        *median = sortBuffer->data.F32[1];
+        *median = values->data.F32[sortBuffer->data.S32[1]];
         if (stdev) {
-            float diff1 = sortBuffer->data.F32[0] - *median;
-            float diff2 = sortBuffer->data.F32[2] - *median;
+            float diff1 = values->data.F32[sortBuffer->data.S32[0]] - *median;
+            float diff2 = values->data.F32[sortBuffer->data.S32[2]] - *median;
             // This factor of sqrt(2) might not be exact, but it's about right
             *stdev = M_SQRT2 * PS_MIN(fabsf(diff1), fabsf(diff2));
         }
     } else {
-        *median = num % 2 ? sortBuffer->data.F32[num / 2] :
-            (sortBuffer->data.F32[num / 2 - 1] + sortBuffer->data.F32[num / 2]) / 2.0;
+        *median = num % 2 ? values->data.F32[sortBuffer->data.S32[num / 2]] :
+            (values->data.F32[sortBuffer->data.S32[num / 2 - 1]] +
+             values->data.F32[sortBuffer->data.S32[num / 2]]) / 2.0;
         if (stdev) {
             if (num <= NUM_DIRECT_STDEV) {
@@ -182,11 +198,11 @@
                 double sum = 0.0;
                 for (int i = 0; i < num; i++) {
-                    sum += PS_SQR(sortBuffer->data.F32[i] - *median);
+                    sum += PS_SQR(values->data.F32[sortBuffer->data.S32[i]] - *median);
                 }
                 *stdev = sqrt(sum / (double)(num - 1));
             } else {
                 // Standard deviation from the interquartile range
-                *stdev = 0.74 * (sortBuffer->data.F32[(int)(0.75 * num)] -
-                                 sortBuffer->data.F32[(int)(0.25 * num)]);
+                *stdev = 0.74 * (values->data.F32[sortBuffer->data.S32[(int)(0.75 * num)]] -
+                                 values->data.F32[sortBuffer->data.S32[(int)(0.25 * num)]]);
             }
         }
@@ -195,72 +211,13 @@
     return true;
 }
-
-#if 0
-// Return the weighted median for the pixels
-// This does not appear to produce as clean images as the weighted Olympic mean
-static float combinationWeightedMedian(const psVector *values, // Values to combine
-                                       const psVector *weights, // Weights to combine
-                                       const psVector *masks, // Mask to apply
-                                       psVector *sortBuffer // Buffer for sorting
-                                       )
-{
-    double sumWeight = 0.0;             // Sum of weights
-    for (int j = 0; j < values->n; j++) {
-        if (masks->data.PS_TYPE_VECTOR_MASK_DATA[j]) {
-            continue;
-        }
-        sumWeight += weights->data.F32[j];
-    }
-
-    sortBuffer = psVectorSortIndex(sortBuffer, values);
-    double target = sumWeight / 2.0;    // Target weight
-
-    int dominant = -1;                  // Index of dominant value, if any
-    double cumulativeWeight = 0.0;      // Sum of weights
-    for (int j = 0; j < values->n; j++) {
-        if (masks->data.PS_TYPE_VECTOR_MASK_DATA[j]) {
-            continue;
-        }
-        int index = sortBuffer->data.S32[j];  // Index of value of interest
-        float weight = weights->data.F32[index]; // Weight for value of interest
-        if (weight >= target) {
-            // Get the weighted median of the rest
-            dominant = index;
-            sumWeight -= weight;
-            target = sumWeight / 2.0;
-            continue;
-        }
-        cumulativeWeight += weight;
-        if (cumulativeWeight >= target) {
-            float median = values->data.F32[index]; // Weighted median median
-            if (dominant != -1) {
-                // In the case that a single value contains a disproportionate weight compared to the rest,
-                // we use a weighted mean between that dominant value and the weighted median of the rest.
-                return (values->data.F32[dominant] * weights->data.F32[dominant] + median * sumWeight) /
-                    (weights->data.F32[dominant] + sumWeight);
-            }
-            return median;
-        }
-    }
-
-    return NAN;
-}
-#endif
 
 // Return the weighted Olympic mean for the pixels
 static float combinationWeightedOlympic(const psVector *values, // Values to combine
                                         const psVector *weights, // Weights to combine
-                                        const psVector *masks, // Mask to apply
                                         float frac, // Fraction to discard
                                         psVector *sortBuffer // Buffer for sorting
                                         )
 {
-    int numGood = 0;                    // Number of good values
-    for (int i = 0; i < values->n; i++) {
-        if (masks->data.PS_TYPE_VECTOR_MASK_DATA[i]) {
-            continue;
-        }
-        numGood++;
-    }
+    int numGood = values->n;            // Number of good values
 
     int numBad = frac * numGood + 0.5;  // Number of bad values
@@ -272,7 +229,4 @@
     for (int i = 0, j = 0; i < values->n; i++) {
         int index = sortBuffer->data.S32[i]; // Index of interest
-        if (masks->data.PS_TYPE_VECTOR_MASK_DATA[index]) {
-            continue;
-        }
         j++;
         if (j > high) {
@@ -290,9 +244,15 @@
 
 // Mark a pixel for inspection
-static inline void combineInspect(const psArray *inputs, // Stack data
-                                  int x, int y, // Pixel
-                                  int source // Source image index
-                                  )
-{
+// Value in pixel doesn't seem to agree with the stack, so need to look closer
+static inline void combineMarkInspect(const psArray *inputs, // Stack data
+                                      int x, int y, // Pixel
+                                      int source // Source image index
+                                      )
+{
+#ifdef TESTING
+    if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+        fprintf(stderr, "Marking image %d, pixel %d,%d for inspection\n", source, x, y);
+    }
+#endif
     pmStackData *data = inputs->data[source]; // Stack data of interest
     if (!data) {
@@ -304,45 +264,64 @@
 }
 
-// Given a stack of images, combine with optional rejection.
-// Pixels in the stack that are rejected are marked for subsequent inspection
-static void combinePixels(psImage *image, // Combined image, for output
-                          psImage *mask, // Combined mask, for output
-                          psImage *variance, // Combined variance map, for output
-                          const psArray *inputs, // Stack data
-                          const psVector *weights, // Global (single value) weights for data, or NULL
-                          const psVector *addVariance, // Additional variance for data
-                          const psVector *reject, // Indices of pixels to reject, or NULL
-                          int x, int y, // Coordinates of interest; frame of output image
-                          psImageMaskType maskVal, // Value to mask
-                          psImageMaskType bad, // Value to give bad pixels
-                          int numIter, // Number of rejection iterations
-                          float rej, // Number of standard deviations at which to reject
-                          float sys,    // Relative systematic error
-                          float discard,// Fraction of values to discard (Olympic weighted mean)
-                          bool useVariance, // Use variance for rejection when combining?
-                          bool safe,    // Combine safely?
-                          bool rejectInspect, // Reject values marked for inspection from combination?
-                          combineBuffer *buffer // Buffer for combination; to avoid multiple allocations
-                         )
+// Mark a pixel for rejection
+// Cannot possibly inspect this pixel and confirm that it's good.
+// e.g., Only a single input
+static inline void combineMarkReject(const psArray *inputs, // Stack data
+                                     int x, int y, // Pixel
+                                     int source // Source image index
+                                     )
+{
+#ifdef TESTING
+    if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+        fprintf(stderr, "Marking pixel image %d, pixel %d,%d for rejection\n", source, x, y);
+    }
+#endif
+    pmStackData *data = inputs->data[source]; // Stack data of interest
+    if (!data) {
+        psWarning("Can't find input data for source %d", source);
+        return;
+    }
+    data->reject = psPixelsAdd(data->reject, data->reject->nalloc, x, y);
+    return;
+}
+
+
+// Extract vectors for simple combination/rejection operations
+static void combineExtract(int *num,                        // Number of good pixels
+                           bool *suspect,                   // Any suspect pixels?
+                           combineBuffer *buffer, // Buffer with vectors
+                           psImage *image, // Combined image, for output
+                           psImage *mask, // Combined mask, for output
+                           psImage *variance, // Combined variance map, for output
+                           const psArray *inputs, // Stack data
+                           const psVector *weights, // Global (single value) weights for data, or NULL
+                           const psVector *exps,    // Exposures for data, or NULL
+                           const psVector *addVariance, // Additional variance for data
+                           const psVector *reject, // Indices of pixels to reject, or NULL
+                           int x, int y, // Coordinates of interest; frame of output image
+                           psImageMaskType maskVal, // Value to mask
+                           psImageMaskType maskSuspect // Value to suspect
+                           )
 {
     // Rudimentary error checking
+    assert(buffer);
     assert(image);
     assert(mask);
     assert(inputs);
-    assert(numIter >= 0);
-    assert(buffer);
-    assert(addVariance);
-    assert((useVariance && variance) || !useVariance);
 
     psVector *pixelData = buffer->pixels; // Values for the pixel of interest
-    psVector *pixelMasks = buffer->masks; // Masks for the pixel of interest
     psVector *pixelVariances = variance ? buffer->variances : NULL; // Variances for the pixel of interest
     psVector *pixelWeights = buffer->weights; // Image weights for the pixel of interest
+    psVector *pixelExps = buffer->exps;       // Exposure times
     psVector *pixelSources = buffer->sources; // Sources for the pixel of interest
     psVector *pixelLimits = buffer->limits; // Limits for the pixel of interest
-    psVector *sort = buffer->sort;      // Sort buffer
+    psVector *pixelSuspects = buffer->suspects; // Is the pixel suspect?
+
+    if (suspect) {
+        *suspect = false;
+    }
 
     // Extract the pixel and mask data
-    int num = 0;                        // Number of good images
+    int numGood = 0;                    // Number of good pixels
     for (int i = 0, j = 0; i < inputs->n; i++) {
         // Check if this pixel has been rejected.  Assumes that the rejection pixel list is sorted --- it
@@ -364,62 +343,100 @@
         }
 
+        pixelSuspects->data.U8[numGood] = mask->data.PS_TYPE_IMAGE_MASK_DATA[yIn][xIn] & maskSuspect ?
+            true : false;
+
         psImage *image = data->readout->image; // Image of interest
         psImage *variance = data->readout->variance; // Variance map of interest
-        pixelData->data.F32[num] = image->data.F32[yIn][xIn];
+        pixelData->data.F32[numGood] = image->data.F32[yIn][xIn];
         if (variance) {
-            pixelVariances->data.F32[num] = variance->data.F32[yIn][xIn] * addVariance->data.F32[i];
-        }
-        pixelWeights->data.F32[num] = data->weight;
-        pixelSources->data.U16[num] = i;
-        num++;
-    }
-    pixelData->n = num;
+            pixelVariances->data.F32[numGood] = variance->data.F32[yIn][xIn] * addVariance->data.F32[i];
+        }
+        pixelWeights->data.F32[numGood] = data->weight;
+        pixelExps->data.F32[numGood] = data->exp;
+        pixelSources->data.U16[numGood] = i;
+        numGood++;
+    }
+    pixelData->n = numGood;
     if (variance) {
-        pixelVariances->n = num;
-    }
-    pixelWeights->n = num;
-    pixelSources->n = num;
-    pixelLimits->n = num;
-
-#ifdef TESTING
-    if (x == TEST_X && y == TEST_Y) {
-        for (int i = 0; i < num; i++) {
-            fprintf(stderr, "Input %d (%" PRIu16 "): %f %f %f\n",
-                    i, pixelSources->data.U16[i], pixelData->data.F32[i], pixelVariances->data.F32[i],
-                    pixelWeights->data.F32[i]);
-        }
-    }
-#endif
-
-    // The sensible thing to do varies according to how many good pixels there are.
+        pixelVariances->n = numGood;
+    }
+    pixelWeights->n = numGood;
+    pixelSources->n = numGood;
+    pixelLimits->n = numGood;
+    pixelSuspects->n = numGood;
+    *num = numGood;
+
+#ifdef TESTING
+    if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+        for (int i = 0; i < numGood; i++) {
+            fprintf(stderr, "Input %d, pixel %d,%d (%" PRIu16 "): %f %f (%f) %f %f %d\n",
+                    i, x, y, pixelSources->data.U16[i], pixelData->data.F32[i], pixelVariances->data.F32[i],
+                    addVariance->data.F32[i], pixelWeights->data.F32[i], pixelExps->data.F32[i],
+                    pixelSuspects->data.U8[i]);
+        }
+    }
+#endif
+
+    return;
+}
+
+
+// Combine pixels
+static void combinePixels(psImage *image, // Combined image, for output
+                          psImage *mask, // Combined mask, for output
+                          psImage *variance, // Combined variance map, for output
+                          psImage *exp,   // Exposure map (time), for output
+                          psImage *expnum,       // Exposure map (number) for output
+                          psImage *expweight,    // Exposure map (weighted time) for output
+                          int num,      // Number of good pixels
+                          combineBuffer *buffer, // Buffer with vectors
+                          int x, int y, // Coordinates of interest; frame of output image
+                          psImageMaskType bad, // Value for bad pixels
+                          bool safe,           // Safe combination?
+                          float invTotalWeight    // Inverse of total weight for all inputs
+                          )
+{
+    psVector *pixelData = buffer->pixels; // Values for the pixel of interest
+    psVector *pixelVariances = variance ? buffer->variances : NULL; // Variances for the pixel of interest
+    psVector *pixelWeights = buffer->weights; // Image weights for the pixel of interest
+    psVector *pixelExps = buffer->exps;       // Exposure times
+
     // Default option is that the pixel is bad
     float imageValue = NAN, varianceValue = NAN; // Value for combined image and variance map
     psImageMaskType maskValue = bad;    // Value for combined mask
+    float expValue = 0.0, expWeightValue = NAN; // Exposure value (straight, and weighted)
+
     switch (num) {
-      case 0:
-        // Nothing to combine: it's bad
-#ifdef TESTING
-    if (x == TEST_X && y == TEST_Y) {
-        fprintf(stderr, "No inputs to combine, pixel is bad.\n");
-    }
-#endif
-        break;
+      case 0: {
+          // Nothing to combine: it's bad
+#ifdef TESTING
+          if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+              fprintf(stderr, "No inputs to combine, pixel %d,%d is bad.\n", x, y);
+          }
+#endif
+          break;
+      }
       case 1: {
           // Accept the single pixel unless we have to be safe
           if (!safe) {
-#ifdef TESTING
-              if (x == TEST_X && y == TEST_Y) {
-                  fprintf(stderr, "Single input to combine, safety off.\n");
-              }
-#endif
               imageValue = pixelData->data.F32[0];
               if (variance) {
                   varianceValue = pixelVariances->data.F32[0];
               }
+              if (exp) {
+                  expValue = pixelExps->data.F32[0];
+                  expWeightValue = pixelExps->data.F32[0];
+              }
               maskValue = 0;
+#ifdef TESTING
+              if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                  fprintf(stderr, "Single input to combine, safety off, pixel %d,%d --> %f\n",
+                          x, y, imageValue);
+              }
+#endif
           }
 #ifdef TESTING
-          else if (x == TEST_X && y == TEST_Y) {
-              fprintf(stderr, "Single input to combine, safety on, pixel is bad.\n");
+          else if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+              fprintf(stderr, "Single input to combine, safety on, pixel %d,%d is bad.\n", x, y);
           }
 #endif
@@ -427,200 +444,38 @@
       }
       case 2: {
-          // Accept the mean of the pixels only if we're going to reject based on the variance, or we're not
-          // playing safe
-          if (useVariance || !safe) {
-              float mean, var;   // Mean and variance from combination
-              if (combinationMeanVariance(&mean, &var, pixelData, pixelVariances, pixelWeights)) {
-                  imageValue = mean;
-                  varianceValue = var;
+          // Automatically accept the mean of the pixels only if we're not playing safe
+          if (!safe) {
+              if (combinationMeanVariance(&imageValue, &varianceValue, &expValue, &expWeightValue,
+                                          pixelData, pixelVariances, pixelExps, pixelWeights)) {
                   maskValue = 0;
 #ifdef TESTING
-                  if (x == TEST_X && y == TEST_Y) {
-                      fprintf(stderr, "Two inputs to combine using variance/unsafe --> %f %f\n",
-                              mean, var);
+                  if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                      fprintf(stderr, "Two inputs to combine using unsafe, pixel %d,%d --> %f %f\n",
+                              x, y, imageValue, varianceValue);
                   }
 #endif
               }
           }
-          if (useVariance && numIter > 0) {
-              // Use variance to check that the two are consistent
-              float diff = 0.5 * (pixelData->data.F32[0] - pixelData->data.F32[1]); // Mean flux difference
-              float var1 = pixelVariances->data.F32[0]; // Variance of first
-              float var2 = pixelVariances->data.F32[1]; // Variance of second
-              // Systematic error contributes to the rejection level
-              var1 += PS_SQR(sys * pixelData->data.F32[0]);
-              var2 += PS_SQR(sys * pixelData->data.F32[1]);
-
-              float sigma2 = var1 + var2; // Combined variance
-              if (PS_SQR(diff) > PS_SQR(rej) * sigma2) {
-                  // Not consistent: mark both for inspection
-                  if (rejectInspect) {
-                      imageValue = NAN;
-                      varianceValue = NAN;
-                      maskValue = bad;
-                  } else {
-                      combineInspect(inputs, x, y, pixelSources->data.U16[0]);
-                      combineInspect(inputs, x, y, pixelSources->data.U16[1]);
-                  }
-#ifdef TESTING
-                  if (x == TEST_X && y == TEST_Y) {
-                      fprintf(stderr, "Both pixels marked for inspection (%f > %f x %f\n)",
-                              diff, rej, sqrtf(sigma2));
-                  }
-#endif
+#ifdef TESTING
+          else {
+              if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                  fprintf(stderr, "Two inputs to combine, safety on, pixel %d,%d is bad\n", x, y);
               }
           }
+#endif
           break;
       }
       default: {
-          // Record the value derived with no clipping, because pixels rejected using the harsh clipping
-          // applied in the first pass might later be accepted.
-          float mean, var;           // Mean and variance of the combination
-          if (!combinationMeanVariance(&mean, &var, pixelData, pixelVariances, pixelWeights)) {
+          // Can combine without too much worrying
+          if (!combinationMeanVariance(&imageValue, &varianceValue, &expValue, &expWeightValue,
+                                       pixelData, pixelVariances, pixelExps, pixelWeights)) {
               break;
           }
-          imageValue = mean;
-          varianceValue = var;
           maskValue = 0;
 #ifdef TESTING
-          if (x == TEST_X && y == TEST_Y) {
-              fprintf(stderr, "Combined inputs: %f %f\n", mean, var);
+          if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+              fprintf(stderr, "Combined inputs, pixel %d,%d --> %f %f\n", x, y, imageValue, varianceValue);
           }
 #endif
-
-          // Prepare for clipping iteration
-          if (numIter > 0) {
-              pixelMasks->n = num;
-              psVectorInit(pixelMasks, 0);
-              if (useVariance) {
-                  // Convert to rejection limits --- saves doing it later.
-                  // Using squared rejection limit because it's cheaper than sqrts
-                  float rej2 = PS_SQR(rej); // Rejection level squared
-                  double sumWeights = 0.0;
-                  for (int i = 0; i < num; i++) {
-                      sumWeights += pixelWeights->data.F32[i];
-                  }
-                  for (int i = 0; i < num; i++) {
-                      // Systematic error contributes to the rejection level
-                      float sysVar = PS_SQR(sys * pixelData->data.F32[i]);
-#ifdef TESTING
-                      // Correct variance for comparison against weighted mean including itself
-                      float compare = 1.0 - pixelWeights->data.F32[i] / sumWeights;
-                      if (x == TEST_X && y == TEST_Y) {
-                          fprintf(stderr, "Variance %d (%d): %f %f %f\n", i, pixelSources->data.U16[i],
-                                  pixelVariances->data.F32[i], sysVar, compare);
-                      }
-#endif
-
-                      pixelLimits->data.F32[i] = rej2 * (pixelVariances->data.F32[i] + sysVar);
-                  }
-              }
-          }
-
-          // The clipping that follows is solely to identify suspect pixels.
-          // These suspect pixels will be inspected in more detail by other functions.
-          int numClipped = INT_MAX;     // Number of pixels clipped per iteration
-          int totalClipped = 0;         // Total number of pixels clipped
-          for (int i = 0; i < numIter && numClipped > 0 && num - totalClipped > 2; i++) {
-              numClipped = 0;
-              float median = NAN;       // Middle of distribution
-              float limit = NAN;        // Rejection limit
-              if (!useVariance) {
-                  float stdev;  // Median and stdev of the combination, for rejection
-                  if (!combinationMedianStdev(&median, useVariance ? NULL : &stdev,
-                                              pixelData, pixelMasks, sort)) {
-                      psWarning("Bad median/stdev at %d,%d", x, y);
-                      break;
-                  }
-                  limit = rej * stdev;
-#ifdef TESTING
-                  if (x == TEST_X && y == TEST_Y) {
-                      fprintf(stderr, "Rejecting without variance; rejection limit: %f\n", limit);
-                  }
-#endif
-              } else {
-#ifdef TESTING
-                  if (x == TEST_X && y == TEST_Y) {
-                      fprintf(stderr, "Rejecting with variance...\n");
-                  }
-#endif
-                  median = combinationWeightedOlympic(pixelData, pixelWeights, pixelMasks, discard, sort);
-              }
-
-#ifdef TESTING
-              if (x == TEST_X && y == TEST_Y) {
-                  fprintf(stderr, "Median: %f\n", median);
-              }
-#endif
-
-
-// Mask a pixel for inspection
-#define MASK_PIXEL_FOR_INSPECTION() \
-    pixelMasks->data.PS_TYPE_VECTOR_MASK_DATA[j] = 0xff; \
-    if (!rejectInspect) { \
-        combineInspect(inputs, x, y, pixelSources->data.U16[j]); \
-    } \
-    numClipped++; \
-    totalClipped++;
-
-              for (int j = 0; j < num; j++) {
-                  if (pixelMasks->data.PS_TYPE_VECTOR_MASK_DATA[j]) {
-                      continue;
-                  }
-                  float diff = pixelData->data.F32[j] - median; // Difference from expected
-                  if (useVariance) {
-                      // Comparing squares --- cheaper than lots of sqrts
-                      // pixelVariances includes the rejection limit, from above
-                      if (PS_SQR(diff) > pixelLimits->data.F32[j]) {
-                          MASK_PIXEL_FOR_INSPECTION();
-#ifdef TESTING
-                          if (x == TEST_X && y == TEST_Y) {
-                              fprintf(stderr, "Rejecting input %d based on variance: %f > %f\n",
-                                      j, diff, sqrtf(pixelLimits->data.F32[j]));
-                          }
-#endif
-                      }
-                  } else if (fabsf(diff) > limit) {
-                      MASK_PIXEL_FOR_INSPECTION();
-#ifdef TESTING
-                      if (x == TEST_X && y == TEST_Y) {
-                          fprintf(stderr, "Rejecting input %d based on distribution: %f > %f\n",
-                                  j, diff, limit);
-                      }
-#endif
-                  }
-              }
-          }
-
-          if (rejectInspect && totalClipped > 0) {
-              // Get rid of the masked values
-              // The alternative to this is to make combinationMeanVariance() accept a mask
-              int good = 0;            // Index of good value
-              for (int i = 0; i < num; i++) {
-                  if (pixelMasks->data.PS_TYPE_VECTOR_MASK_DATA[i]) {
-                      continue;
-                  }
-                  if (i != good) {
-                      pixelData->data.F32[good] = pixelData->data.F32[i];
-                      pixelVariances->data.F32[good] = pixelVariances->data.F32[i];
-                      pixelWeights->data.F32[good] = pixelWeights->data.F32[i];
-                      pixelData->data.F32[good] = pixelData->data.F32[i];
-                  }
-                  good++;
-              }
-              pixelData->n = good;
-              pixelVariances->n = good;
-              pixelWeights->n = good;
-              if (combinationMeanVariance(&mean, &var, pixelData, pixelVariances, pixelWeights)) {
-                  imageValue = mean;
-                  varianceValue = var;
-                  maskValue = 0;
-              } else {
-                  imageValue = NAN;
-                  varianceValue = NAN;
-                  maskValue = bad;
-              }
-          }
-
           break;
       }
@@ -632,6 +487,347 @@
         variance->data.F32[y][x] = varianceValue;
     }
+    if (exp) {
+        exp->data.F32[y][x] = expValue;
+    }
+    if (expnum) {
+        expnum->data.PS_TYPE_IMAGE_MASK_DATA[y][x] = num;
+    }
+    if (expweight) {
+        expweight->data.F32[y][x] = expWeightValue * invTotalWeight;
+    }
 
     return;
+}
+
+
+// Test pixels to be combined
+// Returns false to repeat without suspect pixels
+static bool combineTest(int num,      // Number of good pixels
+                        bool suspect, // Does the stack contain suspect pixels?
+                        psArray *inputs,       // Original inputs (for flagging)
+                        combineBuffer *buffer, // Buffer with vectors
+                        int x, int y, // Coordinates of interest; frame of output image
+                        float iter, // Number of rejection iterations per input
+                        float rej, // Number of standard deviations at which to reject
+                        float sys,    // Relative systematic error
+                        float olympic,// Fraction of values to discard (Olympic weighted mean)
+                        bool useVariance, // Use variance for rejection when combining?
+                        bool safe    // Combine safely?
+                        )
+{
+    if (iter <= 0) {
+        return true;
+    }
+
+    int numIter = PS_MAX(iter * num, 1); // Number of iterations
+
+#ifdef TESTING
+    if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+        fprintf(stderr, "Testing pixel %d,%d: %d %f %f %f %d %d\n",
+                x, y, numIter, rej, sys, olympic, useVariance, safe);
+    }
+#endif
+
+    psVector *pixelData = buffer->pixels; // Values for the pixel of interest
+    psVector *pixelWeights = buffer->weights; // Is the pixel suspect?
+    psVector *pixelVariances = buffer->variances; // Variances for the pixel of interest
+    psVector *pixelSources = buffer->sources; // Sources for the pixel of interest
+    psVector *pixelSuspects = buffer->suspects; // Is the pixel suspect?
+    psVector *pixelLimits = buffer->limits; // Is the pixel suspect?
+
+    // Set up rejection limits
+    float rej2 = PS_SQR(rej); // Rejection level squared
+    if (num > 2 && useVariance) {
+        // Convert rejection limits --- saves doing it later multiple times
+        // Using squared rejection limit because it's cheaper than sqrts
+        double sumWeights = 0.0;
+        for (int i = 0; i < num; i++) {
+            sumWeights += pixelWeights->data.F32[i];
+        }
+        for (int i = 0; i < num; i++) {
+            // Systematic error contributes to the rejection level
+            float sysVar = PS_SQR(sys * pixelData->data.F32[i]);
+#ifdef TESTING
+            // Correct variance for comparison against weighted mean including itself
+            float compare = 1.0 - pixelWeights->data.F32[i] / sumWeights;
+            if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                fprintf(stderr, "Variance %d (%d), pixel %d,%d: %f %f %f\n", i, pixelSources->data.U16[i],
+                        x, y, pixelVariances->data.F32[i], sysVar, compare);
+            }
+#endif
+            pixelLimits->data.F32[i] = rej2 * (pixelVariances->data.F32[i] + sysVar);
+        }
+    }
+
+    int maskIndex = 0;                  // Index of pixel to mask
+    int totalClipped = 0;               // Total number of pixels clipped
+    for (int i = 0; i < numIter && maskIndex >= 0; i++) {
+        maskIndex = -1;                 // Nothing to reject
+
+        switch (num) {
+          case 0:
+            break;
+          case 1:
+            if (i == 0 && safe) {
+                combineMarkReject(inputs, x, y, pixelSources->data.U16[0]);
+            }
+            break;
+          case 2: {
+              if (useVariance) {
+                  // Use variance to check that the two are consistent
+                  float diff = 0.5 * (pixelData->data.F32[0] - pixelData->data.F32[1]); // Mean flux difference
+                  float var1 = pixelVariances->data.F32[0]; // Variance of first
+                  float var2 = pixelVariances->data.F32[1]; // Variance of second
+                  // Systematic error contributes to the rejection level
+                  var1 += PS_SQR(sys * pixelData->data.F32[0]);
+                  var2 += PS_SQR(sys * pixelData->data.F32[1]);
+
+                  float sigma2 = var1 + var2; // Combined variance
+                  if (PS_SQR(diff) > rej2 * sigma2) {
+                      // Not consistent: don't believe either!
+                      if (i == 0 && suspect) {
+                          combineMarkReject(inputs, x, y, pixelSources->data.U16[0]);
+                          combineMarkReject(inputs, x, y, pixelSources->data.U16[1]);
+                      } else {
+                          combineMarkInspect(inputs, x, y, pixelSources->data.U16[0]);
+                          combineMarkInspect(inputs, x, y, pixelSources->data.U16[1]);
+                      }
+#ifdef TESTING
+                      if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                          fprintf(stderr, "Flagged both inputs for pixel %d,%d (%f > %f x %f\n)",
+                                  x, y, diff, rej, sqrtf(sigma2));
+                      }
+#endif
+                  }
+              } else if (i == 0 && safe) {
+                  // Can't test them, and we want to be safe, so reject
+                  combineMarkReject(inputs, x, y, pixelSources->data.U16[0]);
+                  combineMarkReject(inputs, x, y, pixelSources->data.U16[1]);
+              }
+              break;
+          }
+#if 0
+          case 3: {
+              // Want to be a bit careful on the rejection than for a larger number of inputs
+              if (!useVariance) {
+                  return combineTestGeneral(num, suspect, inputs, buffer, x, y, numIter, rej, sys,
+                                            olympic, useVariance, safe, allowSuspect);
+              }
+
+              // Differences between pixel values
+              float diff01 = pixelData->data.F32[0] - pixelData->data.F32[1];
+              float diff12 = pixelData->data.F32[1] - pixelData->data.F32[2];
+              float diff20 = pixelData->data.F32[2] - pixelData->data.F32[0];
+              // Variance for each pixel
+              float var0 = pixelVariances->data.F32[0] + PS_SQR(sys * pixelData->data.F32[0]);
+              float var1 = pixelVariances->data.F32[1] + PS_SQR(sys * pixelData->data.F32[1]);
+              float var2 = pixelVariances->data.F32[2] + PS_SQR(sys * pixelData->data.F32[2]);
+              // Errors in pixel differences
+              float err01 = var0 + var1;
+              float err12 = var1 + var2;
+              float err20 = var2 + var0;
+
+#ifdef TESTING
+              if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                  fprintf(stderr, "Diff 0-1: %f %f\n", diff01, err01);
+                  fprintf(stderr, "Diff 1-2: %f %f\n", diff12, err12);
+                  fprintf(stderr, "Diff 2-0: %f %f\n", diff20, err20);
+              }
+#endif
+
+              int badPairs = 0;         // Number of bad pairs
+              bool bad01 = false, bad12 = false, bad20 = false; // Pair is bad?
+              if (PS_SQR(diff01) > rej2 * err01) {
+                  bad01 = true;
+                  badPairs++;
+              }
+              if (PS_SQR(diff12) > rej2 * err12) {
+                  bad12 = true;
+                  badPairs++;
+              }
+              if (PS_SQR(diff20) > rej2 * err20) {
+                  bad20 = true;
+                  badPairs++;
+              }
+
+              if (badPairs > 0 && allowSuspect && suspect) {
+                  return false;
+              }
+
+              switch (badPairs) {
+                case 0:
+                  // Nothing to worry about!
+                  break;
+                case 1:
+                  // Can't tell which image is bad, so be sure to get it
+                  if (bad01) {
+                      combineMarkInspect(inputs, x, y, pixelSources->data.U16[0]);
+                      combineMarkInspect(inputs, x, y, pixelSources->data.U16[1]);
+                      break;
+                  }
+                  if (bad12) {
+                      combineMarkInspect(inputs, x, y, pixelSources->data.U16[1]);
+                      combineMarkInspect(inputs, x, y, pixelSources->data.U16[2]);
+                      break;
+                  }
+                  if (bad20) {
+                      combineMarkInspect(inputs, x, y, pixelSources->data.U16[2]);
+                      combineMarkInspect(inputs, x, y, pixelSources->data.U16[0]);
+                      break;
+                  }
+                  psAbort("Should never get here");
+                case 2:
+                  if (bad01 && bad12) {
+                      // 2 and 0 are good
+                      combineMarkInspect(inputs, x, y, pixelSources->data.U16[1]);
+                      break;
+                  }
+                  if (bad12 && bad20) {
+                      // 0 and 1 are good
+                      combineMarkInspect(inputs, x, y, pixelSources->data.U16[2]);
+                      break;
+                  }
+                  if (bad20 && bad01) {
+                      // 1 and 2 are good
+                      combineMarkInspect(inputs, x, y, pixelSources->data.U16[0]);
+                      break;
+                  }
+                  psAbort("Should never get here");
+                case 3:
+                  // Everything's bad
+                  combineMarkInspect(inputs, x, y, pixelSources->data.U16[0]);
+                  combineMarkInspect(inputs, x, y, pixelSources->data.U16[1]);
+                  combineMarkInspect(inputs, x, y, pixelSources->data.U16[2]);
+                  break;
+              }
+              break;
+          }
+#endif
+          default: {
+              if (useVariance) {
+                  float median = combinationWeightedOlympic(pixelData, pixelWeights,
+                                                            olympic, buffer->sort); // Median for stack
+#ifdef TESTING
+                  if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                      fprintf(stderr, "Flag with variance pixel %d,%d: median = %f\n", x, y, median);
+                  }
+#endif
+                  float worst = -INFINITY; // Largest deviation
+                  for (int j = 0; j < num; j++) {
+                      float diff = pixelData->data.F32[j] - median; // Difference from expected
+#ifdef TESTING
+                      if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                          fprintf(stderr, "Testing input %d for pixel %d,%d: %f\n", j, x, y, diff);
+                      }
+#endif
+
+                      // Comparing squares --- cheaper than lots of sqrts
+                      // pixelVariances includes the rejection limit, from above
+                      float diff2 = PS_SQR(diff); // Square difference
+                      if (diff2 > pixelLimits->data.F32[j]) {
+                          float dev = diff2 / pixelLimits->data.F32[j]; // Deviation
+                          if (dev > worst) {
+                              worst = dev;
+                              maskIndex = j;
+                          }
+                      }
+                  }
+              } else {
+                  float median = NAN, stdev = NAN;  // Median and stdev of the combination, for rejection
+                  combinationMedianStdev(&median, &stdev, pixelData, buffer->sort);
+                  float limit = rej * stdev; // Rejection limit
+#ifdef TESTING
+                  if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                      fprintf(stderr,
+                              "Flag without variance pixel %d,%d; median = %f, stdev = %f, limit = %f\n",
+                              x, y, median, stdev, limit);
+                  }
+#endif
+                  float worst = -INFINITY; // Largest deviation
+                  for (int j = 0; j < num; j++) {
+                      float diff = fabsf(pixelData->data.F32[j] - median); // Difference from expected
+
+                      if (diff > limit) {
+                          float dev = diff / limit; // Deviation
+                          if (dev > worst) {
+                              worst = dev;
+                              maskIndex = j;
+                          }
+                      }
+                  }
+              }
+          }
+        }
+
+        // Do the actual rejection of the pixel
+        if (maskIndex >= 0) {
+            if (suspect) {
+#ifdef TESTING
+                if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                    fprintf(stderr, "Throwing out all suspect pixels for %d,%d\n", x, y);
+                }
+#endif
+                // Throw out all suspect pixels
+                int numGood = 0;        // Number of good pixels
+                for (int j = 0; j < num; j++) {
+                    if (pixelSuspects->data.U8[j]) {
+                        combineMarkReject(inputs, x, y, pixelSources->data.U16[j]);
+                        continue;
+                    }
+                    if (numGood == j) {
+                        numGood++;
+                        continue;
+                    }
+                    pixelData->data.F32[numGood] = pixelData->data.F32[j];
+                    pixelWeights->data.F32[numGood] = pixelWeights->data.F32[j];
+                    pixelSources->data.U16[numGood] = pixelSources->data.U16[j];
+                    pixelLimits->data.F32[numGood] = pixelLimits->data.F32[j];
+                    pixelVariances->data.F32[numGood] = pixelVariances->data.F32[j];
+                    numGood++;
+                }
+                pixelData->n = numGood;
+                pixelWeights->n = numGood;
+                pixelSources->n = numGood;
+                pixelLimits->n = numGood;
+                pixelVariances->n = numGood;
+                totalClipped += num - numGood;
+                num = numGood;
+                suspect = false;
+            } else {
+                // Throw out masked pixel
+#ifdef TESTING
+                if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                    fprintf(stderr, "Throwing out input %d for pixel %d,%d\n", maskIndex, x, y);
+                }
+#endif
+                combineMarkInspect(inputs, x, y, pixelSources->data.U16[maskIndex]);
+                int numGood = 0;        // Number of good pixels
+                for (int j = 0; j < num; j++) {
+                    if (j == maskIndex) {
+                        continue;
+                    }
+                    if (numGood == j) {
+                        numGood++;
+                        continue;
+                    }
+                    pixelData->data.F32[numGood] = pixelData->data.F32[j];
+                    pixelWeights->data.F32[numGood] = pixelWeights->data.F32[j];
+                    pixelSources->data.U16[numGood] = pixelSources->data.U16[j];
+                    pixelLimits->data.F32[numGood] = pixelLimits->data.F32[j];
+                    pixelVariances->data.F32[numGood] = pixelVariances->data.F32[j];
+                    numGood++;
+                }
+                pixelData->n = numGood;
+                pixelWeights->n = numGood;
+                pixelSources->n = numGood;
+                pixelLimits->n = numGood;
+                pixelVariances->n = numGood;
+                totalClipped++;
+                num--;
+            }
+        }
+    }
+
+    return true;
 }
 
@@ -639,8 +835,9 @@
 // Ensure the input array of pmStackData is valid, and get some details out of it
 static bool validateInputData(bool *haveVariances, // Do we have variance maps in the sky images?
-                              bool *haveRejects, // Do we have lists of rejected pixels?
                               int *num,    // Number of inputs
                               int *numCols, int *numRows, // Size of (sky) images
-                              psArray *input // Input array of pmStackData to validate
+                              const psArray *input, // Input array of pmStackData to validate
+                              const pmReadout *output, // Output readout
+                              const pmReadout *exp    // Exposure map
     )
 {
@@ -668,5 +865,5 @@
         PS_ASSERT_IMAGE_TYPE(data->readout->variance, PS_TYPE_F32, false);
     }
-    *haveRejects = (data->reject != NULL);
+    bool haveRejects = (data->reject != NULL); // Do we have rejected pixels?
 
     // Make sure the rest correspond with the first
@@ -681,5 +878,5 @@
             return false;
         }
-        if ((*haveRejects && !data->reject) || (data->reject && !*haveRejects)) {
+        if ((haveRejects && !data->reject) || (data->reject && !haveRejects)) {
             psError(PS_ERR_UNEXPECTED_NULL, true,
                     "The rejected pixels are specified in some but not all inputs.");
@@ -696,4 +893,23 @@
             PS_ASSERT_IMAGES_SIZE_EQUAL(data->readout->image, data->readout->variance, false);
             PS_ASSERT_IMAGE_TYPE(data->readout->variance, PS_TYPE_F32, false);
+        }
+    }
+
+    PM_ASSERT_READOUT_NON_NULL(output, false);
+    if (output->image) {
+        PS_ASSERT_IMAGE_NON_NULL(output->image, false);
+        PS_ASSERT_IMAGE_TYPE(output->image, PS_TYPE_F32, false);
+        PS_ASSERT_IMAGE_NON_NULL(output->mask, false);
+        PS_ASSERT_IMAGE_TYPE(output->mask, PS_TYPE_IMAGE_MASK, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(output->image, output->mask, false);
+    }
+
+    if (exp) {
+        PM_ASSERT_READOUT_NON_NULL(exp, false);
+        if (exp->image) {
+            PS_ASSERT_IMAGES_SIZE_EQUAL(exp->image, output->image, false);
+        }
+        if (exp->mask) {
+            PS_ASSERT_IMAGES_SIZE_EQUAL(exp->mask, output->image, false);
         }
     }
@@ -767,5 +983,5 @@
 
 /// Constructor
-pmStackData *pmStackDataAlloc(pmReadout *readout, float weight, float addVariance)
+pmStackData *pmStackDataAlloc(pmReadout *readout, float weight, float exp, float addVariance)
 {
     pmStackData *data = psAlloc(sizeof(pmStackData)); // Stack data, to return
@@ -776,4 +992,5 @@
     data->inspect = NULL;
     data->weight = weight;
+    data->exp = exp;
     data->addVariance = addVariance;
 
@@ -782,31 +999,23 @@
 
 /// Stack input images
-bool pmStackCombine(pmReadout *combined, psArray *input, psImageMaskType maskVal, psImageMaskType bad,
-                    int kernelSize, int numIter, float rej, float sys, float discard,
-                    bool entire, bool useVariance, bool safe, bool rejectInspect)
-{
-    PS_ASSERT_PTR_NON_NULL(combined, false);
+bool pmStackCombine(pmReadout *combined, pmReadout *expmaps, psArray *input,
+                    psImageMaskType maskVal, psImageMaskType maskSuspect,
+                    psImageMaskType bad, int kernelSize,
+                    float iter, float rej, float sys, float olympic,
+                    bool useVariance, bool safe, bool rejection)
+{
     bool haveVariances;                 // Do we have the variance maps?
-    bool haveRejects;                   // Do we have lists of rejected pixels?
     int num;                            // Number of inputs
     int numCols, numRows;               // Size of (sky) images
-    if (!validateInputData(&haveVariances, &haveRejects, &num, &numCols, &numRows, input)) {
+    if (!validateInputData(&haveVariances, &num, &numCols, &numRows, input, combined, expmaps)) {
         return false;
     }
     PS_ASSERT_INT_NONNEGATIVE(kernelSize, false);
     PS_ASSERT_INT_POSITIVE(bad, false);
-    PS_ASSERT_INT_NONNEGATIVE(numIter, false);
     if (isnan(rej)) {
-        PS_ASSERT_INT_EQUAL(numIter, 0, false);
+        PS_ASSERT_FLOAT_EQUAL(iter, 0, false);
     } else {
+        PS_ASSERT_FLOAT_LARGER_THAN(iter, 0, false);
         PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, false);
-    }
-    if (haveRejects) {
-        // This is a subsequent combination, so expect that the image and mask already exist
-        PS_ASSERT_IMAGE_NON_NULL(combined->image, false);
-        PS_ASSERT_IMAGE_TYPE(combined->image, PS_TYPE_F32, false);
-        PS_ASSERT_IMAGE_NON_NULL(combined->mask, false);
-        PS_ASSERT_IMAGE_TYPE(combined->mask, PS_TYPE_IMAGE_MASK, false);
-        PS_ASSERT_IMAGES_SIZE_EQUAL(combined->image, combined->mask, false);
     }
     if (useVariance && !haveVariances) {
@@ -817,12 +1026,19 @@
     psVector *addVariance = psVectorAlloc(num, PS_TYPE_F32); // Additional variance for each image
     psVector *weights = psVectorAlloc(num, PS_TYPE_F32); // Relative weighting for each image
+    psVector *exps = psVectorAlloc(num, PS_TYPE_F32);    // Exposure times for each image
     psArray *stack = psArrayAlloc(num); // Stack of readouts
+    float totalExpWeight = 0.0;           // Total value of all weighted exposure times
+    float totalExp = 0.0;                 // Total exposure time
     for (int i = 0; i < num; i++) {
         pmStackData *data = input->data[i]; // Stack data for this input
         if (!data) {
             weights->data.F32[i] = 0.0;
+            exps->data.F32[i] = NAN;
             continue;
         }
         weights->data.F32[i] = data->weight;
+        exps->data.F32[i] = data->exp;
+        totalExp += exps->data.F32[i];
+        totalExpWeight += exps->data.F32[i] * weights->data.F32[i];
         pmReadout *ro = data->readout;  // Readout of interest
         stack->data[i] = psMemIncrRefCounter(ro);
@@ -833,8 +1049,15 @@
         }
 #endif
-        if (!haveRejects && !data->inspect) {
-            data->inspect = psPixelsAllocEmpty(PIXEL_LIST_BUFFER);
-        }
-    }
+        if (!rejection) {
+            // Ensure pixels can be put on the appropriate list
+            if (!data->inspect) {
+                data->inspect = psPixelsAllocEmpty(PIXEL_LIST_BUFFER);
+            }
+            if (!data->reject) {
+                data->reject = psPixelsAllocEmpty(PIXEL_LIST_BUFFER);
+            }
+        }
+    }
+    totalExpWeight = totalExp / totalExpWeight;    // Convert to inverse
 
     int minInputCols, maxInputCols, minInputRows, maxInputRows; // Smallest and largest values to combine
@@ -842,5 +1065,5 @@
     if (!pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows, &xSize, &ySize,
                                 stack)) {
-        psError(PS_ERR_UNKNOWN, false, "Input stack is not valid.");
+        psError(psErrorCodeLast(), false, "Input stack is not valid.");
         psFree(stack);
         return false;
@@ -863,91 +1086,104 @@
     combineBuffer *buffer = combineBufferAlloc(num);
 
-    if (haveRejects) {
-        psImage *combinedImage = combined->image; // Combined image
-        psImage *combinedMask = combined->mask; // Combined mask
-        psImage *combinedVariance = combined->variance; // Combined variance map
-
-        psArray *pixelMap = pixelMapGenerate(input, minInputCols, maxInputCols,
-                                             minInputRows, maxInputRows); // Map of pixels to source
-        psPixels *pixels = NULL;            // Total list of pixels, with no duplicates
+    // Pull the products out, allocate if necessary
+    psImage *combinedImage = combined->image; // Combined image
+    if (!combinedImage) {
+        combined->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        combinedImage = combined->image;
+    }
+    psImage *combinedMask = combined->mask; // Combined mask
+    if (!combinedMask) {
+        combined->mask = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK);
+        combinedMask = combined->mask;
+    }
+
+    psImage *combinedVariance = combined->variance; // Combined variance map
+    if (haveVariances && !combinedVariance) {
+        combined->variance = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        combinedVariance = combined->variance;
+    }
+
+    psImage *exp = NULL, *expnum = NULL, *expweight = NULL; // Exposure map and exposure number
+    if (expmaps) {
+        if (!expmaps->image) {
+            expmaps->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        }
+        exp = expmaps->image;
+
+        if (!expmaps->mask) {
+            expmaps->mask = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK);
+        }
+        expnum = expmaps->mask;
+
+        if (!expmaps->variance) {
+            expmaps->variance = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        }
+        expweight = expmaps->variance;
+    }
+
+    // Set up rejection list
+    psArray *pixelMap = NULL;           // Map of pixels to source
+    if (rejection) {
+        pixelMap = pixelMapGenerate(input, minInputCols, maxInputCols, minInputRows, maxInputRows);
+    }
+
+    // Combine each pixel
+    for (int y = minInputRows; y < maxInputRows; y++) {
+        for (int x = minInputCols; x < maxInputCols; x++) {
+#ifdef TESTING
+            if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                fprintf(stderr, "Combining pixel %d,%d: %x %x %f %f %f %f %d %d %d\n",
+                        x, y, maskVal, bad, iter, rej, sys, olympic, useVariance, safe, rejection);
+            }
+#endif
+            psVector *reject = NULL; // Images to reject for this pixel
+            if (rejection) {
+                reject = pixelMapQuery(pixelMap, minInputCols, minInputRows, x, y);
+#ifdef TESTING
+                if (PS_SQR(x - TEST_X) + PS_SQR(y - TEST_Y) <= PS_SQR(TEST_RADIUS)) {
+                    fprintf(stderr, "Rejected inputs for pixel %d,%d: ", x, y);
+                    if (!reject) {
+                        fprintf(stderr, "<none>\n");
+                    } else {
+                        for (int i = 0; i < reject->n; i++) {
+                            fprintf(stderr, "%d ", reject->data.U16[i]);
+                        }
+                        fprintf(stderr, "\n");
+                    }
+                }
+#endif
+            }
+
+            int num;                    // Number of good pixels
+            bool suspect;               // Suspect pixels in stack?
+            combineExtract(&num, &suspect, buffer, combinedImage, combinedMask, combinedVariance,
+                           input, weights, exps, addVariance, reject, x, y, maskVal, maskSuspect);
+            combinePixels(combinedImage, combinedMask, combinedVariance, exp, expnum, expweight,
+                          num, buffer, x, y, bad, safe, totalExpWeight);
+
+            if (iter > 0) {
+                combineTest(num, suspect, input, buffer, x, y, iter, rej, sys, olympic,
+                            useVariance, safe);
+            }
+        }
+    }
+
+    psFree(pixelMap);
+    psFree(weights);
+    psFree(buffer);
+    psFree(addVariance);
+
+
+#ifndef PS_NO_TRACE
+    if (!rejection && psTraceGetLevel("psModules.imcombine") >= 5) {
         for (int i = 0; i < num; i++) {
-            pmStackData *data = input->data[i]; // Stacking data; contains the list of pixels
-            if (!data) {
+            pmStackData *data = input->data[i]; // Stack data for this input
+            if (!data || !data->inspect) {
                 continue;
             }
-            pixels = psPixelsConcatenate(pixels, data->reject);
-        }
-        pixels = psPixelsDuplicates(pixels, pixels);
-
-        if (entire) {
-            // Combine entire image
-            for (int y = minInputRows; y < maxInputRows; y++) {
-                for (int x = minInputCols; x < maxInputCols; x++) {
-                    psVector *reject = pixelMapQuery(pixelMap, minInputCols, minInputRows,
-                                                     x, y); // Reject these images
-                    combinePixels(combinedImage, combinedMask, combinedVariance, input, weights,
-                                  addVariance, reject, x, y, maskVal, bad, numIter, rej, sys, discard,
-                                  useVariance, safe, rejectInspect, buffer);
-                }
-            }
-        } else {
-            // Only combine previously rejected pixels
-            for (int i = 0; i < pixels->n; i++) {
-                // Pixel coordinates are in the frame of the original image
-                int x = pixels->data[i].x, y = pixels->data[i].y; // Coordinates of interest
-                if (x < minInputCols || x >= maxInputCols || y < minInputRows || y >= maxInputRows) {
-                    continue;
-                }
-                psVector *reject = pixelMapQuery(pixelMap, minInputCols, minInputRows,
-                                                 x, y); // Reject these images
-                combinePixels(combinedImage, combinedMask, combinedVariance, input, weights,
-                              addVariance, reject, x, y, maskVal, bad, numIter, rej, sys, discard,
-                              useVariance, safe, rejectInspect, buffer);
-            }
-        }
-        psFree(pixels);
-        psFree(pixelMap);
-    } else {
-        // Pull the products out, allocate if necessary
-        psImage *combinedImage = combined->image; // Combined image
-        if (!combinedImage) {
-            combined->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
-            combinedImage = combined->image;
-        }
-        psImage *combinedMask = combined->mask; // Combined mask
-        if (!combinedMask) {
-            combined->mask = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK);
-            combinedMask = combined->mask;
-        }
-
-        psImage *combinedVariance = combined->variance; // Combined variance map
-        if (haveVariances && !combinedVariance) {
-            combined->variance = psImageAlloc(numCols, numRows, PS_TYPE_F32);
-            combinedVariance = combined->variance;
-        }
-
-        for (int y = minInputRows; y < maxInputRows; y++) {
-            for (int x = minInputCols; x < maxInputCols; x++) {
-                combinePixels(combinedImage, combinedMask, combinedVariance, input, weights,
-                              addVariance, NULL, x, y, maskVal, bad, numIter, rej, sys, discard,
-                              useVariance, safe, rejectInspect, buffer);
-            }
-        }
-
-#ifndef PS_NO_TRACE
-        if (psTraceGetLevel("psModules.imcombine") >= 5) {
-            for (int i = 0; i < num; i++) {
-                pmStackData *data = input->data[i]; // Stack data for this input
-                if (!data || !data->inspect) {
-                    continue;
-                }
-                psTrace("psModules.imcombine", 5, "Image %d: %ld pixels to inspect.\n", i, data->inspect->n);
-            }
-        }
-#endif
-    }
-
-    psFree(weights);
-    psFree(buffer);
+            psTrace("psModules.imcombine", 5, "Image %d: %ld pixels to inspect.\n", i, data->inspect->n);
+        }
+    }
+#endif
 
     return true;
Index: branches/simmosaic_branches/psModules/src/imcombine/pmStack.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmStack.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmStack.h	(revision 27839)
@@ -31,4 +31,5 @@
     psPixels *inspect;                  ///< Pixels to inspect
     float weight;                       ///< Relative weighting for image
+    float exp;                          ///< Exposure time
     float addVariance;                  ///< Additional variance when rejecting
 } pmStackData;
@@ -37,4 +38,5 @@
 pmStackData *pmStackDataAlloc(pmReadout *readout, ///< Warped readout (sky cell)
                               float weight, ///< Weight to apply
+                              float exp,    ///< Exposure time
                               float addVariance ///< Additional variance when rejecting
     );
@@ -42,13 +44,14 @@
 /// Stack input images
 bool pmStackCombine(pmReadout *combined,///< Combined readout (output)
+                    pmReadout *expmaps, ///< Exposure maps (output)
                     psArray *input,     ///< Input array of pmStackData
                     psImageMaskType maskVal, ///< Mask value of bad pixels
+                    psImageMaskType suspect, ///< Mask value of suspect pixels
                     psImageMaskType bad,     ///< Mask value to give rejected pixels
                     int kernelSize,     ///< Half-size of the convolution kernel
-                    int numIter,        ///< Number of iterations
+                    float iter,         ///< Number of iterations per input
                     float rej,          ///< Rejection limit (standard deviations)
                     float sys,          ///< Relative systematic error
                     float discard,      ///< Fraction of values to discard for Olympic weighted mean
-                    bool entire,        ///< Combine entire image even if rejection lists provided?
                     bool useVariance,   ///< Use variance values for rejection?
                     bool safe,          ///< Play safe with small numbers of input pixels (mask if N <= 2)?
Index: branches/simmosaic_branches/psModules/src/imcombine/pmStackReject.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmStackReject.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmStackReject.c	(revision 27839)
@@ -10,4 +10,6 @@
 #include "pmSubtractionThreads.h"
 #include "pmSubtractionKernels.h"
+
+#include "pmStackReject.h"
 
 #define PIXEL_LIST_BUFFER 100           // Number of pixels to add to list at a time
@@ -35,6 +37,8 @@
 {
     int size = kernels->size;           // Half-size of convolution kernel
-    psImage *polyValues = p_pmSubtractionPolynomialFromCoords(NULL, kernels, numCols, numRows,
-                                                              xMin + size + 1, yMin + size + 1); // Polynomial
+    int x = PS_MIN(xMin + size + 1, kernels->xMax); // x coordinate of interest
+    int y = PS_MIN(yMin + size + 1, kernels->yMax); // y coordinate of interest
+
+    psImage *polyValues = p_pmSubtractionPolynomialFromCoords(NULL, kernels, x, y); // Polynomial
     int box = p_pmSubtractionBadRadius(NULL, kernels, polyValues, false, poorFrac); // Radius of bad box
     psTrace("psModules.imcombine", 10, "Growing by %d", box);
@@ -43,21 +47,9 @@
     if (box > 0) {
         // Convolve a subimage, then stick it in the target
-        // XXX if (threaded) {
-        // XXX     psMutexLock(source);
-        // XXX }
         psImage *mask = psImageSubset(source, psRegionSet(xMin - box, xMax + box,
                                                           yMin - box, yMax + box)); // Mask to convolve
-        // XXX if (threaded) {
-        // XXX     psMutexUnlock(source);
-        // XXX }
         psImage *convolved = psImageConvolveMask(NULL, mask, PM_STACK_MASK_BAD, PM_STACK_MASK_CONVOLVE,
                                                  -box, box, -box, box); // Convolved mask
-        // XXX if (threaded) {
-        // XXX     psMutexLock(source);
-        // XXX }
         psFree(mask);
-        // XXX if (threaded) {
-        // XXX     psMutexUnlock(source);
-        // XXX }
 
         int numBytes = (xMax - xMin) * PSELEMTYPE_SIZEOF(PS_TYPE_IMAGE_MASK); // Number of bytes to copy
@@ -111,5 +103,5 @@
 
     if (!pmSubtractionThreaded()) {
-        pmSubtractionThreadsInit(NULL, NULL);
+        pmSubtractionThreadsInit();
     }
 
@@ -125,6 +117,6 @@
 
 
-psPixels *pmStackReject(const psPixels *in, int numCols, int numRows, float threshold, float poorFrac,
-                        int stride, const psArray *subRegions, const psArray *subKernels)
+psPixels *pmStackReject(const psPixels *in, int numCols, int numRows, float threshold, int stride,
+                        const psArray *subRegions, const psArray *subKernels)
 {
     PS_ASSERT_PIXELS_NON_NULL(in, NULL);
@@ -162,13 +154,11 @@
     pmReadout *inRO = pmReadoutAlloc(NULL); // Readout with input image
     inRO->image = image;
-    // XXX if (threaded) {
-    // XXX     psMutexInit(image);
-    // XXX }
+    convRO->image = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
     for (int i = 0; i < numRegions; i++) {
         psRegion *region = subRegions->data[i]; // Region of interest
         pmSubtractionKernels *kernels = subKernels->data[i]; // Kernel of interest
-        if (!pmSubtractionConvolve(convRO, NULL, inRO, NULL, NULL, stride, 0, 0, 1.0, 0.0,
+        if (!pmSubtractionConvolve(NULL, convRO, NULL, inRO, NULL, stride, 0, 0, 1.0, 0.0, 0.0,
                                    region, kernels, false, true)) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to convolve mask image in region %d.", i);
+            psError(psErrorCodeLast(), false, "Unable to convolve mask image in region %d.", i);
             psFree(convRO);
             psFree(inRO);
@@ -180,11 +170,7 @@
 
         // Image of the kernel at the centre of the region
-        float xNorm = (region->x0 + 0.5 * (region->x1 - region->x0) - kernels->numCols/2.0) /
-            (float)kernels->numCols;
-        float yNorm = (region->y0 + 0.5 * (region->y1 - region->y0) - kernels->numRows/2.0) /
-            (float)kernels->numRows;
-        psImage *kernel = pmSubtractionKernelImage(kernels, xNorm, yNorm, false);
+        psImage *kernel = pmSubtractionKernelImage(kernels, 0.5, 0.5, false);
         if (!kernel) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to generate kernel image.");
+            psError(psErrorCodeLast(), false, "Unable to generate kernel image.");
             psFree(convRO);
             psFree(inRO);
@@ -211,7 +197,4 @@
         }
     }
-    // XXX if (threaded) {
-    // XXX     psMutexDestroy(image);
-    // XXX }
     psFree(inRO);
     psImage *convolved = psMemIncrRefCounter(convRO->image);
@@ -241,7 +224,19 @@
     }
     psTrace("psModules.imcombine", 7, "Found %ld bad pixels", bad->n);
-
-    // Now, grow the mask to include everything that touches a bad pixel in the convolution
-    psImage *source = psPixelsToMask(NULL, bad, psRegionSet(0, numCols - 1, 0, numRows - 1),
+    psFree(convolved);
+
+    return bad;
+}
+
+
+psPixels *pmStackRejectGrow(const psPixels *in, int numCols, int numRows, float poorFrac,
+                            const psArray *subRegions, const psArray *subKernels)
+{
+    PS_ASSERT_PIXELS_NON_NULL(in, NULL);
+    PS_ASSERT_ARRAY_NON_NULL(subRegions, NULL);
+    PS_ASSERT_ARRAY_NON_NULL(subKernels, NULL);
+    PS_ASSERT_ARRAYS_SIZE_EQUAL(subRegions, subKernels, NULL);
+
+    psImage *source = psPixelsToMask(NULL, in, psRegionSet(0, numCols - 1, 0, numRows - 1),
                                      PM_STACK_MASK_BAD); // Mask image to grow
 
@@ -262,9 +257,6 @@
     bool oldThreads = psImageConvolveSetThreads(false); // Old value of threading for psImageColvolve
 
-    psImage *target = psImageRecycle(convolved, numCols, numRows, PS_TYPE_IMAGE_MASK); // Grown image
+    psImage *target = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK); // Grown image
     psImageInit(target, 0);
-    // XXX if (threaded) {
-    // XXX     psMutexInit(source);
-    // XXX }
     for (int i = 0; i < subRegions->n; i++) {
         psRegion *region = subRegions->data[i]; // Subtraction region
@@ -287,7 +279,5 @@
                     psArray *args = job->args; // Job arguments
                     psArrayAdd(args, 1, target);
-                    // XXX psMutexLock(source);
                     psArrayAdd(args, 1, source);
-                    // XXX psMutexUnlock(source);
                     psArrayAdd(args, 1, kernels);
                     PS_ARRAY_ADD_SCALAR(args, numCols, PS_TYPE_S32);
@@ -307,5 +297,5 @@
                 } else if (!stackRejectGrow(target, source, kernels, numCols, numRows,
                                             i, xSubMax, j, ySubMax, poorFrac)) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to grow bad pixels.");
+                    psError(psErrorCodeLast(), false, "Unable to grow bad pixels.");
                     psFree(source);
                     psFree(target);
@@ -317,5 +307,5 @@
 
     if (!psThreadPoolWait(false)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to grow bad pixels.");
+        psError(psErrorCodeLast(), false, "Unable to grow bad pixels.");
         psFree(source);
         psFree(target);
@@ -332,5 +322,4 @@
         }
 
-        // XXX psMutexDestroy(source);
     }
 
@@ -351,5 +340,6 @@
 
     psFree(source);
-    bad = psPixelsFromMask(bad, target, PM_STACK_MASK_ALL);
+    psPixels *bad = psPixelsFromMask(NULL, target, PM_STACK_MASK_ALL); // All bad pixels
+    psFree(target);
     psTrace("psModules.imcombine", 7, "Total %ld bad pixels", bad->n);
 
Index: branches/simmosaic_branches/psModules/src/imcombine/pmStackReject.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmStackReject.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmStackReject.h	(revision 27839)
@@ -12,8 +12,16 @@
                         int numCols, int numRows, ///< Size of image of interest
                         float threshold, ///< Threshold on convolved image, 0..1
-                        float poorFrac, ///< Fraction for "poor"
                         int stride,     ///< Size of convolution patches
                         const psArray *regions, ///< Array of image regions for image
                         const psArray *kernels ///< Array of kernel parameters for each region
+    );
+
+/// Given a list of pixels from the convolved image, we grow them by convolution to get the list of all pixels
+/// which should be rejected.
+psPixels *pmStackRejectGrow(const psPixels *in, ///< List of pixels in the convolved image
+                            int numCols, int numRows, ///< Size of image of interest
+                            float poorFrac, ///< Fraction for "poor"
+                            const psArray *regions, ///< Array of image regions for image
+                            const psArray *kernels ///< Array of kernel parameters for each region
     );
 
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtraction.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtraction.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtraction.c	(revision 27839)
@@ -17,8 +17,10 @@
 #include <pslib.h>
 
+#include "pmErrorCodes.h"
 #include "pmHDU.h"                      // Required for pmFPA.h
 #include "pmFPA.h"
 #include "pmSubtractionStamps.h"
 #include "pmSubtractionEquation.h"
+#include "pmSubtractionVisual.h"
 #include "pmSubtractionThreads.h"
 
@@ -29,4 +31,5 @@
 #define PIXEL_LIST_BUFFER 100           // Number of entries to add to pixel list at a time
 #define MIN_SAMPLE_STATS    7           // Minimum number to use sample statistics; otherwise use quartiles
+#define USE_KERNEL_ERR                  // Use kernel error image?
 
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -36,5 +39,5 @@
 // Generate the kernel to apply to the variance from the normal kernel
 static psKernel *varianceKernel(psKernel *out, // Output kernel
-                                psKernel *normalKernel // Normal kernel
+                                const psKernel *normalKernel // Normal kernel
                                 )
 {
@@ -48,12 +51,8 @@
 
     // Take the square of the normal kernel
-    double sumNormal = 0.0, sumVariance = 0.0; // Sum of the normal and variance kernels
+    double sumVariance = 0.0; // Sum of the variance kernels
     for (int v = yMin; v <= yMax; v++) {
         for (int u = xMin; u <= xMax; u++) {
-            float value = normalKernel->kernel[v][u]; // Value of interest
-            float value2 = PS_SQR(value); // Value squared
-            sumNormal += value;
-            sumVariance += value2;
-            out->kernel[v][u] = value2;
+            sumVariance += out->kernel[v][u] = PS_SQR(normalKernel->kernel[v][u]);
         }
     }
@@ -65,4 +64,37 @@
     return out;
 }
+
+// Contribute to an image of the solved kernel component using the preCalculated image
+static void solvedKernelPreCalc(psKernel *kernel, // Kernel, updated
+                                const pmSubtractionKernels *kernels, // Kernel basis functions
+                                float value,                         // Normalisation value for basis function
+                                int index                  // Index of basis function of interest
+    )
+{
+    int size = kernels->size;           // Kernel half-size
+    pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[index]; // Precalculated values
+#if 0
+    // Iterating over the kernel
+    for (int y = 0, v = -size; v <= size; y++, v++) {
+        float yValue = value * preCalc->yKernel->data.F32[y];
+        for (int x = 0, u = -size; u <= size; x++, u++) {
+            kernel->kernel[v][u] +=  yValue * preCalc->xKernel->data.F32[x];
+        }
+    }
+    // Photometric scaling for even kernels only
+    if (kernels->u->data.S32[i] % 2 == 0 && kernels->v->data.S32[i] % 2 == 0) {
+        kernel->kernel[0][0] -= value;
+    }
+#else
+    for (int v = -size; v <= size; v++) {
+        for (int u = -size; u <= size; u++) {
+            kernel->kernel[v][u] +=  value * preCalc->kernel->kernel[v][u];
+        }
+    }
+#endif
+
+    return;
+}
+
 
 // Generate an image of the solved kernel
@@ -70,4 +102,5 @@
                               const pmSubtractionKernels *kernels, // Kernel basis functions
                               const psImage *polyValues, // Spatial polynomial values
+                              bool normalise,            // Add normalisation?
                               bool wantDual // Want the dual (second) kernel?
                               )
@@ -85,4 +118,9 @@
     for (int i = 0; i < numKernels; i++) {
         double value = p_pmSubtractionSolutionCoeff(kernels, polyValues, i, wantDual); // Polynomial value
+        if (wantDual) {
+            // The model is built with the dual convolution terms added, so to produce zero residual the
+            // equation results in negative coefficients which we must undo.
+            value *= -1.0;
+        }
 
         switch (kernels->type) {
@@ -115,13 +153,5 @@
           case PM_SUBTRACTION_KERNEL_GUNK: {
               if (i < kernels->inner) {
-                  // Using pre-calculated function
-                  psKernel *preCalc = kernels->preCalc->data[i]; // Precalculated values
-                  // Iterating over the kernel
-                  for (int v = -size; v <= size; v++) {
-                      for (int u = -size; u <= size; u++) {
-                          kernel->kernel[v][u] += preCalc->kernel[v][u] * value;
-                          // Photometric scaling is built into the preCalc kernel --- no subtraction!
-                      }
-                  }
+                  solvedKernelPreCalc(kernel, kernels, value, i);
               } else {
                   // Using delta function
@@ -133,30 +163,19 @@
               break;
           }
-          case PM_SUBTRACTION_KERNEL_ISIS: {
-              psArray *preCalc = kernels->preCalc->data[i]; // Precalculated values
-              psVector *xKernel = preCalc->data[0]; // Kernel in x
-              psVector *yKernel = preCalc->data[1]; // Kernel in y
-              // Iterating over the kernel
-              for (int y = 0, v = -size; v <= size; y++, v++) {
-                  for (int x = 0, u = -size; u <= size; x++, u++) {
-                      kernel->kernel[v][u] +=  value * xKernel->data.F32[x] * yKernel->data.F32[y];
-                  }
-              }
-              // Photometric scaling for even kernels only
-              if (kernels->u->data.S32[i] % 2 == 0 && kernels->v->data.S32[i] % 2 == 0) {
-                  kernel->kernel[0][0] -= value;
-              }
+          case PM_SUBTRACTION_KERNEL_ISIS:
+          case PM_SUBTRACTION_KERNEL_ISIS_RADIAL:
+          case PM_SUBTRACTION_KERNEL_HERM:
+          case PM_SUBTRACTION_KERNEL_DECONV_HERM: {
+              solvedKernelPreCalc(kernel, kernels, value, i);
               break;
           }
           case PM_SUBTRACTION_KERNEL_RINGS: {
-              psArray *preCalc = kernels->preCalc->data[i]; // Precalculated data
-              psVector *uCoords = preCalc->data[0]; // u coordinates
-              psVector *vCoords = preCalc->data[1]; // v coordinates
-              psVector *poly = preCalc->data[2]; // Polynomial values
-              int num = uCoords->n;     // Number of pixels
+              pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[i]; // Precalculated kernels
+              int num = preCalc->uCoords->n;     // Number of pixels
 
               for (int j = 0; j < num; j++) {
-                  int u = uCoords->data.S32[j], v = vCoords->data.S32[j]; // Kernel coordinates
-                  kernel->kernel[v][u] += poly->data.F32[j] * value;
+                  int u = preCalc->uCoords->data.S32[j];
+                  int v = preCalc->vCoords->data.S32[j]; // Kernel coordinates
+                  kernel->kernel[v][u] += preCalc->poly->data.F32[j] * value;
               }
               // Photometric scaling is built into the kernel --- no subtraction!
@@ -168,6 +187,8 @@
     }
 
-    // Put in the normalisation component
-    kernel->kernel[0][0] += (wantDual ? 1.0 : p_pmSubtractionSolutionNorm(kernels));
+    if (normalise) {
+        // Put in the normalisation component
+        kernel->kernel[0][0] += (wantDual ? 1.0 : p_pmSubtractionSolutionNorm(kernels));
+    }
 
     return kernel;
@@ -250,5 +271,5 @@
 static void convolveVarianceFFT(psImage *target,// Place the result in here
                               psImage *variance, // Variance map to convolve
-                              psImage *sys, // Systematic error image
+                              psImage *kernelErr, // Kernel error image
                               psImage *mask, // Mask image
                               psImageMaskType maskVal, // Value to mask
@@ -262,22 +283,22 @@
 
     psImage *subVariance = variance ? psImageSubset(variance, border) : NULL; // Variance map
-    psImage *subSys = sys ? psImageSubset(sys, border) : NULL; // Systematic error image
+    psImage *subKE = kernelErr ? psImageSubset(kernelErr, border) : NULL; // Kernel error image
     psImage *subMask = mask ? psImageSubset(mask, border) : NULL; // Mask
 
     // XXX Can trim this a little by combining the convolution: only have to take the FFT of the kernel once
     psImage *convVariance = psImageConvolveFFT(NULL, subVariance, subMask, maskVal, kernel); // Convolved variance
-    psImage *convSys = subSys ? psImageConvolveFFT(NULL, subSys, subMask, maskVal, kernel) : NULL; // Conv sys
+    psImage *convKE = subKE ? psImageConvolveFFT(NULL, subKE, subMask, maskVal, kernel) : NULL; // Conv KE
 
     psFree(subVariance);
-    psFree(subSys);
+    psFree(subKE);
     psFree(subMask);
 
     // Now, we have to stick it in where it belongs
     int xMin = region.x0, xMax = region.x1, yMin = region.y0, yMax = region.y1; // Bounds of region
-    if (convSys) {
+    if (convKE) {
         for (int yTarget = yMin, ySource = size; yTarget < yMax; yTarget++, ySource++) {
             for (int xTarget = xMin, xSource = size; xTarget < xMax; xTarget++, xSource++) {
                 target->data.F32[yTarget][xTarget] = convVariance->data.F32[ySource][xSource] +
-                    convSys->data.F32[ySource][xSource];
+                    convKE->data.F32[ySource][xSource];
             }
         }
@@ -290,5 +311,5 @@
 
     psFree(convVariance);
-    psFree(convSys);
+    psFree(convKE);
 
     return;
@@ -326,5 +347,5 @@
                                   psImage *image, // Image to convolve
                                   psImage *variance, // Variance map to convolve, or NULL
-                                  psImage *sys, // Systematic error image, or NULL
+                                  psImage *kernelErr, // Kernel error image, or NULL
                                   psImage *subMask, // Subtraction mask
                                   const pmSubtractionKernels *kernels, // Kernels
@@ -339,5 +360,5 @@
     )
 {
-    *kernelImage = solvedKernel(*kernelImage, kernels, polyValues, wantDual);
+    *kernelImage = solvedKernel(*kernelImage, kernels, polyValues, true, wantDual);
     if (variance || subMask) {
         *kernelVariance = varianceKernel(*kernelVariance, *kernelImage);
@@ -363,5 +384,6 @@
         convolveFFT(convImage, image, subMask, subBad, *kernelImage, region, background, kernels->size);
         if (variance) {
-            convolveVarianceFFT(convVariance, variance, sys, subMask, subBad, *kernelVariance, region, kernels->size);
+            convolveVarianceFFT(convVariance, variance, kernelErr, subMask, subBad, *kernelVariance,
+                                region, kernels->size);
         }
     } else {
@@ -373,39 +395,42 @@
     }
 
-    // Convolve the mask for bad pixels
+    // Convolve the mask for bad/poor pixels
     if (subMask && convMask) {
         int box = p_pmSubtractionBadRadius(*kernelImage, kernels, polyValues,
                                            wantDual, poorFrac); // Size of bad box
+        psAssert(box >= 0, "Bad radius must be >= 0");
+
+        int colMin = region.x0, colMax = region.x1, rowMin = region.y0, rowMax = region.y1; // Bounds
+        psImage *convolved = NULL; // Convolved subtraction mask
         if (box > 0) {
-            int colMin = region.x0, colMax = region.x1, rowMin = region.y0, rowMax = region.y1; // Bounds
-            psRegion region = psRegionSet(colMin - box, colMax + box,
-                                          rowMin - box, rowMax + box); // Region to convolve
-
-            psImage *image = subMask ? psImageSubset(subMask, region) : NULL; // Mask to convolve
-
-            psImage *convolved = psImageConvolveMask(NULL, image, subBad, subConvBad,
-                                                     -box, box, -box, box); // Convolved subtraction mask
-
+            psRegion maskRegion = psRegionSet(colMin - box, colMax + box,
+                                              rowMin - box, rowMax + box); // Region to convolve
+            psImage *image = subMask ? psImageSubset(subMask, maskRegion) : NULL; // Mask to convolve
+            convolved = psImageConvolveMask(NULL, image, subBad, subConvBad, -box, box, -box, box);
             psFree(image);
-
-            psAssert(convolved->numCols - 2 * box == colMax - colMin, "Bad number of columns");
-            psAssert(convolved->numRows - 2 * box == rowMax - rowMin, "Bad number of rows");
-
-            for (int yTarget = rowMin, ySource = box; yTarget < rowMax; yTarget++, ySource++) {
-                // Dereference images
-                psImageMaskType *target = &convMask->data.PS_TYPE_IMAGE_MASK_DATA[yTarget][colMin]; // Target values
-                psImageMaskType *source = &convolved->data.PS_TYPE_IMAGE_MASK_DATA[ySource][box]; // Source values
-                for (int xTarget = colMin; xTarget < colMax; xTarget++, target++, source++) {
-                    if (*source & subConvBad) {
-                        *target |= maskBad;
-                    } else if (*source & subConvPoor) {
-                        *target |= maskPoor;
-                    }
+        } else {
+            convolved = psImageSubset(subMask, region);
+        }
+
+        psAssert(convolved->numCols - 2 * box == colMax - colMin, "Bad number of columns");
+        psAssert(convolved->numRows - 2 * box == rowMax - rowMin, "Bad number of rows");
+
+        for (int yTarget = rowMin, ySource = box; yTarget < rowMax; yTarget++, ySource++) {
+            // Dereference images
+            psImageMaskType *target = &convMask->data.PS_TYPE_IMAGE_MASK_DATA[yTarget][colMin]; // Target values
+            psImageMaskType *source = &convolved->data.PS_TYPE_IMAGE_MASK_DATA[ySource][box]; // Source values
+            for (int xTarget = colMin; xTarget < colMax; xTarget++, target++, source++) {
+                if (*source & subConvBad) {
+                    *target |= maskBad;
+                } else if (*source & subConvPoor) {
+                    *target &= ~maskBad;
+                    *target |= maskPoor;
+                } else {
+                    *target &= ~maskBad & ~maskPoor;
                 }
             }
-
-            // No need to lock: we own this
-            psFree(convolved);
-        }
+        }
+
+        psFree(convolved);
     }
 
@@ -413,24 +438,83 @@
 }
 
-// Generate an image that can be used to track systematic errors
-static psImage *subtractionSysErrImage(const psImage *image, // Image from which to make sys err image
-                                       float sysError // Relative systematic error
-                                       )
-{
-    if (!isfinite(sysError) || sysError == 0.0) {
+#ifdef USE_KERNEL_ERR
+// Generate an image that can be used to track systematic errors in the kernel
+static psImage *subtractionKernelErrImage(const psImage *image, // Image from which to make kernel error image
+                                          float kernelError // Relative systematic error in kernel
+    )
+{
+    if (!isfinite(kernelError) || kernelError == 0.0) {
         return NULL;
     }
 
     int numCols = image->numCols, numRows = image->numRows; // Size of image
-    psImage *sys = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Systematic error image
-
-    float sysError2 = PS_SQR(sysError); // Square of the systematic error
+    psImage *kernelErr = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Kernel error image
+
+    float kernelError2 = PS_SQR(kernelError); // Square of the kernel error
     for (int y = 0; y < numRows; y++) {
         for (int x = 0; x < numCols; x++) {
-            sys->data.F32[y][x] = PS_SQR(image->data.F32[y][x]) * sysError2;
-        }
-    }
-
-    return sys;
+            kernelErr->data.F32[y][x] = PS_SQR(image->data.F32[y][x]) * kernelError2;
+        }
+    }
+
+    return kernelErr;
+}
+#endif
+
+// Convolve a stamp using a pre-calculated kernel basis function
+static psKernel *convolveStampPreCalc(const psKernel *image, // Image to convolve
+                                      const pmSubtractionKernels *kernels, // Kernel basis functions
+                                      int index,                            // Index of basis function of interest
+                                      int footprint                         // Half-size of stamp
+    )
+{
+    pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[index]; // Precalculated data
+#if 0
+    // Convolving using separable convolution
+    int size = kernels->size;     // Size of kernel
+
+    // Convolve in x
+    // Need to convolve a bit more than the footprint, for the y convolution
+    int yMin = -size - footprint, yMax = size + footprint; // Range for y
+    psKernel *temp = psKernelAlloc(yMin, yMax,
+                                   -footprint, footprint); // Temporary convolution; NOTE: wrong way!
+    for (int y = yMin; y <= yMax; y++) {
+        for (int x = -footprint; x <= footprint; x++) {
+            float value = 0.0;    // Value of convolved pixel
+            int uMin = x - size, uMax = x + size; // Range for u
+            psF32 *xKernelData = &preCalc->xKernel->data.F32[xKernel->n - 1]; // Kernel values
+            psF32 *imageData = &image->kernel[y][uMin]; // Image values
+            for (int u = uMin; u <= uMax; u++, xKernelData--, imageData++) {
+                value += *xKernelData * *imageData;
+            }
+            temp->kernel[x][y] = value; // NOTE: putting in wrong way!
+        }
+    }
+
+    // Convolve in y
+    psKernel *convolved = psKernelAlloc(-footprint, footprint, -footprint, footprint);// Convolved image
+    for (int x = -footprint; x <= footprint; x++) {
+        for (int y = -footprint; y <= footprint; y++) {
+            float value = 0.0;    // Value of convolved pixel
+            int vMin = y - size, vMax = y + size; // Range for v
+            psF32 *yKernelData = &preCalc->yKernel->data.F32[yKernel->n - 1]; // Kernel values
+            psF32 *imageData = &temp->kernel[x][vMin]; // Image values; NOTE: wrong way!
+            for (int v = vMin; v <= vMax; v++, yKernelData--, imageData++) {
+                value += *yKernelData * *imageData;
+            }
+            convolved->kernel[y][x] = value;
+        }
+    }
+    psFree(temp);
+
+    // Photometric scaling for even kernels only
+    if (kernels->u->data.S32[index] % 2 == 0 && kernels->v->data.S32[index] % 2 == 0) {
+        convolveSub(convolved, image, footprint);
+    }
+    return convolved;
+#else
+    // Convolving using precalculated kernel
+    return p_pmSubtractionConvolveStampPrecalc(image, preCalc->kernel);
+#endif
 }
 
@@ -447,4 +531,7 @@
     int x0 = - image->xMin, y0 = - image->yMin; // Position of centre of convolved image
     psKernel *convolved = psKernelAllocFromImage(conv, x0, y0); // Kernel version
+
+    // pmSubtractionVisualShowSubtraction(image->image, kernel->image, conv);
+
     psFree(conv);
     return convolved;
@@ -456,5 +543,5 @@
     psKernel *kernel;                   // Kernel to use
     if (!preKernel) {
-        kernel = solvedKernel(NULL, kernels, polyValues, wantDual);
+        kernel = solvedKernel(NULL, kernels, polyValues, true, wantDual);
     } else {
         kernel = psMemIncrRefCounter(preKernel);
@@ -491,19 +578,21 @@
 }
 
+void p_pmSubtractionPolynomialNormCoords(float *xOut, float *yOut, float xIn, float yIn,
+                                         int xMin, int xMax, int yMin, int yMax)
+{
+    float xNormSize = xMax - xMin, yNormSize = yMax - yMin; // Size to use for normalisation
+    *xOut = 2.0 * (float)(xIn - xMin - xNormSize/2.0) / xNormSize;
+    *yOut = 2.0 * (float)(yIn - yMin - yNormSize/2.0) / yNormSize;
+    return;
+}
+
 psImage *p_pmSubtractionPolynomialFromCoords(psImage *output, const pmSubtractionKernels *kernels,
-                                             int numCols, int numRows, int x, int y)
+                                             int x, int y)
 {
     assert(kernels);
-    assert(numCols > 0 && numRows > 0);
-
-    // Size to use when calculating normalised coordinates (different from actual size when convolving
-    // subimage)
-    int xNormSize = (kernels->numCols > 0 ? kernels->numCols : numCols);
-    int yNormSize = (kernels->numRows > 0 ? kernels->numRows : numRows);
-
-    // Normalised coordinates
-    float yNorm = 2.0 * (float)(y - yNormSize/2.0) / (float)yNormSize;
-    float xNorm = 2.0 * (float)(x - xNormSize/2.0) / (float)xNormSize;
-
+
+    float xNorm, yNorm;                 // Normalised coordinates
+    p_pmSubtractionPolynomialNormCoords(&xNorm, &yNorm, x, y,
+                                        kernels->xMin, kernels->xMax, kernels->yMin, kernels->yMax);
     return p_pmSubtractionPolynomial(output, kernels->spatialOrder, xNorm, yNorm);
 }
@@ -586,5 +675,5 @@
           if (index < kernels->inner) {
               // Photometric scaling is already built in to the precalculated kernel
-              return p_pmSubtractionConvolveStampPrecalc(image, kernels->preCalc->data[index]);
+              return convolveStampPreCalc(image, kernels, index, footprint);
           }
           // Using delta function
@@ -595,60 +684,18 @@
           return convolved;
       }
-      case PM_SUBTRACTION_KERNEL_ISIS: {
-          psArray *preCalc = kernels->preCalc->data[index]; // Precalculated values
-          psVector *xKernel = preCalc->data[0]; // Kernel in x
-          psVector *yKernel = preCalc->data[1]; // Kernel in y
-          int size = kernels->size;     // Size of kernel
-
-          // Convolve in x
-          // Need to convolve a bit more than the footprint, for the y convolution
-          int yMin = -size - footprint, yMax = size + footprint; // Range for y
-          psKernel *temp = psKernelAlloc(yMin, yMax,
-                                         -footprint, footprint); // Temporary convolution; NOTE: wrong way!
-          for (int y = yMin; y <= yMax; y++) {
-              for (int x = -footprint; x <= footprint; x++) {
-                  float value = 0.0;    // Value of convolved pixel
-                  int uMin = x - size, uMax = x + size; // Range for u
-                  psF32 *xKernelData = &xKernel->data.F32[xKernel->n - 1]; // Kernel values
-                  psF32 *imageData = &image->kernel[y][uMin]; // Image values
-                  for (int u = uMin; u <= uMax; u++, xKernelData--, imageData++) {
-                      value += *xKernelData * *imageData;
-                  }
-                  temp->kernel[x][y] = value; // NOTE: putting in wrong way!
-              }
-          }
-
-          // Convolve in y
-          psKernel *convolved = psKernelAlloc(-footprint, footprint, -footprint, footprint);// Convolved image
-          for (int x = -footprint; x <= footprint; x++) {
-              for (int y = -footprint; y <= footprint; y++) {
-                  float value = 0.0;    // Value of convolved pixel
-                  int vMin = y - size, vMax = y + size; // Range for v
-                  psF32 *yKernelData = &yKernel->data.F32[yKernel->n - 1]; // Kernel values
-                  psF32 *imageData = &temp->kernel[x][vMin]; // Image values; NOTE: wrong way!
-                  for (int v = vMin; v <= vMax; v++, yKernelData--, imageData++) {
-                      value += *yKernelData * *imageData;
-                  }
-                  convolved->kernel[y][x] = value;
-              }
-          }
-          psFree(temp);
-
-          // Photometric scaling for even kernels only
-          if (kernels->u->data.S32[index] % 2 == 0 && kernels->v->data.S32[index] % 2 == 0) {
-              convolveSub(convolved, image, footprint);
-          }
-          return convolved;
-      }
+      case PM_SUBTRACTION_KERNEL_ISIS:
+      case PM_SUBTRACTION_KERNEL_ISIS_RADIAL:
+      case PM_SUBTRACTION_KERNEL_HERM:
+      case PM_SUBTRACTION_KERNEL_DECONV_HERM: {
+            return convolveStampPreCalc(image, kernels, index, footprint);
+        }
       case PM_SUBTRACTION_KERNEL_RINGS: {
-          psKernel *convolved = psKernelAlloc(-footprint, footprint,
-                                              -footprint, footprint); // Convolved image
-          psArray *preCalc = kernels->preCalc->data[index]; // Precalculated data
-          psVector *uCoords = preCalc->data[0]; // u coordinates
-          psVector *vCoords = preCalc->data[1]; // v coordinates
-          psVector *poly = preCalc->data[2]; // Polynomial values
-          int num = uCoords->n;         // Number of pixels
-          psS32 *uData = uCoords->data.S32, *vData = vCoords->data.S32; // Dereference u,v coordinates
-          psF32 *polyData = poly->data.F32; // Dereference polynomial values
+          psKernel *convolved = psKernelAlloc(-footprint, footprint, -footprint, footprint); // Convolved image
+          pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[index]; // Precalculated data
+
+          int num = preCalc->uCoords->n;         // Number of pixels
+          psS32 *uData = preCalc->uCoords->data.S32; // Dereference v coordinate
+          psS32 *vData = preCalc->vCoords->data.S32; // Dereference u coordinate
+          psF32 *polyData = preCalc->poly->data.F32; // Dereference polynomial values
           psF32 **imageData = image->kernel;  // Dereference image
           psF32 **convData = convolved->kernel; // Dereference convolved image
@@ -706,5 +753,5 @@
 
     if (stamp->status != PM_SUBTRACTION_STAMP_CALCULATE) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Stamp not marked for calculation.");
+        psError(PM_ERR_PROG, true, "Stamp not marked for calculation.");
         return false;
     }
@@ -730,8 +777,6 @@
 
 
-
-
 int pmSubtractionRejectStamps(pmSubtractionKernels *kernels, pmSubtractionStampList *stamps,
-                              const psVector *deviations, psImage *subMask, float sigmaRej, int footprint)
+                              const psVector *deviations, psImage *subMask, float sigmaRej)
 {
     PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, false);
@@ -764,5 +809,5 @@
 
     if (numStamps == 0) {
-        psError(PS_ERR_UNKNOWN, true, "No good stamps found.");
+        psError(PM_ERR_STAMPS, true, "No good stamps found.");
         psFree(mask);
         return -1;
@@ -772,5 +817,5 @@
                                   PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_QUARTILE); // Statistics for deviatns
     if (!psVectorStats(stats, deviations, NULL, mask, 0xff)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to measure statistics for deviations.");
+        psError(PM_ERR_DATA, false, "Unable to measure statistics for deviations.");
         psFree(stats);
         psFree(mask);
@@ -821,7 +866,10 @@
     ds9num++;
 
+    int footprint = stamps->footprint;  // Half-size of stamp region of interest
     int numRejected = 0;                // Number of stamps rejected
     int numGood = 0;                    // Number of good stamps
     double newMean = 0.0;               // New mean
+    psString log = NULL;                // Log message
+    psStringAppend(&log, "Rejecting stamps, mean = %f, threshold = %f\n", mean, limit);
     for (int i = 0; i < stamps->num; i++) {
         pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
@@ -836,5 +884,8 @@
                 // Mask out the stamp in the image so you it's not found again
                 psTrace("psModules.imcombine", 3, "Rejecting stamp %d (%d,%d)\n", i,
-                        (int)(stamp->x + 0.5), (int)(stamp->y + 0.5));
+                        (int)(stamp->x - 0.5), (int)(stamp->y - 0.5));
+                psStringAppend(&log, "Stamp %d (%d,%d): %f\n", i,
+                               (int)(stamp->x - 0.5), (int)(stamp->y - 0.5),
+                               fabsf(deviations->data.F32[i] - mean));
                 numRejected++;
                 for (int y = stamp->y - footprint; y <= stamp->y + footprint; y++) {
@@ -857,13 +908,10 @@
                 psFree(stamp->image1);
                 psFree(stamp->image2);
-                psFree(stamp->variance);
-                stamp->image1 = stamp->image2 = stamp->variance = NULL;
-                psFree(stamp->matrix1);
-                psFree(stamp->matrix2);
-                psFree(stamp->matrixX);
-                stamp->matrix1 = stamp->matrix2 = stamp->matrixX = NULL;
-                psFree(stamp->vector1);
-                psFree(stamp->vector2);
-                stamp->vector1 = stamp->vector2 = NULL;
+                psFree(stamp->weight);
+                stamp->image1 = stamp->image2 = stamp->weight = NULL;
+                psFree(stamp->matrix);
+                stamp->matrix = NULL;
+                psFree(stamp->vector);
+                stamp->vector = NULL;
             } else {
                 numGood++;
@@ -874,4 +922,10 @@
     }
     newMean /= numGood;
+
+    if (numRejected == 0) {
+        psStringAppend(&log, "<none>\n");
+    }
+    psLogMsg("psModules.imcombine", PS_LOG_DETAIL, "%s", log);
+    psFree(log);
 
     if (ds9) {
@@ -900,5 +954,5 @@
 
     psImage *polyValues = p_pmSubtractionPolynomial(NULL, kernels->spatialOrder, x, y); // Solved polynomial
-    psKernel *kernel = solvedKernel(NULL, kernels, polyValues, wantDual); // The appropriate kernel
+    psKernel *kernel = solvedKernel(NULL, kernels, polyValues, true, wantDual); // The appropriate kernel
     psFree(polyValues);
 
@@ -931,5 +985,5 @@
     psImage *polyValues = p_pmSubtractionPolynomial(NULL, kernels->spatialOrder, x, y);
 
-    psKernel *kernel = solvedKernel(NULL, kernels, polyValues, wantDual); // The appropriate kernel
+    psKernel *kernel = solvedKernel(NULL, kernels, polyValues, true, wantDual); // The appropriate kernel
     psFree(polyValues);
 
@@ -948,5 +1002,5 @@
 }
 
-#if 0
+#if 1
 psArray *pmSubtractionKernelSolutions(const pmSubtractionKernels *kernels, float x, float y, bool wantDual)
 {
@@ -956,15 +1010,41 @@
     PS_ASSERT_FLOAT_WITHIN_RANGE(y, -1.0, 1.0, NULL);
 
-    psArray *images = psArrayAlloc(solution->n - 1); // Images of each kernel to return
-    psVector *fakeSolution = psVectorAlloc(solution->n, PS_TYPE_F64); // Fake solution vector
-    psVectorInit(fakeSolution, 0.0);
-
-    for (int i = 0; i < solution->n - 1; i++) {
-        fakeSolution->data.F64[i] = solution->data.F64[i];
-        images->data[i] = pmSubtractionKernelImage(kernels, x, y, wantDual);
-        fakeSolution->data.F64[i] = 0.0;
-    }
-
-    psFree(fakeSolution);
+    psVector *solution = wantDual ? kernels->solution2 : kernels->solution1; // Solution of interest
+    psVector *backup = psVectorCopy(NULL, solution, PS_TYPE_F64);  // Backup version
+
+    int num = kernels->num;             // Number of kernel basis functions
+
+    psImage *polyValues = p_pmSubtractionPolynomial(NULL, kernels->spatialOrder, x, y); // Solved polynomial
+    psArray *images = psArrayAlloc(num + 1); // Images of each kernel to return
+
+    // The whole kernel
+    {
+        psKernel *kernel = solvedKernel(NULL, kernels, polyValues, true, wantDual); // The appropriate kernel
+        images->data[0] = psMemIncrRefCounter(kernel->image);
+        psFree(kernel);
+    }
+
+    // The parts
+    psVectorInit(solution, 0.0);
+    for (int i = 0; i < num; i++) {
+        solution->data.F64[i] = backup->data.F64[i];
+        psKernel *kernel = solvedKernel(NULL, kernels, polyValues, false, wantDual); // The appropriate kernel
+#if 0
+        int size = kernels->size;
+        double sum = 0.0;
+        for (int v = -size; v <= size; v++) {
+            for (int u = -size; u <= size; u++) {
+                sum += kernel->kernel[v][u];
+            }
+        }
+        fprintf(stderr, "Kernel %d: %lf\n", i, sum);
+#endif
+        images->data[i + 1] = psMemIncrRefCounter(kernel->image);
+        psFree(kernel);
+        solution->data.F64[i] = 0.0;
+    }
+    psFree(polyValues);
+    psVectorCopy(solution, backup, PS_TYPE_F64);
+    psFree(backup);
 
     return images;
@@ -979,5 +1059,5 @@
                                      psImage *convMask, // Output convolved mask
                                      const pmReadout *ro1, const pmReadout *ro2, // Input readouts
-                                     psImage *sys1, psImage *sys2, // Systematic error images
+                                     psImage *kernelErr1, psImage *kernelErr2, // Kernel error images
                                      psImage *subMask, // Input subtraction mask
                                      psImageMaskType maskBad, // Mask value to give bad pixels
@@ -998,18 +1078,18 @@
     // Only generate polynomial values every kernel footprint, since we have already assumed
     // (with the stamps) that it does not vary rapidly on this scale.
-    psImage *polyValues = p_pmSubtractionPolynomialFromCoords(NULL, kernels, numCols, numRows,
-                                                              xMin + x0 + size + 1,
-                                                              yMin + y0 + size + 1);
+    psImage *polyValues = p_pmSubtractionPolynomialFromCoords(NULL, kernels, xMin + x0 + size + 1,
+                                                              yMin + y0 + size + 1);        // Polynomial
     float background = doBG ? p_pmSubtractionSolutionBackground(kernels, polyValues) : 0.0; // Background term
 
     if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-        convolveRegion(out1->image, out1->variance, convMask, &kernelImage, &kernelVariance,
-                       ro1->image, ro1->variance, sys1, subMask, kernels, polyValues, background, *region,
-                       maskBad, maskPoor, poorFrac, useFFT, false);
+        convolveRegion(out1->image, out1->variance, out1->mask, &kernelImage, &kernelVariance,
+                       ro1->image, ro1->variance, kernelErr1, subMask, kernels, polyValues, background,
+                       *region, maskBad, maskPoor, poorFrac, useFFT, false);
     }
     if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-        convolveRegion(out2->image, out2->variance, convMask, &kernelImage, &kernelVariance,
-                       ro2->image, ro2->variance, sys2, subMask, kernels, polyValues, background, *region,
-                       maskBad, maskPoor, poorFrac, useFFT, kernels->mode == PM_SUBTRACTION_MODE_DUAL);
+        convolveRegion(out2->image, out2->variance, out2->mask, &kernelImage, &kernelVariance,
+                       ro2->image, ro2->variance, kernelErr2, subMask, kernels, polyValues, background,
+                       *region, maskBad, maskPoor, poorFrac, useFFT,
+                       kernels->mode == PM_SUBTRACTION_MODE_DUAL);
     }
 
@@ -1019,5 +1099,5 @@
 
     if ((kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) && ro1->mask) {
-        psImageMaskType **target = convMask->data.PS_TYPE_IMAGE_MASK_DATA; // Target mask
+        psImageMaskType **target = out1->mask->data.PS_TYPE_IMAGE_MASK_DATA; // Target mask
         psImageMaskType **source = ro1->mask->data.PS_TYPE_IMAGE_MASK_DATA; // Source mask
 
@@ -1029,5 +1109,5 @@
     }
     if ((kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) && ro2->mask) {
-        psImageMaskType **target = convMask->data.PS_TYPE_IMAGE_MASK_DATA; // Target mask
+        psImageMaskType **target = out2->mask->data.PS_TYPE_IMAGE_MASK_DATA; // Target mask
         psImageMaskType **source = ro2->mask->data.PS_TYPE_IMAGE_MASK_DATA; // Source mask
 
@@ -1056,6 +1136,6 @@
     const pmReadout *ro1 = args->data[7]; // Input readout 1
     const pmReadout *ro2 = args->data[8]; // Input readout 2
-    psImage *sys1 = args->data[9]; // Systematic error image 1
-    psImage *sys2 = args->data[10]; // Systematic error image 2
+    psImage *kernelErr1 = args->data[9]; // Kernel error image 1
+    psImage *kernelErr2 = args->data[10]; // Kernel error image 2
     psImage *subMask = args->data[11]; // Subtraction mask
     psImageMaskType maskBad = PS_SCALAR_VALUE(args->data[12], PS_TYPE_IMAGE_MASK_DATA); // Output mask value for bad pixels
@@ -1067,11 +1147,12 @@
     bool useFFT = PS_SCALAR_VALUE(args->data[18], U8); // Use FFT for convolution?
 
-    return subtractionConvolvePatch(numCols, numRows, x0, y0, out1, out2, convMask, ro1, ro2, sys1, sys2,
-                                    subMask, maskBad, maskPoor, poorFrac, region, kernels, doBG, useFFT);
+    return subtractionConvolvePatch(numCols, numRows, x0, y0, out1, out2, convMask, ro1, ro2, kernelErr1,
+                                    kernelErr2, subMask, maskBad, maskPoor, poorFrac, region, kernels,
+                                    doBG, useFFT);
 }
 
 bool pmSubtractionConvolve(pmReadout *out1, pmReadout *out2, const pmReadout *ro1, const pmReadout *ro2,
                            psImage *subMask, int stride, psImageMaskType maskBad, psImageMaskType maskPoor,
-                           float poorFrac, float sysError, const psRegion *region,
+                           float poorFrac, float kernelError, float covarFrac, const psRegion *region,
                            const pmSubtractionKernels *kernels, bool doBG, bool useFFT)
 {
@@ -1082,4 +1163,5 @@
         PM_ASSERT_READOUT_NON_NULL(ro1, false);
         PM_ASSERT_READOUT_IMAGE(ro1, false);
+        PM_ASSERT_READOUT_IMAGE(out1, false);
         numCols = ro1->image->numCols;
         numRows = ro1->image->numRows;
@@ -1091,4 +1173,5 @@
         PM_ASSERT_READOUT_NON_NULL(ro2, false);
         PM_ASSERT_READOUT_IMAGE(ro2, false);
+        PM_ASSERT_READOUT_IMAGE(out2, false);
         if (numCols == 0 && numRows == 0) {
             numCols = ro2->image->numCols;
@@ -1111,9 +1194,11 @@
     PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(poorFrac, 0.0, false);
     PS_ASSERT_FLOAT_LESS_THAN_OR_EQUAL(poorFrac, 1.0, false);
-    PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(sysError, 0.0, false);
-    PS_ASSERT_FLOAT_LESS_THAN_OR_EQUAL(sysError, 1.0, false);
+    PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(kernelError, 0.0, false);
+    PS_ASSERT_FLOAT_LESS_THAN_OR_EQUAL(kernelError, 1.0, false);
+    PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(covarFrac, 0.0, false);
+    PS_ASSERT_FLOAT_LESS_THAN(covarFrac, 1.0, false);
     if (region && psRegionIsNaN(*region)) {
         psString string = psRegionToString(*region);
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Input region (%s) contains NAN values", string);
+        psError(PM_ERR_PROG, true, "Input region (%s) contains NAN values", string);
         psFree(string);
         return false;
@@ -1124,85 +1209,31 @@
     bool threaded = pmSubtractionThreaded(); // Running threaded?
 
-    // Outputs
-    if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-        if (!out1->image) {
-            out1->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
-            // XXX if (threaded) {
-            // XXX     psMutexInit(out1->image);
-            // XXX }
-        }
-        if (ro1->variance) {
-            if (!out1->variance) {
-                out1->variance = psImageAlloc(numCols, numRows, PS_TYPE_F32);
-                // XXX if (threaded) {
-                // XXX     psMutexInit(out1->variance);
-                // XXX }
-            }
-            psImageInit(out1->variance, 0.0);
-        }
-    }
-    if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-        if (!out2->image) {
-            out2->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
-            // XXX if (threaded) {
-            // XXX     psMutexInit(out2->image);
-            // XXX }
-        }
-        if (ro2->variance) {
-            if (!out2->variance) {
-                out2->variance = psImageAlloc(numCols, numRows, PS_TYPE_F32);
-                // XXX if (threaded) {
-                // XXX     psMutexInit(out2->variance);
-                // XXX }
-            }
-            psImageInit(out2->variance, 0.0);
-        }
-    }
     psImage *convMask = NULL;           // Convolved mask image (common to inputs 1 and 2)
     if (subMask) {
-        // XXX if (threaded) {
-        // XXX     psMutexInit(subMask);
-        // XXX }
         if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-            if (!out1->mask) {
-                out1->mask = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK);
-            }
             convMask = out1->mask;
         }
         if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-            if (convMask) {
-                if (out2->mask) {
-                    psFree(out2->mask);
-                }
-                out2->mask = psMemIncrRefCounter(convMask);
-            } else {
-                if (!out2->mask) {
-                    out2->mask = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK);
-                }
+            if (!convMask) {
                 convMask = out2->mask;
             }
         }
-        psImageInit(convMask, 0);
-    }
-
-    psImage *sys1 = NULL, *sys2 = NULL; // Systematic error images
+    }
+
+    psImage *kernelErr1 = NULL, *kernelErr2 = NULL; // Kernel error images
+#ifdef USE_KERNEL_ERR
     if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-        sys1 = subtractionSysErrImage(ro1->image, sysError);
-        // XXX if (threaded && sys1) {
-        // XXX     psMutexInit(sys1);
-        // XXX }
+        kernelErr1 = subtractionKernelErrImage(ro1->image, kernelError);
     }
     if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-        sys2 = subtractionSysErrImage(ro2->image, sysError);
-        // XXX if (threaded && sys2) {
-        // XXX     psMutexInit(sys2);
-        // XXX }
-    }
+        kernelErr2 = subtractionKernelErrImage(ro2->image, kernelError);
+    }
+#endif
 
     int size = kernels->size;           // Half-size of kernel
 
     // Get region for convolution: [xMin:xMax,yMin:yMax]
-    int xMin = size, xMax = numCols - size;
-    int yMin = size, yMax = numRows - size;
+    int xMin = kernels->xMin + size, xMax = kernels->xMax - size;
+    int yMin = kernels->yMin + size, yMax = kernels->yMax - size;
     if (region) {
         xMin = PS_MAX(region->x0, xMin);
@@ -1247,26 +1278,7 @@
                 psArrayAdd(args, 1, (pmReadout*)ro1); // Casting away const
                 psArrayAdd(args, 1, (pmReadout*)ro2); // Casting away const
-                // Since adding to the array can impact the reference count, we need to lock
-                // XXX if (sys1) {
-                // XXX     psMutexLock(sys1);
-                // XXX }
-                psArrayAdd(args, 1, sys1);
-                // XXX if (sys1) {
-                // XXX     psMutexUnlock(sys1);
-                // XXX }
-                // XXX if (sys2) {
-                // XXX     psMutexLock(sys2);
-                // XXX }
-                psArrayAdd(args, 1, sys2);
-                // XXX if (sys2) {
-                // XXX     psMutexUnlock(sys2);
-                // XXX }
-                // XXX if (subMask) {
-                // XXX     psMutexLock(subMask);
-                // XXX }
+                psArrayAdd(args, 1, kernelErr1);
+                psArrayAdd(args, 1, kernelErr2);
                 psArrayAdd(args, 1, subMask);
-                // XXX if (subMask) {
-                // XXX     psMutexUnlock(subMask);
-                // XXX }
                 PS_ARRAY_ADD_SCALAR(args, maskBad, PS_TYPE_IMAGE_MASK);
                 PS_ARRAY_ADD_SCALAR(args, maskPoor, PS_TYPE_IMAGE_MASK);
@@ -1283,7 +1295,7 @@
                 psFree(job);
             } else {
-                subtractionConvolvePatch(numCols, numRows, x0, y0, out1, out2, convMask, ro1, ro2, sys1, sys2,
-                                         subMask, maskBad, maskPoor, poorFrac, subRegion, kernels, doBG,
-                                         useFFT);
+                subtractionConvolvePatch(numCols, numRows, x0, y0, out1, out2, convMask, ro1, ro2,
+                                         kernelErr1, kernelErr2, subMask, maskBad, maskPoor, poorFrac,
+                                         subRegion, kernels, doBG, useFFT);
             }
             psFree(subRegion);
@@ -1292,5 +1304,5 @@
 
     if (!psThreadPoolWait(false)) {
-        psError(PS_ERR_UNKNOWN, false, "Error waiting for threads.");
+        psError(psErrorCodeLast(), false, "Error waiting for threads.");
         return false;
     }
@@ -1307,25 +1319,17 @@
             psFree(job);
         }
-
-        // XXX if (subMask) {
-        // XXX     psMutexDestroy(subMask);
-        // XXX }
-        // XXX if (sys1) {
-        // XXX     psMutexDestroy(sys1);
-        // XXX }
-        // XXX if (sys2) {
-        // XXX     psMutexDestroy(sys2);
-        // XXX }
     }
     psImageConvolveSetThreads(oldThreads);
 
-    psFree(sys1);
-    psFree(sys2);
+    psFree(kernelErr1);
+    psFree(kernelErr2);
 
     // Calculate covariances
-    // This can take a while, so we only do it for a single instance
-    // XXX psImageCovarianceCalculate could be multithreaded
+    // This can be fairly involved, so we only do it for a single instance
+    // Enable threads for covariance calculation, since we're not threading on top of it.
+    oldThreads = psImageCovarianceSetThreads(true);
     if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
         psKernel *kernel = pmSubtractionKernel(kernels, 0.0, 0.0, false); // Convolution kernel
+        psKernelTruncate(kernel, covarFrac);
         out1->covariance = psImageCovarianceCalculate(kernel, ro1->covariance);
         psFree(kernel);
@@ -1334,7 +1338,9 @@
         psKernel *kernel = pmSubtractionKernel(kernels, 0.0, 0.0,
                                                kernels->mode == PM_SUBTRACTION_MODE_DUAL); // Conv. kernel
+        psKernelTruncate(kernel, covarFrac);
         out2->covariance = psImageCovarianceCalculate(kernel, ro2->covariance);
         psFree(kernel);
     }
+    psImageCovarianceSetThreads(oldThreads);
 
     // Copy anything that wasn't convolved
@@ -1376,5 +1382,4 @@
     }
 
-
     psLogMsg("psModules.imcombine", PS_LOG_INFO, "Convolve image: %f sec",
              psTimerClear("pmSubtractionConvolve"));
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtraction.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtraction.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtraction.h	(revision 27839)
@@ -68,6 +68,5 @@
                               const psVector *deviations, ///< Deviations for each stamp
                               psImage *subMask, ///< Subtraction mask
-                              float sigmaRej, ///< Number of RMS deviations above zero at which to reject
-                              int footprint ///< Half-size of stamp
+                              float sigmaRej ///< Number of RMS deviations above zero at which to reject
     );
 
@@ -94,8 +93,9 @@
 
 /// Generate images of the convolution kernel elements
-psArray *pmSubtractionKernelSolutions(const psVector *solution, ///< Solution vector
-                                      const pmSubtractionKernels *kernels, ///< Kernel parameters
-                                      float x, float y ///< Normalised position [-1,1] for images
+psArray *pmSubtractionKernelSolutions(const pmSubtractionKernels *kernels, ///< Kernel parameters
+                                      float x, float y, ///< Normalised position [-1,1] for images
+                                      bool wantDual ///< Calculate for the dual kernel?
     );
+
 
 /// Execute a thread job to convolve a patch of the image
@@ -113,5 +113,6 @@
                            psImageMaskType maskPoor, ///< Mask value to give poor pixels
                            float poorFrac, ///< Fraction for "poor"
-                           float sysError, ///< Relative systematic error
+                           float kernelError, ///< Relative systematic error in kernel
+                           float covarFrac,  ///< Truncation fraction for kernel before covariance calculation
                            const psRegion *region, ///< Region to convolve (or NULL)
                            const pmSubtractionKernels *kernels, ///< Kernel parameters
@@ -127,4 +128,11 @@
     );
 
+/// Return normalised coordinates
+void p_pmSubtractionPolynomialNormCoords(
+    float *xOut, float *yOut,           ///< Normalised coordinates, returned
+    float xIn, float yIn,               ///< Input coordinates
+    int xMin, int xMax, int yMin, int yMax ///< Bounds of validity
+    );
+
 /// Given (normalised) coordinates (x,y), generate a matrix where the elements (i,j) are x^i * y^j
 psImage *p_pmSubtractionPolynomial(psImage *output, ///< Output matrix, or NULL
@@ -138,5 +146,4 @@
 psImage *p_pmSubtractionPolynomialFromCoords(psImage *output, ///< Output matrix, or NULL
                                              const pmSubtractionKernels *kernels, ///< Kernel parameters
-                                             int numCols, int numRows, ///< Size of image of interest
                                              int x, int y ///< Position of interest
     );
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionAnalysis.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionAnalysis.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionAnalysis.c	(revision 27839)
@@ -16,11 +16,12 @@
 #define KERNEL_MOSAIC 2                 // Half-number of kernel instances in the mosaic image
 
-
-bool pmSubtractionAnalysis(psMetadata *analysis, pmSubtractionKernels *kernels, psRegion *region,
+//#define TESTING
+
+bool pmSubtractionAnalysis(psMetadata *analysis, psMetadata *header,
+                           pmSubtractionKernels *kernels, psRegion *region,
                            int numCols, int numRows)
 {
-    if (analysis) {
-        PS_ASSERT_METADATA_NON_NULL(analysis, false);
-    }
+    PS_ASSERT_METADATA_NON_NULL(analysis, false);
+    PS_ASSERT_METADATA_NON_NULL(header, false);
     PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, false);
     PM_ASSERT_SUBTRACTION_KERNELS_SOLUTION(kernels, false);
@@ -38,5 +39,11 @@
                          PS_DATA_REGION | PS_META_DUPLICATE_OK,
                          "Region over which subtraction was performed", subRegion);
+
+        psString string = psRegionToString(*subRegion);
         psFree(subRegion);
+
+        psMetadataAddStr(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_REGION, PS_META_DUPLICATE_OK,
+                         "Region over which subtraction was performed", string);
+        psFree(string);
     }
 
@@ -45,5 +52,7 @@
                      PS_DATA_UNKNOWN | PS_META_DUPLICATE_OK, "Subtraction kernels", kernels);
     psMetadataAddS32(analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_MODE,
-                     PS_META_DUPLICATE_OK, "Subtraction kernels", kernels->mode);
+                     PS_META_DUPLICATE_OK, "Subtraction mode", kernels->mode);
+    psMetadataAddS32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_MODE,
+                     PS_META_DUPLICATE_OK, "Subtraction mode", kernels->mode);
 
     // Realisations of kernel
@@ -65,5 +74,5 @@
                                                            false); // Image of the kernel
                 if (!kernel) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to generate kernel image.");
+                    psError(psErrorCodeLast(), false, "Unable to generate kernel image.");
                     psFree(convKernels);
                     return false;
@@ -72,5 +81,5 @@
                 if (psImageOverlaySection(convKernels, kernel, (i + KERNEL_MOSAIC) * fullSize,
                                           (j + KERNEL_MOSAIC) * fullSize, "=") == 0) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to overlay kernel image.");
+                    psError(psErrorCodeLast(), false, "Unable to overlay kernel image.");
                     psFree(kernel);
                     psFree(convKernels);
@@ -84,5 +93,5 @@
                                                       true); // Image of the kernel
                     if (!kernel) {
-                        psError(PS_ERR_UNKNOWN, false, "Unable to generate kernel image.");
+                        psError(psErrorCodeLast(), false, "Unable to generate kernel image.");
                         psFree(convKernels);
                         return false;
@@ -92,5 +101,5 @@
                                               (2 * KERNEL_MOSAIC + 1 + i + KERNEL_MOSAIC) * fullSize + 4,
                                               (j + KERNEL_MOSAIC) * fullSize, "=") == 0) {
-                        psError(PS_ERR_UNKNOWN, false, "Unable to overlay kernel image.");
+                        psError(psErrorCodeLast(), false, "Unable to overlay kernel image.");
                         psFree(kernel);
                         psFree(convKernels);
@@ -108,17 +117,21 @@
     }
 
-
-#if 0
+    // sample difference images
+    {
+        psMetadataAddArray(analysis, PS_LIST_TAIL, "SUBTRACTION.SAMPLE.STAMP.SET", PS_META_DUPLICATE_OK, "Sample Difference Stamps", kernels->sampleStamps);
+    }
+
+#ifdef TESTING
     // Generate images of the kernel components
     {
         psMetadata *header = psMetadataAlloc(); // Header
-        for (int i = 0; i < solution->n; i++) {
+        for (int i = 0; i < kernels->solution1->n; i++) {
             psString name = NULL;       // Header keyword
             psStringAppend(&name, "SOLN%04d", i);
-            psMetadataAddF64(header, PS_LIST_TAIL, name, 0, NULL, solution->data.F64[i]);
+            psMetadataAddF64(header, PS_LIST_TAIL, name, 0, NULL, kernels->solution1->data.F64[i]);
             psFree(name);
         }
-        psArray *kernelImages = pmSubtractionKernelSolutions(solution, kernels, 0.0, 0.0);
-        psFits *kernelFile = psFitsOpen("kernels.fits", "w");
+        psArray *kernelImages = pmSubtractionKernelSolutions(kernels, 0.0, 0.0, false);
+        psFits *kernelFile = psFitsOpen("kernels1.fits", "w");
         (void)psFitsWriteImageCube(kernelFile, header, kernelImages, NULL);
         psFitsClose(kernelFile);
@@ -126,44 +139,21 @@
         psFree(header);
     }
+    if (kernels->solution2) {
+        psMetadata *header = psMetadataAlloc(); // Header
+        for (int i = 0; i < kernels->solution2->n; i++) {
+            psString name = NULL;       // Header keyword
+            psStringAppend(&name, "SOLN%04d", i);
+            psMetadataAddF64(header, PS_LIST_TAIL, name, 0, NULL, kernels->solution2->data.F64[i]);
+            psFree(name);
+        }
+        psArray *kernelImages = pmSubtractionKernelSolutions(kernels, 0.0, 0.0, true);
+        psFits *kernelFile = psFitsOpen("kernels2.fits", "w");
+        (void)psFitsWriteImageCube(kernelFile, header, kernelImages, NULL);
+        psFitsClose(kernelFile);
+        psFree(kernelImages);
+        psFree(header);
+    }
 #endif
 
-
-    // Set the variance factors
-    float vf1 = 1.0, vf2 = 1.0;         // Variance factors for each image
-    switch (kernels->mode) {
-      case PM_SUBTRACTION_MODE_1:
-        vf1 = pmSubtractionVarianceFactor(kernels, 0.5, 0.5, false);
-        break;
-      case PM_SUBTRACTION_MODE_2:
-        vf2 = pmSubtractionVarianceFactor(kernels, 0.5, 0.5, false);
-        break;
-      case PM_SUBTRACTION_MODE_DUAL:
-        vf1 = pmSubtractionVarianceFactor(kernels, 0.5, 0.5, false);
-        vf2 = pmSubtractionVarianceFactor(kernels, 0.5, 0.5, true);
-        break;
-      default:
-        psAbort("Invalid subtraction mode: %x", kernels->mode);
-    }
-
-    // Weight by the area
-    if (region) {
-        float norm = (region->x1 - region->x0 + 1) * (region->y1 - region->y0 + 1) / (numCols * numRows);
-        vf1 *= norm;
-        vf2 *= norm;
-    }
-
-    // Update the variance factor
-#define UPDATE_VARFACTOR(VF, ANALYSIS) { \
-    psMetadataItem *vfItem = psMetadataLookup(analysis, ANALYSIS); \
-    if (vfItem) { \
-        psAssert(vfItem->type == PS_TYPE_F32, "Should be the type we said."); \
-        vfItem->data.F32 += VF; \
-    } else { \
-        psMetadataAddF32(analysis, PS_LIST_TAIL, ANALYSIS, 0, "Variance factor weighted by the area", VF); \
-    } \
-}
-
-    UPDATE_VARFACTOR(vf1, PM_SUBTRACTION_ANALYSIS_VARFACTOR_1);
-    UPDATE_VARFACTOR(vf2, PM_SUBTRACTION_ANALYSIS_VARFACTOR_2);
 
     // Kernel shape
@@ -171,5 +161,5 @@
         psImage *image = pmSubtractionKernelImage(kernels, 0.5, 0.5, false); // Image of the kernel
         if (!image) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to generate image of kernel.");
+            psError(psErrorCodeLast(), false, "Unable to generate image of kernel.");
             return false;
         }
@@ -201,10 +191,22 @@
         psFree(image);
 
-        psMetadataItem *item = psMetadataLookup(analysis, PM_SUBTRACTION_ANALYSIS_DECONV_MAX); // Previous
-        if (item) {
-            item->data.F32 = PS_MAX(item->data.F32, max);
-        } else {
-            psMetadataAddF32(analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_DECONV_MAX, 0,
-                             "Maximum deconvolution fraction", max);
+        {
+            psMetadataItem *item = psMetadataLookup(analysis, PM_SUBTRACTION_ANALYSIS_DECONV_MAX); // Previous
+            if (item) {
+                max = item->data.F32 = PS_MAX(item->data.F32, max);
+            } else {
+                psMetadataAddF32(analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_DECONV_MAX, 0,
+                                 "Maximum deconvolution fraction", max);
+            }
+        }
+
+        {
+            psMetadataItem *item = psMetadataLookup(header, PM_SUBTRACTION_ANALYSIS_DECONV_MAX); // Previous
+            if (item) {
+                item->data.F32 = max;
+            } else {
+                psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_DECONV_MAX, 0,
+                                 "Maximum deconvolution fraction", max);
+            }
         }
     }
@@ -214,5 +216,5 @@
         psImage *image = pmSubtractionKernelImage(kernels, 0.5, 0.5, false); // Image of the kernel
         if (!image) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to generate image of kernel.");
+            psError(psErrorCodeLast(), false, "Unable to generate image of kernel.");
             return false;
         }
@@ -254,13 +256,27 @@
         psMetadataAddF32(analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_MYY,
                          PS_META_DUPLICATE_OK, "Moment in yy", m02);
+
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_NORM,
+                         PS_META_DUPLICATE_OK, "Normalisation", m00);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_MX,
+                         PS_META_DUPLICATE_OK, "Moment in x", m10);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_MY,
+                         PS_META_DUPLICATE_OK, "Moment in y", m01);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_MXX,
+                         PS_META_DUPLICATE_OK, "Moment in xx", m20);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_MXY,
+                         PS_META_DUPLICATE_OK, "Moment in xy", m11);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_MYY,
+                         PS_META_DUPLICATE_OK, "Moment in yy", m02);
     }
 
     // Difference in background
     {
-        psImage *polyValues = p_pmSubtractionPolynomialFromCoords(NULL, kernels, numCols, numRows,
-                                                                  numCols / 2.0, numRows / 2.0); // Polynomial
+        psImage *polyValues = p_pmSubtractionPolynomial(NULL, kernels->spatialOrder, 0.0, 0.0); // Polynomial
         float bg = p_pmSubtractionSolutionBackground(kernels, polyValues); // Background difference
 
         psMetadataAddF32(analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_BGDIFF,
+                         PS_META_DUPLICATE_OK, "Background difference", bg);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_BGDIFF,
                          PS_META_DUPLICATE_OK, "Background difference", bg);
         psFree(polyValues);
@@ -275,4 +291,25 @@
         psMetadataAddF32(analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_DEV_RMS, 0, "RMS stamp deviation",
                          kernels->rms);
+
+        psMetadataAddS32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_STAMPS, 0, "Number of stamps",
+                         kernels->numStamps);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_DEV_MEAN, 0, "Mean stamp deviation",
+                         kernels->mean);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_DEV_RMS, 0, "RMS stamp deviation",
+                         kernels->rms);
+
+        psMetadataAddF32(analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_FSIGMA_RES_MEAN,  0, "Mean Fractional Sigma of Residuals", kernels->fSigResMean);
+        psMetadataAddF32(analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_FSIGMA_RES_STDEV, 0, "Mean Fractional Sigma of Residuals", kernels->fSigResStdev);
+        psMetadataAddF32(analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_FMIN_RES_MEAN,  0, "Mean Fractional Sigma of Residuals", kernels->fSigResMean);
+        psMetadataAddF32(analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_FMIN_RES_STDEV, 0, "Mean Fractional Sigma of Residuals", kernels->fSigResStdev);
+        psMetadataAddF32(analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_FMAX_RES_MEAN,  0, "Mean Fractional Sigma of Residuals", kernels->fSigResMean);
+        psMetadataAddF32(analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_FMAX_RES_STDEV, 0, "Mean Fractional Sigma of Residuals", kernels->fSigResStdev);
+
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_FSIGMA_RES_MEAN,  0, "Mean Fractional Sigma of Residuals", kernels->fSigResMean);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_FSIGMA_RES_STDEV, 0, "Mean Fractional Sigma of Residuals", kernels->fSigResStdev);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_FMIN_RES_MEAN,  0, "Mean Fractional Sigma of Residuals", kernels->fSigResMean);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_FMIN_RES_STDEV, 0, "Mean Fractional Sigma of Residuals", kernels->fSigResStdev);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_FMAX_RES_MEAN,  0, "Mean Fractional Sigma of Residuals", kernels->fSigResMean);
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_FMAX_RES_STDEV, 0, "Mean Fractional Sigma of Residuals", kernels->fSigResStdev);
     }
 
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionAnalysis.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionAnalysis.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionAnalysis.h	(revision 27839)
@@ -24,7 +24,15 @@
 #define PM_SUBTRACTION_ANALYSIS_DECONV_MAX   "SUBTRACTION.DECONV.MAX"   // Maximum deconvolution fraction
 
+#define PM_SUBTRACTION_ANALYSIS_FSIGMA_RES_MEAN  "SUBTRACTION.RES.FSIGMA.MEAN"      // RMS stamp deviation
+#define PM_SUBTRACTION_ANALYSIS_FSIGMA_RES_STDEV "SUBTRACTION.RES.FSIGMA.STDEV"      // RMS stamp deviation
+#define PM_SUBTRACTION_ANALYSIS_FMIN_RES_MEAN    "SUBTRACTION.RES.FMIN.MEAN"      // RMS stamp deviation
+#define PM_SUBTRACTION_ANALYSIS_FMIN_RES_STDEV   "SUBTRACTION.RES.FMIN.STDEV"      // RMS stamp deviation
+#define PM_SUBTRACTION_ANALYSIS_FMAX_RES_MEAN    "SUBTRACTION.RES.FMAX.MEAN"      // RMS stamp deviation
+#define PM_SUBTRACTION_ANALYSIS_FMAX_RES_STDEV   "SUBTRACTION.RES.FMAX.STDEV"      // RMS stamp deviation
+
 // Derive QA information about the subtraction
 bool pmSubtractionAnalysis(
     psMetadata *analysis,               ///< Metadata container for QA information
+    psMetadata *header,                 ///< Metadata container for QA information to put in header
     pmSubtractionKernels *kernels,      ///< Kernels
     psRegion *region,                   ///< Region for subtraction
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionDeconvolve.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionDeconvolve.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionDeconvolve.c	(revision 27839)
@@ -0,0 +1,282 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <fftw3.h>
+#include <pslib.h>
+
+#include "pmFPA.h"
+#include "pmSubtractionKernels.h"
+#include "pmSubtractionDeconvolve.h"
+#include "pmSubtractionStamps.h"
+#include "pmSubtractionVisual.h"
+
+// Lock FFTW access
+#define FFTW_LOCK \
+if (threaded) { \
+    psFFTLock(); \
+}
+// Unlock FFTW access
+#define FFTW_UNLOCK \
+if (threaded) { \
+    psFFTUnlock(); \
+}
+
+#define FFTW_PLAN_RIGOR FFTW_ESTIMATE   // How rigorous the FFTW planning is
+
+psKernel *pmSubtractionDeconvolveGauss (int size, float sigma) {
+
+    psKernel *kernel = psKernelAlloc (-size, size, -size, size);
+
+    // build the gaussian from 2 1-D Gaussians
+    psVector *vector = pmSubtractionKernelISIS(sigma, 0, size);
+
+    // generate 2D kernel, calculate moments
+    for (int v = -size, y = 0; v <= size; v++, y++) {
+	for (int u = -size, x = 0; u <= size; u++, x++) {
+	    double value = vector->data.F32[x] * vector->data.F32[y]; // Value of kernel
+	    kernel->kernel[v][u] = value;
+	}
+    }
+
+    psFree (vector);
+    return kernel;
+}
+
+// deconvolve kernelTarget by kernelConv to get the kernel which, when convolved
+// by kernelConv results in kernelTarget...
+// XXX using complex to complex, explicitly setting the imaginary part to zero
+psKernel *pmSubtractionDeconvolveKernel (psKernel *kernelTarg, psKernel *kernelConv) {
+
+    PS_ASSERT_KERNEL_NON_NULL(kernelTarg, NULL);
+    PS_ASSERT_KERNEL_NON_NULL(kernelConv, NULL);
+
+    bool threaded = psThreadPoolSize(); // Are we running threaded?
+
+    // Size of image
+    int numCols = kernelConv->image->numCols;
+    int numRows = kernelConv->image->numRows;
+
+    // kernel sizes
+    int xMin = kernelConv->xMin;
+    int xMax = kernelConv->xMax;
+    int yMin = kernelConv->yMin;
+    int yMax = kernelConv->yMax;
+    if (xMin != kernelTarg->xMin) goto escape;
+    if (xMax != kernelTarg->xMax) goto escape;
+    if (yMin != kernelTarg->yMin) goto escape;
+    if (yMax != kernelTarg->yMax) goto escape;
+
+    int numPixels = numCols * numRows; // Number of pixels in padded image
+
+    // operation is: Kt = FFT(kernelTarg), Kc = FFT(kernelConv)
+    // Kd = (Kt * Kc) / (Kc * Kc^*)
+
+    // Create data array containing the image and kernel
+    FFTW_LOCK;
+    // psF32 *dataTarg = fftwf_malloc(numPixels * PSELEMTYPE_SIZEOF(PS_TYPE_F32)); // Data for FFTW
+    // psF32 *dataConv = fftwf_malloc(numPixels * PSELEMTYPE_SIZEOF(PS_TYPE_F32)); // Data for FFTW
+    fftwf_complex *dataTarg = fftwf_malloc(numPixels * sizeof(fftwf_complex)); // Data for FFTW
+    fftwf_complex *dataConv = fftwf_malloc(numPixels * sizeof(fftwf_complex)); // Data for FFTW
+    FFTW_UNLOCK;
+
+    // size_t numBytes = numCols * PSELEMTYPE_SIZEOF(PS_TYPE_F32); // Number of bytes per image row
+
+    // copy data from kernelTarg image to dataTarg array
+    for (int y = 0; y < numRows; y++) {
+	for (int x = 0; x < numCols; x++) {
+	    dataTarg[x + y*numCols][0] = kernelTarg->image->data.F32[y][x];
+	    dataTarg[x + y*numCols][1] = 0.0;
+	}
+    }
+    
+    // kernel must be copied to corners of image (0,0 pixel is center of kernel)
+    // copy data from kernelConv image to dataConv array
+    int oy = 0;
+    for (int iy = 0; iy <= yMax; iy++, oy++) {
+	int ox = 0;
+	for (int ix = 0; ix <= xMax; ix++, ox++) {
+	    dataConv[ox + oy*numCols][0] = kernelConv->kernel[iy][ix];
+	    dataConv[ox + oy*numCols][1] = 0.0;
+	}
+	for (int ix = xMin; ix <= -1; ix++, ox++) {
+	    dataConv[ox + oy*numCols][0] = kernelConv->kernel[iy][ix];
+	    dataConv[ox + oy*numCols][1] = 0.0;
+	}
+    }
+    for (int iy = yMin; iy <= -1; iy++, oy++) {
+	int ox = 0;
+	for (int ix = 0; ix <= xMax; ix++, ox++) {
+	    dataConv[ox + oy*numCols][0] = kernelConv->kernel[iy][ix];
+	    dataConv[ox + oy*numCols][1] = 0.0;
+	}
+	for (int ix = xMin; ix <= -1; ix++, ox++) {
+	    dataConv[ox + oy*numCols][0] = kernelConv->kernel[iy][ix];
+	    dataConv[ox + oy*numCols][1] = 0.0;
+	}
+    }
+
+    // Do the forward FFTs
+    // Note that the FFT images have different size from the input
+    FFTW_LOCK;
+    fftwf_complex *fftTarg = fftwf_malloc(numCols * numRows * sizeof(fftwf_complex)); // FFT
+    fftwf_complex *fftConv = fftwf_malloc(numCols * numRows * sizeof(fftwf_complex)); // FFT
+    FFTW_UNLOCK;
+
+    FFTW_LOCK;
+    fftwf_plan forwardTarg = fftwf_plan_dft_2d(numRows, numCols, dataTarg, fftTarg, FFTW_FORWARD, FFTW_PLAN_RIGOR);
+    fftwf_plan forwardConv = fftwf_plan_dft_2d(numRows, numCols, dataConv, fftConv, FFTW_FORWARD, FFTW_PLAN_RIGOR);
+    FFTW_UNLOCK;
+
+    fftwf_execute(forwardTarg);
+    fftwf_execute(forwardConv);
+
+    FFTW_LOCK;
+    fftwf_destroy_plan(forwardTarg);
+    fftwf_destroy_plan(forwardConv);
+    FFTW_UNLOCK;
+
+    // Combine the two transforms 
+    // Targ = Tr + iTi, Conv = Cr + iCi
+    // Deco = Dr + iDi
+    // (Dr + i Di) = (Tr + iTi) / (Cr + iCi)
+    // (Dr + i Di) = (Tr + iTi) * (Cr - iCi) / (Cr^2 - Ci^2)
+
+    // but anywhere Cr^2 - Ci^2 < 1e-7 of the max, mask it
+
+    // the X dimension is halved by FFTW
+    // int numColsOut = numCols / 2 + 1;
+
+    // generate Det = Cr^2 - Ci^2
+    float maxValue = 0.0;
+    psImage *det = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tR  = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tI  = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *cR  = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *cI  = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    for (int iy = 0; iy < numRows; iy++) {
+	for (int ix = 0; ix < numCols; ix++) {
+	    float convReal = fftConv[ix + iy*numCols][0];
+	    float convImag = fftConv[ix + iy*numCols][1];
+	    det->data.F32[iy][ix] = convReal*convReal - convImag*convImag;
+	    maxValue = PS_MAX(maxValue, fabs(det->data.F32[iy][ix]));
+
+	    tR->data.F32[iy][ix] = fftTarg[ix + iy*numCols][0];
+	    tI->data.F32[iy][ix] = fftTarg[ix + iy*numCols][1];
+	    cR->data.F32[iy][ix] = fftConv[ix + iy*numCols][0];
+	    cI->data.F32[iy][ix] = fftConv[ix + iy*numCols][1];
+	}
+    }
+
+    // pmSubtractionVisualShowSubtraction (det, tR, tI);
+    // pmSubtractionVisualShowSubtraction (det, cR, cI);
+
+# if 1
+# define TOL 1e-7
+    float limit = TOL*maxValue;
+    // generate Deco = targ * conv^* / (Cr^2 - Ci^2)
+    for (int iy = 0; iy < numRows; iy++) {
+	for (int ix = 0; ix < numCols; ix++) {
+	    float targReal = fftTarg[ix + iy*numCols][0];
+	    float targImag = fftTarg[ix + iy*numCols][1];
+	    float convReal = fftConv[ix + iy*numCols][0];
+	    float convImag = fftConv[ix + iy*numCols][1];
+	    if (fabs(det->data.F32[iy][ix]) < limit) {
+		fftTarg[ix + iy*numCols][0] = 0.0;
+		fftTarg[ix + iy*numCols][1] = 0.0;
+	    } else {
+		fftTarg[ix + iy*numCols][0] = (targReal*convReal + targImag*convImag) / det->data.F32[iy][ix];
+		fftTarg[ix + iy*numCols][1] = (targImag*convReal - targReal*convImag) / det->data.F32[iy][ix];
+		// fftTarg[ix + iy*numCols][0] = (targReal*convReal + targImag*convImag);
+		// fftTarg[ix + iy*numCols][1] = (targImag*convReal - targReal*convImag);
+	    }
+	}
+    }
+# else
+    for (int iy = 0; iy < numRows; iy++) {
+	for (int ix = 0; ix < numCols; ix++) {
+	    float targReal = fftTarg[ix + iy*numCols][0];
+	    float targImag = fftTarg[ix + iy*numCols][1];
+	    float convReal = fftConv[ix + iy*numCols][0];
+	    float convImag = fftConv[ix + iy*numCols][1];
+	    fftTarg[ix + iy*numCols][0] = targReal*convReal - targImag*convImag;
+	    fftTarg[ix + iy*numCols][1] = targImag*convReal + targReal*convImag;
+	}
+    }
+# endif
+
+    for (int iy = 0; iy < numRows; iy++) {
+	for (int ix = 0; ix < numCols; ix++) {
+	    tR->data.F32[iy][ix] = fftTarg[ix + iy*numCols][0];
+	    tI->data.F32[iy][ix] = fftTarg[ix + iy*numCols][1];
+	}
+    }
+    // pmSubtractionVisualShowSubtraction (det, tR, tI);
+
+    // Do the backward FFT
+    FFTW_LOCK;
+    fftwf_plan backward = fftwf_plan_dft_2d(numRows, numCols, fftTarg, dataTarg, FFTW_BACKWARD, FFTW_PLAN_RIGOR);
+    FFTW_UNLOCK;
+
+    fftwf_execute(backward);
+
+    FFTW_LOCK;
+    fftwf_destroy_plan(backward);
+    fftwf_free(fftTarg);
+    fftwf_free(fftConv);
+    FFTW_UNLOCK;
+
+    psKernel *output = psKernelAlloc (kernelTarg->xMin, kernelTarg->xMax, kernelTarg->yMin, kernelTarg->yMax);
+    for (int y = 0; y < numRows; y++) {
+	for (int x = 0; x < numCols; x++) {
+	    output->image->data.F32[y][x] = dataTarg[x + y*numCols][0];
+	}
+    }
+
+    FFTW_LOCK;
+    fftwf_free(dataTarg);
+    fftwf_free(dataConv);
+    FFTW_UNLOCK;
+
+    return output;
+
+ escape:
+    psError(PS_ERR_BAD_PARAMETER_VALUE, true, "mismatch between kernel and image");
+    return NULL;
+
+}
+
+bool pmSubtractionDeconvolutionTest (int order) {
+
+    float sigma = 1.0;
+    int size = 31;
+
+    // generate a Hermite polynomial 
+    psVector *xKernel = pmSubtractionKernelHERM(sigma, order, size); // x Kernel
+    psVector *yKernel = pmSubtractionKernelHERM(sigma, order, size); // y Kernel
+    psKernel *kernelTarget = psKernelAlloc(-size, size, -size, size);	// Kernel
+
+    // generate 2D kernel, calculate moments
+    for (int v = -size, y = 0; v <= size; v++, y++) {
+	for (int u = -size, x = 0; u <= size; u++, x++) {
+	    double value = xKernel->data.F32[x] * yKernel->data.F32[y]; // Value of kernel
+	    kernelTarget->kernel[v][u] = value;
+	}
+    }
+
+    // Gaussian convolution kernel
+    psKernel *kernelGauss = pmSubtractionDeconvolveGauss (size, 3.0);
+
+    // deconvolve the target by the gaussian:
+    psKernel *kernel = pmSubtractionDeconvolveKernel(kernelTarget, kernelGauss); // Kernel
+
+    // re-convolve the kernel
+    psImage *kernelConv = psImageConvolveFFT(NULL, kernel->image, NULL, 0, kernelGauss);
+    pmSubtractionVisualShowSubtraction (kernelTarget->image, kernel->image, kernelConv);
+
+    return true;
+
+}
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionDeconvolve.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionDeconvolve.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionDeconvolve.h	(revision 27839)
@@ -0,0 +1,15 @@
+#ifndef PM_SUBTRACTION_DECONVOLVE_H
+#define PM_SUBTRACTION_DECONVOLVE_H
+
+/* these function support deconvolution operations used to generate deconvolved kernels.  These
+   are kernels which, when convolved with the image sources, will yield a nearly orthonormal
+   basis set.  The analysis starts with an orthonormal basis set (eg, Hermitian functions) and
+   deconvolves those basis functions with a Gaussian approximating the Gaussian of the image of
+   interest */
+
+psKernel *pmSubtractionDeconvolveGauss (int size, float sigma);
+psKernel *pmSubtractionDeconvolveKernel (psKernel *kernelTarg, psKernel *kernelConv);
+
+bool pmSubtractionDeconvolutionTest (int order);
+
+# endif
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionEquation.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionEquation.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionEquation.c	(revision 27839)
@@ -7,4 +7,5 @@
 #include <pslib.h>
 
+#include "pmErrorCodes.h"
 #include "pmSubtraction.h"
 #include "pmSubtractionKernels.h"
@@ -15,7 +16,9 @@
 #include "pmSubtractionVisual.h"
 
-// #define TESTING                         // TESTING output for debugging; may not work with threads!
-
-#define USE_VARIANCE                    // Include variance in equation?
+//#define TESTING                         // TESTING output for debugging; may not work with threads!
+
+//#define USE_WEIGHT                      // Include weight (1/variance) in equation?
+//#define USE_WINDOW                      // Include weight (1/variance) in equation?
+
 
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -23,392 +26,540 @@
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
-// Calculate the sum over a stamp product
-static inline double calculateSumProduct(const psKernel *image1, // First image in multiplication
-                                         const psKernel *image2, // Second image in multiplication
-                                         const psKernel *variance, // Variance image
-                                         int footprint // (Half-)Size of stamp
-    )
+// Calculate the least-squares matrix and vector
+static bool calculateMatrixVector(psImage *matrix, // Least-squares matrix, updated
+                                  psVector *vector, // Least-squares vector, updated
+                                  double *norm,     // Normalisation, updated
+                                  const psKernel *input, // Input image (target)
+                                  const psKernel *reference, // Reference image (convolution source)
+                                  const psKernel *weight,  // Weight image
+                                  const psKernel *window,  // Window image
+                                  const psArray *convolutions,         // Convolutions for each kernel
+                                  const pmSubtractionKernels *kernels, // Kernels
+                                  const psImage *polyValues, // Spatial polynomial values
+                                  int footprint, // (Half-)Size of stamp
+                                  int normWindow, // Window (half-)size for normalisation measurement
+                                  const pmSubtractionEquationCalculationMode mode
+                                  )
 {
-    double sum = 0.0;                   // Sum of the image products
+    // (I - R * sum_i a_i k_i - g) (R * k_j) = 0
+    // I C_j = sum_i C_i C_j
+
+    // Background: C_i = 1.0
+    // Normalisation: C_i = R
+
+    int numKernels = kernels->num;                      // Number of kernels
+    int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+    int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index in matrix for background
+    int spatialOrder = kernels->spatialOrder;       // Order of spatial variation
+    int numPoly = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of polynomial terms
+    double poly[numPoly];                                 // Polynomial terms
+    double poly2[numPoly][numPoly];                       // Polynomial-polynomial values
+
+    // Evaluate polynomial-polynomial terms
+    // XXX we can skip this if we are not calculating kernel coeffs
+    for (int iyOrder = 0, iIndex = 0; iyOrder <= spatialOrder; iyOrder++) {
+        for (int ixOrder = 0; ixOrder <= spatialOrder - iyOrder; ixOrder++, iIndex++) {
+            double iPoly = polyValues->data.F64[iyOrder][ixOrder]; // Value of polynomial
+            poly[iIndex] = iPoly;
+            for (int jyOrder = 0, jIndex = 0; jyOrder <= spatialOrder; jyOrder++) {
+                for (int jxOrder = 0; jxOrder <= spatialOrder - jyOrder; jxOrder++, jIndex++) {
+                    double jPoly = polyValues->data.F64[jyOrder][jxOrder];
+                    poly2[iIndex][jIndex] = iPoly * jPoly;
+                }
+            }
+        }
+    }
+
+    // initialize the matrix and vector for NOP on all coeffs.  we only fill in the coeffs we
+    // choose to calculate
+    psImageInit(matrix, 0.0);
+    psVectorInit(vector, 1.0);
+    for (int i = 0; i < matrix->numCols; i++) {
+        matrix->data.F64[i][i] = 1.0;
+    }
+
+    // the order of the elements in the matrix and vector is:
+    // [kernel 0, x^0 y^0][kernel 1 x^0 y^0]...[kernel N, x^0 y^0]
+    // [kernel 0, x^1 y^0][kernel 1 x^1 y^0]...[kernel N, x^1 y^0]
+    // [kernel 0, x^n y^m][kernel 1 x^n y^m]...[kernel N, x^n y^m]
+    // normalization
+    // bg 0, bg 1, bg 2 (only 0 is currently used?)
+
+    for (int i = 0; i < numKernels; i++) {
+        psKernel *iConv = convolutions->data[i]; // Convolution for index i
+        for (int j = i; j < numKernels; j++) {
+            psKernel *jConv = convolutions->data[j]; // Convolution for index j
+
+            double sumCC = 0.0;         // Sum of convolution products
+            for (int y = - footprint; y <= footprint; y++) {
+                for (int x = - footprint; x <= footprint; x++) {
+                    double cc = iConv->kernel[y][x] * jConv->kernel[y][x];
+                    if (weight) {
+                        cc *= weight->kernel[y][x];
+                    }
+                    if (window) {
+                        cc *= window->kernel[y][x];
+                    }
+                    sumCC += cc;
+                }
+            }
+
+            // Spatial variation of kernel coeffs
+            if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+                for (int iTerm = 0, iIndex = i; iTerm < numPoly; iTerm++, iIndex += numKernels) {
+                    for (int jTerm = 0, jIndex = j; jTerm < numPoly; jTerm++, jIndex += numKernels) {
+                        double value = sumCC * poly2[iTerm][jTerm];
+                        matrix->data.F64[iIndex][jIndex] = value;
+                        matrix->data.F64[jIndex][iIndex] = value;
+                    }
+                }
+            }
+        }
+
+        double sumRC = 0.0;             // Sum of the reference-convolution products
+        double sumIC = 0.0;             // Sum of the input-convolution products
+        double sumC = 0.0;              // Sum of the convolution
+        for (int y = - footprint; y <= footprint; y++) {
+            for (int x = - footprint; x <= footprint; x++) {
+                float conv = iConv->kernel[y][x];
+                float in = input->kernel[y][x];
+                float ref = reference->kernel[y][x];
+                double ic = in * conv;
+                double rc = ref * conv;
+                double c = conv;
+                if (weight) {
+                    float wtVal = weight->kernel[y][x];
+                    ic *= wtVal;
+                    rc *= wtVal;
+                    c *= wtVal;
+                }
+                if (window) {
+                    float winVal = window->kernel[y][x];
+                    ic *= winVal;
+                    rc *= winVal;
+                    c  *= winVal;
+                }
+                sumIC += ic;
+                sumRC += rc;
+                sumC += c;
+            }
+        }
+        // Spatial variation
+        for (int iTerm = 0, iIndex = i; iTerm < numPoly; iTerm++, iIndex += numKernels) {
+            double normTerm = sumRC * poly[iTerm];
+            double bgTerm = sumC * poly[iTerm];
+            if ((mode & PM_SUBTRACTION_EQUATION_NORM) && (mode & PM_SUBTRACTION_EQUATION_KERNELS)) {
+                matrix->data.F64[iIndex][normIndex] = normTerm;
+                matrix->data.F64[normIndex][iIndex] = normTerm;
+            }
+            if ((mode & PM_SUBTRACTION_EQUATION_BG) && (mode & PM_SUBTRACTION_EQUATION_KERNELS)) {
+                matrix->data.F64[iIndex][bgIndex] = bgTerm;
+                matrix->data.F64[bgIndex][iIndex] = bgTerm;
+            }
+            if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+                vector->data.F64[iIndex] = sumIC * poly[iTerm];
+                if (!(mode & PM_SUBTRACTION_EQUATION_NORM)) {
+                    // subtract norm * sumRC * poly[iTerm]
+                    psAssert (kernels->solution1, "programming error: define solution first!");
+                    int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+                    double norm = fabs(kernels->solution1->data.F64[normIndex]);  // Normalisation
+                    vector->data.F64[iIndex] -= norm * normTerm;
+                }
+            }
+        }
+    }
+
+    double sumRR = 0.0;                 // Sum of the reference product
+    double sumIR = 0.0;                 // Sum of the input-reference product
+    double sum1 = 0.0;                  // Sum of the background
+    double sumR = 0.0;                  // Sum of the reference
+    double sumI = 0.0;                  // Sum of the input
+    double normI1 = 0.0, normI2 = 0.0;  // Sum of I_1 and I_2 within the normalisation window
     for (int y = - footprint; y <= footprint; y++) {
         for (int x = - footprint; x <= footprint; x++) {
-            double value = image1->kernel[y][x] * image2->kernel[y][x];
-#ifdef USE_VARIANCE
-            value /= variance->kernel[y][x];
-#endif
-            sum += value;
-        }
-    }
-    return sum;
-}
-
-// Calculate a single element of the least-squares matrix, with the polynomial expansions in one direction
-static inline bool calculateMatrixElement1(psImage *matrix, // Matrix to calculate
-                                           int i, int j, // Coordinates of element
-                                           const psKernel *image1, // First image in multiplication
-                                           const psKernel *image2, // Second image in multiplication
-                                           const psKernel *variance, // Variance image
-                                           const psImage *polyValues, // Spatial polynomial values
-                                           int numKernels, // Number of kernel basis functions
-                                           int footprint, // (Half-)Size of stamp
-                                           int spatialOrder, // Maximum order of spatial variation
-                                           bool symmetric // Is the matrix symmetric?
-    )
+            double in = input->kernel[y][x];
+            double ref = reference->kernel[y][x];
+            double ir = in * ref;
+            double rr = PS_SQR(ref);
+            double one = 1.0;
+
+            if (PS_SQR(x) + PS_SQR(y) <= PS_SQR(normWindow)) {
+                normI1 += ref;
+                normI2 += in;
+            }
+
+            if (weight) {
+                float wtVal = weight->kernel[y][x];
+                rr *= wtVal;
+                ir *= wtVal;
+                in *= wtVal;
+                ref *= wtVal;
+                one *= wtVal;
+            }
+            if (window) {
+                float  winVal = window->kernel[y][x];
+                rr      *= winVal;
+                ir      *= winVal;
+                in      *= winVal;
+                ref *= winVal;
+                one *= winVal;
+            }
+            sumRR += rr;
+            sumIR += ir;
+            sumR += ref;
+            sumI += in;
+            sum1 += one;
+        }
+    }
+
+    *norm = normI2 / normI1;
+
+    if (mode & PM_SUBTRACTION_EQUATION_NORM) {
+        matrix->data.F64[normIndex][normIndex] = sumRR;
+        vector->data.F64[normIndex] = sumIR;
+        // subtract sum over kernels * kernel solution
+    }
+    if (mode & PM_SUBTRACTION_EQUATION_BG) {
+        matrix->data.F64[bgIndex][bgIndex] = sum1;
+        vector->data.F64[bgIndex] = sumI;
+    }
+    if ((mode & PM_SUBTRACTION_EQUATION_NORM) && (mode & PM_SUBTRACTION_EQUATION_BG)) {
+        matrix->data.F64[normIndex][bgIndex] = sumR;
+        matrix->data.F64[bgIndex][normIndex] = sumR;
+    }
+
+    // check for any NAN values in the result, skip if found:
+    for (int iy = 0; iy < matrix->numRows; iy++) {
+        for (int ix = 0; ix < matrix->numCols; ix++) {
+            if (!isfinite(matrix->data.F64[iy][ix])) {
+                fprintf (stderr, "WARNING: NAN in matrix\n");
+                return false;
+            }
+        }
+    }
+    for (int ix = 0; ix < vector->n; ix++) {
+        if (!isfinite(vector->data.F64[ix])) {
+            fprintf (stderr, "WARNING: NAN in vector\n");
+            return false;
+        }
+    }
+
+    return true;
+}
+
+
+// Calculate the least-squares matrix and vector for dual convolution
+static bool calculateDualMatrixVector(psImage *matrix, // Least-squares matrix, updated
+                                      psVector *vector, // Least-squares vector, updated
+                                      double *norm,     // Normalisation, updated
+                                      const psKernel *image1, // Image 1
+                                      const psKernel *image2, // Image 2
+                                      const psKernel *weight,  // Weight image
+                                      const psKernel *window,  // Window image
+                                      const psArray *convolutions1, // Convolutions of image 1 for each kernel
+                                      const psArray *convolutions2, // Convolutions of image 2 for each kernel
+                                      const pmSubtractionKernels *kernels, // Kernels
+                                      const psImage *polyValues, // Spatial polynomial values
+                                      int footprint, // (Half-)Size of stamp
+                                      int normWindow, // Window (half-)size for normalisation measurement
+                                      const pmSubtractionEquationCalculationMode mode
+                                      )
 {
-    double sum = calculateSumProduct(image1, image2, variance, footprint); // Sum of the image products
-    if (!isfinite(sum)) {
-        return false;
-    }
-
-    // Generate the pseudo-convolutions from the spatial polynomial terms
-    for (int iyOrder = 0, iIndex = i; iyOrder <= spatialOrder; iyOrder++) {
-        for (int ixOrder = 0; ixOrder <= spatialOrder - iyOrder; ixOrder++, iIndex += numKernels) {
-            double convPoly = sum * polyValues->data.F64[iyOrder][ixOrder];
-
-            assert(iIndex < matrix->numRows && j < matrix->numCols);
-
-            matrix->data.F64[iIndex][j] = convPoly;
-            if (symmetric) {
-
-                assert(iIndex < matrix->numCols && j < matrix->numRows);
-
-                matrix->data.F64[j][iIndex] = convPoly;
-            }
-        }
-    }
-    return true;
-}
-
-// Calculate a single element of the least-squares matrix, with the polynomial expansions in both directions
-static inline bool calculateMatrixElement2(psImage *matrix, // Matrix to calculate
-                                           int i, int j, // Coordinates of element
-                                           const psKernel *image1, // First image in multiplication
-                                           const psKernel *image2, // Second image in multiplication
-                                           const psKernel *variance, // Variance image
-                                           const psImage *polyValues, // Spatial polynomial values
-                                           int numKernels, // Number of kernel basis functions
-                                           int footprint, // (Half-)Size of stamp
-                                           int spatialOrder, // Maximum order of spatial variation
-                                           bool symmetric // Is the matrix symmetric?
-    )
-{
-    double sum = calculateSumProduct(image1, image2, variance, footprint); // Sum of the image products
-    if (!isfinite(sum)) {
-        return false;
-    }
-
-    // Generate the pseudo-convolutions from the spatial polynomial terms
-    for (int iyOrder = 0, iIndex = i; iyOrder <= spatialOrder; iyOrder++) {
-        for (int ixOrder = 0; ixOrder <= spatialOrder - iyOrder; ixOrder++, iIndex += numKernels) {
+    int numKernels = kernels->num;                      // Number of kernels
+    int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+    int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index in matrix for background
+    int spatialOrder = kernels->spatialOrder;       // Order of spatial variation
+    int numPoly = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of polynomial terms
+    double poly[numPoly];                                 // Polynomial terms
+    double poly2[numPoly][numPoly];                       // Polynomial-polynomial values
+
+    int numBackground = PM_SUBTRACTION_POLYTERMS(kernels->bgOrder); // Number of background terms
+    int numParams = numKernels * numPoly + 1 + numBackground;       // Number of regular parameters
+    int numParams2 = numKernels * numPoly;                          // Number of additional parameters for dual
+    int numDual = numParams + numParams2;                           // Total number of parameters for dual
+
+    psAssert(matrix &&
+             matrix->type.type == PS_TYPE_F64 &&
+             matrix->numCols == numDual &&
+             matrix->numRows == numDual,
+             "Least-squares matrix is bad.");
+    psAssert(vector &&
+             vector->type.type == PS_TYPE_F64 &&
+             vector->n == numDual,
+             "Least-squares vector is bad.");
+
+    // Evaluate polynomial-polynomial terms
+    for (int iyOrder = 0, iIndex = 0; iyOrder <= spatialOrder; iyOrder++) {
+        for (int ixOrder = 0; ixOrder <= spatialOrder - iyOrder; ixOrder++, iIndex++) {
             double iPoly = polyValues->data.F64[iyOrder][ixOrder]; // Value of polynomial
-            for (int jyOrder = 0, jIndex = j; jyOrder <= spatialOrder; jyOrder++) {
-                for (int jxOrder = 0; jxOrder <= spatialOrder - jyOrder; jxOrder++, jIndex += numKernels) {
-                    double convPoly = sum * iPoly * polyValues->data.F64[jyOrder][jxOrder];
-
-                    assert(iIndex < matrix->numRows && jIndex < matrix->numCols);
-
-                    matrix->data.F64[iIndex][jIndex] = convPoly;
-                    if (symmetric) {
-
-                        assert(iIndex < matrix->numCols && jIndex < matrix->numRows);
-
-                        matrix->data.F64[jIndex][iIndex] = convPoly;
-                    }
-                }
-            }
-        }
-    }
-    return true;
-}
-
-// Calculate the square part of the matrix derived from multiplying convolutions
-static bool calculateMatrixSquare(psImage *matrix, // Matrix to calculate
-                                  const psArray *convolutions1, // Convolutions for element 1
-                                  const psArray *convolutions2, // Convolutions for element 2
-                                  const psKernel *variance, // Variance image
-                                  const psImage *polyValues, // Polynomial values
-                                  int numKernels, // Number of kernel basis functions
-                                  int spatialOrder, // Order of spatial variation
-                                  int footprint // Half-size of stamp
-                                  )
-{
-    bool symmetric = (convolutions1 == convolutions2 ? true : false); // Is matrix symmetric?
+            poly[iIndex] = iPoly;
+            for (int jyOrder = 0, jIndex = 0; jyOrder <= spatialOrder; jyOrder++) {
+                for (int jxOrder = 0; jxOrder <= spatialOrder - jyOrder; jxOrder++, jIndex++) {
+                    double jPoly = polyValues->data.F64[jyOrder][jxOrder];
+                    poly2[iIndex][jIndex] = iPoly * jPoly;
+                }
+            }
+        }
+    }
+
+
+    // initialize the matrix and vector for NOP on all coeffs.  we only fill in the coeffs we
+    // choose to calculate
+    psImageInit(matrix, 0.0);
+    psVectorInit(vector, 1.0);
+    for (int i = 0; i < matrix->numCols; i++) {
+        matrix->data.F64[i][i] = 1.0;
+    }
 
     for (int i = 0; i < numKernels; i++) {
-        psKernel *iConv = convolutions1->data[i]; // Convolution for i-th element
-
-        for (int j = (symmetric ? i : 0); j < numKernels; j++) {
-            psKernel *jConv = convolutions2->data[j]; // Convolution for j-th element
-
-            if (!calculateMatrixElement2(matrix, i, j, iConv, jConv, variance, polyValues, numKernels,
-                                         footprint, spatialOrder, symmetric)) {
-                psTrace("psModules.imcombine", 2, "Bad sumCC at %d, %d", i, j);
-                return false;
-            }
-        }
-    }
-
-    return true;
-}
-
-// Calculate least-squares matrix and vector
-static bool calculateMatrix(psImage *matrix, // Matrix to calculate
-                            const pmSubtractionKernels *kernels, // Kernel components
-                            const psArray *convolutions, // Convolutions of source with kernels
-                            const psKernel *input, // Input stamp, or NULL
-                            const psKernel *variance, // Variance stamp
-                            const psImage *polyValues, // Spatial polynomial values
-                            int footprint, // (Half-)Size of stamp
-                            bool normAndBG // Calculate normalisation and background terms?
-    )
-{
-    int numKernels = kernels->num;      // Number of kernel components
-    int spatialOrder = kernels->spatialOrder; // Maximum order of spatial variation
-    int numSpatial = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of spatial variation terms
-    int bgOrder = kernels->bgOrder;     // Maximum order of background fit
-    int numBackground = normAndBG ? PM_SUBTRACTION_POLYTERMS(bgOrder) : 0; // Number of background terms
-    int numTerms = numKernels * numSpatial + (normAndBG ? 1 + numBackground : 0); // Total number of terms
-    assert(matrix);
-    assert(matrix->numCols == matrix->numRows);
-    assert(matrix->numCols == numTerms);
-    assert(convolutions && convolutions->n == numKernels);
-    assert(polyValues);
-    assert(!normAndBG || input);        // If we want the normalisation and BG, then we need the input image
-
-    // Square part of the matrix (convolution-convolution products)
-    if (!calculateMatrixSquare(matrix, convolutions, convolutions, variance, polyValues, numKernels,
-                               spatialOrder, footprint)) {
-        return false;
-    }
-
-    // XXX To support higher-order background model than simply constant, the below code needs to be updated.
-    if (normAndBG) {
-        int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
-        int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index in matrix for background
-
-        for (int i = 0; i < numKernels; i++) {
-            psKernel *conv = convolutions->data[i]; // Convolution for i-th element
-
-            // Normalisation-convolution terms
-            if (!calculateMatrixElement1(matrix, i, normIndex, conv, input, variance, polyValues, numKernels,
-                                         footprint, spatialOrder, true)) {
-                psTrace("psModules.imcombine", 2, "Bad sumIC at %d", i);
-                return false;
-            }
-
-            // Background-convolution terms
-            double sumC = 0.0;          // Sum of the convolution
+        psKernel *iConv1 = convolutions1->data[i]; // Convolution 1 for index i
+        psKernel *iConv2 = convolutions2->data[i]; // Convolution 2 for index i
+        for (int j = i; j < numKernels; j++) {
+            psKernel *jConv1 = convolutions1->data[j]; // Convolution 1 for index j
+            psKernel *jConv2 = convolutions2->data[j]; // Convolution 2 for index j
+
+            double sumAA = 0.0;         // Sum of convolution products between image 1
+            double sumBB = 0.0;         // Sum of convolution products between image 2
+            double sumAB = 0.0;         // Sum of convolution products across images 1 and 2
             for (int y = - footprint; y <= footprint; y++) {
                 for (int x = - footprint; x <= footprint; x++) {
-                    double value = conv->kernel[y][x];
-#ifdef USE_VARIANCE
-                    value /= variance->kernel[y][x];
-#endif
-                    sumC += value;
-                }
-            }
-            if (!isfinite(sumC)) {
-                psTrace("psModules.imcombine", 2, "Bad sumC at %d", i);
-                return false;
-            }
-
-            for (int yOrder = 0, index = i; yOrder <= spatialOrder; yOrder++) {
-                for (int xOrder = 0; xOrder <= spatialOrder - yOrder; xOrder++, index += numKernels) {
-                    double value = sumC * polyValues->data.F64[yOrder][xOrder];
-                    matrix->data.F64[index][bgIndex] = value;
-                    matrix->data.F64[bgIndex][index] = value;
-                }
-            }
-        }
-
-        // Background only, normalisation only, and background-normalisation terms
-        double sum1 = 0.0;              // Sum of the weighting
-        double sumI = 0.0;              // Sum of the input
-        double sumII = 0.0;             // Sum of the input squared
+                    double aa = iConv1->kernel[y][x] * jConv1->kernel[y][x];
+                    double bb = iConv2->kernel[y][x] * jConv2->kernel[y][x];
+                    double ab = iConv1->kernel[y][x] * jConv2->kernel[y][x];
+                    if (weight) {
+                        float wtVal = weight->kernel[y][x];
+                        aa *= wtVal;
+                        bb *= wtVal;
+                        ab *= wtVal;
+                    }
+                    if (window) {
+                        float wtVal = window->kernel[y][x];
+                        aa *= wtVal;
+                        bb *= wtVal;
+                        ab *= wtVal;
+                    }
+                    sumAA += aa;
+                    sumBB += bb;
+                    sumAB += ab;
+                }
+            }
+
+            // Spatial variation of kernel coeffs
+            if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+                for (int iTerm = 0, iIndex = i; iTerm < numPoly; iTerm++, iIndex += numKernels) {
+                    for (int jTerm = 0, jIndex = j; jTerm < numPoly; jTerm++, jIndex += numKernels) {
+                        double aa = sumAA * poly2[iTerm][jTerm];
+                        double bb = sumBB * poly2[iTerm][jTerm];
+                        double ab = sumAB * poly2[iTerm][jTerm];
+
+                        matrix->data.F64[iIndex][jIndex] = aa;
+                        matrix->data.F64[jIndex][iIndex] = aa;
+
+                        matrix->data.F64[iIndex + numParams][jIndex + numParams] = bb;
+                        matrix->data.F64[jIndex + numParams][iIndex + numParams] = bb;
+
+                        matrix->data.F64[iIndex][jIndex + numParams] = ab;
+                        matrix->data.F64[jIndex + numParams][iIndex] = ab;
+                    }
+                }
+            }
+        }
+        for (int j = 0; j < i; j++) {
+            psKernel *jConv2 = convolutions2->data[j]; // Convolution 2 for index j
+            double sumAB = 0.0;         // Sum of convolution products for matrix C
+            for (int y = - footprint; y <= footprint; y++) {
+                for (int x = - footprint; x <= footprint; x++) {
+                    double ab = iConv1->kernel[y][x] * jConv2->kernel[y][x];
+                    if (weight) {
+                        ab *= weight->kernel[y][x];
+                    }
+                    if (window) {
+                        ab *= window->kernel[y][x];
+                    }
+                    sumAB += ab;
+                }
+            }
+
+            // Spatial variation of kernel coeffs
+            if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+                for (int iTerm = 0, iIndex = i; iTerm < numPoly; iTerm++, iIndex += numKernels) {
+                    for (int jTerm = 0, jIndex = j; jTerm < numPoly; jTerm++, jIndex += numKernels) {
+                        double ab = sumAB * poly2[iTerm][jTerm];
+                        matrix->data.F64[iIndex][jIndex + numParams] = ab;
+                        matrix->data.F64[jIndex + numParams][iIndex] = ab;
+                    }
+                }
+            }
+        }
+
+        double sumAI2 = 0.0;            // Sum of A.I_2 products (for vector)
+        double sumBI2 = 0.0;            // Sum of B.I_2 products (for vector)
+        double sumAI1 = 0.0;            // Sum of A.I_1 products (for matrix, normalisation)
+        double sumA = 0.0;              // Sum of A (for matrix, background)
+        double sumBI1 = 0.0;            // Sum of B.I_1 products (for matrix, normalisation)
+        double sumB = 0.0;              // Sum of B products (for matrix, background)
+        double sumI2 = 0.0;             // Sum of I_2 (for vector, background)
         for (int y = - footprint; y <= footprint; y++) {
             for (int x = - footprint; x <= footprint; x++) {
-                double invNoise2 = 1.0;
-#ifdef USE_VARIANCE
-                invNoise2 /= variance->kernel[y][x];
-#endif
-                double value = input->kernel[y][x] * invNoise2;
-                sumI += value;
-                sumII += value * input->kernel[y][x];
-                sum1 += invNoise2;
-            }
-        }
-        if (!isfinite(sumI)) {
-            psTrace("psModules.imcombine", 2, "Bad sumI detected");
+                double a = iConv1->kernel[y][x];
+                double b = iConv2->kernel[y][x];
+                float i1 = image1->kernel[y][x];
+                float i2 = image2->kernel[y][x];
+
+                double ai2 = a * i2;
+                double bi2 = b * i2;
+                double ai1 = a * i1;
+                double bi1 = b * i1;
+
+                if (weight) {
+                    float wtVal = weight->kernel[y][x];
+                    ai2 *= wtVal;
+                    bi2 *= wtVal;
+                    ai1 *= wtVal;
+                    bi1 *= wtVal;
+                    a *= wtVal;
+                    b *= wtVal;
+                    i2 *= wtVal;
+                }
+                if (window) {
+                    float wtVal = window->kernel[y][x];
+                    ai2 *= wtVal;
+                    bi2 *= wtVal;
+                    ai1 *= wtVal;
+                    bi1 *= wtVal;
+                    a *= wtVal;
+                    b *= wtVal;
+                    i2 *= wtVal;
+                }
+                sumAI2 += ai2;
+                sumBI2 += bi2;
+                sumAI1 += ai1;
+                sumA += a;
+                sumBI1 += bi1;
+                sumB += b;
+                sumI2 += i2;
+            }
+        }
+        // Spatial variation
+        for (int iTerm = 0, iIndex = i; iTerm < numPoly; iTerm++, iIndex += numKernels) {
+            double ai2 = sumAI2 * poly[iTerm];
+            double bi2 = sumBI2 * poly[iTerm];
+            double ai1 = sumAI1 * poly[iTerm];
+            double a   = sumA * poly[iTerm];
+            double bi1 = sumBI1 * poly[iTerm];
+            double b   = sumB * poly[iTerm];
+
+            if ((mode & PM_SUBTRACTION_EQUATION_NORM) && (mode & PM_SUBTRACTION_EQUATION_KERNELS)) {
+                matrix->data.F64[iIndex][normIndex] = ai1;
+                matrix->data.F64[normIndex][iIndex] = ai1;
+                matrix->data.F64[iIndex + numParams][normIndex] = bi1;
+                matrix->data.F64[normIndex][iIndex + numParams] = bi1;
+            }
+            if ((mode & PM_SUBTRACTION_EQUATION_BG) && (mode & PM_SUBTRACTION_EQUATION_KERNELS)) {
+                matrix->data.F64[iIndex][bgIndex] = a;
+                matrix->data.F64[bgIndex][iIndex] = a;
+                matrix->data.F64[iIndex + numParams][bgIndex] = b;
+                matrix->data.F64[bgIndex][iIndex + numParams] = b;
+            }
+            if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+                vector->data.F64[iIndex] = ai2;
+                vector->data.F64[iIndex + numParams] = bi2;
+                if (!(mode & PM_SUBTRACTION_EQUATION_NORM)) {
+                    // subtract norm * sumRC * poly[iTerm]
+                    psAssert (kernels->solution1, "programming error: define solution first!");
+                    int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+                    double norm = fabs(kernels->solution1->data.F64[normIndex]);  // Normalisation
+                    vector->data.F64[iIndex] -= norm * ai1;
+                    vector->data.F64[iIndex + numParams] -= norm * bi1;
+                }
+            }
+        }
+    }
+
+    double sumI1 = 0.0;                 // Sum of I_1 (for matrix, background-normalisation)
+    double sumI1I1 = 0.0;               // Sum of I_1^2 (for matrix, normalisation-normalisation)
+    double sum1 = 0.0;                  // Sum of 1 (for matrix, background-background)
+    double sumI2 = 0.0;                 // Sum of I_2 (for vector, background)
+    double sumI1I2 = 0.0;               // Sum of I_1.I_2 (for vector, normalisation)
+    double normI1 = 0.0, normI2 = 0.0;  // Sum of I_1 and I_2 within the normalisation window
+    for (int y = - footprint; y <= footprint; y++) {
+        for (int x = - footprint; x <= footprint; x++) {
+            double i1 = image1->kernel[y][x];
+            double i2 = image2->kernel[y][x];
+
+            double i1i1 = i1 * i1;
+            double one = 1.0;
+            double i1i2 = i1 * i2;
+
+            if (PS_SQR(x) + PS_SQR(y) <= PS_SQR(normWindow)) {
+                normI1 += i1;
+                normI2 += i2;
+            }
+
+            if (weight) {
+                float wtVal = weight->kernel[y][x];
+                i1 *= wtVal;
+                i1i1 *= wtVal;
+                one *= wtVal;
+                i2 *= wtVal;
+                i1i2 *= wtVal;
+            }
+            if (window) {
+                float wtVal = window->kernel[y][x];
+                i1 *= wtVal;
+                i1i1 *= wtVal;
+                one *= wtVal;
+                i2 *= wtVal;
+                i1i2 *= wtVal;
+            }
+            sumI1 += i1;
+            sumI1I1 += i1i1;
+            sum1 += one;
+            sumI2 += i2;
+            sumI1I2 += i1i2;
+        }
+    }
+
+    *norm = normI2 / normI1;
+
+    if (mode & PM_SUBTRACTION_EQUATION_NORM) {
+        matrix->data.F64[normIndex][normIndex] = sumI1I1;
+        vector->data.F64[normIndex] = sumI1I2;
+    }
+    if (mode & PM_SUBTRACTION_EQUATION_BG) {
+        matrix->data.F64[bgIndex][bgIndex] = sum1;
+        vector->data.F64[bgIndex] = sumI2;
+    }
+    if ((mode & PM_SUBTRACTION_EQUATION_NORM) && (mode & PM_SUBTRACTION_EQUATION_BG)) {
+        matrix->data.F64[bgIndex][normIndex] = sumI1;
+        matrix->data.F64[normIndex][bgIndex] = sumI1;
+    }
+
+    // check for any NAN values in the result, skip if found:
+    for (int iy = 0; iy < matrix->numRows; iy++) {
+        for (int ix = 0; ix < matrix->numCols; ix++) {
+            if (!isfinite(matrix->data.F64[iy][ix])) {
+                fprintf (stderr, "WARNING: NAN in matrix\n");
+                return false;
+            }
+        }
+    }
+    for (int ix = 0; ix < vector->n; ix++) {
+        if (!isfinite(vector->data.F64[ix])) {
+            fprintf (stderr, "WARNING: NAN in vector\n");
             return false;
         }
-        if (!isfinite(sumII)) {
-            psTrace("psModules.imcombine", 2, "Bad sumII detected");
-            return false;
-        }
-        if (!isfinite(sum1)) {
-            psTrace("psModules.imcombine", 2, "Bad sum1 detected");
-            return false;
-        }
-        matrix->data.F64[normIndex][normIndex] = sumII;
-        matrix->data.F64[bgIndex][bgIndex] = sum1;
-        matrix->data.F64[normIndex][bgIndex] = sumI;
-        matrix->data.F64[bgIndex][normIndex] = sumI;
-    }
+    }
+
 
     return true;
 }
 
-
-// Calculate least-squares matrix and vector
-static bool calculateVector(psVector *vector, // Vector to calculate, or NULL
-                            const pmSubtractionKernels *kernels, // Kernel components
-                            const psArray *convolutions, // Convolutions of source with kernels
-                            const psKernel *input, // Input stamp, or NULL if !normAndBG
-                            const psKernel *target, // Target stamp
-                            const psKernel *variance, // Variance stamp
-                            const psImage *polyValues, // Spatial polynomial values
-                            int footprint, // (Half-)Size of stamp
-                            bool normAndBG // Calculate normalisation and background terms?
-    )
-{
-    int numKernels = kernels->num;      // Number of kernel components
-    int spatialOrder = kernels->spatialOrder; // Maximum order of spatial variation
-    int numSpatial = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of spatial variation terms
-    int bgOrder = kernels->bgOrder;     // Maximum order of background fit
-    int numBackground = normAndBG ? PM_SUBTRACTION_POLYTERMS(bgOrder) : 0; // Number of background terms
-    int numTerms = numKernels * numSpatial + (normAndBG ? 1 + numBackground : 0); // Total number of terms
-    assert(vector && vector->n == numTerms);
-    assert(convolutions && convolutions->n == numKernels);
-    assert(target);
-    assert(polyValues);
-    assert(!normAndBG || input);       // If we want the normalisation and BG, then we need the input image
-
-    // Convolution terms
-    for (int i = 0; i < numKernels; i++) {
-        psKernel *conv = convolutions->data[i]; // Convolution for i-th element
-        double sumTC = 0.0;          // Sum of the target and convolution
-        for (int y = - footprint; y <= footprint; y++) {
-            for (int x = - footprint; x <= footprint; x++) {
-                double value = target->kernel[y][x] * conv->kernel[y][x];
-#ifdef USE_VARIANCE
-                value /= variance->kernel[y][x];
-#endif
-                sumTC += value;
-            }
-        }
-        if (!isfinite(sumTC)) {
-            psTrace("psModules.imcombine", 2, "Bad sumTC at %d", i);
-            return false;
-        }
-        for (int yOrder = 0, index = i; yOrder <= spatialOrder; yOrder++) {
-            for (int xOrder = 0; xOrder <= spatialOrder - yOrder; xOrder++, index += numKernels) {
-                vector->data.F64[index] = sumTC * polyValues->data.F64[yOrder][xOrder];
-            }
-        }
-    }
-
-    if (normAndBG) {
-        // Background terms
-        double sumT = 0.0;              // Sum of the target
-        double sumIT = 0.0;             // Sum of the input-target product
-        for (int y = - footprint; y <= footprint; y++) {
-            for (int x = - footprint; x <= footprint; x++) {
-                double value = target->kernel[y][x];
-#ifdef USE_VARIANCE
-                value /= variance->kernel[y][x];
-#endif
-                sumIT += value * input->kernel[y][x];
-                sumT += value;
-            }
-        }
-        if (!isfinite(sumT)) {
-            psTrace("psModules.imcombine", 2, "Bad sumI detected");
-            return false;
-        }
-        if (!isfinite(sumIT)) {
-            psTrace("psModules.imcombine", 2, "Bad sumIT detected");
-            return false;
-        }
-
-        int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation term
-        vector->data.F64[normIndex] = sumIT;
-        int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background term
-        vector->data.F64[bgIndex] = sumT;
-    }
-
-    return true;
-}
-
-
-
-// Calculate the cross-matrix, composed of convolutions of each image
-// Note that the cross-matrix is NOT square
-static bool calculateMatrixCross(psImage *matrix, // Matrix to calculate
-                                 const pmSubtractionKernels *kernels, // Kernel components
-                                 const psArray *convolutions1, // Convolutions of image 1
-                                 const psArray *convolutions2, // Convolutions of image 2
-                                 const psKernel *image1, // Image 1 stamp
-                                 const psKernel *variance, // Variance stamp
-                                 const psImage *polyValues, // Spatial polynomial values
-                                 int footprint // (Half-)Size of stamp
-                                 )
-{
-    assert(matrix);
-    int numKernels = kernels->num;      // Number of kernel components
-    int spatialOrder = kernels->spatialOrder; // Maximum order of spatial variation
-    int numSpatial = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of spatial polynomial terms
-    int numBackground = PM_SUBTRACTION_POLYTERMS(kernels->bgOrder); // Number of background terms
-    int numCols = numKernels * numSpatial + 1 + numBackground; // Number of columns
-    int numRows = numKernels * numSpatial; // Number of rows
-    assert(matrix->numCols == numCols && matrix->numRows == numRows);
-    assert(convolutions1 && convolutions1->n == numKernels);
-    assert(convolutions2 && convolutions2->n == numKernels);
-
-    int normIndex, bgIndex;             // Indices in matrix for normalisation and background terms
-    PM_SUBTRACTION_INDICES(normIndex, bgIndex, kernels);
-
-    if (!calculateMatrixSquare(matrix, convolutions1, convolutions2, variance, polyValues, numKernels,
-                               spatialOrder, footprint)) {
-        return false;
-    }
-
-    for (int i = 0; i < numKernels; i++) {
-        // Normalisation
-        psKernel *conv = convolutions2->data[i]; // Convolution
-        if (!calculateMatrixElement1(matrix, i, normIndex, conv, image1, variance, polyValues, numKernels,
-                                     footprint, spatialOrder, false)) {
-            psTrace("psModules.imcombine", 2, "Bad sumIC at %d", i);
-            return false;
-        }
-
-        // Background
-        double sumC = 0.0;              // Sum of the weighting
-        for (int y = - footprint; y <= footprint; y++) {
-            for (int x = - footprint; x <= footprint; x++) {
-                double value = conv->kernel[y][x];
-#ifdef USE_VARIANCE
-                value /= variance->kernel[y][x];
-#endif
-                sumC += value;
-            }
-        }
-        if (!isfinite(sumC)) {
-            psTrace("psModules.imcombine", 2, "Bad sumC detected at %d", i);
-            return false;
-        }
-        for (int yOrder = 0, index = i; yOrder <= spatialOrder; yOrder++) {
-            for (int xOrder = 0; xOrder <= spatialOrder - yOrder; xOrder++, index += numKernels) {
-                matrix->data.F64[index][bgIndex] = sumC * polyValues->data.F64[yOrder][xOrder];
-            }
-        }
-    }
-
-    return true;
-}
-
-
+#if 1
 // Add in penalty term to least-squares vector
-static bool calculatePenalty(psVector *vector, // Vector to which to add in penalty term
-                             const pmSubtractionKernels *kernels // Kernel parameters
+bool calculatePenalty(psImage *matrix,                     // Matrix to which to add in penalty term
+                             psVector *vector,                    // Vector to which to add in penalty term
+                             const pmSubtractionKernels *kernels, // Kernel parameters
+                             float norm                           // Normalisation
     )
 {
@@ -420,8 +571,29 @@
     int spatialOrder = kernels->spatialOrder; // Order of spatial variations
     int numKernels = kernels->num; // Number of kernel components
+    int numSpatial = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of spatial variations
+    int numParams = numKernels * numSpatial;                 // Number of kernel parameters
+
+    // order is :
+    // [p_0,x_0,y_0 p_1,x_0,y_0, p_2,x_0,y_0]
+    // [p_0,x_1,y_0 p_1,x_1,y_0, p_2,x_1,y_0]
+    // [p_0,x_0,y_1 p_1,x_0,y_1, p_2,x_0,y_1]
+    // [norm]
+    // [bg]
+    // [q_0,x_0,y_0 q_1,x_0,y_0, q_2,x_0,y_0]
+    // [q_0,x_1,y_0 q_1,x_1,y_0, q_2,x_1,y_0]
+    // [q_0,x_0,y_1 q_1,x_0,y_1, q_2,x_0,y_1]
+
     for (int i = 0; i < numKernels; i++) {
         for (int yOrder = 0, index = i; yOrder <= spatialOrder; yOrder++) {
             for (int xOrder = 0; xOrder <= spatialOrder - yOrder; xOrder++, index += numKernels) {
-                vector->data.F64[index] -= penalties->data.F32[i];
+                // Contribution to chi^2: a_i^2 P_i
+                psAssert(isfinite(penalties->data.F32[i]), "Invalid penalty");
+                matrix->data.F64[index][index] += norm * penalties->data.F32[i];
+                if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+                    matrix->data.F64[index + numParams + 2][index + numParams + 2] += norm * penalties->data.F32[i];
+                    // matrix[i][i] is ~ (k_i * I_1)(k_i * I_1)
+                    // penalties scale with second moments
+                    //
+                }
             }
         }
@@ -430,4 +602,5 @@
     return true;
 }
+# endif
 
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -439,9 +612,9 @@
 // Calculate the value of a polynomial, specified by coefficients and polynomial values
 double p_pmSubtractionCalculatePolynomial(const psVector *coeff, // Coefficients
-                                                 const psImage *polyValues, // Polynomial values
-                                                 int order, // Order of polynomials
-                                                 int index, // Index at which to begin
-                                                 int step // Step between subsequent indices
-                                                 )
+                                          const psImage *polyValues, // Polynomial values
+                                          int order, // Order of polynomials
+                                          int index, // Index at which to begin
+                                          int step // Step between subsequent indices
+                                          )
 {
     double sum = 0.0;                   // Value of the polynomial sum
@@ -458,5 +631,5 @@
 
 double p_pmSubtractionSolutionCoeff(const pmSubtractionKernels *kernels, const psImage *polyValues,
-                                           int index, bool wantDual)
+                                    int index, bool wantDual)
 {
 #if 0
@@ -511,10 +684,11 @@
     const pmSubtractionKernels *kernels = job->args->data[1]; // Kernels
     int index = PS_SCALAR_VALUE(job->args->data[2], S32); // Stamp index
-
-    return pmSubtractionCalculateEquationStamp(stamps, kernels, index);
+    pmSubtractionEquationCalculationMode mode  = PS_SCALAR_VALUE(job->args->data[3], S32); // calculation model
+
+    return pmSubtractionCalculateEquationStamp(stamps, kernels, index, mode);
 }
 
 bool pmSubtractionCalculateEquationStamp(pmSubtractionStampList *stamps, const pmSubtractionKernels *kernels,
-                                         int index)
+                                         int index, const pmSubtractionEquationCalculationMode mode)
 {
     PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, false);
@@ -529,14 +703,21 @@
     int numBackground = PM_SUBTRACTION_POLYTERMS(kernels->bgOrder); // Number of background terms
 
+    // numKernels is the number of unique kernel images (one for each Gaussian modified by a specific polynomial).
+    // = \sum_i^N_Gaussians [(order + 1) * (order + 2) / 2], eg for 1 Gauss and 1st order, numKernels = 3
+
     // Total number of parameters to solve for: coefficient of each kernel basis function, multipled by the
     // number of coefficients for the spatial polynomial, normalisation and a constant background offset.
     int numParams = numKernels * numSpatial + 1 + numBackground;
+    if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+        // An additional image is convolved
+        numParams += numKernels * numSpatial;
+    }
 
     pmSubtractionStamp *stamp = stamps->stamps->data[index]; // Stamp of interest
     psAssert(stamp->status == PM_SUBTRACTION_STAMP_CALCULATE, "We only operate on stamps with this state.");
 
-    // Generate convolutions
+    // Generate convolutions: these are generated once and saved
     if (!pmSubtractionConvolveStamp(stamp, kernels, footprint)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to convolve stamp %d.", index);
+        psError(psErrorCodeLast(), false, "Unable to convolve stamp %d.", index);
         return NULL;
     }
@@ -566,53 +747,47 @@
 #endif
 
+    // XXX visualize the set of convolved stamps
+
     psImage *polyValues = p_pmSubtractionPolynomial(NULL, spatialOrder,
                                                     stamp->xNorm, stamp->yNorm); // Polynomial terms
 
-    bool new = stamp->vector1 ? false : true; // Is this a new run?
+    bool new = stamp->vector ? false : true; // Is this a new run?
     if (new) {
-        stamp->matrix1 = psImageAlloc(numParams, numParams, PS_TYPE_F64);
-        stamp->vector1 = psVectorAlloc(numParams, PS_TYPE_F64);
+        stamp->matrix = psImageAlloc(numParams, numParams, PS_TYPE_F64);
+        stamp->vector = psVectorAlloc(numParams, PS_TYPE_F64);
     }
 #ifdef TESTING
-    psImageInit(stamp->matrix1, NAN);
-    psVectorInit(stamp->vector1, NAN);
+    psImageInit(stamp->matrix, NAN);
+    psVectorInit(stamp->vector, NAN);
 #endif
 
     bool status;                    // Status of least-squares matrix/vector calculation
+
+    psKernel *weight = NULL;
+    psKernel *window = NULL;
+
+#ifdef USE_WEIGHT
+    weight = stamp->weight;
+#endif
+#ifdef USE_WINDOW
+    window = stamps->window;
+#endif
+
     switch (kernels->mode) {
       case PM_SUBTRACTION_MODE_1:
-        status = calculateMatrix(stamp->matrix1, kernels, stamp->convolutions1, stamp->image1,
-                                 stamp->variance, polyValues, footprint, true);
-        status &= calculateVector(stamp->vector1, kernels, stamp->convolutions1, stamp->image1,
-                                  stamp->image2, stamp->variance, polyValues, footprint, true);
+        status = calculateMatrixVector(stamp->matrix, stamp->vector, &stamp->norm, stamp->image2, stamp->image1,
+                                       weight, window, stamp->convolutions1, kernels,
+                                       polyValues, footprint, stamps->normWindow, mode);
         break;
       case PM_SUBTRACTION_MODE_2:
-        status = calculateMatrix(stamp->matrix1, kernels, stamp->convolutions2, stamp->image2,
-                                 stamp->variance, polyValues, footprint, true);
-        status &= calculateVector(stamp->vector1, kernels, stamp->convolutions2, stamp->image2,
-                                  stamp->image1, stamp->variance, polyValues, footprint, true);
+        status = calculateMatrixVector(stamp->matrix, stamp->vector, &stamp->norm, stamp->image1, stamp->image2,
+                                       weight, window, stamp->convolutions2, kernels,
+                                       polyValues, footprint, stamps->normWindow, mode);
         break;
       case PM_SUBTRACTION_MODE_DUAL:
-        if (new) {
-            stamp->matrix2 = psImageAlloc(numKernels * numSpatial, numKernels * numSpatial, PS_TYPE_F64);
-            stamp->matrixX = psImageAlloc(numParams, numKernels * numSpatial, PS_TYPE_F64);
-            stamp->vector2 = psVectorAlloc(numKernels * numSpatial, PS_TYPE_F64);
-        }
-#ifdef TESTING
-        psImageInit(stamp->matrix2, NAN);
-        psImageInit(stamp->matrixX, NAN);
-        psVectorInit(stamp->vector2, NAN);
-#endif
-        status  = calculateMatrix(stamp->matrix1, kernels, stamp->convolutions1, stamp->image1,
-                                  stamp->variance, polyValues, footprint, true);
-        status &= calculateMatrix(stamp->matrix2, kernels, stamp->convolutions2, NULL,
-                                  stamp->variance, polyValues, footprint, false);
-        status &= calculateMatrixCross(stamp->matrixX, kernels, stamp->convolutions1,
-                                       stamp->convolutions2, stamp->image1, stamp->variance, polyValues,
-                                       footprint);
-        status &= calculateVector(stamp->vector1, kernels, stamp->convolutions1, stamp->image1,
-                                  stamp->image2, stamp->variance, polyValues, footprint, true);
-        status &= calculateVector(stamp->vector2, kernels, stamp->convolutions2, NULL,
-                                  stamp->image2, stamp->variance, polyValues, footprint, false);
+        status = calculateDualMatrixVector(stamp->matrix, stamp->vector, &stamp->norm,
+                                           stamp->image1, stamp->image2,
+                                           weight, window, stamp->convolutions1, stamp->convolutions2,
+                                           kernels, polyValues, footprint, stamps->normWindow, mode);
         break;
       default:
@@ -623,5 +798,5 @@
         stamp->status = PM_SUBTRACTION_STAMP_REJECTED;
         psWarning("Rejecting stamp %d (%d,%d) because of bad equation",
-                  index, (int)(stamp->x + 0.5), (int)(stamp->y + 0.5));
+                  index, (int)(stamp->x - 0.5), (int)(stamp->y - 0.5));
     } else {
         stamp->status = PM_SUBTRACTION_STAMP_USED;
@@ -629,17 +804,17 @@
 
 #ifdef TESTING
-    if (psTraceGetLevel("psModules.imcombine.equation") >= 10) {
+    {
         psString matrixName = NULL;
-        psStringAppend(&matrixName, "matrix1_%d.fits", index);
+        psStringAppend(&matrixName, "matrix_%d.fits", index);
         psFits *matrixFile = psFitsOpen(matrixName, "w");
         psFree(matrixName);
-        psFitsWriteImage(matrixFile, NULL, stamp->matrix1, 0, NULL);
+        psFitsWriteImage(matrixFile, NULL, stamp->matrix, 0, NULL);
         psFitsClose(matrixFile);
 
         matrixName = NULL;
-        psStringAppend(&matrixName, "vector1_%d.fits", index);
-        psImage *dummy = psImageAlloc(stamp->vector1->n, 1, PS_TYPE_F64);
-        memcpy(dummy->data.F64[0], stamp->vector1->data.F64,
-               PSELEMTYPE_SIZEOF(PS_TYPE_F64) * stamp->vector1->n);
+        psStringAppend(&matrixName, "vector_%d.fits", index);
+        psImage *dummy = psImageAlloc(stamp->vector->n, 1, PS_TYPE_F64);
+        memcpy(dummy->data.F64[0], stamp->vector->data.F64,
+               PSELEMTYPE_SIZEOF(PS_TYPE_F64) * stamp->vector->n);
         matrixFile = psFitsOpen(matrixName, "w");
         psFree(matrixName);
@@ -647,33 +822,4 @@
         psFree(dummy);
         psFitsClose(matrixFile);
-
-        if (stamp->vector2) {
-            matrixName = NULL;
-            psStringAppend(&matrixName, "vector2_%d.fits", index);
-            dummy = psImageAlloc(stamp->vector2->n, 1, PS_TYPE_F64);
-            memcpy(dummy->data.F64[0], stamp->vector2->data.F64,
-                   PSELEMTYPE_SIZEOF(PS_TYPE_F64) * stamp->vector2->n);
-            matrixFile = psFitsOpen(matrixName, "w");
-            psFree(matrixName);
-            psFitsWriteImage(matrixFile, NULL, dummy, 0, NULL);
-            psFree(dummy);
-            psFitsClose(matrixFile);
-        }
-
-        if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-            matrixName = NULL;
-            psStringAppend(&matrixName, "matrix2_%d.fits", index);
-            matrixFile = psFitsOpen(matrixName, "w");
-            psFree(matrixName);
-            psFitsWriteImage(matrixFile, NULL, stamp->matrix2, 0, NULL);
-            psFitsClose(matrixFile);
-
-            matrixName = NULL;
-            psStringAppend(&matrixName, "matrixX_%d.fits", index);
-            matrixFile = psFitsOpen(matrixName, "w");
-            psFree(matrixName);
-            psFitsWriteImage(matrixFile, NULL, stamp->matrixX, 0, NULL);
-            psFitsClose(matrixFile);
-        }
     }
 #endif
@@ -684,5 +830,6 @@
 }
 
-bool pmSubtractionCalculateEquation(pmSubtractionStampList *stamps, const pmSubtractionKernels *kernels)
+bool pmSubtractionCalculateEquation(pmSubtractionStampList *stamps, const pmSubtractionKernels *kernels,
+                                    const pmSubtractionEquationCalculationMode mode)
 {
     PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, false);
@@ -699,4 +846,11 @@
         }
 
+        if ((stamp->x <= 0.0) && (stamp->y <= 0.0)) {
+            psAbort ("bad stamp");
+        }
+        if (!isfinite(stamp->x) && !isfinite(stamp->y)) {
+            psAbort ("bad stamp");
+        }
+
         if (pmSubtractionThreaded()) {
             psThreadJob *job = psThreadJobAlloc("PSMODULES_SUBTRACTION_CALCULATE_EQUATION");
@@ -704,4 +858,5 @@
             psArrayAdd(job->args, 1, (pmSubtractionKernels*)kernels); // Casting away const to put on array
             PS_ARRAY_ADD_SCALAR(job->args, i, PS_TYPE_S32);
+            PS_ARRAY_ADD_SCALAR(job->args, mode, PS_TYPE_S32);
             if (!psThreadJobAddPending(job)) {
                 psFree(job);
@@ -710,14 +865,16 @@
             psFree(job);
         } else {
-            pmSubtractionCalculateEquationStamp(stamps, kernels, i);
+            pmSubtractionCalculateEquationStamp(stamps, kernels, i, mode);
         }
     }
 
     if (!psThreadPoolWait(true)) {
-        psError(PS_ERR_UNKNOWN, false, "Error waiting for threads.");
+        psError(psErrorCodeLast(), false, "Error waiting for threads.");
         return false;
     }
 
     pmSubtractionVisualPlotLeastSquares(stamps);
+    pmSubtractionVisualShowKernels((pmSubtractionKernels  *)kernels);
+    pmSubtractionVisualShowBasis(stamps);
 
     psLogMsg("psModules.imcombine", PS_LOG_INFO, "Calculate equation: %f sec",
@@ -728,5 +885,28 @@
 }
 
-bool pmSubtractionSolveEquation(pmSubtractionKernels *kernels, const pmSubtractionStampList *stamps)
+// private functions used on pmSubtractionSolveEquation
+bool psVectorWriteFile (char *filename, const psVector *vector);
+bool psFitsWriteImageSimple (char *filename, psImage *image, psMetadata *header);
+
+psImage *p_pmSubSolve_wUt (psVector *w, psImage *U);
+psImage *p_pmSubSolve_VwUt (psImage *V, psImage *wUt);
+
+bool p_pmSubSolve_SetWeights (psVector *wApply, psVector *w, psVector *wMask);
+
+bool p_pmSubSolve_UtB (psVector **UtB, psImage *U, psVector *B);
+bool p_pmSubSolve_wUtB (psVector **wUtB, psVector *w, psVector *UtB);
+bool p_pmSubSolve_VwUtB (psVector **VwUtB, psImage *V, psVector *wUtB);
+
+bool p_pmSubSolve_Ax (psVector **B, psImage *A, psVector *x);
+bool p_pmSubSolve_VdV (double *value, psVector *x, psVector *y);
+bool p_pmSubSolve_y2 (double *y2, pmSubtractionKernels *kernels, const pmSubtractionStampList *stamps);
+
+psImage *p_pmSubSolve_Xvar (psImage *V, psVector *w);
+
+double p_pmSubSolve_ChiSquare (pmSubtractionKernels *kernels, const pmSubtractionStampList *stamps);
+
+bool pmSubtractionSolveEquation(pmSubtractionKernels *kernels,
+                                const pmSubtractionStampList *stamps,
+                                const pmSubtractionEquationCalculationMode mode)
 {
     PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, false);
@@ -734,6 +914,15 @@
 
     // Check inputs
-    int numParams = -1;                // Number of parameters
-    int numParams2 = 0;                // Number of parameters for part solution (DUAL mode)
+    int numKernels = kernels->num;      // Number of kernel basis functions
+    int numSpatial = PM_SUBTRACTION_POLYTERMS(kernels->spatialOrder); // Number of spatial variations
+    int numBackground = PM_SUBTRACTION_POLYTERMS(kernels->bgOrder); // Number of background terms
+    int numParams = numKernels * numSpatial + 1 + numBackground;    // Number of parameters being solved for
+    int numSolution1 = numParams, numSolution2 = 0;                 // Number of parameters for each solution
+    if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+        // An additional image is convolved
+        numSolution2 = numKernels * numSpatial;
+        numParams += numSolution2;
+    }
+
     for (int i = 0; i < stamps->num; i++) {
         pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
@@ -743,32 +932,10 @@
         }
 
-        PS_ASSERT_VECTOR_NON_NULL(stamp->vector1, false);
-        if (numParams == -1) {
-            numParams = stamp->vector1->n;
-        }
-        PS_ASSERT_VECTOR_SIZE(stamp->vector1, (long)numParams, false);
-        PS_ASSERT_VECTOR_TYPE(stamp->vector1, PS_TYPE_F64, false);
-        PS_ASSERT_IMAGE_NON_NULL(stamp->matrix1, false);
-        PS_ASSERT_IMAGE_SIZE(stamp->matrix1, numParams, numParams, false);
-        PS_ASSERT_IMAGE_TYPE(stamp->matrix1, PS_TYPE_F64, false);
-
-        if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-            PS_ASSERT_IMAGE_NON_NULL(stamp->matrix2, false);
-            PS_ASSERT_IMAGE_NON_NULL(stamp->matrixX, false);
-            if (numParams2 == 0) {
-                numParams2 = stamp->matrix2->numCols;
-            }
-            PS_ASSERT_IMAGE_SIZE(stamp->matrix2, numParams2, numParams2, false);
-            PS_ASSERT_IMAGE_SIZE(stamp->matrixX, numParams, numParams2, false);
-            PS_ASSERT_IMAGE_TYPE(stamp->matrix2, PS_TYPE_F64, false);
-            PS_ASSERT_IMAGE_TYPE(stamp->matrixX, PS_TYPE_F64, false);
-            PS_ASSERT_VECTOR_NON_NULL(stamp->vector2, false);
-            PS_ASSERT_VECTOR_SIZE(stamp->vector2, (long)numParams2, false);
-            PS_ASSERT_VECTOR_TYPE(stamp->vector2, PS_TYPE_F64, false);
-        }
-    }
-    if (numParams == -1) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "No suitable stamps found.");
-        return NULL;
+        PS_ASSERT_VECTOR_NON_NULL(stamp->vector, false);
+        PS_ASSERT_VECTOR_SIZE(stamp->vector, (long)numParams, false);
+        PS_ASSERT_VECTOR_TYPE(stamp->vector, PS_TYPE_F64, false);
+        PS_ASSERT_IMAGE_NON_NULL(stamp->matrix, false);
+        PS_ASSERT_IMAGE_SIZE(stamp->matrix, numParams, numParams, false);
+        PS_ASSERT_IMAGE_TYPE(stamp->matrix, PS_TYPE_F64, false);
     }
 
@@ -786,28 +953,14 @@
         psVectorInit(sumVector, 0.0);
         psImageInit(sumMatrix, 0.0);
+
+        psVector *norms = psVectorAllocEmpty(stamps->num, PS_TYPE_F64); // Normalisations
+
         int numStamps = 0;              // Number of good stamps
         for (int i = 0; i < stamps->num; i++) {
             pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
-
             if (stamp->status == PM_SUBTRACTION_STAMP_USED) {
-
-#ifdef TESTING
-              // XXX double-check for NAN in data:
-                for (int iy = 0; iy < stamp->matrix1->numRows; iy++) {
-                    for (int ix = 0; ix < stamp->matrix1->numCols; ix++) {
-                        if (!isfinite(stamp->matrix1->data.F64[iy][ix])) {
-                            fprintf (stderr, "WARNING: NAN in matrix1\n");
-                        }
-                    }
-                }
-                for (int ix = 0; ix < stamp->vector1->n; ix++) {
-                    if (!isfinite(stamp->vector1->data.F64[ix])) {
-                        fprintf (stderr, "WARNING: NAN in vector1\n");
-                    }
-                }
-#endif
-
-                (void)psBinaryOp(sumMatrix, sumMatrix, "+", stamp->matrix1);
-                (void)psBinaryOp(sumVector, sumVector, "+", stamp->vector1);
+                (void)psBinaryOp(sumMatrix, sumMatrix, "+", stamp->matrix);
+                (void)psBinaryOp(sumVector, sumVector, "+", stamp->vector);
+                psVectorAppend(norms, stamp->norm);
                 pmSubtractionStampPrint(ds9, stamp->x, stamp->y, stamps->footprint, "green");
                 numStamps++;
@@ -817,51 +970,87 @@
         }
 
+#if 0
+        int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background
+        calculatePenalty(sumMatrix, sumVector, kernels, sumMatrix->data.F64[bgIndex][bgIndex]);
+#endif
+
+        psVector *solution = NULL;                       // Solution to equation!
+        solution = psVectorAlloc(numParams, PS_TYPE_F64);
+        psVectorInit(solution, 0);
+
+#if 0
+        // Regular, straight-forward solution
+        solution = psMatrixSolveSVD(solution, sumMatrix, sumVector, NAN);
+#else
+        {
+            // Solve normalisation and background separately
+            int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+            int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background
+
+            psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN); // Statistics for norm
+            if (!psVectorStats(stats, norms, NULL, NULL, 0)) {
+                psError(PM_ERR_DATA, false, "Unable to determine median normalisation");
+                psFree(stats);
+                psFree(sumMatrix);
+                psFree(sumVector);
+                psFree(norms);
+                return false;
+            }
+
+            double normValue = stats->robustMedian;
+            // double bgValue = 0.0;
+
+            psFree(stats);
+
 #ifdef TESTING
-        for (int ix = 0; ix < sumVector->n; ix++) {
-            if (!isfinite(sumVector->data.F64[ix])) {
-                fprintf (stderr, "WARNING: NAN in vector1\n");
-            }
-        }
-#endif
-
-        calculatePenalty(sumVector, kernels);
-
-#ifdef TESTING
-        for (int ix = 0; ix < sumVector->n; ix++) {
-            if (!isfinite(sumVector->data.F64[ix])) {
-                fprintf (stderr, "WARNING: NAN in vector1\n");
-            }
-        }
-        {
-            psImage *inverse = psMatrixInvert(NULL, sumMatrix, NULL);
-            psFits *fits = psFitsOpen("matrixInv.fits", "w");
-            psFitsWriteImage(fits, NULL, inverse, 0, NULL);
-            psFitsClose(fits);
-            psFree(inverse);
-        }
-        {
-            psImage *X = psMatrixInvert(NULL, sumMatrix, NULL);
-            psImage *Xt = psMatrixTranspose(NULL, X);
-            psImage *XtX = psMatrixMultiply(NULL, Xt, X);
-            psFits *fits = psFitsOpen("matrixErr.fits", "w");
-            psFitsWriteImage(fits, NULL, XtX, 0, NULL);
-            psFitsClose(fits);
-            psFree(X);
-            psFree(Xt);
-            psFree(XtX);
-        }
-#endif
-
-        psVector *permutation = NULL;       // Permutation vector, required for LU decomposition
-        psImage *luMatrix = psMatrixLUDecomposition(NULL, &permutation, sumMatrix);
+            fprintf(stderr, "Norm: %lf\n", normValue);
+#endif
+            // Solve kernel components
+            for (int i = 0; i < numSolution1; i++) {
+                sumVector->data.F64[i] -= normValue * sumMatrix->data.F64[normIndex][i];
+
+                sumMatrix->data.F64[i][normIndex] = 0.0;
+                sumMatrix->data.F64[normIndex][i] = 0.0;
+            }
+            sumVector->data.F64[bgIndex] -= normValue * sumMatrix->data.F64[normIndex][bgIndex];
+            sumMatrix->data.F64[bgIndex][normIndex] = 0.0;
+            sumMatrix->data.F64[normIndex][bgIndex] = 0.0;
+
+            sumMatrix->data.F64[normIndex][normIndex] = 1.0;
+            sumVector->data.F64[normIndex] = 0.0;
+
+            solution = psMatrixSolveSVD(solution, sumMatrix, sumVector, NAN);
+
+            solution->data.F64[normIndex] = normValue;
+        }
+# endif
+
+        if (!kernels->solution1) {
+            kernels->solution1 = psVectorAlloc(sumVector->n, PS_TYPE_F64);
+            psVectorInit(kernels->solution1, 0.0);
+        }
+
+        // only update the solutions that we chose to calculate:
+        if (mode & PM_SUBTRACTION_EQUATION_NORM) {
+            int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+            kernels->solution1->data.F64[normIndex] = solution->data.F64[normIndex];
+        }
+        if (mode & PM_SUBTRACTION_EQUATION_BG) {
+            int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index in matrix for background
+            kernels->solution1->data.F64[bgIndex] = solution->data.F64[bgIndex];
+        }
+        if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+            int numKernels = kernels->num;
+            int spatialOrder = kernels->spatialOrder;       // Order of spatial variation
+            int numPoly = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of polynomial terms
+            for (int i = 0; i < numKernels * numPoly; i++) {
+                kernels->solution1->data.F64[i] = solution->data.F64[i];
+            }
+        }
+
+        psFree(norms);
+        psFree(solution);
+        psFree(sumVector);
         psFree(sumMatrix);
-        if (!luMatrix) {
-            psError(PS_ERR_UNKNOWN, true, "LU Decomposition of least-squares matrix failed.\n");
-            psFree(sumVector);
-            psFree(luMatrix);
-            psFree(permutation);
-            return NULL;
-        }
-        kernels->solution1 = psMatrixLUSolution(kernels->solution1, luMatrix, sumVector, permutation);
 
 #ifdef TESTING
@@ -869,30 +1058,19 @@
         for (int ix = 0; ix < kernels->solution1->n; ix++) {
             if (!isfinite(kernels->solution1->data.F64[ix])) {
-                fprintf (stderr, "WARNING: NAN in vector1\n");
-            }
-        }
-#endif
-
-        psFree(sumVector);
-        psFree(luMatrix);
-        psFree(permutation);
-        if (!kernels->solution1) {
-            psError(PS_ERR_UNKNOWN, true, "Failed to solve the least-squares system.\n");
-            return NULL;
-        }
+                fprintf (stderr, "WARNING: NAN in vector\n");
+            }
+        }
+#endif
+
     } else {
         // Dual convolution solution
 
         // Accumulation of stamp matrices/vectors
-        psImage *sumMatrix1 = psImageAlloc(numParams, numParams, PS_TYPE_F64);
-        psImage *sumMatrix2 = psImageAlloc(numParams2, numParams2, PS_TYPE_F64);
-        psImage *sumMatrixX = psImageAlloc(numParams, numParams2, PS_TYPE_F64);
-        psVector *sumVector1 = psVectorAlloc(numParams, PS_TYPE_F64);
-        psVector *sumVector2 = psVectorAlloc(numParams, PS_TYPE_F64);
-        psImageInit(sumMatrix1, 0.0);
-        psImageInit(sumMatrix2, 0.0);
-        psImageInit(sumMatrixX, 0.0);
-        psVectorInit(sumVector1, 0.0);
-        psVectorInit(sumVector2, 0.0);
+        psImage *sumMatrix = psImageAlloc(numParams, numParams, PS_TYPE_F64);
+        psVector *sumVector = psVectorAlloc(numParams, PS_TYPE_F64);
+        psImageInit(sumMatrix, 0.0);
+        psVectorInit(sumVector, 0.0);
+
+        psVector *norms = psVectorAllocEmpty(stamps->num, PS_TYPE_F64); // Normalisations
 
         int numStamps = 0;              // Number of good stamps
@@ -900,169 +1078,150 @@
             pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
             if (stamp->status == PM_SUBTRACTION_STAMP_USED) {
-                (void)psBinaryOp(sumMatrix1, sumMatrix1, "+", stamp->matrix1);
-                (void)psBinaryOp(sumMatrix2, sumMatrix2, "+", stamp->matrix2);
-                (void)psBinaryOp(sumMatrixX, sumMatrixX, "+", stamp->matrixX);
-                (void)psBinaryOp(sumVector1, sumVector1, "+", stamp->vector1);
-                (void)psBinaryOp(sumVector2, sumVector2, "+", stamp->vector2);
+                (void)psBinaryOp(sumMatrix, sumMatrix, "+", stamp->matrix);
+                (void)psBinaryOp(sumVector, sumVector, "+", stamp->vector);
+
+                psVectorAppend(norms, stamp->norm);
+
                 pmSubtractionStampPrint(ds9, stamp->x, stamp->y, stamps->footprint, "green");
                 numStamps++;
             }
         }
-        calculatePenalty(sumVector1, kernels);
-        calculatePenalty(sumVector2, kernels);
-
-        // Pure matrix operations
-
-        // A * a = Ct * b + d
-        // C * a = B  * b + e
-        //
-        // a = (Ct * Bi * C - A)i (Ct * Bi * e - d)
-        // b = Bi * (C * a - e)
-        psVector *a = psVectorRecycle(kernels->solution1, numParams, PS_TYPE_F64);
-        psVector *b = psVectorRecycle(kernels->solution2, numParams2, PS_TYPE_F64);
+
 #ifdef TESTING
-        psVectorInit(a, NAN);
-        psVectorInit(b, NAN);
-#endif
-        psImage *A = sumMatrix1;
-        psImage *B = sumMatrix2;
-        psImage *C = sumMatrixX;
-        psVector *d = sumVector1;
-        psVector *e = sumVector2;
-
-        assert(a->n == numParams);
-        assert(b->n == numParams2);
-        assert(A->numRows == numParams && A->numCols == numParams);
-        assert(B->numRows == numParams2 && B->numCols == numParams2);
-        assert(C->numRows == numParams2 && C->numCols == numParams);
-        assert(d->n == numParams);
-        assert(e->n == numParams2);
-
-        psImage *Bi = psMatrixInvert(NULL, B, NULL);
-        assert(Bi->numRows == numParams2 && Bi->numCols == numParams2);
-        psImage *Ct = psMatrixTranspose(NULL, C);
-        assert(Ct->numRows == numParams && Ct->numCols == numParams2);
-
-        psImage *BiC = psMatrixMultiply(NULL, Bi, C);
-        assert(BiC->numRows == numParams2 && BiC->numCols == numParams);
-        psImage *CtBi = psMatrixMultiply(NULL, Ct, Bi);
-        assert(CtBi->numRows == numParams && CtBi->numCols == numParams2);
-
-        psImage *CtBiC = psMatrixMultiply(NULL, Ct, BiC);
-        assert(CtBiC->numRows == numParams && CtBiC->numCols == numParams);
-
-        psImage *F = (psImage*)psBinaryOp(NULL, CtBiC, "-", A);
-        assert(F->numRows == numParams && F->numCols == numParams);
-        float det = 0.0;
-        psImage *Fi = psMatrixInvert(NULL, F, &det);
-        assert(Fi->numRows == numParams && Fi->numCols == numParams);
-        psTrace("psModules.imcombine", 4, "Determinant of F: %f\n", det);
-
-        psVector *g = psVectorAlloc(numParams, PS_TYPE_F64);
+        psFitsWriteImageSimple ("sumMatrix.fits", sumMatrix, NULL);
+        psVectorWriteFile("sumVector.dat", sumVector);
+#endif
+
+#if 1
+        // int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background
+        // calculatePenalty(sumMatrix, sumVector, kernels, sumMatrix->data.F64[bgIndex][bgIndex]);
+
+        int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+        calculatePenalty(sumMatrix, sumVector, kernels, sumMatrix->data.F64[normIndex][normIndex] / 1000.0);
+#endif
+
+        psVector *solution = NULL;                       // Solution to equation!
+        solution = psVectorAlloc(numParams, PS_TYPE_F64);
+        psVectorInit(solution, 0);
+
+#if 0
+        // Regular, straight-forward solution
+        solution = psMatrixSolveSVD(solution, sumMatrix, sumVector, NAN);
+#else
+        {
+            // Solve normalisation and background separately
+            int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+            int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background
+
+#if 0
+            psImage *normMatrix = psImageAlloc(2, 2, PS_TYPE_F64);
+            psVector *normVector = psVectorAlloc(2, PS_TYPE_F64);
+
+            normMatrix->data.F64[0][0] = sumMatrix->data.F64[normIndex][normIndex];
+            normMatrix->data.F64[1][1] = sumMatrix->data.F64[bgIndex][bgIndex];
+            normMatrix->data.F64[0][1] = normMatrix->data.F64[1][0] = sumMatrix->data.F64[normIndex][bgIndex];
+
+            normVector->data.F64[0] = sumVector->data.F64[normIndex];
+            normVector->data.F64[1] = sumVector->data.F64[bgIndex];
+
+            psVector *normSolution = psMatrixSolveSVD(NULL, normMatrix, normVector, NAN);
+
+            double normValue = normSolution->data.F64[0];
+            double bgValue = normSolution->data.F64[1];
+
+            psFree(normMatrix);
+            psFree(normVector);
+            psFree(normSolution);
+#endif
+
+            psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN); // Statistics for norm
+            if (!psVectorStats(stats, norms, NULL, NULL, 0)) {
+                psError(PM_ERR_DATA, false, "Unable to determine median normalisation");
+                psFree(stats);
+                psFree(sumMatrix);
+                psFree(sumVector);
+                psFree(norms);
+                return false;
+            }
+
+            double normValue = stats->robustMedian;
+
+            psFree(stats);
+
 #ifdef TESTING
-        psVectorInit(g, NAN);
-#endif
-        assert(CtBi->numRows == numParams && CtBi->numCols == numParams2);
-        assert(e->n == numParams2);
-        assert(d->n == numParams);
-        for (int i = 0; i < numParams; i++) {
-            double value = 0.0;
-            for (int j = 0; j < numParams2; j++) {
-                value += CtBi->data.F64[i][j] * e->data.F64[j];
-            }
-            g->data.F64[i] = value - d->data.F64[i];
-        }
-
-        assert(Fi->numRows == numParams && Fi->numCols == numParams);
-        assert(g->n == numParams);
-        for (int i = 0; i < numParams; i++) {
-            double value = 0.0;
-            for (int j = 0; j < numParams; j++) {
-                value += Fi->data.F64[i][j] * g->data.F64[j];
-            }
-            a->data.F64[i] = value;
-        }
-
-        psVector *h = psVectorAlloc(numParams2, PS_TYPE_F64);
+            fprintf(stderr, "Norm: %lf\n", normValue);
+#endif
+
+            // Solve kernel components
+            for (int i = 0; i < numSolution2; i++) {
+                sumVector->data.F64[i] -= normValue * sumMatrix->data.F64[normIndex][i];
+                sumVector->data.F64[i + numSolution1] -= normValue * sumMatrix->data.F64[normIndex][i + numSolution1];
+
+                sumMatrix->data.F64[i][normIndex] = 0.0;
+                sumMatrix->data.F64[normIndex][i] = 0.0;
+
+                sumMatrix->data.F64[i + numSolution1][normIndex] = 0.0;
+                sumMatrix->data.F64[normIndex][i + numSolution1] = 0.0;
+            }
+            sumVector->data.F64[bgIndex] -= normValue * sumMatrix->data.F64[normIndex][bgIndex];
+            sumMatrix->data.F64[bgIndex][normIndex] = 0.0;
+            sumMatrix->data.F64[normIndex][bgIndex] = 0.0;
+
+            sumMatrix->data.F64[normIndex][normIndex] = 1.0;
+
+            sumVector->data.F64[normIndex] = 0.0;
+
+            solution = psMatrixSolveSVD(solution, sumMatrix, sumVector, NAN);
+
+            solution->data.F64[normIndex] = normValue;
+        }
+#endif
+
+
 #ifdef TESTING
-        psVectorInit(h, NAN);
-#endif
-        assert(C->numRows == numParams2 && C->numCols == numParams);
-        assert(a->n == numParams);
-        assert(e->n == numParams2);
-        for (int i = 0; i < numParams2; i++) {
-            double value = 0.0;
-            for (int j = 0; j < numParams; j++) {
-                value += C->data.F64[i][j] * a->data.F64[j];
-            }
-            h->data.F64[i] = value - e->data.F64[i];
-        }
-
-        assert(Bi->numRows == numParams2 && Bi->numCols == numParams2);
-        assert(h->n == numParams2);
-        for (int i = 0; i < numParams2; i++) {
-            double value = 0.0;
-            for (int j = 0; j < numParams2; j++) {
-                value += Bi->data.F64[i][j] * h->data.F64[j];
-            }
-            b->data.F64[i] = value;
-        }
-
-
-#if 0
-        for (int i = 0; i < numParams; i++) {
-            double aVal1 = 0.0, bVal1 = 0.0;
-            for (int j = 0; j < numParams2; j++) {
-                aVal1 += A->data.F64[i][j] * a->data.F64[j];
-                bVal1 += Ct->data.F64[i][j] * b->data.F64[j];
-            }
-            bVal1 += d->data.F64[i];
-            for (int j = numParams2; j < numParams; j++) {
-                aVal1 += A->data.F64[i][j] * a->data.F64[j];
-            }
-            printf("%d: %lf\n", i, aVal1 - bVal1);
-        }
-
-        for (int i = 0; i < numParams2; i++) {
-            double aVal2 = 0.0, bVal2 = 0.0;
-            for (int j = 0; j < numParams2; j++) {
-                aVal2 += C->data.F64[i][j] * a->data.F64[j];
-                bVal2 += B->data.F64[i][j] * b->data.F64[j];
-            }
-            bVal2 += e->data.F64[i];
-            for (int j = numParams2; j < numParams; j++) {
-                aVal2 += C->data.F64[i][j] * a->data.F64[j];
-            }
-            printf("%d: %lf\n", i, aVal2 - bVal2);
-        }
-#endif
-
-#ifdef TESTING
-        {
-            psFits *fits = psFitsOpen("sumMatrix1.fits", "w");
-            psFitsWriteImage(fits, NULL, sumMatrix1, 0, NULL);
-            psFitsClose(fits);
-        }
-        {
-            psFits *fits = psFitsOpen("sumMatrix2.fits", "w");
-            psFitsWriteImage(fits, NULL, sumMatrix2, 0, NULL);
-            psFitsClose(fits);
-        }
-        {
-            psFits *fits = psFitsOpen("sumMatrixX.fits", "w");
-            psFitsWriteImage(fits, NULL, sumMatrixX, 0, NULL);
-            psFitsClose(fits);
-        }
-        {
-            psFits *fits = psFitsOpen("sumFinverse.fits", "w");
-            psFitsWriteImage(fits, NULL, Fi, 0, NULL);
-            psFitsClose(fits);
-        }
-#endif
-
-        kernels->solution1 = a;
-        kernels->solution2 = b;
-
-        // XXXXX Free temporary matrices and vectors
+        for (int i = 0; i < solution->n; i++) {
+            fprintf(stderr, "Dual solution %d: %lf\n", i, solution->data.F64[i]);
+        }
+#endif
+
+        psFree(sumMatrix);
+        psFree(sumVector);
+
+        psFree(norms);
+
+        if (!kernels->solution1) {
+            kernels->solution1 = psVectorAlloc(numSolution1, PS_TYPE_F64);
+            psVectorInit (kernels->solution1, 0.0);
+        }
+        if (!kernels->solution2) {
+            kernels->solution2 = psVectorAlloc(numSolution2, PS_TYPE_F64);
+            psVectorInit (kernels->solution2, 0.0);
+        }
+
+        // only update the solutions that we chose to calculate:
+        if (mode & PM_SUBTRACTION_EQUATION_NORM) {
+            int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+            kernels->solution1->data.F64[normIndex] = solution->data.F64[normIndex];
+        }
+        if (mode & PM_SUBTRACTION_EQUATION_BG) {
+            int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index in matrix for background
+            kernels->solution1->data.F64[bgIndex] = solution->data.F64[bgIndex];
+        }
+        if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+            int numKernels = kernels->num;
+            for (int i = 0; i < numKernels * numSpatial; i++) {
+                // XXX fprintf (stderr, "keep\n");
+                kernels->solution1->data.F64[i] = solution->data.F64[i];
+                kernels->solution2->data.F64[i] = solution->data.F64[i + numSolution1];
+            }
+        }
+
+
+        memcpy(kernels->solution1->data.F64, solution->data.F64,
+               numSolution1 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
+        memcpy(kernels->solution2->data.F64, &solution->data.F64[numSolution1],
+               numSolution2 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
+
+        psFree(solution);
 
     }
@@ -1083,10 +1242,55 @@
      }
 
-    pmSubtractionVisualPlotLeastSquares((pmSubtractionStampList *) stamps); //casting away const
+    // pmSubtractionVisualPlotLeastSquares((pmSubtractionStampList *) stamps); //casting away const
     return true;
 }
 
+bool pmSubtractionResidualStats(psVector *fSigRes, psVector *fMaxRes, psVector *fMinRes, psKernel *target, psKernel *source, psKernel *residual, double norm, int footprint) {
+
+    // XXX measure some useful stats on the residuals
+    float sum = 0.0;
+    float peak = 0.0;
+    for (int y = - footprint; y <= footprint; y++) {
+        for (int x = - footprint; x <= footprint; x++) {
+            sum += 0.5*(target->kernel[y][x] + source->kernel[y][x] * norm);
+            peak = PS_MAX(peak, 0.5*(target->kernel[y][x] + source->kernel[y][x] * norm));
+        }
+    }
+
+    // only count pixels with more than X% of the source flux
+    // calculate stdev(dflux)
+    float dflux1 = 0.0;
+    float dflux2 = 0.0;
+    int npix = 0;
+
+    float dmax = 0.0;
+    float dmin = 0.0;
+
+    for (int y = - footprint; y <= footprint; y++) {
+        for (int x = - footprint; x <= footprint; x++) {
+            float dflux = 0.5*(target->kernel[y][x] + source->kernel[y][x] * norm);
+            if (dflux < 0.02*sum) continue;
+            dflux1 += residual->kernel[y][x];
+            dflux2 += PS_SQR(residual->kernel[y][x]);
+            dmax = PS_MAX(residual->kernel[y][x], dmax);
+            dmin = PS_MIN(residual->kernel[y][x], dmin);
+            npix ++;
+        }
+    }
+    float sigma = sqrt(dflux2 / npix - PS_SQR(dflux1/npix));
+    if (!isfinite(sum))  return false;
+    if (!isfinite(dmax)) return false;
+    if (!isfinite(dmin)) return false;
+    if (!isfinite(peak)) return false;
+
+    // fprintf (stderr, "sum: %f, peak: %f, sigma: %f, fsigma: %f, fmax: %f, fmin: %f\n", sum, peak, sigma, sigma/sum, dmax/peak, dmin/peak);
+    psVectorAppend(fSigRes, sigma/sum);
+    psVectorAppend(fMaxRes, dmax/peak);
+    psVectorAppend(fMinRes, dmin/peak);
+    return true;
+}
+
 psVector *pmSubtractionCalculateDeviations(pmSubtractionStampList *stamps,
-                                           const pmSubtractionKernels *kernels)
+                                           pmSubtractionKernels *kernels)
 {
     PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, NULL);
@@ -1103,4 +1307,42 @@
     psKernel *residual = psKernelAlloc(-footprint, footprint, -footprint, footprint); // Residual image
 
+    // set up holding images for the visualization
+    pmSubtractionVisualShowFitInit (stamps);
+
+    psVector *fSigRes = psVectorAllocEmpty(stamps->num, PS_TYPE_F32);
+    psVector *fMinRes = psVectorAllocEmpty(stamps->num, PS_TYPE_F32);
+    psVector *fMaxRes = psVectorAllocEmpty(stamps->num, PS_TYPE_F32);
+
+    // we want to save the residual images for the 9 brightest stamps.
+    // identify the 9 brightest stamps
+    psVector *keepStamps  = psVectorAlloc(stamps->num, PS_TYPE_S32);
+    psVectorInit (keepStamps, 0);
+    {
+        psVector *flux  = psVectorAlloc(stamps->num, PS_TYPE_F32);
+        psVectorInit (flux, 0.0);
+
+        for (int i = 0; i < stamps->num; i++) {
+            pmSubtractionStamp *stamp = stamps->stamps->data[i];
+            if (!isfinite(stamp->flux)) continue;
+            flux->data.F32[i] = stamp->flux;
+        }
+
+        psVector *index = psVectorSortIndex(NULL, flux);
+        for (int i = 0; (i < stamps->num) && (i < 9); i++) {
+            int n = stamps->num - i - 1;
+            keepStamps->data.S32[index->data.S32[n]] = 1;
+        }
+        psFree (flux);
+        psFree (index);
+
+        // this function is called multiple times in the iteration, but
+        // we only know after the interation is done if we will try again.
+        // therefore we must save the sample each time, and blow away the old one
+        // if it exists.
+        psFree (kernels->sampleStamps);
+        kernels->sampleStamps = psArrayAllocEmpty(9);
+    }
+
+    psString log = psStringCopy("Deviations:\n");               // Log message with deviations
     for (int i = 0; i < stamps->num; i++) {
         pmSubtractionStamp *stamp = stamps->stamps->data[i]; // The stamp of interest
@@ -1116,5 +1358,5 @@
 
         // Calculate residuals
-        psKernel *variance = stamp->variance; // Variance postage stamp
+        psKernel *weight = stamp->weight; // Weight postage stamp
         psImageInit(residual->image, 0.0);
         if (kernels->mode != PM_SUBTRACTION_MODE_DUAL) {
@@ -1133,5 +1375,5 @@
                                                           false); // Kernel image
                 if (!image) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to generate image of kernel.");
+                    psError(psErrorCodeLast(), false, "Unable to generate image of kernel.");
                     return false;
                 }
@@ -1162,13 +1404,26 @@
                 for (int y = - footprint; y <= footprint; y++) {
                     for (int x = - footprint; x <= footprint; x++) {
-                        residual->kernel[y][x] -= convolution->kernel[y][x] * coefficient;
+                        residual->kernel[y][x] += convolution->kernel[y][x] * coefficient;
                     }
                 }
             }
+
+            // XXX visualize the target, source, convolution and residual
+            pmSubtractionVisualShowFitAddStamp (target, source, residual, background, norm, i);
+
             for (int y = - footprint; y <= footprint; y++) {
                 for (int x = - footprint; x <= footprint; x++) {
-                    residual->kernel[y][x] += target->kernel[y][x] - background - source->kernel[y][x] * norm;
-                }
-            }
+                    residual->kernel[y][x] += background + source->kernel[y][x] * norm - target->kernel[y][x];
+                }
+            }
+
+            if (keepStamps->data.S32[i]) {
+                psImage *sample = psImageCopy(NULL, residual->image, PS_TYPE_F32);
+                psArrayAdd (kernels->sampleStamps, 9, sample);
+                psFree (sample);
+            }
+
+            pmSubtractionResidualStats(fSigRes, fMaxRes, fMinRes, target, source, residual, norm, footprint);
+
         } else {
             // Dual convolution
@@ -1186,13 +1441,24 @@
                 for (int y = - footprint; y <= footprint; y++) {
                     for (int x = - footprint; x <= footprint; x++) {
-                        residual->kernel[y][x] += conv2->kernel[y][x] * coeff2 - conv1->kernel[y][x] * coeff1;
+                        residual->kernel[y][x] += conv2->kernel[y][x] * coeff2 + conv1->kernel[y][x] * coeff1;
                     }
                 }
             }
+
+            // XXX visualize the target, source, convolution and residual
+            pmSubtractionVisualShowFitAddStamp (image2, image1, residual, background, norm, i);
+
             for (int y = - footprint; y <= footprint; y++) {
                 for (int x = - footprint; x <= footprint; x++) {
-                    residual->kernel[y][x] += image2->kernel[y][x] - background - image1->kernel[y][x] * norm;
-                }
-            }
+                    residual->kernel[y][x] += background + image1->kernel[y][x] * norm - image2->kernel[y][x];
+                }
+            }
+            if (keepStamps->data.S32[i]) {
+                psImage *sample = psImageCopy(NULL, residual->image, PS_TYPE_F32);
+                psArrayAdd (kernels->sampleStamps, 9, sample);
+                psFree (sample);
+            }
+
+            pmSubtractionResidualStats(fSigRes, fMaxRes, fMinRes, image1, image2, residual, norm, footprint);
         }
 
@@ -1200,5 +1466,5 @@
         for (int y = - footprint; y <= footprint; y++) {
             for (int x = - footprint; x <= footprint; x++) {
-                double dev = PS_SQR(residual->kernel[y][x]) / variance->kernel[y][x];
+                double dev = PS_SQR(residual->kernel[y][x]) * weight->kernel[y][x];
                 deviation += dev;
 #ifdef TESTING
@@ -1209,10 +1475,12 @@
         deviations->data.F32[i] = devNorm * deviation;
         psTrace("psModules.imcombine", 5, "Deviation for stamp %d (%d,%d): %f\n",
-                i, (int)(stamp->x + 0.5), (int)(stamp->y + 0.5), deviations->data.F32[i]);
+                i, (int)(stamp->x - 0.5), (int)(stamp->y - 0.5), deviations->data.F32[i]);
+        psStringAppend(&log, "Stamp %d (%d,%d): %f\n",
+                       i, (int)(stamp->x - 0.5), (int)(stamp->y - 0.5), deviations->data.F32[i]);
         if (!isfinite(deviations->data.F32[i])) {
             stamp->status = PM_SUBTRACTION_STAMP_REJECTED;
             psTrace("psModules.imcombine", 5,
                     "Rejecting stamp %d (%d,%d) because of non-finite deviation\n",
-                    i, (int)(stamp->x + 0.5), (int)(stamp->y + 0.5));
+                    i, (int)(stamp->x - 0.5), (int)(stamp->y - 0.5));
             continue;
         }
@@ -1243,10 +1511,10 @@
             psFitsClose(fits);
         }
-        if (stamp->variance) {
+        if (stamp->weight) {
             psString filename = NULL;
-            psStringAppend(&filename, "stamp_variance_%03d.fits", i);
+            psStringAppend(&filename, "stamp_weight_%03d.fits", i);
             psFits *fits = psFitsOpen(filename, "w");
             psFree(filename);
-            psFitsWriteImage(fits, NULL, stamp->variance->image, 0, NULL);
+            psFitsWriteImage(fits, NULL, stamp->weight->image, 0, NULL);
             psFitsClose(fits);
         }
@@ -1254,7 +1522,683 @@
 
     }
+
+    psFree(keepStamps);
+
+    psLogMsg("psModules.imcombine", PS_LOG_DETAIL, "%s", log);
+    psFree(log);
+
+    // calculate and report the normalization and background for the image center
+    {
+        polyValues = p_pmSubtractionPolynomial(polyValues, kernels->spatialOrder, 0.0, 0.0);
+        double norm = p_pmSubtractionSolutionNorm(kernels); // Normalisation
+        double background = p_pmSubtractionSolutionBackground(kernels, polyValues);// Difference in background
+        psLogMsg("psModules.imcombine", PS_LOG_INFO, "normalization: %f, background: %f", norm, background);
+
+        pmSubtractionVisualShowFit(norm);
+        pmSubtractionVisualPlotFit(kernels);
+
+        psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+        psVectorStats (stats, fSigRes, NULL, NULL, 0);
+        kernels->fSigResMean = stats->robustMedian;
+        kernels->fSigResStdev = stats->robustStdev;
+
+        psStatsInit (stats);
+        psVectorStats (stats, fMaxRes, NULL, NULL, 0);
+        kernels->fMaxResMean = stats->robustMedian;
+        kernels->fMaxResStdev = stats->robustStdev;
+
+        psStatsInit (stats);
+        psVectorStats (stats, fMinRes, NULL, NULL, 0);
+        kernels->fMinResMean = stats->robustMedian;
+        kernels->fMinResStdev = stats->robustStdev;
+
+        // XXX save these values somewhere
+        psLogMsg("psModules.imcombine", PS_LOG_INFO, "fSigma: %f +/- %f, fMaxRes: %f +/- %f, fMinRes: %f +/- %f",
+                 kernels->fSigResMean, kernels->fSigResStdev,
+                 kernels->fMaxResMean, kernels->fMaxResStdev,
+                 kernels->fMinResMean, kernels->fMinResStdev);
+
+        psFree (fSigRes);
+        psFree (fMaxRes);
+        psFree (fMinRes);
+        psFree (stats);
+    }
+
     psFree(residual);
     psFree(polyValues);
 
+
     return deviations;
 }
+
+// we are supplied U, not Ut; w represents a diagonal matrix (also, we apply 1/w instead of w)
+psImage *p_pmSubSolve_wUt (psVector *w, psImage *U) {
+
+    psAssert (w->n == U->numCols, "w and U dimensions do not match");
+
+    // wUt has dimensions transposed relative to Ut.
+    psImage *wUt = psImageAlloc (U->numRows, U->numCols, PS_TYPE_F64);
+    psImageInit (wUt, 0.0);
+
+    for (int i = 0; i < wUt->numCols; i++) {
+        for (int j = 0; j < wUt->numRows; j++) {
+            if (!isfinite(w->data.F64[j])) continue;
+            if (w->data.F64[j] == 0.0) continue;
+            wUt->data.F64[j][i] = U->data.F64[i][j] / w->data.F64[j];
+        }
+    }
+    return wUt;
+}
+
+// XXX this is just standard matrix multiplication: use psMatrixMultiply?
+psImage *p_pmSubSolve_VwUt (psImage *V, psImage *wUt) {
+
+    psAssert (V->numCols == wUt->numRows, "matrix dimensions do not match");
+
+    psImage *Ainv = psImageAlloc (wUt->numCols, V->numRows, PS_TYPE_F64);
+
+    for (int i = 0; i < Ainv->numCols; i++) {
+        for (int j = 0; j < Ainv->numRows; j++) {
+            double sum = 0.0;
+            for (int k = 0; k < V->numCols; k++) {
+                sum += V->data.F64[j][k] * wUt->data.F64[k][i];
+            }
+            Ainv->data.F64[j][i] = sum;
+        }
+    }
+    return Ainv;
+}
+
+// we are supplied U, not Ut
+bool p_pmSubSolve_UtB (psVector **UtB, psImage *U, psVector *B) {
+
+    psAssert (U->numRows == B->n, "U and B dimensions do not match");
+
+    UtB[0] = psVectorRecycle (UtB[0], U->numCols, PS_TYPE_F64);
+
+    for (int i = 0; i < U->numCols; i++) {
+        double sum = 0.0;
+        for (int j = 0; j < U->numRows; j++) {
+            sum += B->data.F64[j] * U->data.F64[j][i];
+        }
+        UtB[0]->data.F64[i] = sum;
+    }
+    return true;
+}
+
+// w is diagonal
+bool p_pmSubSolve_wUtB (psVector **wUtB, psVector *w, psVector *UtB) {
+
+    psAssert (w->n == UtB->n, "w and UtB dimensions do not match");
+
+    // wUt has dimensions transposed relative to Ut.
+    wUtB[0] = psVectorRecycle (wUtB[0], w->n, PS_TYPE_F64);
+    psVectorInit (wUtB[0], 0.0);
+
+    for (int i = 0; i < w->n; i++) {
+        if (!isfinite(w->data.F64[i])) continue;
+        if (w->data.F64[i] == 0.0) continue;
+        wUtB[0]->data.F64[i] = UtB->data.F64[i] / w->data.F64[i];
+    }
+    return true;
+}
+
+// this is basically matrix * vector
+bool p_pmSubSolve_VwUtB (psVector **VwUtB, psImage *V, psVector *wUtB) {
+
+    psAssert (V->numCols == wUtB->n, "V and wUtB dimensions do not match");
+
+    VwUtB[0] = psVectorRecycle (*VwUtB, V->numRows, PS_TYPE_F64);
+
+    for (int j = 0; j < V->numRows; j++) {
+        double sum = 0.0;
+        for (int i = 0; i < V->numCols; i++) {
+            sum += V->data.F64[j][i] * wUtB->data.F64[i];
+        }
+        VwUtB[0]->data.F64[j] = sum;
+    }
+    return true;
+}
+
+// this is basically matrix * vector
+bool p_pmSubSolve_Ax (psVector **B, psImage *A, psVector *x) {
+
+    psAssert (A->numCols == x->n, "A and x dimensions do not match");
+
+    B[0] = psVectorRecycle (*B, A->numRows, PS_TYPE_F64);
+
+    for (int j = 0; j < A->numRows; j++) {
+        double sum = 0.0;
+        for (int i = 0; i < A->numCols; i++) {
+            sum += A->data.F64[j][i] * x->data.F64[i];
+        }
+        B[0]->data.F64[j] = sum;
+    }
+    return true;
+}
+
+// this is basically Vector * vector
+bool p_pmSubSolve_VdV (double *value, psVector *x, psVector *y) {
+
+    psAssert (x->n == y->n, "x and y dimensions do not match");
+
+    double sum = 0.0;
+    for (int i = 0; i < x->n; i++) {
+        sum += x->data.F64[i] * y->data.F64[i];
+    }
+    *value = sum;
+    return true;
+}
+
+bool p_pmSubSolve_y2 (double *y2, pmSubtractionKernels *kernels, const pmSubtractionStampList *stamps) {
+
+    int footprint = stamps->footprint; // Half-size of stamps
+
+    double sum = 0.0;
+    for (int i = 0; i < stamps->num; i++) {
+
+        pmSubtractionStamp *stamp = stamps->stamps->data[i];
+        if (stamp->status != PM_SUBTRACTION_STAMP_USED) continue;
+
+        psKernel *weight = NULL;
+        psKernel *window = NULL;
+        psKernel *input = NULL;
+
+#ifdef USE_WEIGHT
+        weight = stamp->weight;
+#endif
+#ifdef USE_WINDOW
+        window = stamps->window;
+#endif
+
+        switch (kernels->mode) {
+            // MODE_1 : convolve image 1 to match image 2 (and vice versa)
+          case PM_SUBTRACTION_MODE_1:
+            input = stamp->image2;
+            break;
+          case PM_SUBTRACTION_MODE_2:
+            input = stamp->image1;
+            break;
+          default:
+            psAbort ("programming error");
+        }
+
+        for (int y = - footprint; y <= footprint; y++) {
+            for (int x = - footprint; x <= footprint; x++) {
+                double in = input->kernel[y][x];
+                double value = in*in;
+                if (weight) {
+                    float wtVal = weight->kernel[y][x];
+                    value *= wtVal;
+                }
+                if (window) {
+                    float  winVal = window->kernel[y][x];
+                    value *= winVal;
+                }
+                sum += value;
+            }
+        }
+    }
+    *y2 = sum;
+    return true;
+}
+
+double p_pmSubSolve_ChiSquare (pmSubtractionKernels *kernels, const pmSubtractionStampList *stamps) {
+
+    int footprint = stamps->footprint; // Half-size of stamps
+    int numKernels = kernels->num;      // Number of kernels
+
+    double sum = 0.0;
+
+    psKernel *residual = psKernelAlloc(-footprint, footprint, -footprint, footprint); // Residual image
+    psImageInit(residual->image, 0.0);
+
+    psImage *polyValues = NULL;         // Polynomial values
+
+    for (int i = 0; i < stamps->num; i++) {
+
+        pmSubtractionStamp *stamp = stamps->stamps->data[i];
+        if (stamp->status != PM_SUBTRACTION_STAMP_USED) continue;
+
+        psKernel *weight = NULL;
+        psKernel *window = NULL;
+        psKernel *target = NULL;
+        psKernel *source = NULL;
+
+        psArray *convolutions = NULL;
+
+#ifdef USE_WEIGHT
+        weight = stamp->weight;
+#endif
+#ifdef USE_WINDOW
+        window = stamps->window;
+#endif
+
+        switch (kernels->mode) {
+            // MODE_1 : convolve image 1 to match image 2 (and vice versa)
+          case PM_SUBTRACTION_MODE_1:
+            target = stamp->image2;
+            source = stamp->image1;
+            convolutions = stamp->convolutions1;
+            break;
+          case PM_SUBTRACTION_MODE_2:
+            target = stamp->image1;
+            source = stamp->image2;
+            convolutions = stamp->convolutions2;
+            break;
+          default:
+            psAbort ("programming error");
+        }
+
+        // Calculate coefficients of the kernel basis functions
+        polyValues = p_pmSubtractionPolynomial(polyValues, kernels->spatialOrder, stamp->xNorm, stamp->yNorm);
+        double norm = p_pmSubtractionSolutionNorm(kernels); // Normalisation
+        double background = p_pmSubtractionSolutionBackground(kernels, polyValues);// Difference in background
+
+        psImageInit(residual->image, 0.0);
+        for (int j = 0; j < numKernels; j++) {
+            psKernel *convolution = convolutions->data[j]; // Convolution
+            double coefficient = p_pmSubtractionSolutionCoeff(kernels, polyValues, j, false); // Coefficient
+            for (int y = - footprint; y <= footprint; y++) {
+                for (int x = - footprint; x <= footprint; x++) {
+                    residual->kernel[y][x] -= convolution->kernel[y][x] * coefficient;
+                }
+            }
+        }
+
+        for (int y = - footprint; y <= footprint; y++) {
+            for (int x = - footprint; x <= footprint; x++) {
+                double resid = target->kernel[y][x] - background - source->kernel[y][x] * norm + residual->kernel[y][x];
+                double value = PS_SQR(resid);
+                if (weight) {
+                    float wtVal = weight->kernel[y][x];
+                    value *= wtVal;
+                }
+                if (window) {
+                    float  winVal = window->kernel[y][x];
+                    value *= winVal;
+                }
+                sum += value;
+            }
+        }
+    }
+    psFree (polyValues);
+    psFree (residual);
+
+    return sum;
+}
+
+bool p_pmSubSolve_SetWeights (psVector *wApply, psVector *w, psVector *wMask) {
+
+    for (int i = 0; i < w->n; i++) {
+        wApply->data.F64[i] = wMask->data.U8[i] ? 0.0 : w->data.F64[i];
+    }
+    return true;
+}
+
+// we are supplied V and w; w represents a diagonal matrix (also, we apply 1/w instead of w)
+psImage *p_pmSubSolve_Xvar (psImage *V, psVector *w) {
+
+    psAssert (w->n == V->numCols, "w and U dimensions do not match");
+
+    psImage *Vn = psImageAlloc (V->numCols, V->numRows, PS_TYPE_F64);
+    psImageInit (Vn, 0.0);
+
+    // generate Vn = V * w^{-1}
+    for (int j = 0; j < Vn->numRows; j++) {
+        for (int i = 0; i < Vn->numCols; i++) {
+            if (!isfinite(w->data.F64[i])) continue;
+            if (w->data.F64[i] == 0.0) continue;
+            Vn->data.F64[j][i] = V->data.F64[j][i] / w->data.F64[i];
+        }
+    }
+
+    psImage *Xvar = psImageAlloc (V->numCols, V->numRows, PS_TYPE_F64);
+    psImageInit (Xvar, 0.0);
+
+    // generate Xvar = Vn * Vn^T
+    for (int j = 0; j < Vn->numRows; j++) {
+        for (int i = 0; i < Vn->numCols; i++) {
+            double sum = 0.0;
+            for (int k = 0; k < Vn->numCols; k++) {
+                sum += Vn->data.F64[k][i]*Vn->data.F64[k][j];
+            }
+            Xvar->data.F64[j][i] = sum;
+        }
+    }
+    return Xvar;
+}
+
+// I get confused by the index values between the image vs matrix usage:  In terms
+// of the elements of an image A(x,y) = A->data.F64[y][x] = A_x,y, a matrix
+// multiplication is: A_k,j * B_i,k = C_i,j
+
+
+bool psFitsWriteImageSimple (char *filename, psImage *image, psMetadata *header) {
+
+    psFits *fits = psFitsOpen(filename, "w");
+    psFitsWriteImage(fits, header, image, 0, NULL);
+    psFitsClose(fits);
+
+    return true;
+}
+
+bool psVectorWriteFile (char *filename, const psVector *vector) {
+
+    FILE *f = fopen (filename, "w");
+    int fd = fileno(f);
+    p_psVectorPrint (fd, vector, "unnamed");
+    fclose (f);
+
+    return true;
+}
+
+
+# if 0
+
+#ifdef TESTING
+        psFitsWriteImageSimple("A.fits", sumMatrix, NULL);
+        psVectorWriteFile ("B.dat", sumVector);
+#endif
+
+# define SVD_ANALYSIS 0
+# define COEFF_SIG 0.0
+# define SVD_TOL 0.0
+
+        // Use SVD to determine the kernel coeffs (and validate)
+        if (SVD_ANALYSIS) {
+
+            // We have sumVector and sumMatrix.  we are trying to solve the following equation:
+            // sumMatrix * x = sumVector.
+
+            // we can use any standard matrix inversion to solve this.  However, the basis
+            // functions in general have substantial correlation, so that the solution may be
+            // somewhat poorly determined or unstable.  If not numerically ill-conditioned, the
+            // system of equations may be statistically ill-conditioned.  Noise in the image
+            // will drive insignificant, but correlated, terms in the solution.  To avoid these
+            // problems, we can use SVD to identify numerically unconstrained values and to
+            // avoid statistically badly determined value.
+
+            // A = sumMatrix, B = sumVector
+            // SVD: A = U w V^T  -> A^{-1} = V (1/w) U^T
+            // x = V (1/w) (U^T B)
+            // \sigma_x = sqrt(diag(A^{-1}))
+            // solve for x and A^{-1} to get x & dx
+            // identify the elements of (1/w) that are nan (1/0.0) -> set to 0.0
+            // identify the elements of x that are insignificant (x / dx < 1.0? < 0.5?) -> set to 0.0
+
+            // If I use the SVD trick to re-condition the matrix, I need to break out the
+            // kernel and normalization terms from the background term.
+            // XXX is this true?  or was this due to an error in the analysis?
+
+            int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index in matrix for background
+
+            // now pull out the kernel elements into their own square matrix
+            psImage  *kernelMatrix = psImageAlloc  (sumMatrix->numCols - 1, sumMatrix->numRows - 1, PS_TYPE_F64);
+            psVector *kernelVector = psVectorAlloc (sumMatrix->numCols - 1, PS_TYPE_F64);
+
+            for (int ix = 0, kx = 0; ix < sumMatrix->numCols; ix++) {
+                if (ix == bgIndex) continue;
+                for (int iy = 0, ky = 0; iy < sumMatrix->numRows; iy++) {
+                    if (iy == bgIndex) continue;
+                    kernelMatrix->data.F64[ky][kx] = sumMatrix->data.F64[iy][ix];
+                    ky++;
+                }
+                kernelVector->data.F64[kx] = sumVector->data.F64[ix];
+                kx++;
+            }
+
+            psImage *U = NULL;
+            psImage *V = NULL;
+            psVector *w = NULL;
+            if (!psMatrixSVD (&U, &w, &V, kernelMatrix)) {
+                psError(psErrorCodeLast(), false, "failed to perform SVD on sumMatrix\n");
+                return NULL;
+            }
+
+            // calculate A_inverse:
+            // Ainv = V * w * U^T
+            psImage *wUt  = p_pmSubSolve_wUt (w, U);
+            psImage *Ainv = p_pmSubSolve_VwUt (V, wUt);
+            psImage *Xvar = NULL;
+            psFree (wUt);
+
+# ifdef TESTING
+            // kernel terms:
+            for (int i = 0; i < w->n; i++) {
+                fprintf (stderr, "w: %f\n", w->data.F64[i]);
+            }
+# endif
+            // loop over w adding in more and more of the values until chisquare is no longer
+            // dropping significantly.
+            // XXX this does not seem to work very well: we seem to need all terms even for
+            // simple cases...
+
+            psVector *Xsvd = NULL;
+            {
+                psVector *Ax = NULL;
+                psVector *UtB = NULL;
+                psVector *wUtB = NULL;
+
+                psVector *wApply = psVectorAlloc(w->n, PS_TYPE_F64);
+                psVector *wMask = psVectorAlloc(w->n, PS_TYPE_U8);
+                psVectorInit (wMask, 1); // start by masking everything
+
+                double chiSquareLast = NAN;
+                int maxWeight = 0;
+
+                double Axx, Bx, y2;
+
+                // XXX this is an attempt to exclude insignificant modes.
+                // it was not successful with the ISIS kernel set: removing even
+                // the least significant mode leaves additional ringing / noise
+                // because the terms are so coupled.
+                for (int k = 0; false && (k < w->n); k++) {
+
+                    // unmask the k-th weight
+                    wMask->data.U8[k] = 0;
+                    p_pmSubSolve_SetWeights(wApply, w, wMask);
+
+                    // solve for x:
+                    // x = V * w * (U^T * B)
+                    p_pmSubSolve_UtB (&UtB, U, kernelVector);
+                    p_pmSubSolve_wUtB (&wUtB, wApply, UtB);
+                    p_pmSubSolve_VwUtB (&Xsvd, V, wUtB);
+
+                    // chi-square for this system of equations:
+                    // chi-square = sum over terms of: (Ax - B)*x - b*x - y^2
+                    // y^2 = \sum_stamps \sum_pixels input->kernel[y][x]^2
+                    p_pmSubSolve_Ax (&Ax, kernelMatrix, Xsvd);
+                    p_pmSubSolve_VdV (&Axx, Ax, Xsvd);
+                    p_pmSubSolve_VdV (&Bx, kernelVector, Xsvd);
+                    p_pmSubSolve_y2 (&y2, kernels, stamps);
+
+                    // apparently, this works (compare with the brute force value below
+                    double chiSquare = Axx - 2.0*Bx + y2;
+                    double deltaChi = (k == 0) ? chiSquare : chiSquareLast - chiSquare;
+                    chiSquareLast = chiSquare;
+
+                    // fprintf (stderr, "chi square = %f, delta: %f\n", chiSquare, deltaChi);
+                    if (k && !maxWeight && (deltaChi < 1.0)) {
+                        maxWeight = k;
+                    }
+                }
+
+                // keep all terms or we get extra ringing
+                maxWeight = w->n;
+                psVectorInit (wMask, 1);
+                for (int k = 0; k < maxWeight; k++) {
+                    wMask->data.U8[k] = 0;
+                }
+                p_pmSubSolve_SetWeights(wApply, w, wMask);
+
+                // solve for x:
+                // x = V * w * (U^T * B)
+                p_pmSubSolve_UtB (&UtB, U, kernelVector);
+                p_pmSubSolve_wUtB (&wUtB, wApply, UtB);
+                p_pmSubSolve_VwUtB (&Xsvd, V, wUtB);
+
+                // chi-square for this system of equations:
+                // chi-square = sum over terms of: (Ax - B)*x - b*x - y^2
+                // y^2 = \sum_stamps \sum_pixels input->kernel[y][x]^2
+                p_pmSubSolve_Ax (&Ax, kernelMatrix, Xsvd);
+                p_pmSubSolve_VdV (&Axx, Ax, Xsvd);
+                p_pmSubSolve_VdV (&Bx, kernelVector, Xsvd);
+                p_pmSubSolve_y2 (&y2, kernels, stamps);
+
+                // apparently, this works (compare with the brute force value below
+                double chiSquare = Axx - 2.0*Bx + y2;
+                psLogMsg ("psModules.imcombine", PS_LOG_INFO, "model kernel with %d terms; chi square = %f\n", maxWeight, chiSquare);
+
+                // re-calculate A^{-1} to get new variances:
+                // Ainv = V * w * U^T
+                // XXX since we keep all terms, this is identical to Ainv
+                psImage *wUt  = p_pmSubSolve_wUt (wApply, U);
+                Xvar = p_pmSubSolve_VwUt (V, wUt);
+                psFree (wUt);
+
+                psFree (Ax);
+                psFree (UtB);
+                psFree (wUtB);
+                psFree (wApply);
+                psFree (wMask);
+            }
+
+            // copy the kernel solutions to the full solution vector:
+            solution = psVectorAlloc(sumVector->n, PS_TYPE_F64);
+            solutionErr = psVectorAlloc(sumVector->n, PS_TYPE_F64);
+
+            for (int ix = 0, kx = 0; ix < sumVector->n; ix++) {
+                if (ix == bgIndex) {
+                    solution->data.F64[ix] = 0;
+                    solutionErr->data.F64[ix] = 0.001;
+                    continue;
+                }
+                solutionErr->data.F64[ix] = sqrt(Ainv->data.F64[kx][kx]);
+                solution->data.F64[ix] = Xsvd->data.F64[kx];
+                kx++;
+            }
+
+            psFree (kernelMatrix);
+            psFree (kernelVector);
+
+            psFree (U);
+            psFree (V);
+            psFree (w);
+
+            psFree (Ainv);
+            psFree (Xsvd);
+        } else {
+            psVector *permutation = NULL;       // Permutation vector, required for LU decomposition
+            psImage *luMatrix = psMatrixLUDecomposition(NULL, &permutation, sumMatrix);
+            if (!luMatrix) {
+                psError(PM_ERR_DATA, true, "LU Decomposition of least-squares matrix failed.\n");
+                psFree(solution);
+                psFree(sumVector);
+                psFree(sumMatrix);
+                psFree(luMatrix);
+                psFree(permutation);
+                return NULL;
+            }
+
+            solution = psMatrixLUSolution(NULL, luMatrix, sumVector, permutation);
+            psFree(luMatrix);
+            psFree(permutation);
+            if (!solution) {
+                psError(PM_ERR_DATA, true, "Failed to solve the least-squares system.\n");
+                psFree(solution);
+                psFree(sumVector);
+                psFree(sumMatrix);
+                return NULL;
+            }
+
+            // XXX LUD does not provide A^{-1}?  fake the error for now
+            solutionErr = psVectorAlloc(sumVector->n, PS_TYPE_F64);
+            for (int ix = 0; ix < sumVector->n; ix++) {
+                solutionErr->data.F64[ix] = 0.1*solution->data.F64[ix];
+            }
+        }
+
+        if (!kernels->solution1) {
+            kernels->solution1 = psVectorAlloc (sumVector->n, PS_TYPE_F64);
+            psVectorInit (kernels->solution1, 0.0);
+        }
+
+        // only update the solutions that we chose to calculate:
+        if (mode & PM_SUBTRACTION_EQUATION_NORM) {
+            int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+            kernels->solution1->data.F64[normIndex] = solution->data.F64[normIndex];
+        }
+        if (mode & PM_SUBTRACTION_EQUATION_BG) {
+            int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index in matrix for background
+            kernels->solution1->data.F64[bgIndex] = solution->data.F64[bgIndex];
+        }
+        if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+            int numKernels = kernels->num;
+            int spatialOrder = kernels->spatialOrder;       // Order of spatial variation
+            int numPoly = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of polynomial terms
+            for (int i = 0; i < numKernels * numPoly; i++) {
+                // XXX fprintf (stderr, "%f +/- %f (%f) -> ", solution->data.F64[i], solutionErr->data.F64[i], fabs(solution->data.F64[i]/solutionErr->data.F64[i]));
+                if (fabs(solution->data.F64[i] / solutionErr->data.F64[i]) < COEFF_SIG) {
+                    // XXX fprintf (stderr, "drop\n");
+                    kernels->solution1->data.F64[i] = 0.0;
+                } else {
+                    // XXX fprintf (stderr, "keep\n");
+                    kernels->solution1->data.F64[i] = solution->data.F64[i];
+                }
+            }
+        }
+        // double chiSquare = p_pmSubSolve_ChiSquare (kernels, stamps);
+        // fprintf (stderr, "chi square Brute = %f\n", chiSquare);
+
+        psFree(solution);
+        psFree(sumVector);
+        psFree(sumMatrix);
+# endif
+
+#ifdef TESTING
+              // XXX double-check for NAN in data:
+                for (int iy = 0; iy < stamp->matrix->numRows; iy++) {
+                    for (int ix = 0; ix < stamp->matrix->numCols; ix++) {
+                        if (!isfinite(stamp->matrix->data.F64[iy][ix])) {
+                            fprintf (stderr, "WARNING: NAN in matrix\n");
+                        }
+                    }
+                }
+                for (int ix = 0; ix < stamp->vector->n; ix++) {
+                    if (!isfinite(stamp->vector->data.F64[ix])) {
+                        fprintf (stderr, "WARNING: NAN in vector\n");
+                    }
+                }
+#endif
+
+#ifdef TESTING
+        for (int ix = 0; ix < sumVector->n; ix++) {
+            if (!isfinite(sumVector->data.F64[ix])) {
+                fprintf (stderr, "WARNING: NAN in vector\n");
+            }
+        }
+#endif
+
+#ifdef TESTING
+        for (int ix = 0; ix < sumVector->n; ix++) {
+            if (!isfinite(sumVector->data.F64[ix])) {
+                fprintf (stderr, "WARNING: NAN in vector\n");
+            }
+        }
+        {
+            psImage *inverse = psMatrixInvert(NULL, sumMatrix, NULL);
+            psFitsWriteImageSimple("matrixInv.fits", inverse, NULL);
+            psFree(inverse);
+        }
+        {
+            psImage *X = psMatrixInvert(NULL, sumMatrix, NULL);
+            psImage *Xt = psMatrixTranspose(NULL, X);
+            psImage *XtX = psMatrixMultiply(NULL, Xt, X);
+            psFitsWriteImageSimple("matrixErr.fits", XtX, NULL);
+            psFree(X);
+            psFree(Xt);
+            psFree(XtX);
+        }
+#endif
+
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionEquation.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionEquation.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionEquation.h	(revision 27839)
@@ -4,4 +4,12 @@
 #include "pmSubtractionStamps.h"
 #include "pmSubtractionKernels.h"
+
+typedef enum {
+    PM_SUBTRACTION_EQUATION_NONE    = 0x00,
+    PM_SUBTRACTION_EQUATION_NORM    = 0x01,
+    PM_SUBTRACTION_EQUATION_BG      = 0x02,
+    PM_SUBTRACTION_EQUATION_KERNELS = 0x04,
+    PM_SUBTRACTION_EQUATION_ALL     = 0x07, // value should be NORM | BG | KERNELS
+} pmSubtractionEquationCalculationMode;
 
 /// Execute a thread job to calculate the least-squares equation for a stamp
@@ -12,20 +20,23 @@
 bool pmSubtractionCalculateEquationStamp(pmSubtractionStampList *stamps, ///< Stamps
                                          const pmSubtractionKernels *kernels, ///< Kernel parameters
-                                         int index ///< Index of stamp
-                                    );
+                                         int index, ///< Index of stamp
+                                         const pmSubtractionEquationCalculationMode mode
+    );
 
 /// Calculate the least-squares equation to match the image quality
 bool pmSubtractionCalculateEquation(pmSubtractionStampList *stamps, ///< Stamps
-                                    const pmSubtractionKernels *kernels ///< Kernel parameters
-                                    );
+                                    const pmSubtractionKernels *kernels, ///< Kernel parameters
+                                    const pmSubtractionEquationCalculationMode mode
+    );
 
 /// Solve the least-squares equation to match the image quality
 bool pmSubtractionSolveEquation(pmSubtractionKernels *kernels, ///< Kernel parameters
-                                const pmSubtractionStampList *stamps ///< Stamps
+                                const pmSubtractionStampList *stamps, ///< Stamps
+                                const pmSubtractionEquationCalculationMode mode
     );
 
 /// Calculate deviations
 psVector *pmSubtractionCalculateDeviations(pmSubtractionStampList *stamps, ///< Stamps
-                                           const pmSubtractionKernels *kernels ///< Kernel parameters
+                                           pmSubtractionKernels *kernels ///< Kernel parameters
     );
 
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionHermitian.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionHermitian.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionHermitian.c	(revision 27839)
@@ -0,0 +1,129 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <pslib.h>
+
+#include "pmSubtractionHermitian.h"
+
+double p_pmSubtractionHermitianPolynomial (double x, int order) {
+  double value;
+    switch (order) {
+      case 0:
+	value = p_pmSubtractionHermitianPolynomial_00(x);
+	break;
+      case 1:
+	value = p_pmSubtractionHermitianPolynomial_01(x);
+	break;
+      case 2:
+	value = p_pmSubtractionHermitianPolynomial_02(x);
+	break;
+      case 3:
+	value = p_pmSubtractionHermitianPolynomial_03(x);
+	break;
+      case 4:
+	value = p_pmSubtractionHermitianPolynomial_04(x);
+	break;
+      case 5:
+	value = p_pmSubtractionHermitianPolynomial_05(x);
+	break;
+      case 6:
+	value = p_pmSubtractionHermitianPolynomial_06(x);
+	break;
+      case 7:
+	value = p_pmSubtractionHermitianPolynomial_07(x);
+	break;
+      case 8:
+	value = p_pmSubtractionHermitianPolynomial_08(x);
+	break;
+      case 9:
+	value = p_pmSubtractionHermitianPolynomial_09(x);
+	break;
+      case 10:
+	value = p_pmSubtractionHermitianPolynomial_10(x);
+	break;
+      default:
+	value = NAN;
+	break;
+    }
+    return value;
+}
+
+double p_pmSubtractionHermitianPolynomial_00(double x) {
+    double value;
+    // H_0(x) = 1
+    value = 1;
+    return value;
+}
+double p_pmSubtractionHermitianPolynomial_01(double x) {
+    double value;
+    // H_1(x) = x
+    value = x;
+    return value;
+}
+double p_pmSubtractionHermitianPolynomial_02(double x) {
+    double value, x2;
+    // H_2(x) = x^2-1
+    x2 = x*x;
+    value = x2 - 1.0;
+    return value;
+}
+double p_pmSubtractionHermitianPolynomial_03(double x) {
+    double value, x2;
+    // H_3(x) = x^3-3x
+    x2 = x*x;
+    value = x*(x2 - 3.0);
+    return value;
+}
+double p_pmSubtractionHermitianPolynomial_04(double x) {
+    double value, x2;
+    // H_4(x) = x^4-6x^2+3
+    x2 = x*x;
+    value = (x2 - 6.0)*x2 + 3.0;
+    return value;
+}
+double p_pmSubtractionHermitianPolynomial_05(double x) {
+    double value, x2;
+    // H_5(x) = x^5-10x^3+15x
+    x2 = x*x;
+    value = ((x2 - 10.0)*x2 + 15.0)*x;
+    return value;
+}
+double p_pmSubtractionHermitianPolynomial_06(double x) {
+    double value, x2;
+    // H_6(x) = x^6-15x^4+45x^2-15
+    x2 = x*x;
+    value = (((x2 - 15.0)*x2 + 45.0)*x2) - 15.0;
+    return value;
+}
+double p_pmSubtractionHermitianPolynomial_07(double x) {
+    double value, x2;
+    // H_7(x) = x^7-21x^5+105x^3-105x
+    x2 = x*x;
+    value = (((x2 - 21.0)*x2+105.0)*x2 - 105.0)*x;
+    return value;
+}
+double p_pmSubtractionHermitianPolynomial_08(double x) {
+    double value, x2;
+    // H_8(x) = x^8-28x^6+210x^4-420x^2+105
+    x2 = x*x;
+    value = ((((x2 - 28.0)*x2 + 210.0)*x2 - 420.0)*x2 + 105.0);
+    return value;
+}
+double p_pmSubtractionHermitianPolynomial_09(double x) {
+    double value, x2;
+    // H_9(x) = x^9-36x^7+378x^5-1260x^3+945x
+    x2 = x*x;
+    value = ((((x2 - 36.0)*x2 + 378.0)*x2 - 1260.0)*x2 + 945.0)*x;
+    return value;
+}
+double p_pmSubtractionHermitianPolynomial_10(double x) {
+    double value, x2;
+    // H_{10}(x) = x^{10}-45x^8+630x^6-3150x^4+4725x^2-945 
+    x2 = x*x;
+    value = (((((x2 - 45.0)*x2 + 630.0)*x2 - 3150.0)*x2 + 4725.0)*x2 - 945.0);
+    return value;
+}
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionHermitian.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionHermitian.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionHermitian.h	(revision 27839)
@@ -0,0 +1,28 @@
+#ifndef PM_SUBTRACTION_HERMITIAN_H
+#define PM_SUBTRACTION_HERMITIAN_H
+
+double p_pmSubtractionHermitianPolynomial (double x, int order);
+
+double p_pmSubtractionHermitianPolynomial_00(double x);
+
+double p_pmSubtractionHermitianPolynomial_01(double x);
+
+double p_pmSubtractionHermitianPolynomial_02(double x);
+
+double p_pmSubtractionHermitianPolynomial_03(double x);
+
+double p_pmSubtractionHermitianPolynomial_04(double x);
+
+double p_pmSubtractionHermitianPolynomial_05(double x);
+
+double p_pmSubtractionHermitianPolynomial_06(double x);
+
+double p_pmSubtractionHermitianPolynomial_07(double x);
+
+double p_pmSubtractionHermitianPolynomial_08(double x);
+
+double p_pmSubtractionHermitianPolynomial_09(double x);
+
+double p_pmSubtractionHermitianPolynomial_10(double x);
+
+# endif
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionIO.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionIO.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionIO.c	(revision 27839)
@@ -3,4 +3,5 @@
 #include <string.h>
 
+#include "pmErrorCodes.h"
 #include "pmHDU.h"
 #include "pmHDUUtils.h"
@@ -66,7 +67,7 @@
     }
     if (regions->n == 0) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "No subtraction regions found.");
+        // We wrote everything we could find
         psFree(regions);
-        return false;
+        return true;
     }
 
@@ -80,10 +81,5 @@
         while ((item = psMetadataGetAndIncrement(iter))) {
             assert(item->type == PS_DATA_UNKNOWN);
-            // Set the normalisation dimensions, since these will be otherwise unavailable when reading the
-            // images by scans.
             pmSubtractionKernels *kernel = item->data.V; // Kernel used in subtraction
-            kernel->numCols = ro->image->numCols;
-            kernel->numRows = ro->image->numRows;
-
             kernels = psArrayAdd(kernels, ARRAY_BUFFER, kernel);
         }
@@ -92,5 +88,5 @@
 
     if (regions->n != kernels->n) {
-        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Number of regions (%ld) and kernels (%ld) don't match.\n",
+        psError(PM_ERR_PROG, true, "Number of regions (%ld) and kernels (%ld) don't match.\n",
                 regions->n, kernels->n);
         psFree(regions);
@@ -103,6 +99,5 @@
     psArray *rows = psArrayAlloc(num); // Array of FITS table rows
     for (int i = 0; i < num; i++) {
-        psMetadata *row = psMetadataAlloc(); // Row of interest
-        rows->data[i] = psMemIncrRefCounter(row);
+        psMetadata *row = rows->data[i] = psMetadataAlloc(); // Row of interest
 
         psRegion *region = regions->data[i]; // Region of interest
@@ -126,6 +121,4 @@
                          kernel->bgOrder);
         psMetadataAddS32(row, PS_LIST_TAIL, NAME_MODE,  0, "Matching mode (enum)", kernel->mode);
-        psMetadataAddS32(row, PS_LIST_TAIL, NAME_COLS,  0, "Number of columns", kernel->numCols);
-        psMetadataAddS32(row, PS_LIST_TAIL, NAME_ROWS,  0, "Number of rows", kernel->numRows);
         if (kernel->mode == PM_SUBTRACTION_MODE_1 || kernel->mode == PM_SUBTRACTION_MODE_2) {
             psMetadataAddVector(row, PS_LIST_TAIL, NAME_SOL1, 0, "Solution vector 1", kernel->solution1);
@@ -172,5 +165,5 @@
 
     if (!psFitsWriteTable(fits, header, rows, EXTNAME_KERNEL)) {
-        psError(PS_ERR_IO, false, "Unable to write subtraction kernel to FITS table.");
+        psError(psErrorCodeLast(), false, "Unable to write subtraction kernel to FITS table.");
         psFree(header);
         psFree(rows);
@@ -182,5 +175,5 @@
 
     if (image && !psFitsWriteImage(fits, header, image, 0, EXTNAME_IMAGE)) {
-        psError(PS_ERR_IO, false, "Unable to write subtraction kernel image.");
+        psError(psErrorCodeLast(), false, "Unable to write subtraction kernel image.");
         psFree(header);
         psFree(rows);
@@ -208,5 +201,5 @@
         thisView->readout = i;
         if (!pmReadoutWriteSubtractionKernels(readout, file->fits)) {
-            psError(PS_ERR_IO, false, "Failed to write %dth readout", i);
+            psError(psErrorCodeLast(), false, "Failed to write %dth readout", i);
             psFree(thisView);
             return false;
@@ -231,5 +224,5 @@
         thisView->cell = i;
         if (!pmCellWriteSubtractionKernels(cell, thisView, file, config)) {
-            psError(PS_ERR_IO, false, "Failed to write %dth cell", i);
+            psError(psErrorCodeLast(), false, "Failed to write %dth cell", i);
             psFree(thisView);
             return false;
@@ -254,5 +247,5 @@
         thisView->chip = i;
         if (!pmChipWriteSubtractionKernels(chip, thisView, file, config)) {
-            psError(PS_ERR_IO, false, "Failed to write %dth chip", i);
+            psError(psErrorCodeLast(), false, "Failed to write %dth chip", i);
             psFree(thisView);
             return false;
@@ -272,5 +265,5 @@
 
     if (!psFitsMoveExtName(fits, EXTNAME_KERNEL)) {
-        psError(PS_ERR_IO, false, "Unable to move to subtraction kernel table.");
+        psError(psErrorCodeLast(), false, "Unable to move to subtraction kernel table.");
         return false;
     }
@@ -278,5 +271,5 @@
     psArray *table = psFitsReadTable(fits); // Table of interest
     if (!table) {
-        psError(PS_ERR_IO, false, "Unable to read FITS table");
+        psError(psErrorCodeLast(), false, "Unable to read FITS table");
         return false;
     }
@@ -289,5 +282,5 @@
         TARGET = psMetadataLookup##SUFFIX(&mdok, row, NAME); \
         if (!mdok) { \
-            psError(PS_ERR_UNKNOWN, false, "Unable to find column %s in subtraction kernel table.", NAME); \
+            psError(PM_ERR_PROG, false, "Unable to find column %s in subtraction kernel table.", NAME); \
             psFree(table); \
             return false; \
@@ -318,6 +311,4 @@
 
         TABLE_LOOKUP(int, S32, bg,      NAME_BG);
-        TABLE_LOOKUP(int, S32, numCols, NAME_COLS);
-        TABLE_LOOKUP(int, S32, numRows, NAME_ROWS);
 
         TABLE_LOOKUP(float, F32, mean,      NAME_MEAN);
@@ -325,7 +316,5 @@
         TABLE_LOOKUP(int,   S32, numStamps, NAME_NUMSTAMPS);
 
-        pmSubtractionKernels *kernels = pmSubtractionKernelsFromDescription(description, bg, mode);
-        kernels->numCols = numCols;
-        kernels->numRows = numRows;
+        pmSubtractionKernels *kernels = pmSubtractionKernelsFromDescription(description, bg, *region, mode);
         kernels->mean = mean;
         kernels->rms = rms;
@@ -336,5 +325,5 @@
             kernels->solution1 = psMemIncrRefCounter(psMetadataLookupPtr(&mdok, row, NAME_SOL1));
             if (!mdok) {
-                psError(PS_ERR_UNKNOWN, false, "Unable to find column %s in subtraction kernel table.",
+                psError(PM_ERR_PROG, false, "Unable to find column %s in subtraction kernel table.",
                         NAME_SOL1);
                 psFree(kernels);
@@ -346,5 +335,5 @@
             kernels->solution1 = psMemIncrRefCounter(psMetadataLookupPtr(&mdok, row, NAME_SOL1));
             if (!mdok) {
-                psError(PS_ERR_UNKNOWN, false, "Unable to find column %s in subtraction kernel table.",
+                psError(PM_ERR_PROG, false, "Unable to find column %s in subtraction kernel table.",
                         NAME_SOL1);
                 psFree(kernels);
@@ -354,5 +343,5 @@
             kernels->solution2 = psMemIncrRefCounter(psMetadataLookupPtr(&mdok, row, NAME_SOL2));
             if (!mdok) {
-                psError(PS_ERR_UNKNOWN, false, "Unable to find column %s in subtraction kernel table.",
+                psError(PM_ERR_PROG, false, "Unable to find column %s in subtraction kernel table.",
                         NAME_SOL2);
                 psFree(kernels);
@@ -390,5 +379,9 @@
         pmReadout *readout = cell->readouts->data[i];
         thisView->readout = i;
-        pmReadoutReadSubtractionKernels(readout, file->fits);
+        if (!pmReadoutReadSubtractionKernels(readout, file->fits)) {
+            psError(psErrorCodeLast(), false, "Unable to read subtraction kernels from cell");
+            psFree(thisView);
+            return false;
+        }
         if (!readout->data_exists) {
             continue;
@@ -421,6 +414,10 @@
         pmCell *cell = chip->cells->data[i];
         thisView->cell = i;
-        pmCellReadSubtractionKernels(cell, thisView, file, config);
-        if (!cell->data_exists) {
+        if (!pmCellReadSubtractionKernels(cell, thisView, file, config)) {
+            psError(psErrorCodeLast(), false, "Unable to read subtraction kernels from cell");
+            psFree(thisView);
+            return false;
+        }
+         if (!cell->data_exists) {
             continue;
         }
@@ -430,5 +427,5 @@
 
     if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, true, NULL)) {
-        psError(PS_ERR_IO, false, "Failed to read concepts for chip.\n");
+        psError(psErrorCodeLast(), false, "Failed to read concepts for chip.\n");
         return false;
     }
@@ -451,10 +448,14 @@
         pmChip *chip = fpa->chips->data[i];
         thisView->chip = i;
-        pmChipReadSubtractionKernels(chip, thisView, file, config);
+        if (!pmChipReadSubtractionKernels(chip, thisView, file, config)) {
+            psError(psErrorCodeLast(), false, "Unable to read subtraction kernels from chip");
+            psFree(thisView);
+            return false;
+        }
     }
     psFree(thisView);
 
     if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
-        psError(PS_ERR_IO, false, "Failed to read concepts for fpa.\n");
+        psError(psErrorCodeLast(), false, "Failed to read concepts for fpa.\n");
         return false;
     }
@@ -475,5 +476,5 @@
     if (view->chip == -1) {
         if (!pmFPAWriteSubtractionKernels(fpa, view, file, config)) {
-            psError(PS_ERR_IO, false, "Failed to write subtraction kernels from fpa");
+            psError(psErrorCodeLast(), false, "Failed to write subtraction kernels from fpa");
             psFree(fpa);
             return false;
@@ -484,5 +485,5 @@
 
     if (view->chip >= fpa->chips->n) {
-        psError(PS_ERR_UNKNOWN, false, "Writing chip == %d (>= chips->n == %ld)", view->chip, fpa->chips->n);
+        psError(PM_ERR_PROG, true, "Writing chip == %d (>= chips->n == %ld)", view->chip, fpa->chips->n);
         psFree(fpa);
         return false;
@@ -492,5 +493,5 @@
     if (view->cell == -1) {
         if (!pmChipWriteSubtractionKernels(chip, view, file, config)) {
-            psError(PS_ERR_IO, false, "Failed to write objects from chip");
+            psError(psErrorCodeLast(), false, "Failed to write objects from chip");
             psFree(fpa);
             return false;
@@ -501,5 +502,5 @@
 
     if (view->cell >= chip->cells->n) {
-        psError(PS_ERR_UNKNOWN, false, "Writing cell == %d (>= cells->n == %ld)",
+        psError(PM_ERR_PROG, true, "Writing cell == %d (>= cells->n == %ld)",
                 view->cell, chip->cells->n);
         psFree(fpa);
@@ -510,5 +511,5 @@
     if (view->readout == -1) {
         if (!pmCellWriteSubtractionKernels(cell, view, file, config)) {
-            psError(PS_ERR_IO, false, "Failed to write objects from cell");
+            psError(psErrorCodeLast(), false, "Failed to write objects from cell");
             psFree(fpa);
             return false;
@@ -519,5 +520,5 @@
 
     if (view->readout >= cell->readouts->n) {
-        psError(PS_ERR_UNKNOWN, false, "Writing readout == %d (>= readouts->n == %ld)",
+        psError(PM_ERR_PROG, true, "Writing readout == %d (>= readouts->n == %ld)",
                 view->readout, cell->readouts->n);
         psFree(fpa);
@@ -527,5 +528,5 @@
 
     if (!pmReadoutWriteSubtractionKernels(readout, file->fits)) {
-        psError(PS_ERR_IO, false, "Failed to write objects from readout %d", view->readout);
+        psError(psErrorCodeLast(), false, "Failed to write objects from readout %d", view->readout);
         psFree(fpa);
         return false;
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionKernels.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionKernels.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionKernels.c	(revision 27839)
@@ -10,7 +10,9 @@
 #include "pmSubtraction.h"
 #include "pmSubtractionKernels.h"
+#include "pmSubtractionHermitian.h"
+#include "pmSubtractionDeconvolve.h"
+#include "pmSubtractionVisual.h"
 
 #define RINGS_BUFFER 10                 // Buffer size for RINGS data
-
 
 // Free function for pmSubtractionKernels
@@ -27,4 +29,17 @@
     psFree(kernels->solution1);
     psFree(kernels->solution2);
+    psFree(kernels->sampleStamps);
+}
+
+// Free function for pmSubtractionPreCalcKernel
+static void pmSubtractionKernelPreCalcFree(pmSubtractionKernelPreCalc *kernel)
+{
+    psFree(kernel->xKernel);
+    psFree(kernel->yKernel);
+    psFree(kernel->kernel);
+
+    psFree(kernel->uCoords);
+    psFree(kernel->vCoords);
+    psFree(kernel->poly);
 }
 
@@ -45,5 +60,5 @@
 
 // Generate 1D convolution kernel for ISIS
-static psVector *subtractionKernelISIS(float sigma, // Gaussian width
+psVector *pmSubtractionKernelISIS(float sigma, // Gaussian width
                                        int order, // Polynomial order
                                        int size // Kernel half-size
@@ -57,4 +72,50 @@
     for (int i = 0, x = -size; x <= size; i++, x++) {
         kernel->data.F32[i] = norm * power(x, order) * expf(expNorm * PS_SQR(x));
+    }
+
+    return kernel;
+}
+
+// Generate 1D convolution kernel for HERM (normalized for 2D)
+psVector *pmSubtractionKernelHERM(float sigma, // Gaussian width
+                                       int order, // Polynomial order
+                                       int size // Kernel half-size
+    )
+{
+    int fullSize = 2 * size + 1;        // Full size of kernel
+    psVector *kernel = psVectorAlloc(fullSize, PS_TYPE_F32); // Kernel to return
+
+    // for now, we are only allowing equal orders and sigmas in X and Y
+    float nf = exp(lgamma(order + 1));
+    float norm = 1.0 / sqrt(nf*sigma*sqrt(M_2_PI));
+
+    for (int i = 0, x = -size; x <= size; i++, x++) {
+        float xf = x / sigma;
+        float z = -0.25*xf*xf;
+        kernel->data.F32[i] = norm * p_pmSubtractionHermitianPolynomial(xf, order) * exp(z);
+    }
+
+    return kernel;
+}
+
+// Generate 1D convolution kernel for HERM (normalized for 2D)
+psKernel *pmSubtractionKernelHERM_RADIAL(float sigma, // Gaussian width
+                                         int order, // Polynomial order
+                                         int size // Kernel half-size
+    )
+{
+    psKernel *kernel = psKernelAlloc(-size, size, -size, size); // 2D Kernel
+
+    // for now, we are only allowing equal orders and sigmas in X and Y
+    float nf = exp(lgamma(order + 1));
+    float norm = 1.0 / sqrt(nf*sigma*sqrt(M_2_PI));
+
+    // generate 2D radial hermitian
+    for (int v = -size; v <= size; v++) {
+        for (int u = -size; u <= size; u++) {
+            float r = hypot(u, v) / sigma;
+            float z = -0.25*r*r;
+            kernel->kernel[v][u] = norm * p_pmSubtractionHermitianPolynomial(r, order) * exp(z);
+        }
     }
 
@@ -81,4 +142,5 @@
     kernels->penalties = psVectorRealloc(kernels->penalties, start + numNew);
     kernels->inner = start;
+    kernels->num += numNew;
 
     // Generate a set of kernels for each (u,v)
@@ -94,24 +156,142 @@
             kernels->v->data.S32[index] = v;
             kernels->preCalc->data[index] = NULL;
-            kernels->penalties->data.F32[index] = kernels->penalty * (PS_SQR(u) + PS_SQR(v));
-
+            kernels->penalties->data.F32[index] = kernels->penalty * PS_SQR(PS_SQR(u) + PS_SQR(v));
+            psAssert (isfinite(kernels->penalties->data.F32[index]), "invalid penalty");
             psTrace("psModules.imcombine", 7, "Kernel %d: %d %d\n", index, u, v);
         }
     }
 
+    kernels->widths->n = start + numNew;
+    kernels->u->n = start + numNew;
+    kernels->v->n = start + numNew;
+    kernels->preCalc->n = start + numNew;
+    kernels->penalties->n = start + numNew;
+
     return true;
 }
 
+bool pmSubtractionKernelPreCalcNormalize(pmSubtractionKernels *kernels, pmSubtractionKernelPreCalc *preCalc,
+                                         int index, int size, int uOrder, int vOrder, float fwhm,
+                                         bool AlardLuptonStyle, bool forceZeroNull)
+{
+    // we have 4 cases here:
+    // 1) for odd functions, normalize the kernel by the maximum swing / Npix
+    // 2) for even functions, normalize the kernel to unity
+    // 3) for alard-lupton style normalization, subtract 1 from the 0,0 pixel for all even functions
+    // 4) for deconvolved hermitians, subtract 1 from the 0,0 pixel for the 0,0 function(s)
+
+    // Calculate moments
+    double penalty = 0.0;                   // Moment, for penalty
+    double sum = 0.0, sum2 = 0.0;           // Sum of kernel component
+    float min = INFINITY, max = -INFINITY;  // Minimum and maximum kernel value
+    for (int v = -size; v <= size; v++) {
+        for (int u = -size; u <= size; u++) {
+            double value = preCalc->kernel->kernel[v][u];
+            double value2 = PS_SQR(value);
+            sum += value;
+            sum2 += value2;
+            penalty += value2 * PS_SQR((PS_SQR(u) + PS_SQR(v)));
+            min = PS_MIN(value, min);
+            max = PS_MAX(value, max);
+        }
+    }
+
+#if 0
+    fprintf(stderr, "%d raw: %lf, null: %f, min: %lf, max: %lf, moment: %lf\n", index, sum, preCalc->kernel->kernel[0][0], min, max, penalty);
+#endif
+
+    bool zeroNull = false;              // Zero out using the null position?
+    float scale2D = NAN;                // Scaling for 2-D kernels
+
+    if (AlardLuptonStyle) {
+        if (uOrder % 2 == 0 && vOrder % 2 == 0) {
+            // Even functions: normalise to unit sum and subtract null pixel so that sum is zero
+            scale2D = 1.0 / fabs(sum);
+            zeroNull = true;
+        } else {
+            // Odd functions: choose normalisation so that parameters have about the same strength as for even
+            // functions, no subtraction of null pixel because the sum is already (near) zero
+            scale2D = 1.0 / sqrt(sum2);
+            zeroNull = false;
+        }
+    }
+
+    if (!AlardLuptonStyle && (uOrder == 0 && vOrder == 0)) {
+        zeroNull = true;
+    }
+    if (forceZeroNull) {
+        // Force rescaling and subtraction of null pixel even though the order doesn't indicate it's even
+        scale2D = 1.0 / fabs(sum);
+        zeroNull = true;
+    }
+    if (!forceZeroNull && ((uOrder % 2) || (vOrder % 2))) {
+        // Odd function
+        scale2D = 1.0 / sqrt(sum2);
+    }
+
+    float scale1D = sqrtf(scale2D);     // Scaling for 1-D kernels
+    if (preCalc->xKernel) {
+        psBinaryOp(preCalc->xKernel, preCalc->xKernel, "*", psScalarAlloc(scale1D, PS_TYPE_F32));
+    }
+    if (preCalc->yKernel) {
+        psBinaryOp(preCalc->yKernel, preCalc->yKernel, "*", psScalarAlloc(scale1D, PS_TYPE_F32));
+    }
+
+    psBinaryOp(preCalc->kernel->image, preCalc->kernel->image, "*", psScalarAlloc(scale2D, PS_TYPE_F32));
+    penalty *= 1.0 / sum2;
+
+    if (zeroNull) {
+        preCalc->kernel->kernel[0][0] -= 1.0;
+    }
+
+#if 0
+    {
+        double sum = 0.0;   // Sum of kernel component
+        float min = INFINITY, max = -INFINITY;  // Minimum and maximum kernel value
+        for (int v = -size; v <= size; v++) {
+            for (int u = -size; u <= size; u++) {
+                sum += preCalc->kernel->kernel[v][u];
+                min = PS_MIN(preCalc->kernel->kernel[v][u], min);
+                max = PS_MAX(preCalc->kernel->kernel[v][u], max);
+            }
+        }
+        fprintf(stderr, "%d mod: %lf, null: %f, min: %lf, max: %lf, scale: %f\n", index, sum, preCalc->kernel->kernel[0][0], min, max, scale2D);
+    }
+#endif
+
+    kernels->widths->data.F32[index] = fwhm;
+    kernels->u->data.S32[index] = uOrder;
+    kernels->v->data.S32[index] = vOrder;
+    if (kernels->preCalc->data[index]) {
+        psFree(kernels->preCalc->data[index]);
+    }
+    kernels->preCalc->data[index] = preCalc;
+    kernels->penalties->data.F32[index] = kernels->penalty * penalty;
+    psAssert (isfinite(kernels->penalties->data.F32[index]), "invalid penalty");
+    psTrace("psModules.imcombine", 7, "Kernel %d: %f %d %d %f\n", index, fwhm, uOrder, vOrder, penalty);
+
+    return true;
+}
+
 pmSubtractionKernels *p_pmSubtractionKernelsRawISIS(int size, int spatialOrder,
-                                                    const psVector *fwhms, const psVector *orders,
-                                                    float penalty, pmSubtractionMode mode)
-{
-    PS_ASSERT_VECTOR_NON_NULL(fwhms, NULL);
-    PS_ASSERT_VECTOR_TYPE(fwhms, PS_TYPE_F32, NULL);
-    PS_ASSERT_VECTOR_NON_NULL(orders, NULL);
-    PS_ASSERT_VECTOR_TYPE(orders, PS_TYPE_S32, NULL);
-    PS_ASSERT_VECTORS_SIZE_EQUAL(fwhms, orders, NULL);
+                                                    const psVector *fwhmsIN, const psVector *ordersIN,
+                                                    float penalty, psRegion bounds, pmSubtractionMode mode)
+{
+    PS_ASSERT_VECTOR_NON_NULL(fwhmsIN, NULL);
+    PS_ASSERT_VECTOR_TYPE(fwhmsIN, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(ordersIN, NULL);
+    PS_ASSERT_VECTOR_TYPE(ordersIN, PS_TYPE_S32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(fwhmsIN, ordersIN, NULL);
     PS_ASSERT_INT_POSITIVE(size, NULL);
     PS_ASSERT_INT_NONNEGATIVE(spatialOrder, NULL);
+
+    // check the requested fwhm values: any values <= 0.0 should be dropped
+    psVector *fwhms  = psVectorAllocEmpty (fwhmsIN->n, PS_TYPE_F32);
+    psVector *orders = psVectorAllocEmpty (ordersIN->n, PS_TYPE_S32);
+    for (int i = 0; i < fwhmsIN->n; i++) {
+        if (fwhmsIN->data.F32[i] <= FLT_EPSILON) continue;
+        psVectorAppend(fwhms, fwhmsIN->data.F32[i]);
+        psVectorAppend(orders, ordersIN->data.S32[i]);
+    }
 
     int numGaussians = fwhms->n;       // Number of Gaussians
@@ -126,5 +306,5 @@
 
     pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_ISIS, size,
-                                                              spatialOrder, penalty, mode); // The kernels
+                                                              spatialOrder, penalty, bounds, mode); // Kernels
     psStringAppend(&kernels->description, "ISIS(%d,%s,%d,%.2e)", size, params, spatialOrder, penalty);
 
@@ -134,5 +314,4 @@
 
     // Set the kernel parameters
-    int fullSize = 2 * size + 1;        // Full size of kernels
     for (int i = 0, index = 0; i < numGaussians; i++) {
         float sigma = fwhms->data.F32[i] / (2.0 * sqrtf(2.0 * logf(2.0))); // Gaussian sigma
@@ -140,45 +319,228 @@
         for (int uOrder = 0; uOrder <= orders->data.S32[i]; uOrder++) {
             for (int vOrder = 0; vOrder <= orders->data.S32[i] - uOrder; vOrder++, index++) {
-                psArray *preCalc = psArrayAlloc(2); // Array to hold precalculated values
-                psVector *xKernel = preCalc->data[0] = subtractionKernelISIS(sigma, uOrder, size); // x Kernel
-                psVector *yKernel = preCalc->data[1] = subtractionKernelISIS(sigma, vOrder, size); // y Kernel
-
-                // Calculate moments
-                double moment = 0.0;    // Moment, for penalty
-                for (int v = -size, y = 0; v <= size; v++, y++) {
-                    for (int u = -size, x = 0; u <= size; u++, x++) {
-                        double value = xKernel->data.F32[x] * yKernel->data.F32[y]; // Value of kernel
-                        moment += value * (PS_SQR(u) + PS_SQR(v));
-                    }
+
+                pmSubtractionKernelPreCalc *preCalc = pmSubtractionKernelPreCalcAlloc(PM_SUBTRACTION_KERNEL_ISIS, uOrder, vOrder, size, sigma); // structure to hold precalculated values
+                pmSubtractionKernelPreCalcNormalize (kernels, preCalc, index, size, uOrder, vOrder, fwhms->data.F32[i], true, false);
+                // pmSubtractionKernelPreCalcNormalize (kernels, preCalc, index, size, uOrder, vOrder, fwhms->data.F32[i], false, false);
+            }
+        }
+    }
+
+    psFree(orders);
+    psFree(fwhms);
+
+    return kernels;
+}
+
+pmSubtractionKernels *pmSubtractionKernelsISIS_RADIAL(int size, int spatialOrder,
+                                                      const psVector *fwhmsIN, const psVector *ordersIN,
+                                                      float penalty, psRegion bounds, pmSubtractionMode mode)
+{
+    PS_ASSERT_VECTOR_NON_NULL(fwhmsIN, NULL);
+    PS_ASSERT_VECTOR_TYPE(fwhmsIN, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(ordersIN, NULL);
+    PS_ASSERT_VECTOR_TYPE(ordersIN, PS_TYPE_S32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(fwhmsIN, ordersIN, NULL);
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(spatialOrder, NULL);
+
+    // check the requested fwhm values: any values <= 0.0 should be dropped
+    psVector *fwhms  = psVectorAllocEmpty (fwhmsIN->n, PS_TYPE_F32);
+    psVector *orders = psVectorAllocEmpty (ordersIN->n, PS_TYPE_S32);
+    for (int i = 0; i < fwhmsIN->n; i++) {
+        if (fwhmsIN->data.F32[i] <= FLT_EPSILON) continue;
+        psVectorAppend(fwhms, fwhmsIN->data.F32[i]);
+        psVectorAppend(orders, ordersIN->data.S32[i]);
+    }
+
+    int numGaussians = fwhms->n;       // Number of Gaussians
+
+    int num = 0;                        // Number of basis functions
+    psString params = NULL;             // List of parameters
+    for (int i = 0; i < numGaussians; i++) {
+        int gaussOrder = orders->data.S32[i]; // Polynomial order to apply to Gaussian
+        psStringAppend(&params, "(%.1f,%d)", fwhms->data.F32[i], orders->data.S32[i]);
+        num += (gaussOrder + 1) * (gaussOrder + 2) / 2;
+        num += (11 - gaussOrder - 1);   // include all higher order radial terms
+    }
+
+    pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_ISIS_RADIAL, size,
+                                                              spatialOrder, penalty, bounds, mode); // Kernels
+    psStringAppend(&kernels->description, "ISIS_RADIAL(%d,%s,%d,%.2e)", size, params, spatialOrder, penalty);
+
+    psLogMsg("psModules.imcombine", PS_LOG_INFO, "ISIS_RADIAL kernel: %s,%d --> %d elements", params, spatialOrder, num);
+    psFree(params);
+
+    // Set the kernel parameters
+    for (int i = 0, index = 0; i < numGaussians; i++) {
+        float sigma = fwhms->data.F32[i] / (2.0 * sqrtf(2.0 * logf(2.0))); // Gaussian sigma
+        // Iterate over (u,v) order
+        for (int uOrder = 0; uOrder <= orders->data.S32[i]; uOrder++) {
+            for (int vOrder = 0; vOrder <= orders->data.S32[i] - uOrder; vOrder++, index++) {
+                pmSubtractionKernelPreCalc *preCalc = pmSubtractionKernelPreCalcAlloc(PM_SUBTRACTION_KERNEL_ISIS, uOrder, vOrder, size, sigma); // structure to hold precalculated values
+                pmSubtractionKernelPreCalcNormalize (kernels, preCalc, index, size, uOrder, vOrder, fwhms->data.F32[i], true, false);
+            }
+        }
+        for (int order = orders->data.S32[i] + 1; order < 11; order ++, index ++) {
+            // XXX modify size for hermitians to account for sqrt(2) in Hermitian definition (relative to ISIS Gaussian)
+            pmSubtractionKernelPreCalc *preCalc = pmSubtractionKernelPreCalcAlloc(PM_SUBTRACTION_KERNEL_ISIS_RADIAL, order, order, size, sigma / sqrt(2.0)); // structure to hold precalculated values
+            pmSubtractionKernelPreCalcNormalize (kernels, preCalc, index, size, order, order, fwhms->data.F32[i], true, true);
+        }
+    }
+    return kernels;
+}
+
+pmSubtractionKernels *pmSubtractionKernelsHERM(int size, int spatialOrder,
+                                               const psVector *fwhmsIN, const psVector *ordersIN,
+                                               float penalty, psRegion bounds, pmSubtractionMode mode)
+{
+    PS_ASSERT_VECTOR_NON_NULL(fwhmsIN, NULL);
+    PS_ASSERT_VECTOR_TYPE(fwhmsIN, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(ordersIN, NULL);
+    PS_ASSERT_VECTOR_TYPE(ordersIN, PS_TYPE_S32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(fwhmsIN, ordersIN, NULL);
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(spatialOrder, NULL);
+
+    // check the requested fwhm values: any values <= 0.0 should be dropped
+    psVector *fwhms  = psVectorAllocEmpty (fwhmsIN->n, PS_TYPE_F32);
+    psVector *orders = psVectorAllocEmpty (ordersIN->n, PS_TYPE_S32);
+    for (int i = 0; i < fwhmsIN->n; i++) {
+        if (fwhmsIN->data.F32[i] <= FLT_EPSILON) continue;
+        psVectorAppend(fwhms, fwhmsIN->data.F32[i]);
+        psVectorAppend(orders, ordersIN->data.S32[i]);
+    }
+
+    int numGaussians = fwhms->n;       // Number of Gaussians
+
+    int num = 0;                        // Number of basis functions
+    psString params = NULL;             // List of parameters
+    for (int i = 0; i < numGaussians; i++) {
+        int gaussOrder = orders->data.S32[i]; // Polynomial order to apply to Gaussian
+        psStringAppend(&params, "(%.1f,%d)", fwhms->data.F32[i], orders->data.S32[i]);
+        num += (gaussOrder + 1) * (gaussOrder + 2) / 2;
+    }
+
+    pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_HERM, size,
+                                                              spatialOrder, penalty, bounds, mode); // Kernels
+    psStringAppend(&kernels->description, "HERM(%d,%s,%d,%.2e)", size, params, spatialOrder, penalty);
+
+    psLogMsg("psModules.imcombine", PS_LOG_INFO, "HERM kernel: %s,%d --> %d elements",
+             params, spatialOrder, num);
+    psFree(params);
+
+    // Set the kernel parameters
+    for (int i = 0, index = 0; i < numGaussians; i++) {
+        float sigma = fwhms->data.F32[i] / (2.0 * sqrtf(2.0 * logf(2.0))); // Gaussian sigma
+        // Iterate over (u,v) order
+        for (int uOrder = 0; uOrder <= orders->data.S32[i]; uOrder++) {
+            for (int vOrder = 0; vOrder <= orders->data.S32[i] - uOrder; vOrder++, index++) {
+                pmSubtractionKernelPreCalc *preCalc = pmSubtractionKernelPreCalcAlloc(PM_SUBTRACTION_KERNEL_HERM, uOrder, vOrder, size, sigma); // structure to hold precalculated values
+                pmSubtractionKernelPreCalcNormalize (kernels, preCalc, index, size, uOrder, vOrder, fwhms->data.F32[i], true, false);
+            }
+        }
+    }
+
+    return kernels;
+}
+
+pmSubtractionKernels *pmSubtractionKernelsDECONV_HERM(int size, int spatialOrder,
+                                                      const psVector *fwhmsIN, const psVector *ordersIN,
+                                                      float penalty, psRegion bounds, pmSubtractionMode mode)
+{
+    PS_ASSERT_VECTOR_NON_NULL(fwhmsIN, NULL);
+    PS_ASSERT_VECTOR_TYPE(fwhmsIN, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(ordersIN, NULL);
+    PS_ASSERT_VECTOR_TYPE(ordersIN, PS_TYPE_S32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(fwhmsIN, ordersIN, NULL);
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(spatialOrder, NULL);
+
+    // check the requested fwhm values: any values <= 0.0 should be dropped
+    psVector *fwhms  = psVectorAllocEmpty (fwhmsIN->n, PS_TYPE_F32);
+    psVector *orders = psVectorAllocEmpty (ordersIN->n, PS_TYPE_S32);
+    for (int i = 0; i < fwhmsIN->n; i++) {
+        if (fwhmsIN->data.F32[i] <= FLT_EPSILON) continue;
+        psVectorAppend(fwhms, fwhmsIN->data.F32[i]);
+        psVectorAppend(orders, ordersIN->data.S32[i]);
+    }
+
+    int numGaussians = fwhms->n;       // Number of Gaussians
+
+    int num = 0;                        // Number of basis functions
+    psString params = NULL;             // List of parameters
+    for (int i = 0; i < numGaussians; i++) {
+        int gaussOrder = orders->data.S32[i]; // Polynomial order to apply to Gaussian
+        psStringAppend(&params, "(%.1f,%d)", fwhms->data.F32[i], orders->data.S32[i]);
+        num += PS_SQR(gaussOrder + 1);
+    }
+
+    pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_DECONV_HERM, size,
+                                                              spatialOrder, penalty, bounds, mode); // Kernels
+    psStringAppend(&kernels->description, "DECONV_HERM(%d,%s,%d,%.2e)", size, params, spatialOrder, penalty);
+
+    psLogMsg("psModules.imcombine", PS_LOG_INFO, "DECONVOLVED HERM kernel: %s,%d --> %d elements", params, spatialOrder, num);
+    psFree(params);
+
+    // XXXXX hard-wired reference sigma for now of 1.7 pix (== 4.0 pix fwhm == 1.0 arcsec in simtest)
+    // generate the Gaussian deconvolution kernel
+    # define DECONV_SIGMA 1.6
+    psKernel *kernelGauss = pmSubtractionDeconvolveGauss (size, DECONV_SIGMA);
+
+# if 1
+    psArray *deconKernels = psArrayAllocEmpty(100);
+# endif
+
+    // Set the kernel parameters
+    for (int i = 0, index = 0; i < numGaussians; i++) {
+        float sigma = fwhms->data.F32[i] / (2.0 * sqrtf(2.0 * logf(2.0))); // Gaussian sigma
+        // Iterate over (u,v) order
+        for (int uOrder = 0; uOrder <= orders->data.S32[i]; uOrder++) {
+            for (int vOrder = 0; vOrder <= orders->data.S32[i]; vOrder++, index++) {
+
+                pmSubtractionKernelPreCalc *preCalc = pmSubtractionKernelPreCalcAlloc(PM_SUBTRACTION_KERNEL_HERM, uOrder, vOrder, size, sigma); // structure to hold precalculated values
+
+                // save the generated 2D kernel as the target, deconvolve it by Gaussian, replacing the generated 2D kernel
+                psKernel *kernelTarget = preCalc->kernel;
+                preCalc->kernel = pmSubtractionDeconvolveKernel(kernelTarget, kernelGauss); // Kernel
+
+                // XXX do we use Alard-Lupton normalization (last param true) or not?
+                pmSubtractionKernelPreCalcNormalize (kernels, preCalc, index, size, uOrder, vOrder, fwhms->data.F32[i], true, false);
+
+                // XXXX test demo that deconvolved kernel is valid
+# if 1
+                psImage *kernelConv = psImageConvolveFFT(NULL, preCalc->kernel->image, NULL, 0, kernelGauss);
+                psArrayAdd (deconKernels, 100, kernelConv);
+                psFree (kernelConv);
+
+                if (!uOrder && !vOrder){
+                    pmSubtractionVisualShowSubtraction (kernelTarget->image, preCalc->kernel->image, kernelConv);
                 }
-
-                // Normalise sum of kernel component to unity for even functions
-                if (uOrder % 2 == 0 && vOrder % 2 == 0) {
-                    double sum = 0.0;   // Sum of kernel component
-                    for (int v = 0; v < fullSize; v++) {
-                        for (int u = 0; u < fullSize; u++) {
-                            sum += xKernel->data.F32[u] * yKernel->data.F32[v];
-                        }
-                    }
-                    sum = 1.0 / sqrt(sum);
-                    psBinaryOp(xKernel, xKernel, "*", psScalarAlloc(sum, PS_TYPE_F32));
-                    psBinaryOp(yKernel, yKernel, "*", psScalarAlloc(sum, PS_TYPE_F32));
-                    moment *= PS_SQR(sum);
+# endif
+            }
+        }
+    }
+
+# if 1
+    psImage *dot = psImageAlloc(deconKernels->n, deconKernels->n, PS_TYPE_F32);
+    for (int i = 0; i < deconKernels->n; i++) {
+        for (int j = 0; j <= i; j++) {
+            psImage *t1 = deconKernels->data[i];
+            psImage *t2 = deconKernels->data[j];
+
+            double sum = 0.0;
+            for (int iy = 0; iy < t1->numRows; iy++) {
+                for (int ix = 0; ix < t1->numCols; ix++) {
+                    sum += t1->data.F32[iy][ix] * t2->data.F32[iy][ix];
                 }
-
-                kernels->widths->data.F32[index] = fwhms->data.F32[i];
-                kernels->u->data.S32[index] = uOrder;
-                kernels->v->data.S32[index] = vOrder;
-                if (kernels->preCalc->data[index]) {
-                    psFree(kernels->preCalc->data[index]);
-                }
-                kernels->preCalc->data[index] = preCalc;
-                kernels->penalties->data.F32[index] = kernels->penalty * fabsf(moment);
-
-                psTrace("psModules.imcombine", 7, "Kernel %d: %f %d %d %f\n", index,
-                        fwhms->data.F32[i], uOrder, vOrder, fabsf(moment));
             }
-        }
-    }
+            dot->data.F32[j][i] = sum;
+            dot->data.F32[i][j] = sum;
+        }
+    }
+    pmSubtractionVisualShowSubtraction (dot, NULL, NULL);
+    psFree (dot);
+    psFree (deconKernels);
+# endif
 
     return kernels;
@@ -190,5 +552,5 @@
 
 pmSubtractionKernels *pmSubtractionKernelsAlloc(int numBasisFunctions, pmSubtractionKernelsType type,
-                                                int size, int spatialOrder, float penalty,
+                                                int size, int spatialOrder, float penalty, psRegion bounds,
                                                 pmSubtractionMode mode)
 {
@@ -202,9 +564,13 @@
     kernels->v = psVectorAlloc(numBasisFunctions, PS_TYPE_S32);
     kernels->widths = psVectorAlloc(numBasisFunctions, PS_TYPE_F32);
+    kernels->uStop = NULL;
+    kernels->vStop = NULL;
+    kernels->xMin = bounds.x0;
+    kernels->xMax = bounds.x1;
+    kernels->yMin = bounds.y0;
+    kernels->yMax = bounds.y1;
     kernels->preCalc = psArrayAlloc(numBasisFunctions);
     kernels->penalty = penalty;
     kernels->penalties = psVectorAlloc(numBasisFunctions, PS_TYPE_F32);
-    kernels->uStop = NULL;
-    kernels->vStop = NULL;
     kernels->size = size;
     kernels->inner = 0;
@@ -212,13 +578,77 @@
     kernels->bgOrder = 0;
     kernels->mode = mode;
-    kernels->numCols = 0;
-    kernels->numRows = 0;
     kernels->solution1 = NULL;
     kernels->solution2 = NULL;
+    kernels->mean = NAN;
+    kernels->rms = NAN;
+    kernels->numStamps = 0;
+    kernels->sampleStamps = NULL;
+
+    kernels->fSigResMean  = NAN;
+    kernels->fSigResStdev = NAN;
+    kernels->fMaxResMean  = NAN;
+    kernels->fMaxResStdev = NAN;
+    kernels->fMinResMean  = NAN;
+    kernels->fMinResStdev = NAN;
 
     return kernels;
 }
 
-pmSubtractionKernels *pmSubtractionKernelsPOIS(int size, int spatialOrder, float penalty,
+pmSubtractionKernelPreCalc *pmSubtractionKernelPreCalcAlloc(pmSubtractionKernelsType type, int uOrder, int vOrder, int size, float sigma) {
+
+    pmSubtractionKernelPreCalc *preCalc = psAlloc(sizeof(pmSubtractionKernelPreCalc)); // Kernels, to return
+    psMemSetDeallocator(preCalc, (psFreeFunc)pmSubtractionKernelPreCalcFree);
+
+    // 1D kernel realizations:
+    switch (type) {
+      case PM_SUBTRACTION_KERNEL_ISIS:
+        preCalc->xKernel = pmSubtractionKernelISIS(sigma, uOrder, size);
+        preCalc->yKernel = pmSubtractionKernelISIS(sigma, vOrder, size);
+        preCalc->uCoords = NULL;
+        preCalc->vCoords = NULL;
+        preCalc->poly    = NULL;
+        break;
+      case PM_SUBTRACTION_KERNEL_HERM:
+        preCalc->xKernel = pmSubtractionKernelHERM(sigma, uOrder, size);
+        preCalc->yKernel = pmSubtractionKernelHERM(sigma, vOrder, size);
+        preCalc->uCoords = NULL;
+        preCalc->vCoords = NULL;
+        preCalc->poly    = NULL;
+        break;
+      case PM_SUBTRACTION_KERNEL_RINGS:
+        // the RINGS kernel uses the uCoords, vCoords, and poly elements of the structure
+        // we allocate these vectors here, but leave the kernel generation to the main function
+        preCalc->xKernel = NULL;
+        preCalc->yKernel = NULL;
+        preCalc->kernel  = NULL;
+        preCalc->uCoords = psVectorAllocEmpty(size, PS_TYPE_S32); // u coords
+        preCalc->vCoords = psVectorAllocEmpty(size, PS_TYPE_S32); // v coords
+        preCalc->poly    = psVectorAllocEmpty(size, PS_TYPE_F32); // Polynomial
+        return preCalc;
+      case PM_SUBTRACTION_KERNEL_ISIS_RADIAL:
+        preCalc->kernel  = pmSubtractionKernelHERM_RADIAL(sigma, uOrder, size);
+        preCalc->xKernel = NULL;
+        preCalc->yKernel = NULL;
+        preCalc->uCoords = NULL;
+        preCalc->vCoords = NULL;
+        preCalc->poly    = NULL;
+        return preCalc;
+      default:
+        psAbort("programming error: invalid type for PreCalc kernel");
+    }
+
+    preCalc->kernel = psKernelAlloc(-size, size, -size, size); // 2D Kernel
+
+    // generate 2D kernel from 1D realizations
+    for (int v = -size, y = 0; v <= size; v++, y++) {
+        for (int u = -size, x = 0; u <= size; u++, x++) {
+            preCalc->kernel->kernel[v][u] = preCalc->xKernel->data.F32[x] * preCalc->yKernel->data.F32[y]; // Value of kernel
+        }
+    }
+
+    return preCalc;
+}
+
+pmSubtractionKernels *pmSubtractionKernelsPOIS(int size, int spatialOrder, float penalty, psRegion bounds,
                                                pmSubtractionMode mode)
 {
@@ -228,6 +658,6 @@
     int num = PS_SQR(2 * size + 1) - 1; // Number of basis functions
 
-    pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_POIS, size,
-                                                              spatialOrder, penalty, mode); // The kernels
+    pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(0, PM_SUBTRACTION_KERNEL_POIS, size,
+                                                              spatialOrder, penalty, bounds, mode); // Kernels
     psStringAppend(&kernels->description, "POIS(%d,%d,%.2e)", size, spatialOrder, penalty);
     psLogMsg("psModules.imcombine", PS_LOG_INFO, "POIS kernel: %d,%d --> %d elements",
@@ -244,8 +674,8 @@
 pmSubtractionKernels *pmSubtractionKernelsISIS(int size, int spatialOrder,
                                                const psVector *fwhms, const psVector *orders,
-                                               float penalty, pmSubtractionMode mode)
+                                               float penalty, psRegion bounds, pmSubtractionMode mode)
 {
     pmSubtractionKernels *kernels = p_pmSubtractionKernelsRawISIS(size, spatialOrder, fwhms, orders,
-                                                                  penalty, mode); // Kernels
+                                                                  penalty, bounds, mode); // Kernels
     if (!kernels) {
         return NULL;
@@ -256,5 +686,5 @@
 
 pmSubtractionKernels *pmSubtractionKernelsSPAM(int size, int spatialOrder, int inner, int binning,
-                                               float penalty, pmSubtractionMode mode)
+                                               float penalty, psRegion bounds, pmSubtractionMode mode)
 {
     PS_ASSERT_INT_POSITIVE(size, NULL);
@@ -277,5 +707,5 @@
 
     pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_SPAM, size,
-                                                              spatialOrder, penalty, mode); // The kernels
+                                                              spatialOrder, penalty, bounds, mode); // Kernels
     kernels->inner = inner;
     psStringAppend(&kernels->description, "SPAM(%d,%d,%d,%d,%.2e)", size, inner, binning, spatialOrder,
@@ -348,5 +778,5 @@
 
 pmSubtractionKernels *pmSubtractionKernelsFRIES(int size, int spatialOrder, int inner, float penalty,
-                                                pmSubtractionMode mode)
+                                                psRegion bounds, pmSubtractionMode mode)
 {
     PS_ASSERT_INT_POSITIVE(size, NULL);
@@ -375,5 +805,5 @@
 
     pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_FRIES, size,
-                                                              spatialOrder, penalty, mode); // The kernels
+                                                              spatialOrder, penalty, bounds, mode); // Kernels
     kernels->inner = inner;
     psStringAppend(&kernels->description, "FRIES(%d,%d,%d,%.2e)", size, inner, spatialOrder, penalty);
@@ -441,8 +871,8 @@
 }
 
-// Grid United with Normal Kernel
+// Grid United with Normal Kernel [description: GUNK=ISIS(...)+POIS(...)]
 pmSubtractionKernels *pmSubtractionKernelsGUNK(int size, int spatialOrder, const psVector *fwhms,
                                                const psVector *orders, int inner, float penalty,
-                                               pmSubtractionMode mode)
+                                               psRegion bounds, pmSubtractionMode mode)
 {
     PS_ASSERT_INT_POSITIVE(size, NULL);
@@ -456,8 +886,7 @@
     PS_ASSERT_INT_LESS_THAN(inner, size, NULL);
 
-    // XXX GUNK doesn't seem to work --- doesn't add the POIS components, or at least, they're not noticed
-
     pmSubtractionKernels *kernels = p_pmSubtractionKernelsRawISIS(size, spatialOrder, fwhms, orders,
-                                                                  penalty, mode); // Kernels
+                                                                  penalty, bounds, mode); // Kernels
+    kernels->type = PM_SUBTRACTION_KERNEL_GUNK;
     psStringPrepend(&kernels->description, "GUNK=");
     psStringAppend(&kernels->description, "+POIS(%d,%d)", inner, spatialOrder);
@@ -474,5 +903,5 @@
 // RINGS --- just what it says
 pmSubtractionKernels *pmSubtractionKernelsRINGS(int size, int spatialOrder, int inner, int ringsOrder,
-                                                float penalty, pmSubtractionMode mode)
+                                                float penalty, psRegion bounds, pmSubtractionMode mode)
 {
     PS_ASSERT_INT_POSITIVE(size, NULL);
@@ -505,5 +934,5 @@
 
     pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_RINGS, size,
-                                                              spatialOrder, penalty, mode); // The kernels
+                                                              spatialOrder, penalty, bounds, mode); // Kernels
     kernels->inner = inner;
     psStringAppend(&kernels->description, "RINGS(%d,%d,%d,%d,%.2e)", size, inner, ringsOrder, spatialOrder,
@@ -545,15 +974,15 @@
             for (int vOrder = 0; vOrder <= (i == 0 ? 0 : ringsOrder - uOrder); vOrder++, index++) {
 
-                psArray *data = psArrayAlloc(3); // Container for data
-                psVector *uCoords = data->data[0] = psVectorAllocEmpty(RINGS_BUFFER, PS_TYPE_S32); // u coords
-                psVector *vCoords = data->data[1] = psVectorAllocEmpty(RINGS_BUFFER, PS_TYPE_S32); // v coords
-                psVector *poly = data->data[2] = psVectorAllocEmpty(RINGS_BUFFER, PS_TYPE_F32); // Polynomial
+                pmSubtractionKernelPreCalc *preCalc = pmSubtractionKernelPreCalcAlloc (PM_SUBTRACTION_KERNEL_RINGS, 0, 0, RINGS_BUFFER, 0.0);
                 double moment = 0.0;    // Moment, for penalty
 
                 if (i == 0) {
                     // Central pixel is easy
-                    uCoords->data.S32[0] = vCoords->data.S32[0] = 0;
-                    poly->data.F32[0] = 1.0;
-                    uCoords->n = vCoords->n = poly->n = 1;
+                    preCalc->uCoords->data.S32[0] = 0;
+                    preCalc->vCoords->data.S32[0] = 0;
+                    preCalc->poly->data.F32[0] = 1.0;
+                    preCalc->uCoords->n = 1;
+                    preCalc->vCoords->n = 1;
+                    preCalc->poly->n = 1;
                     radiusLast = 0;
                     moment = 0.0;
@@ -573,15 +1002,15 @@
                                 float polyVal = uPoly * vPoly; // Value of polynomial
                                 if (polyVal != 0) { // No point adding it otherwise
-                                    uCoords->data.S32[j] = u;
-                                    vCoords->data.S32[j] = v;
-                                    poly->data.F32[j] = polyVal;
+                                    preCalc->uCoords->data.S32[j] = u;
+                                    preCalc->vCoords->data.S32[j] = v;
+                                    preCalc->poly->data.F32[j] = polyVal;
                                     norm += polyVal;
-                                    moment += polyVal * (PS_SQR(u) + PS_SQR(v));
-
-                                    psVectorExtend(uCoords, RINGS_BUFFER, 1);
-                                    psVectorExtend(vCoords, RINGS_BUFFER, 1);
-                                    psVectorExtend(poly, RINGS_BUFFER, 1);
+                                    moment += PS_SQR(polyVal) * PS_SQR(PS_SQR(u) + PS_SQR(v));
+
+                                    psVectorExtend(preCalc->uCoords, RINGS_BUFFER, 1);
+                                    psVectorExtend(preCalc->vCoords, RINGS_BUFFER, 1);
+                                    psVectorExtend(preCalc->poly, RINGS_BUFFER, 1);
                                     psTrace("psModules.imcombine", 9, "u = %d, v = %d, poly = %f\n",
-                                            u, v, poly->data.F32[j]);
+                                            u, v, preCalc->poly->data.F32[j]);
                                     j++;
                                 }
@@ -591,25 +1020,28 @@
                     // Normalise kernel component to unit sum
                     if (uOrder % 2 == 0 && vOrder % 2 == 0) {
-                        psBinaryOp(poly, poly, "*", psScalarAlloc(1.0 / norm, PS_TYPE_F32));
+                        psBinaryOp(preCalc->poly, preCalc->poly, "*", psScalarAlloc(1.0 / norm, PS_TYPE_F32));
                         // Add subtraction of 0,0 component to preserve photometric scaling
-                        uCoords->data.S32[j] = 0;
-                        vCoords->data.S32[j] = 0;
-                        poly->data.F32[j] = -1.0;
-                        psVectorExtend(uCoords, RINGS_BUFFER, 1);
-                        psVectorExtend(vCoords, RINGS_BUFFER, 1);
-                        psVectorExtend(poly, RINGS_BUFFER, 1);
+                        preCalc->uCoords->data.S32[j] = 0;
+                        preCalc->vCoords->data.S32[j] = 0;
+                        preCalc->poly->data.F32[j] = -1.0;
+                        psVectorExtend(preCalc->uCoords, RINGS_BUFFER, 1);
+                        psVectorExtend(preCalc->vCoords, RINGS_BUFFER, 1);
+                        psVectorExtend(preCalc->poly, RINGS_BUFFER, 1);
                     } else {
                         norm = powf(size, uOrder) * powf(size, vOrder);
-                        psBinaryOp(poly, poly, "*", psScalarAlloc(1.0 / norm, PS_TYPE_F32));
+                        psBinaryOp(preCalc->poly, preCalc->poly, "*", psScalarAlloc(1.0 / norm, PS_TYPE_F32));
                     }
-//                    moment /= norm;
+                    moment /= PS_SQR(norm);
                 }
 
-                psTrace("psModules.imcombine", 8, "%ld pixels in kernel\n", uCoords->n);
-
-                kernels->preCalc->data[index] = data;
+                psTrace("psModules.imcombine", 8, "%ld pixels in kernel\n", preCalc->uCoords->n);
+
+                kernels->preCalc->data[index] = preCalc;
                 kernels->u->data.S32[index] = uOrder;
                 kernels->v->data.S32[index] = vOrder;
                 kernels->penalties->data.F32[index] = kernels->penalty * fabsf(moment);
+                if (!isfinite(kernels->penalties->data.F32[index])) {
+                    psAbort ("invalid penalty");
+                }
 
                 psTrace("psModules.imcombine", 7, "Kernel %d: %d %d %d\n", index,
@@ -624,20 +1056,26 @@
 pmSubtractionKernels *pmSubtractionKernelsGenerate(pmSubtractionKernelsType type, int size, int spatialOrder,
                                                    const psVector *fwhms, const psVector *orders, int inner,
-                                                   int binning, int ringsOrder, float penalty,
+                                                   int binning, int ringsOrder, float penalty, psRegion bounds,
                                                    pmSubtractionMode mode)
 {
     switch (type) {
       case PM_SUBTRACTION_KERNEL_POIS:
-        return pmSubtractionKernelsPOIS(size, spatialOrder, penalty, mode);
+        return pmSubtractionKernelsPOIS(size, spatialOrder, penalty, bounds, mode);
       case PM_SUBTRACTION_KERNEL_ISIS:
-        return pmSubtractionKernelsISIS(size, spatialOrder, fwhms, orders, penalty, mode);
+        return pmSubtractionKernelsISIS(size, spatialOrder, fwhms, orders, penalty, bounds, mode);
+      case PM_SUBTRACTION_KERNEL_ISIS_RADIAL:
+        return pmSubtractionKernelsISIS_RADIAL(size, spatialOrder, fwhms, orders, penalty, bounds, mode);
+      case PM_SUBTRACTION_KERNEL_HERM:
+        return pmSubtractionKernelsHERM(size, spatialOrder, fwhms, orders, penalty, bounds, mode);
+      case PM_SUBTRACTION_KERNEL_DECONV_HERM:
+        return pmSubtractionKernelsDECONV_HERM(size, spatialOrder, fwhms, orders, penalty, bounds, mode);
       case PM_SUBTRACTION_KERNEL_SPAM:
-        return pmSubtractionKernelsSPAM(size, spatialOrder, inner, binning, penalty, mode);
+        return pmSubtractionKernelsSPAM(size, spatialOrder, inner, binning, penalty, bounds, mode);
       case PM_SUBTRACTION_KERNEL_FRIES:
-        return pmSubtractionKernelsFRIES(size, spatialOrder, inner, penalty, mode);
+        return pmSubtractionKernelsFRIES(size, spatialOrder, inner, penalty, bounds, mode);
       case PM_SUBTRACTION_KERNEL_GUNK:
-        return pmSubtractionKernelsGUNK(size, spatialOrder, fwhms, orders, inner, penalty, mode);
+        return pmSubtractionKernelsGUNK(size, spatialOrder, fwhms, orders, inner, penalty, bounds, mode);
       case PM_SUBTRACTION_KERNEL_RINGS:
-        return pmSubtractionKernelsRINGS(size, spatialOrder, inner, ringsOrder, penalty, mode);
+        return pmSubtractionKernelsRINGS(size, spatialOrder, inner, ringsOrder, penalty, bounds, mode);
       default:
         psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unknown kernel type: %x", type);
@@ -675,5 +1113,5 @@
 
 pmSubtractionKernels *pmSubtractionKernelsFromDescription(const char *description, int bgOrder,
-                                                          pmSubtractionMode mode)
+                                                          psRegion bounds, pmSubtractionMode mode)
 {
     PS_ASSERT_STRING_NON_EMPTY(description, NULL);
@@ -694,36 +1132,40 @@
     float penalty = 0.0;                // Penalty for wideness
 
-    if (strncmp(description, "ISIS", 4) == 0) {
-        // XXX Support for GUNK
-        if (strstr(description, "+POIS")) {
-            type = PM_SUBTRACTION_KERNEL_GUNK;
-            psAbort("Deciphering GUNK kernels (%s) is not currently supported.", description);
-        } else {
-            type = PM_SUBTRACTION_KERNEL_ISIS;
-            char *ptr = (char*)description + 5;    // Eat "ISIS("
-            PARSE_STRING_NUMBER(size, ptr, ',', parseStringInt);
-
-            // Count the number of Gaussians
-            int numGauss = 0;
-            for (char *string = ptr; string; string = strchr(string + 1, '(')) {
-                numGauss++;
-            }
-
-            fwhms = psVectorAlloc(numGauss, PS_TYPE_F32);
-            orders = psVectorAlloc(numGauss, PS_TYPE_S32);
-
-            for (int i = 0; i < numGauss; i++) {
-                ptr++;                  // Eat the '('
-                PARSE_STRING_NUMBER(fwhms->data.F32[i], ptr, ',', parseStringFloat); // Eat "1.234,"
-                PARSE_STRING_NUMBER(orders->data.S32[i], ptr, ')', parseStringInt); // Eat "3)"
-            }
-
-            ptr++;                      // Eat ','
-            PARSE_STRING_NUMBER(spatialOrder, ptr, ',', parseStringInt);
-            penalty = parseStringFloat(ptr);
-        }
-    } else if (strncmp(description, "RINGS", 5) == 0) {
-        type = PM_SUBTRACTION_KERNEL_RINGS;
-        char *ptr = (char*)description + 6;
+    // currently known descriptions:
+    // ISIS(...), ISIS_RADIAL(...), HERM(...), DECONV_HERM(...), POIS(...), SPAM(...),
+    // FRIES(...), GUNK=ISIS(...)+POIS(...), RINGS(...),
+    // the descriptive name is the set of characters before the (
+
+    type = pmSubtractionKernelsTypeFromString (description);
+    char *ptr = strchr(description, '(') + 1;
+    psAssert (ptr, "description is missing kernel parameters");
+
+    switch (type) {
+      case PM_SUBTRACTION_KERNEL_ISIS:
+      case PM_SUBTRACTION_KERNEL_ISIS_RADIAL:
+      case PM_SUBTRACTION_KERNEL_HERM:
+      case PM_SUBTRACTION_KERNEL_DECONV_HERM:
+        PARSE_STRING_NUMBER(size, ptr, ',', parseStringInt);
+
+        // Count the number of Gaussians
+        int numGauss = 0;
+        for (char *string = ptr; string; string = strchr(string + 1, '(')) {
+            numGauss++;
+        }
+
+        fwhms = psVectorAlloc(numGauss, PS_TYPE_F32);
+        orders = psVectorAlloc(numGauss, PS_TYPE_S32);
+
+        for (int i = 0; i < numGauss; i++) {
+            ptr++;                                                               // Eat the '('
+            PARSE_STRING_NUMBER(fwhms->data.F32[i], ptr, ',', parseStringFloat); // Eat "1.234,"
+            PARSE_STRING_NUMBER(orders->data.S32[i], ptr, ')', parseStringInt);  // Eat "3)"
+        }
+
+        ptr++;                      // Eat ','
+        PARSE_STRING_NUMBER(spatialOrder, ptr, ',', parseStringInt);
+        penalty = parseStringFloat(ptr);
+        break;
+      case PM_SUBTRACTION_KERNEL_RINGS:
         PARSE_STRING_NUMBER(size, ptr, ',', parseStringInt);
         PARSE_STRING_NUMBER(inner, ptr, ',', parseStringInt);
@@ -731,32 +1173,56 @@
         PARSE_STRING_NUMBER(spatialOrder, ptr, ',', parseStringInt);
         PARSE_STRING_NUMBER(penalty, ptr, ')', parseStringInt);
-    } else {
-        psAbort("Deciphering kernels other than ISIS and RINGS is not currently supported.");
-    }
-
-
-    return pmSubtractionKernelsGenerate(type, size, spatialOrder, fwhms, orders,
-                                        inner, binning, ringsOrder, penalty, mode);
-}
-
-
+        break;
+      default:
+        psAbort("Deciphering kernels other than ISIS, HERM, DECONV_HERM or RINGS is not currently supported.");
+    }
+
+    return pmSubtractionKernelsGenerate(type, size, spatialOrder, fwhms, orders, inner, binning,
+                                        ringsOrder, penalty, bounds, mode);
+}
+
+
+// the input string can either be just the name or the description string.  Currently known
+// descriptions: ISIS(...), ISIS_RADIAL(...), HERM(...), DECONV_HERM(...), POIS(...),
+// SPAM(...), FRIES(...), GUNK=ISIS(...)+POIS(...), RINGS(...),
 pmSubtractionKernelsType pmSubtractionKernelsTypeFromString(const char *type)
 {
-    if (strcasecmp(type, "POIS") == 0) {
+    // for a bare name (ISIS, HERM), use the full string length.
+    // otherwise, use the length up to the first '('
+    int nameLength = strlen(type);
+    char *ptr = strchr(type, '(');
+    if (ptr) {
+        nameLength = ptr - type;
+    }
+
+    if (strncasecmp(type, "POIS", nameLength) == 0) {
         return PM_SUBTRACTION_KERNEL_POIS;
     }
-    if (strcasecmp(type, "ISIS") == 0) {
+    if (strncasecmp(type, "ISIS", nameLength) == 0) {
         return PM_SUBTRACTION_KERNEL_ISIS;
     }
-    if (strcasecmp(type, "SPAM") == 0) {
+    if (strncasecmp(type, "ISIS_RADIAL", nameLength) == 0) {
+        return PM_SUBTRACTION_KERNEL_ISIS_RADIAL;
+    }
+    if (strncasecmp(type, "HERM", nameLength) == 0) {
+        return PM_SUBTRACTION_KERNEL_HERM;
+    }
+    if (strncasecmp(type, "DECONV_HERM", nameLength) == 0) {
+        return PM_SUBTRACTION_KERNEL_DECONV_HERM;
+    }
+    if (strncasecmp(type, "SPAM", nameLength) == 0) {
         return PM_SUBTRACTION_KERNEL_SPAM;
     }
-    if (strcasecmp(type, "FRIES") == 0) {
+    if (strncasecmp(type, "FRIES", nameLength) == 0) {
         return PM_SUBTRACTION_KERNEL_FRIES;
     }
-    if (strcasecmp(type, "GUNK") == 0) {
+    if (strncasecmp(type, "GUNK", nameLength) == 0) {
         return PM_SUBTRACTION_KERNEL_GUNK;
     }
-    if (strcasecmp(type, "RINGS") == 0) {
+    // note that GUNK has a somewhat different description
+    if (strncasecmp(type, "GUNK=ISIS", nameLength) == 0) {
+        return PM_SUBTRACTION_KERNEL_GUNK;
+    }
+    if (strncasecmp(type, "RINGS", nameLength) == 0) {
         return PM_SUBTRACTION_KERNEL_RINGS;
     }
@@ -765,2 +1231,36 @@
     return PM_SUBTRACTION_KERNEL_NONE;
 }
+
+pmSubtractionKernels *pmSubtractionKernelsCopy(const pmSubtractionKernels *in)
+{
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(in, NULL);
+
+    pmSubtractionKernels *out = psAlloc(sizeof(pmSubtractionKernels)); // Kernels, to return
+    psMemSetDeallocator(out, (psFreeFunc)subtractionKernelsFree);
+
+    out->type = in->type;
+    out->description = psMemIncrRefCounter(in->description);
+    out->num = in->num;
+    out->u = psMemIncrRefCounter(in->u);
+    out->v = psMemIncrRefCounter(in->v);
+    out->widths = psMemIncrRefCounter(in->widths);
+    out->preCalc = psMemIncrRefCounter(in->preCalc);
+    out->penalty = in->penalty;
+    out->penalties = psMemIncrRefCounter(in->penalties);
+    out->uStop = psMemIncrRefCounter(in->uStop);
+    out->vStop = psMemIncrRefCounter(in->vStop);
+    out->size = in->size;
+    out->inner = in->inner;
+    out->spatialOrder = in->spatialOrder;
+    out->bgOrder = in->bgOrder;
+    out->mode = in->mode;
+    out->xMin = in->xMin;
+    out->xMax = in->xMax;
+    out->yMin = in->yMin;
+    out->yMax = in->yMax;
+    out->solution1 = in->solution1 ? psVectorCopy(NULL, in->solution1, PS_TYPE_F64) : NULL;
+    out->solution2 = in->solution2 ? psVectorCopy(NULL, in->solution2, PS_TYPE_F64) : NULL;
+    out->sampleStamps = psMemIncrRefCounter(in->sampleStamps);
+
+    return out;
+}
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionKernels.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionKernels.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionKernels.h	(revision 27839)
@@ -10,4 +10,7 @@
     PM_SUBTRACTION_KERNEL_POIS,         ///< Pan-STARRS Optimal Image Subtraction --- delta functions
     PM_SUBTRACTION_KERNEL_ISIS,         ///< Traditional kernel --- gaussians modified by polynomials
+    PM_SUBTRACTION_KERNEL_ISIS_RADIAL,  ///< ISIS + higher-order radial Hermitians
+    PM_SUBTRACTION_KERNEL_HERM,         ///< Hermitian polynomial kernels
+    PM_SUBTRACTION_KERNEL_DECONV_HERM,  ///< Deconvolved Hermitian polynomial kernels
     PM_SUBTRACTION_KERNEL_SPAM,         ///< Summed Pixels for Advanced Matching --- summed delta functions
     PM_SUBTRACTION_KERNEL_FRIES,        ///< Fibonacci Radius Increases Excellence of Subtraction
@@ -29,9 +32,10 @@
     pmSubtractionKernelsType type;      ///< Type of kernels --- allowing the use of multiple kernels
     psString description;               ///< Description of the kernel parameters
+    int xMin, xMax, yMin, yMax;         ///< Bounds of image (for normalisation)
     long num;                           ///< Number of kernel components (not including the spatial ones)
-    psVector *u, *v;                    ///< Offset (for POIS) or polynomial order (for ISIS)
-    psVector *widths;                   ///< Gaussian FWHMs (ISIS)
+    psVector *u, *v;                    ///< Offset (for POIS) or polynomial order (for ISIS, HERM or DECONV_HERM)
+    psVector *widths;                   ///< Gaussian FWHMs (ISIS, HERM or DECONV_HERM)
     psVector *uStop, *vStop;            ///< Width of kernel element (SPAM,FRIES only)
-    psArray *preCalc;                   ///< Array of images containing pre-calculated kernel (for ISIS)
+    psArray *preCalc;                   ///< Array of images containing pre-calculated kernel (for ISIS, HERM or DECONV_HERM)
     float penalty;                      ///< Penalty for wideness
     psVector *penalties;                ///< Penalty for each kernel component
@@ -41,10 +45,27 @@
     int bgOrder;                        ///< The order for the background fitting
     pmSubtractionMode mode;             ///< Mode for subtraction
-    int numCols, numRows;               ///< Size of image (for normalisation), or zero to use image provided
     psVector *solution1, *solution2;    ///< Solution for the PSF matching
     // Quality information
     float mean, rms;                    ///< Mean and RMS of chi^2 from stamps
     int numStamps;                      ///< Number of good stamps
+    float fSigResMean;                  ///< mean fractional stdev of residuals
+    float fSigResStdev;                 ///< stdev of fractional stdev of residuals
+    float fMaxResMean;                  ///< mean fractional positive swing in residuals
+    float fMaxResStdev;                 ///< stdev of fractional positive swing in residuals
+    float fMinResMean;                  ///< mean fractional negative swing in residuals
+    float fMinResStdev;                 ///< stdev of fractional negative swing in residuals
+    psArray *sampleStamps;              ///< array of brightest set of stamps for output visualizations
 } pmSubtractionKernels;
+
+// pmSubtractionKernels->preCalc is an array of pmSubtractionKernelPreCalc structures
+typedef struct {
+    psVector *uCoords;                  // used by RINGS
+    psVector *vCoords;                  // used by RINGS
+    psVector *poly;                     // used by RINGS
+
+    psVector *xKernel;                  // used by ISIS, HERM, DECONV_HERM
+    psVector *yKernel;                  // used by ISIS, HERM, DECONV_HERM
+    psKernel *kernel;                   // used by ISIS, HERM, DECONV_HERM
+} pmSubtractionKernelPreCalc;
 
 // Assertion to check pmSubtractionKernels
@@ -52,5 +73,5 @@
     PS_ASSERT_PTR_NON_NULL(KERNELS, RETURNVALUE); \
     PS_ASSERT_STRING_NON_EMPTY((KERNELS)->description, RETURNVALUE); \
-    PS_ASSERT_INT_POSITIVE((KERNELS)->num, RETURNVALUE); \
+    PS_ASSERT_INT_NONNEGATIVE((KERNELS)->num, RETURNVALUE); \
     PS_ASSERT_VECTOR_NON_NULL((KERNELS)->u, RETURNVALUE); \
     PS_ASSERT_VECTOR_NON_NULL((KERNELS)->v, RETURNVALUE); \
@@ -60,4 +81,19 @@
     PS_ASSERT_VECTOR_SIZE((KERNELS)->v, (KERNELS)->num, RETURNVALUE); \
     if ((KERNELS)->type == PM_SUBTRACTION_KERNEL_ISIS) { \
+        PS_ASSERT_VECTOR_NON_NULL((KERNELS)->widths, RETURNVALUE); \
+        PS_ASSERT_VECTOR_TYPE((KERNELS)->widths, PS_TYPE_F32, RETURNVALUE); \
+        PS_ASSERT_VECTOR_SIZE((KERNELS)->widths, (KERNELS)->num, RETURNVALUE); \
+    } \
+    if ((KERNELS)->type == PM_SUBTRACTION_KERNEL_ISIS_RADIAL) { \
+        PS_ASSERT_VECTOR_NON_NULL((KERNELS)->widths, RETURNVALUE); \
+        PS_ASSERT_VECTOR_TYPE((KERNELS)->widths, PS_TYPE_F32, RETURNVALUE); \
+        PS_ASSERT_VECTOR_SIZE((KERNELS)->widths, (KERNELS)->num, RETURNVALUE); \
+    } \
+    if ((KERNELS)->type == PM_SUBTRACTION_KERNEL_HERM) { \
+        PS_ASSERT_VECTOR_NON_NULL((KERNELS)->widths, RETURNVALUE); \
+        PS_ASSERT_VECTOR_TYPE((KERNELS)->widths, PS_TYPE_F32, RETURNVALUE); \
+        PS_ASSERT_VECTOR_SIZE((KERNELS)->widths, (KERNELS)->num, RETURNVALUE); \
+    } \
+    if ((KERNELS)->type == PM_SUBTRACTION_KERNEL_DECONV_HERM) { \
         PS_ASSERT_VECTOR_NON_NULL((KERNELS)->widths, RETURNVALUE); \
         PS_ASSERT_VECTOR_TYPE((KERNELS)->widths, PS_TYPE_F32, RETURNVALUE); \
@@ -99,4 +135,16 @@
 }
 
+// Generate 1D convolution kernel for ISIS
+psVector *pmSubtractionKernelISIS(float sigma, // Gaussian width
+                                       int order, // Polynomial order
+                                       int size // Kernel half-size
+    );
+
+// Generate 1D convolution kernel for HERM (normalized for 2D)
+psVector *pmSubtractionKernelHERM(float sigma, // Gaussian width
+                                       int order, // Polynomial order
+                                       int size // Kernel half-size
+    );
+
 /// Generate a delta-function grid for subtraction kernels (like the POIS kernel)
 bool p_pmSubtractionKernelsAddGrid(pmSubtractionKernels *kernels, ///< The subtraction kernels to append to
@@ -114,6 +162,17 @@
                                                 int spatialOrder, ///< Order of spatial variations
                                                 float penalty, ///< Penalty for wideness
+                                                psRegion bounds,       ///< Bounds for validity
                                                 pmSubtractionMode mode ///< Mode for subtraction
     );
+
+/// Allocator for pre-calculated kernel data structure
+pmSubtractionKernelPreCalc *pmSubtractionKernelPreCalcAlloc(
+    pmSubtractionKernelsType type, ///< type of kernel to allocate (not all can be pre-calculated)
+    int uOrder,                    ///< order in x-direction
+    int vOrder,                    ///< order in x-direction
+    int size,                      ///< Half-size of the kernel
+    float sigma                    ///< sigma of gaussian kernel
+    );
+
 
 /// Generate POIS kernels
@@ -121,4 +180,5 @@
                                                int spatialOrder, ///< Order of spatial variations
                                                float penalty, ///< Penalty for wideness
+                                               psRegion bounds,       ///< Bounds for validity
                                                pmSubtractionMode mode ///< Mode for subtraction
     );
@@ -130,4 +190,5 @@
                                                     const psVector *orders, ///< Polynomial order of gaussians
                                                     float penalty, ///< Penalty for wideness
+                                                    psRegion bounds,       ///< Bounds for validity
                                                     pmSubtractionMode mode ///< Mode for subtraction
     );
@@ -139,6 +200,37 @@
                                                const psVector *orders, ///< Polynomial order of gaussians
                                                float penalty, ///< Penalty for wideness
+                                               psRegion bounds,       ///< Bounds for validity
                                                pmSubtractionMode mode ///< Mode for subtraction
                                                );
+
+/// Generate ISIS + RADIAL_HERM kernels
+pmSubtractionKernels *pmSubtractionKernelsISIS_RADIAL(int size, ///< Half-size of the kernel
+                                                      int spatialOrder, ///< Order of spatial variations
+                                                      const psVector *fwhms, ///< Gaussian FWHMs
+                                                      const psVector *orders, ///< Polynomial order of gaussians
+                                                      float penalty, ///< Penalty for wideness
+                                                      psRegion bounds,       ///< Bounds for validity
+                                                      pmSubtractionMode mode ///< Mode for subtraction
+                                               );
+
+/// Generate HERM kernels
+pmSubtractionKernels *pmSubtractionKernelsHERM(int size, ///< Half-size of the kernel
+                                               int spatialOrder, ///< Order of spatial variations
+                                               const psVector *fwhms, ///< Gaussian FWHMs
+                                               const psVector *orders, ///< order of hermitian polynomials
+                                               float penalty, ///< Penalty for wideness
+                                               psRegion bounds,       ///< Bounds for validity
+                                               pmSubtractionMode mode ///< Mode for subtraction
+                                               );
+
+/// Generate DECONV_HERM kernels
+pmSubtractionKernels *pmSubtractionKernelsDECONV_HERM(int size, ///< Half-size of the kernel
+                                                      int spatialOrder, ///< Order of spatial variations
+                                                      const psVector *fwhms, ///< Gaussian FWHMs
+                                                      const psVector *orders, ///< order of hermitian polynomials
+                                                      float penalty, ///< Penalty for wideness
+                                                      psRegion bounds,       ///< Bounds for validity
+                                                      pmSubtractionMode mode ///< Mode for subtraction
+    );
 
 /// Generate SPAM kernels
@@ -148,4 +240,5 @@
                                                int binning, ///< Kernel binning factor
                                                float penalty, ///< Penalty for wideness
+                                               psRegion bounds,       ///< Bounds for validity
                                                pmSubtractionMode mode ///< Mode for subtraction
     );
@@ -156,4 +249,5 @@
                                                 int inner, ///< Inner radius to preserve unbinned
                                                 float penalty, ///< Penalty for wideness
+                                                psRegion bounds,       ///< Bounds for validity
                                                 pmSubtractionMode mode ///< Mode for subtraction
     );
@@ -166,4 +260,5 @@
                                                int inner, ///< Inner radius containing grid of delta functions
                                                float penalty, ///< Penalty for wideness
+                                               psRegion bounds,       ///< Bounds for validity
                                                pmSubtractionMode mode ///< Mode for subtraction
     );
@@ -175,4 +270,5 @@
                                                 int ringsOrder, ///< Polynomial order
                                                 float penalty, ///< Penalty for wideness
+                                                psRegion bounds,       ///< Bounds for validity
                                                 pmSubtractionMode mode ///< Mode for subtraction
     );
@@ -189,4 +285,5 @@
                                                    int ringsOrder, ///< Polynomial order for RINGS
                                                    float penalty, ///< Penalty for wideness
+                                                   psRegion bounds,       ///< Bounds for validity
                                                    pmSubtractionMode mode ///< Mode for subtraction
     );
@@ -196,4 +293,5 @@
     const char *description,            ///< Description of kernel
     int bgOrder,                        ///< Polynomial order for background fitting
+    psRegion bounds,                    ///< Bounds for validity
     pmSubtractionMode mode              ///< Mode for subtraction
     );
@@ -203,4 +301,11 @@
     );
 
+/// Copy kernels
+///
+/// A deep copy is performed on the solution only; the other components are merely pointers.
+pmSubtractionKernels *pmSubtractionKernelsCopy(
+    const pmSubtractionKernels *in      // Kernels to copy
+    );
+
 
 #endif
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionMask.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionMask.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionMask.c	(revision 27839)
@@ -38,14 +38,31 @@
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
-psImage *pmSubtractionMask(const psImage *mask1, const psImage *mask2, psImageMaskType maskVal,
-                           int size, int footprint, float badFrac, pmSubtractionMode mode)
-{
-    PS_ASSERT_IMAGE_NON_NULL(mask1, NULL);
-    PS_ASSERT_IMAGE_TYPE(mask1, PS_TYPE_IMAGE_MASK, NULL);
-    if (mask2) {
-        PS_ASSERT_IMAGE_NON_NULL(mask2, NULL);
-        PS_ASSERT_IMAGE_TYPE(mask2, PS_TYPE_IMAGE_MASK, NULL);
-        PS_ASSERT_IMAGES_SIZE_EQUAL(mask2, mask1, NULL);
-    }
+psImage *pmSubtractionMask(psRegion *bounds, const pmReadout *ro1, const pmReadout *ro2,
+                           psImageMaskType maskVal, int size, int footprint, float badFrac,
+                           pmSubtractionMode mode)
+{
+    int numCols = 0, numRows = 0;       // Size of the images
+    if (ro1) {
+        PM_ASSERT_READOUT_NON_NULL(ro1, NULL);
+        PM_ASSERT_READOUT_IMAGE(ro1, NULL);
+        PM_ASSERT_READOUT_MASK(ro1, NULL);
+        numCols = ro1->image->numCols;
+        numRows = ro1->image->numRows;
+            }
+    if (ro2) {
+        PM_ASSERT_READOUT_NON_NULL(ro2, NULL);
+        PM_ASSERT_READOUT_IMAGE(ro2, NULL);
+        PM_ASSERT_READOUT_MASK(ro2, NULL);
+        numCols = ro2->image->numCols;
+        numRows = ro2->image->numRows;
+    }
+    if (ro1 && ro2) {
+        PS_ASSERT_IMAGES_SIZE_EQUAL(ro1->image, ro2->image, NULL);
+    }
+    if (!ro1 && !ro2) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "No image provided.");
+        return false;
+    }
+    psAssert(numCols > 0 && numRows > 0, "There should be an image provided");
     PS_ASSERT_INT_NONNEGATIVE(size, NULL);
     PS_ASSERT_INT_NONNEGATIVE(footprint, NULL);
@@ -55,28 +72,37 @@
     }
 
-    int numCols = mask1->numCols, numRows = mask1->numRows; // Size of the images
-
     // Dereference inputs for convenience
-    psImageMaskType **data1 = mask1->data.PS_TYPE_IMAGE_MASK_DATA;
-    psImageMaskType **data2 = NULL;
-    if (mask2) {
-        data2 = mask2->data.PS_TYPE_IMAGE_MASK_DATA;
-    }
+    psF32 **imageData1 = ro1 ? ro1->image->data.F32 : NULL;
+    psF32 **imageData2 = ro2 ? ro2->image->data.F32 : NULL;
+    psImageMaskType **maskData1 = ro1 ? ro1->mask->data.PS_TYPE_IMAGE_MASK_DATA : NULL;
+    psImageMaskType **maskData2 = ro2 ? ro2->mask->data.PS_TYPE_IMAGE_MASK_DATA : NULL;
 
     // First, a pass through to determine the fraction of bad pixels
-    if (isfinite(badFrac) && badFrac != 1.0) {
+    if (bounds || (isfinite(badFrac) && badFrac != 1.0)) {
+        int xMin = numCols, xMax = 0, yMin = numRows, yMax = 0; // Bounds of good pixels
         int numBad = 0;                 // Number of bad pixels
         for (int y = 0; y < numRows; y++) {
             for (int x = 0; x < numCols; x++) {
-                if (data1[y][x] & maskVal) {
+                if (ro1 && ((maskData1[y][x] & maskVal) || !isfinite(imageData1[y][x]))) {
                     numBad++;
                     continue;
                 }
-                if (data2 && data2[y][x] & maskVal) {
+                if (ro2 && ((maskData2[y][x] & maskVal) || !isfinite(imageData2[y][x]))) {
                     numBad++;
+                    continue;
                 }
-            }
-        }
-        if (numBad > badFrac * numCols * numRows) {
+                xMin = PS_MIN(xMin, x);
+                xMax = PS_MAX(xMax, x);
+                yMin = PS_MIN(yMin, y);
+                yMax = PS_MAX(yMax, y);
+            }
+        }
+        if (bounds) {
+            bounds->x0 = xMin;
+            bounds->x1 = xMax;
+            bounds->y0 = yMin;
+            bounds->y1 = yMax;
+        }
+        if (isfinite(badFrac) && badFrac != 1.0 && numBad > badFrac * numCols * numRows) {
             psError(PM_ERR_SMALL_AREA, true,
                     "Fraction of bad pixels (%d/%d=%f) exceeds limit (%f)\n",
@@ -117,8 +143,8 @@
     for (int y = 0; y < numRows; y++) {
         for (int x = 0; x < numCols; x++) {
-            if (data1[y][x] & maskVal) {
+            if (ro1 && maskData1[y][x] & maskVal) {
                 maskData[y][x] |= PM_SUBTRACTION_MASK_BAD_1;
             }
-            if (data2 && data2[y][x] & maskVal) {
+            if (ro2 && maskData2[y][x] & maskVal) {
                 maskData[y][x] |= PM_SUBTRACTION_MASK_BAD_2;
             }
@@ -133,13 +159,13 @@
 
     // Pixels that will be bad (or poor) if we convolve with a bad reference pixel
-    if (!psImageConvolveMask(mask, mask, PM_SUBTRACTION_MASK_BAD_1, PM_SUBTRACTION_MASK_CONVOLVE_1,
-                             -size, size, -size, size)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to convolve bad pixels from mask 1.");
+    if (ro1 && !psImageConvolveMask(mask, mask, PM_SUBTRACTION_MASK_BAD_1, PM_SUBTRACTION_MASK_CONVOLVE_1,
+                                    -size, size, -size, size)) {
+        psError(psErrorCodeLast(), false, "Unable to convolve bad pixels from mask 1.");
         psFree(mask);
         return NULL;
     }
-    if (!psImageConvolveMask(mask, mask, PM_SUBTRACTION_MASK_BAD_2, PM_SUBTRACTION_MASK_CONVOLVE_2,
-                             -size, size, -size, size)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to convolve bad pixels from mask 2.");
+    if (ro2 && !psImageConvolveMask(mask, mask, PM_SUBTRACTION_MASK_BAD_2, PM_SUBTRACTION_MASK_CONVOLVE_2,
+                                    -size, size, -size, size)) {
+        psError(psErrorCodeLast(), false, "Unable to convolve bad pixels from mask 2.");
         psFree(mask);
         return NULL;
@@ -163,7 +189,7 @@
         psAbort("Unsupported subtraction mode: %x", mode);
     }
-    if (!psImageConvolveMask(mask, mask, maskRej, PM_SUBTRACTION_MASK_REJ,
-                             -footprint, footprint, -footprint, footprint)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to convolve bad pixels.");
+    if (ro1 && ro2 && !psImageConvolveMask(mask, mask, maskRej, PM_SUBTRACTION_MASK_REJ,
+                                           -footprint, footprint, -footprint, footprint)) {
+        psError(psErrorCodeLast(), false, "Unable to convolve bad pixels.");
         psFree(mask);
         return NULL;
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionMask.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionMask.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionMask.h	(revision 27839)
@@ -5,11 +5,13 @@
 
 /// Generate a mask for use in the subtraction process
-psImage *pmSubtractionMask(const psImage *refMask, ///< Mask for the reference image (will be convolved)
-                           const psImage *inMask, ///< Mask for the input image, or NULL
-                           psImageMaskType maskVal, ///< Value to mask out
-                           int size, ///< Half-size of the kernel (pmSubtractionKernels.size)
-                           int footprint, ///< Half-size of the kernel footprint
-                           float badFrac, ///< Maximum fraction of bad input pixels to accept
-                           pmSubtractionMode mode  ///< Subtraction mode
+psImage *pmSubtractionMask(
+    psRegion *bounds,                   ///< Bounds of valid pixels (or NULL), returned
+    const pmReadout *ro1,               ///< Readout 1
+    const pmReadout *ro2,               ///< Readout 2
+    psImageMaskType maskVal,            ///< Value to mask out
+    int size,                           ///< Half-size of the kernel (pmSubtractionKernels.size)
+    int footprint,                      ///< Half-size of the kernel footprint
+    float badFrac,                      ///< Maximum fraction of bad input pixels to accept
+    pmSubtractionMode mode              ///< Subtraction mode
     );
 
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionMatch.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionMatch.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionMatch.c	(revision 27839)
@@ -10,4 +10,5 @@
 #include "pmHDU.h"
 #include "pmFPA.h"
+#include "pmHDUUtils.h"
 #include "pmSubtractionParams.h"
 #include "pmSubtractionKernels.h"
@@ -27,4 +28,10 @@
 static bool useFFT = true;              // Do convolutions using FFT
 
+# define SEPARATE 0
+# if (SEPARATE)
+# define SUBMODE PM_SUBTRACTION_EQUATION_NORM
+# else
+# define SUBMODE PM_SUBTRACTION_EQUATION_ALL
+# endif
 
 //#define TESTING
@@ -58,18 +65,28 @@
 
 
-static bool getStamps(pmSubtractionStampList **stamps, // Stamps to read
-                      const pmReadout *ro1, // Readout 1
-                      const pmReadout *ro2, // Readout 2
-                      const psImage *subMask, // Mask for subtraction, or NULL
-                      psImage *variance,  // Variance map
-                      const psRegion *region, // Region of interest, or NULL
-                      float thresh1,  // Threshold for stamp finding on readout 1
-                      float thresh2,  // Threshold for stamp finding on readout 2
-                      float stampSpacing, // Spacing between stamps
-                      int size,         // Kernel half-size
-                      int footprint,     // Convolution footprint for stamps
-                      pmSubtractionMode mode // Mode for subtraction
+static bool subtractionGetStamps(pmSubtractionStampList **stamps, // Stamps to read
+                                 const pmReadout *ro1, // Readout 1
+                                 const pmReadout *ro2, // Readout 2
+                                 const psImage *subMask, // Mask for subtraction, or NULL
+                                 psImage *variance,  // Variance map
+                                 const psRegion *region, // Region of interest
+                                 float thresh1,  // Threshold for stamp finding on readout 1
+                                 float thresh2,  // Threshold for stamp finding on readout 2
+                                 float stampSpacing, // Spacing between stamps
+                                 float normFrac,     // Fraction of flux in window for normalisation window
+                                 float sysError,     // Relative systematic error in images
+                                 float skyError,     // Relative systematic error in images
+                                 int size,         // Kernel half-size
+                                 int footprint,     // Convolution footprint for stamps
+                                 pmSubtractionMode mode // Mode for subtraction
     )
 {
+    PS_ASSERT_PTR_NON_NULL(stamps, false);
+    PM_ASSERT_READOUT_NON_NULL(ro1, false);
+    PM_ASSERT_READOUT_NON_NULL(ro2, false);
+    PS_ASSERT_IMAGE_NON_NULL(subMask, false);
+    PS_ASSERT_IMAGE_NON_NULL(variance, false);
+    PS_ASSERT_PTR_NON_NULL(region, false);
+
     psTrace("psModules.imcombine", 3, "Finding stamps...\n");
 
@@ -77,5 +94,5 @@
 
     *stamps = pmSubtractionStampsFind(*stamps, image1, image2, subMask, region, thresh1, thresh2,
-                                      size, footprint, stampSpacing, mode);
+                                      size, footprint, stampSpacing, normFrac, sysError, skyError, mode);
     if (!*stamps) {
         psError(psErrorCodeLast(), false, "Unable to find stamps.");
@@ -86,6 +103,6 @@
 
     psTrace("psModules.imcombine", 3, "Extracting stamps...\n");
-    if (!pmSubtractionStampsExtract(*stamps, ro1->image, ro2 ? ro2->image : NULL, variance, size)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to extract stamps.");
+    if (!pmSubtractionStampsExtract(*stamps, ro1->image, ro2->image, variance, size, *region)) {
+        psError(psErrorCodeLast(), false, "Unable to extract stamps.");
         return false;
     }
@@ -100,5 +117,9 @@
                                   const pmReadout *ro1, const pmReadout *ro2, // Input images
                                   int stride, // Size for convolution patches
-                                  float sysError, // Relative systematic error
+                                  float normFrac,           // Fraction of window for normalisation window
+                                  float sysError,           // Systematic error in images
+                                  float skyError,           // Systematic error in images
+                                  float kernelError, // Systematic error in kernel
+                                  float covarFrac,   // Fraction for kernel truncation before covariance
                                   psImageMaskType maskVal, // Value to mask for input
                                   psImageMaskType maskBad, // Mask for output bad pixels
@@ -111,4 +132,6 @@
     if (subMode != PM_SUBTRACTION_MODE_2) {
         PM_ASSERT_READOUT_NON_NULL(conv1, false);
+        PM_ASSERT_READOUT_NON_NULL(ro1, false);
+        PM_ASSERT_READOUT_IMAGE(ro1, false);
         if (conv1->image) {
             psFree(conv1->image);
@@ -126,4 +149,6 @@
     if (subMode != PM_SUBTRACTION_MODE_1) {
         PM_ASSERT_READOUT_NON_NULL(conv2, false);
+        PM_ASSERT_READOUT_NON_NULL(ro2, false);
+        PM_ASSERT_READOUT_IMAGE(ro2, false);
         if (conv2->image) {
             psFree(conv2->image);
@@ -140,14 +165,25 @@
     }
 
-    PM_ASSERT_READOUT_NON_NULL(ro1, false);
-    PM_ASSERT_READOUT_NON_NULL(ro2, false);
-    PM_ASSERT_READOUT_IMAGE(ro1, false);
-    PM_ASSERT_READOUT_IMAGE(ro2, false);
-    PS_ASSERT_IMAGES_SIZE_EQUAL(ro1->image, ro2->image, false);
+    if (ro1 && ro2) {
+        PS_ASSERT_IMAGES_SIZE_EQUAL(ro1->image, ro2->image, false);
+    }
     PS_ASSERT_INT_NONNEGATIVE(stride, false);
+    if (isfinite(normFrac)) {
+        PS_ASSERT_FLOAT_LARGER_THAN(normFrac, 0.0, false);
+        PS_ASSERT_FLOAT_LESS_THAN(normFrac, 1.0, false);
+    }
     if (isfinite(sysError)) {
         PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(sysError, 0.0, false);
         PS_ASSERT_FLOAT_LESS_THAN(sysError, 1.0, false);
     }
+    if (isfinite(sysError)) {
+        PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(skyError, 0.0, false);
+    }
+    if (isfinite(kernelError)) {
+        PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(kernelError, 0.0, false);
+        PS_ASSERT_FLOAT_LESS_THAN(kernelError, 1.0, false);
+    }
+    PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(covarFrac, 0.0, false);
+    PS_ASSERT_FLOAT_LESS_THAN(covarFrac, 1.0, false);
     // Don't care about maskVal
     // Don't care about maskBad
@@ -164,7 +200,109 @@
 
 
+/// Allocate images, as required
+static void subtractionMatchAlloc(pmReadout *conv1, pmReadout *conv2, // Output readouts
+                                  const pmReadout *ro1, const pmReadout *ro2, // Input readouts
+                                  const psImage *subMask,                     // Subtraction mask
+                                  psImageMaskType maskBad,                    // Mask value for bad pixels
+                                  pmSubtractionMode subMode,          // Subtraction mode
+                                  int numCols, int numRows            // Size of image
+    )
+{
+    if (subMode == PM_SUBTRACTION_MODE_1 || subMode == PM_SUBTRACTION_MODE_UNSURE ||
+        subMode == PM_SUBTRACTION_MODE_DUAL) {
+        if (!conv1->image) {
+            conv1->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        }
+        psImageInit(conv1->image, NAN);
+        if (ro1->variance) {
+            if (!conv1->variance) {
+                conv1->variance = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+            }
+            psImageInit(conv1->variance, NAN);
+        }
+        if (subMask) {
+            if (!conv1->mask) {
+                conv1->mask = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK);
+            }
+            psImageInit(conv1->mask, maskBad);
+        }
+    }
+    if (subMode == PM_SUBTRACTION_MODE_2 || subMode == PM_SUBTRACTION_MODE_UNSURE ||
+        subMode == PM_SUBTRACTION_MODE_DUAL) {
+        if (!conv2->image) {
+            conv2->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        }
+        psImageInit(conv2->image, NAN);
+        if (ro2->variance) {
+            if (!conv2->variance) {
+                conv2->variance = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+            }
+            psImageInit(conv2->variance, NAN);
+        }
+        if (subMask) {
+            if (!conv2->mask) {
+                conv2->mask = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK);
+            }
+            psImageInit(conv2->mask, maskBad);
+        }
+    }
+
+    return;
+}
+
+
+static void subtractionAnalysisUpdate(pmReadout *conv1, pmReadout *conv2, // Convolved images
+                                      const psMetadata *analysis, // Analysis metadata
+                                      const psMetadata *header // Header metadata
+    )
+{
+    if (conv1) {
+        conv1->analysis = psMetadataCopy(conv1->analysis, analysis);
+    }
+    if (conv2) {
+        conv2->analysis = psMetadataCopy(conv2->analysis, analysis);
+    }
+
+    if (conv1 && conv1->parent) {
+        pmHDU *hdu = pmHDUFromCell(conv1->parent);
+        if (hdu) {
+            hdu->header = psMetadataCopy(hdu->header, header);
+        }
+    }
+    if (conv2 && conv2->parent) {
+        pmHDU *hdu = pmHDUFromCell(conv2->parent);
+        if (hdu) {
+            hdu->header = psMetadataCopy(hdu->header, header);
+        }
+    }
+
+    return;
+}
+
+bool pmSubtractionMaskInvalid (const pmReadout *readout, psImageMaskType maskVal) {
+
+    if (!readout) return true;
+
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+    psImage *variance = readout->variance;
+    for (int y = 0; y < image->numRows; y++) {
+        for (int x = 0; x < image->numCols; x++) {
+            if (mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskVal) continue;
+            bool valid = false;
+            valid = isfinite(image->data.F32[y][x]);
+            if (variance) {
+                valid &= isfinite(variance->data.F32[y][x]);
+            }
+            if (valid) continue;
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] = maskVal;
+        }
+    }
+
+    return true;
+}
 
 bool pmSubtractionMatchPrecalc(pmReadout *conv1, pmReadout *conv2, const pmReadout *ro1, const pmReadout *ro2,
-                               psMetadata *analysis, int stride, float sysError,
+                               psMetadata *analysis, int stride, float kernelError, float covarFrac,
                                psImageMaskType maskVal, psImageMaskType maskBad, psImageMaskType maskPoor,
                                float poorFrac, float badFrac)
@@ -182,5 +320,5 @@
         while ((item = psMetadataGetAndIncrement(iter))) {
             if (item->type != PS_DATA_UNKNOWN) {
-                psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Unexpected type for kernel.");
+                psError(PM_ERR_PROG, true, "Unexpected type for kernel.");
                 psFree(iter);
                 psFree(kernelList);
@@ -202,5 +340,5 @@
     }
     if (psListLength(kernelList) == 0) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find kernels");
+        psError(PM_ERR_PROG, true, "Unable to find kernels");
         psFree(kernelList);
         return false;
@@ -217,5 +355,5 @@
         while ((item = psMetadataGetAndIncrement(iter))) {
             if (item->type != PS_DATA_REGION) {
-                psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Unexpected type for region.");
+                psError(PM_ERR_PROG, true, "Unexpected type for region.");
                 psFree(iter);
                 psFree(kernels);
@@ -229,5 +367,5 @@
     }
     if (regions->n != kernels->n) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Differing number of kernels (%ld) and regions (%ld)",
+        psError(PM_ERR_PROG, true, "Differing number of kernels (%ld) and regions (%ld)",
                 kernels->n, regions->n);
         psFree(regions);
@@ -236,6 +374,6 @@
     }
 
-    if (!subtractionMatchCheck(conv1, conv2, ro1, ro2, stride, sysError, maskVal, maskBad, maskPoor,
-                               poorFrac, badFrac, mode)) {
+    if (!subtractionMatchCheck(conv1, conv2, ro1, ro2, stride, NAN, NAN, NAN, kernelError, covarFrac,
+                               maskVal, maskBad, maskPoor, poorFrac, badFrac, mode)) {
         psFree(kernels);
         psFree(regions);
@@ -243,5 +381,49 @@
     }
 
-    psImage *subMask = pmSubtractionMask(ro1->mask, ro2 ? ro2->mask : NULL, maskVal, size, 0,
+    int numCols, numRows;       // Size of image
+    if (ro1) {
+        numCols = ro1->image->numCols;
+        numRows = ro1->image->numRows;
+    } else if (ro2) {
+        numCols = ro2->image->numCols;
+        numRows = ro2->image->numRows;
+    } else {
+        psAbort("No input image provided.");
+    }
+
+    pmSubtractionMaskInvalid(ro1, maskVal);
+    pmSubtractionMaskInvalid(ro2, maskVal);
+
+    // General background subtraction, since this is done in pmSubtractionMatch
+    {
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS); // Random number generator
+        psStats *bg = psStatsAlloc(PS_STAT_ROBUST_MEDIAN); // Statistics for background
+        if (ro1) {
+            psStatsInit(bg);
+            if (!psImageBackground(bg, NULL, ro1->image, ro1->mask, maskVal, rng)) {
+                psError(PM_ERR_DATA, false, "Unable to measure background statistics.");
+                psFree(bg);
+                psFree(rng);
+                return false;
+            }
+            psBinaryOp(ro1->image, ro1->image, "-", psScalarAlloc((float)bg->robustMedian, PS_TYPE_F32));
+        }
+        if (ro2) {
+            psStatsInit(bg);
+            if (!psImageBackground(bg, NULL, ro2->image, ro2->mask, maskVal, rng)) {
+                psError(PM_ERR_DATA, false, "Unable to measure background statistics.");
+                psFree(bg);
+                psFree(rng);
+                return false;
+            }
+            psBinaryOp(ro2->image, ro2->image, "-", psScalarAlloc((float)bg->robustMedian, PS_TYPE_F32));
+        }
+        psFree(bg);
+        psFree(rng);
+    }
+
+    psRegion bounds = psRegionSet(NAN, NAN, NAN, NAN); // Bounds of valid pixels
+
+    psImage *subMask = pmSubtractionMask(&bounds, ro1, ro2, maskVal, size, 0,
                                          badFrac, mode); // Subtraction mask
     if (!subMask) {
@@ -253,4 +435,7 @@
 
     psMetadata *outAnalysis = psMetadataAlloc(); // Output analysis values
+    psMetadata *outHeader = psMetadataAlloc(); // Output header values
+
+    subtractionMatchAlloc(conv1, conv2, ro1, ro2, subMask, maskBad, mode, numCols, numRows);
 
     psTrace("psModules.imcombine", 2, "Convolving...\n");
@@ -259,7 +444,8 @@
         psRegion *region = regions->data[i]; // Region of interest
 
-        if (!pmSubtractionAnalysis(outAnalysis, kernel, region, ro1->image->numCols, ro1->image->numRows)) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to generate QA data");
+        if (!pmSubtractionAnalysis(outAnalysis, outHeader, kernel, region, numCols, numRows)) {
+            psError(psErrorCodeLast(), false, "Unable to generate QA data");
             psFree(outAnalysis);
+            psFree(outHeader);
             psFree(subMask);
             psFree(kernels);
@@ -269,7 +455,8 @@
 
         if (!pmSubtractionConvolve(conv1, conv2, ro1, ro2, subMask, stride, maskBad, maskPoor, poorFrac,
-                                   sysError, region, kernel, true, useFFT)) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to convolve image.");
+                                   kernelError, covarFrac, region, kernel, true, useFFT)) {
+            psError(psErrorCodeLast(), false, "Unable to convolve image.");
             psFree(outAnalysis);
+            psFree(outHeader);
             psFree(subMask);
             psFree(kernels);
@@ -283,11 +470,7 @@
     psFree(regions);
 
-    if (conv1) {
-        psMetadataCopy(conv1->analysis, outAnalysis);
-    }
-    if (conv2) {
-        psMetadataCopy(conv2->analysis, outAnalysis);
-    }
+    subtractionAnalysisUpdate(conv1, conv2, outAnalysis, outHeader);
     psFree(outAnalysis);
+    psFree(outHeader);
 
     return true;
@@ -302,11 +485,16 @@
                         int inner, int ringsOrder, int binning, float penalty,
                         bool optimum, const psVector *optFWHMs, int optOrder, float optThreshold,
-                        int iter, float rej, float sysError, psImageMaskType maskVal, psImageMaskType maskBad,
+                        int iter, float rej, float normFrac, float sysError, float skyError,
+                        float kernelError, float covarFrac, psImageMaskType maskVal, psImageMaskType maskBad,
                         psImageMaskType maskPoor, float poorFrac, float badFrac, pmSubtractionMode subMode)
 {
-    if (!subtractionMatchCheck(conv1, conv2, ro1, ro2, stride, sysError, maskVal, maskBad, maskPoor,
-                               poorFrac, badFrac, subMode)) {
+    if (!subtractionMatchCheck(conv1, conv2, ro1, ro2, stride, normFrac, sysError, skyError, kernelError,
+                               covarFrac, maskVal, maskBad, maskPoor, poorFrac, badFrac, subMode)) {
         return false;
     }
+
+    // We need both inputs
+    PM_ASSERT_READOUT_NON_NULL(ro1, false);
+    PM_ASSERT_READOUT_NON_NULL(ro2, false);
 
     PS_ASSERT_INT_NONNEGATIVE(footprint, false);
@@ -364,9 +552,10 @@
     // Putting important variable declarations here, since they are freed after a "goto" if there is an error.
     psImage *subMask = NULL;            // Mask for subtraction
-    psRegion *region = NULL;            // Iso-kernel region
+    psRegion *region = psRegionAlloc(NAN, NAN, NAN, NAN); // Iso-kernel region
     psString regionString = NULL;       // String for region
     pmSubtractionStampList *stamps = NULL; // Stamps for matching PSF
     pmSubtractionKernels *kernels = NULL; // Kernel basis functions
     psMetadata *analysis = psMetadataAlloc(); // QA data
+    psMetadata *header = psMetadataAlloc(); // QA data for header
 
     int numCols = ro1->image->numCols, numRows = ro1->image->numRows; // Image dimensions
@@ -376,6 +565,10 @@
     memCheck("start");
 
-    subMask = pmSubtractionMask(ro1->mask, ro2 ? ro2->mask : NULL, maskVal, size, footprint,
-                                badFrac, subMode);
+    pmSubtractionMaskInvalid(ro1, maskVal);
+    pmSubtractionMaskInvalid(ro2, maskVal);
+
+    psRegion bounds = psRegionSet(NAN, NAN, NAN, NAN); // Bounds of valid pixels
+
+    subMask = pmSubtractionMask(&bounds, ro1, ro2, maskVal, size, footprint, badFrac, subMode);
     if (!subMask) {
         psError(psErrorCodeLast(), false, "Unable to generate subtraction mask.");
@@ -387,34 +580,43 @@
     // Get region of interest
     int xRegions = 1, yRegions = 1;     // Number of iso-kernel regions
-    float xRegionSize = 0, yRegionSize = 0; // Size of iso-kernel regions
+    float xRegionSize = NAN, yRegionSize = NAN; // Size of iso-kernel regions
     if (isfinite(regionSize) && regionSize != 0.0) {
-        xRegions = numCols / regionSize + 1;
-        yRegions = numRows / regionSize + 1;
-        xRegionSize = (float)numCols / (float)xRegions;
-        yRegionSize = (float)numRows / (float)yRegions;
-        region = psRegionAlloc(NAN, NAN, NAN, NAN);
-    }
-
+        xRegions = (bounds.x1 - bounds.x0) / regionSize + 1;
+        yRegions = (bounds.y1 - bounds.y0) / regionSize + 1;
+        xRegionSize = (float)(bounds.x1 - bounds.x0) / (float)xRegions;
+        yRegionSize = (float)(bounds.y1 - bounds.y0) / (float)yRegions;
+    } else {
+        xRegionSize = bounds.x1 - bounds.x0;
+        yRegionSize = bounds.y1 - bounds.y0;
+    }
+
+    // General background subtraction and measurement of stamp threshold
     float stampThresh1 = NAN, stampThresh2 = NAN; // Stamp thresholds for images
     {
-        psStats *bg = psStatsAlloc(PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV); // Statistics for backgroun
+        psStats *bg = psStatsAlloc(PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV); // Statistics for background
         if (ro1) {
+            psStatsInit(bg);
             if (!psImageBackground(bg, NULL, ro1->image, ro1->mask, maskVal, rng)) {
-                psError(PS_ERR_UNKNOWN, false, "Unable to measure background statistics.");
+                psError(PM_ERR_DATA, false, "Unable to measure background statistics.");
                 psFree(bg);
                 goto MATCH_ERROR;
             }
-            stampThresh1 = bg->robustMedian + threshold * bg->robustStdev;
+            stampThresh1 = threshold * bg->robustStdev;
+            psBinaryOp(ro1->image, ro1->image, "-", psScalarAlloc((float)bg->robustMedian, PS_TYPE_F32));
         }
         if (ro2) {
+            psStatsInit(bg);
             if (!psImageBackground(bg, NULL, ro2->image, ro2->mask, maskVal, rng)) {
-                psError(PS_ERR_UNKNOWN, false, "Unable to measure background statistics.");
+                psError(PM_ERR_DATA, false, "Unable to measure background statistics.");
                 psFree(bg);
                 goto MATCH_ERROR;
             }
-            stampThresh2 = bg->robustMedian + threshold * bg->robustStdev;
+            stampThresh2 = threshold * bg->robustStdev;
+            psBinaryOp(ro2->image, ro2->image, "-", psScalarAlloc((float)bg->robustMedian, PS_TYPE_F32));
         }
         psFree(bg);
     }
+
+    subtractionMatchAlloc(conv1, conv2, ro1, ro2, subMask, maskBad, subMode, numCols, numRows);
 
     // Iterate over iso-kernel regions
@@ -423,34 +625,63 @@
             psTrace("psModules.imcombine", 1, "Subtracting region %d of %d...\n",
                     j * xRegions + i + 1, xRegions * yRegions);
-            if (region) {
-                *region = psRegionSet((int)(i * xRegionSize), (int)((i + 1) * xRegionSize),
-                                      (int)(j * yRegionSize), (int)((j + 1) * yRegionSize));
-                psFree(regionString);
-                regionString = psRegionToString(*region);
-                psTrace("psModules.imcombine", 3, "Iso-kernel region: %s out of %d,%d\n",
-                        regionString, numCols, numRows);
-            }
+            *region = psRegionSet(bounds.x0 + (int)(i * xRegionSize),
+                                  bounds.x0 + (int)((i + 1) * xRegionSize),
+                                  bounds.y0 + (int)(j * yRegionSize),
+                                  bounds.y0 + (int)((j + 1) * yRegionSize));
+            psFree(regionString);
+            regionString = psRegionToString(*region);
+            psLogMsg("psModules.imcombine", PS_LOG_DETAIL, "Iso-kernel region: %s out of %d,%d\n",
+                    regionString, numCols, numRows);
 
             if (stampsName && strlen(stampsName) > 0) {
                 stamps = pmSubtractionStampsSetFromFile(stampsName, ro1->image, subMask, region, size,
-                                                        footprint, stampSpacing, subMode);
+                                                        footprint, stampSpacing, normFrac,
+                                                        sysError, skyError, subMode);
             } else if (sources) {
                 stamps = pmSubtractionStampsSetFromSources(sources, ro1->image, subMask, region, size,
-                                                           footprint, stampSpacing, subMode);
+                                                           footprint, stampSpacing, normFrac,
+                                                           sysError, skyError, subMode);
             }
 
             // We get the stamps here; we will also attempt to get stamps at the first iteration, but it
             // doesn't matter.
-            if (!getStamps(&stamps, ro1, ro2, subMask, variance, NULL, stampThresh1, stampThresh2,
-                           stampSpacing, size, footprint, subMode)) {
+            if (!subtractionGetStamps(&stamps, ro1, ro2, subMask, variance, region, stampThresh1, stampThresh2,
+                                      stampSpacing, normFrac, sysError, skyError, size, footprint, subMode)) {
                 goto MATCH_ERROR;
             }
 
+
+            // generate the window function from the set of stamps
+            if (!pmSubtractionStampsGetWindow(stamps, size)) {
+                psError(psErrorCodeLast(), false, "Unable to get stamp window.");
+                goto MATCH_ERROR;
+            }
+
+            // Define kernel basis functions
+            if (optimum && (type == PM_SUBTRACTION_KERNEL_ISIS || type == PM_SUBTRACTION_KERNEL_GUNK)) {
+                kernels = pmSubtractionKernelsOptimumISIS(type, size, inner, spatialOrder,
+                                                          optFWHMs, optOrder, stamps, footprint,
+                                                          optThreshold, penalty, bounds, subMode);
+                if (!kernels) {
+                    psErrorClear();
+                    psWarning("Unable to derive optimum ISIS kernel --- switching to default.");
+                }
+            }
+            if (kernels == NULL) {
+                // Not an ISIS/GUNK kernel, or the optimum kernel search failed
+                kernels = pmSubtractionKernelsGenerate(type, size, spatialOrder, isisWidths, isisOrders,
+                                                       inner, binning, ringsOrder, penalty, bounds, subMode);
+                // pmSubtractionVisualShowKernels(kernels);
+            }
+
+            memCheck("kernels");
+
             if (subMode == PM_SUBTRACTION_MODE_UNSURE) {
+#if 0
                 // Get backgrounds
                 psStats *bgStats = psStatsAlloc(BG_STAT); // Statistics for background
                 psVector *buffer = NULL;// Buffer for stats
                 if (!psImageBackground(bgStats, &buffer, ro1->image, ro1->mask, maskVal, rng)) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to measure background of image 1.");
+                    psError(PM_ERR_DATA, false, "Unable to measure background of image 1.");
                     psFree(bgStats);
                     psFree(buffer);
@@ -459,5 +690,5 @@
                 float bg1 = psStatsGetValue(bgStats, BG_STAT); // Background for image 1
                 if (!psImageBackground(bgStats, &buffer, ro2->image, ro2->mask, maskVal, rng)) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to measure background of image 2.");
+                    psError(PM_ERR_DATA, false, "Unable to measure background of image 2.");
                     psFree(bgStats);
                     psFree(buffer);
@@ -469,4 +700,6 @@
 
                 pmSubtractionMode newMode = pmSubtractionOrder(stamps, bg1, bg2); // Subtraction mode to use
+#endif
+                pmSubtractionMode newMode = pmSubtractionBestMode(&stamps, &kernels, subMask, rej);
                 switch (newMode) {
                   case PM_SUBTRACTION_MODE_1:
@@ -477,26 +710,9 @@
                     break;
                   default:
-                    psError(PS_ERR_UNKNOWN, false, "Unable to determine subtraction order.");
+                    psError(psErrorCodeLast(), false, "Unable to determine subtraction order.");
                     goto MATCH_ERROR;
                 }
                 subMode = newMode;
             }
-
-            // Define kernel basis functions
-            if (optimum && (type == PM_SUBTRACTION_KERNEL_ISIS || type == PM_SUBTRACTION_KERNEL_GUNK)) {
-                kernels = pmSubtractionKernelsOptimumISIS(type, size, inner, spatialOrder, optFWHMs, optOrder,
-                                                          stamps, footprint, optThreshold, penalty, subMode);
-                if (!kernels) {
-                    psErrorClear();
-                    psWarning("Unable to derive optimum ISIS kernel --- switching to default.");
-                }
-            }
-            if (kernels == NULL) {
-                // Not an ISIS/GUNK kernel, or the optimum kernel search failed
-                kernels = pmSubtractionKernelsGenerate(type, size, spatialOrder, isisWidths, isisOrders,
-                                                       inner, binning, ringsOrder, penalty, subMode);
-            }
-
-            memCheck("kernels");
 
             int numRejected = -1;       // Number of rejected stamps in each iteration
@@ -504,29 +720,52 @@
                 psLogMsg("psModules.imcombine", PS_LOG_INFO, "Iteration %d.", k);
 
-                if (!getStamps(&stamps, ro1, ro2, subMask, variance, region, stampThresh1, stampThresh2,
-                               stampSpacing, size, footprint, subMode)) {
-                    goto MATCH_ERROR;
-                }
-
-                psTrace("psModules.imcombine", 3, "Calculating equation...\n");
-                if (!pmSubtractionCalculateEquation(stamps, kernels)) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
-                    goto MATCH_ERROR;
-                }
-
+                if (!subtractionGetStamps(&stamps, ro1, ro2, subMask, variance, region,
+                                          stampThresh1, stampThresh2, stampSpacing, normFrac,
+                                          sysError, skyError, size, footprint, subMode)) {
+                    goto MATCH_ERROR;
+                }
+
+                // generate the window function from the set of stamps
+                if (!pmSubtractionStampsGetWindow(stamps, size)) {
+                    psError(psErrorCodeLast(), false, "Unable to get stamps window.");
+                    goto MATCH_ERROR;
+                }
+
+                // XXX step 1: calculate normalization
+                psTrace("psModules.imcombine", 3, "Calculating equation for normalization...\n");
+                if (!pmSubtractionCalculateEquation(stamps, kernels, SUBMODE)) {
+                    psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
+
+                psTrace("psModules.imcombine", 3, "Solving equation for normalization...\n");
+                if (!pmSubtractionSolveEquation(kernels, stamps, SUBMODE)) {
+                    psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
+                memCheck("  solve equation");
+
+# if (SEPARATE)
+                // set USED -> CALCULATE
+                pmSubtractionStampsResetStatus (stamps);
+
+                // XXX step 2: calculate kernel parameters
+                psTrace("psModules.imcombine", 3, "Calculating equation for kernels...\n");
+                if (!pmSubtractionCalculateEquation(stamps, kernels, PM_SUBTRACTION_EQUATION_KERNELS)) {
+                    psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
                 memCheck("  calculate equation");
 
-                psTrace("psModules.imcombine", 3, "Solving equation...\n");
-
-                if (!pmSubtractionSolveEquation(kernels, stamps)) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
-                    goto MATCH_ERROR;
-                }
-
+                psTrace("psModules.imcombine", 3, "Solving equation for kernels...\n");
+                if (!pmSubtractionSolveEquation(kernels, stamps, PM_SUBTRACTION_EQUATION_KERNELS)) {
+                    psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
                 memCheck("  solve equation");
-
+# endif
                 psVector *deviations = pmSubtractionCalculateDeviations(stamps, kernels); // Stamp deviations
                 if (!deviations) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate deviations.");
+                    psError(psErrorCodeLast(), false, "Unable to calculate deviations.");
                     goto MATCH_ERROR;
                 }
@@ -535,7 +774,7 @@
 
                 psTrace("psModules.imcombine", 3, "Rejecting stamps...\n");
-                numRejected = pmSubtractionRejectStamps(kernels, stamps, deviations, subMask, rej, footprint);
+                numRejected = pmSubtractionRejectStamps(kernels, stamps, deviations, subMask, rej);
                 if (numRejected < 0) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to reject stamps.");
+                    psError(psErrorCodeLast(), false, "Unable to reject stamps.");
                     psFree(deviations);
                     goto MATCH_ERROR;
@@ -546,16 +785,45 @@
             }
 
+            // if we hit the max number of iterations and we have rejected stamps, re-solve
             if (numRejected > 0) {
-                psTrace("psModules.imcombine", 3, "Solving equation...\n");
-                if (!pmSubtractionSolveEquation(kernels, stamps)) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
-                    goto MATCH_ERROR;
-                }
+                // XXX step 1: calculate normalization
+                psTrace("psModules.imcombine", 3, "Calculating equation for normalization...\n");
+                if (!pmSubtractionCalculateEquation(stamps, kernels, SUBMODE)) {
+                    psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
+
+                // solve normalization
+                psTrace("psModules.imcombine", 3, "Solving equation for kernels...\n");
+                if (!pmSubtractionSolveEquation(kernels, stamps, SUBMODE)) {
+                    psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
+
+# if (SEPARATE)
+                // set USED -> CALCULATE
+                pmSubtractionStampsResetStatus (stamps);
+
+                // XXX step 2: calculate kernel parameters
+                psTrace("psModules.imcombine", 3, "Calculating equation for normalization...\n");
+                if (!pmSubtractionCalculateEquation(stamps, kernels, PM_SUBTRACTION_EQUATION_KERNELS)) {
+                    psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
+
+                // solve kernel parameters
+                psTrace("psModules.imcombine", 3, "Solving equation for kernels...\n");
+                if (!pmSubtractionSolveEquation(kernels, stamps, PM_SUBTRACTION_EQUATION_KERNELS)) {
+                    psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
+                memCheck("  solve equation");
+# endif
                 psVector *deviations = pmSubtractionCalculateDeviations(stamps, kernels); // Stamp deviations
                 if (!deviations) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate deviations.");
-                    goto MATCH_ERROR;
-                }
-                pmSubtractionRejectStamps(kernels, stamps, deviations, subMask, NAN, footprint);
+                    psError(psErrorCodeLast(), false, "Unable to calculate deviations.");
+                    goto MATCH_ERROR;
+                }
+                pmSubtractionRejectStamps(kernels, stamps, deviations, subMask, NAN);
                 psFree(deviations);
             }
@@ -565,6 +833,6 @@
             memCheck("solution");
 
-            if (!pmSubtractionAnalysis(analysis, kernels, region, numCols, numRows)) {
-                psError(PS_ERR_UNKNOWN, false, "Unable to generate QA data");
+            if (!pmSubtractionAnalysis(analysis, header, kernels, region, numCols, numRows)) {
+                psError(psErrorCodeLast(), false, "Unable to generate QA data");
                 goto MATCH_ERROR;
             }
@@ -574,6 +842,6 @@
             psTrace("psModules.imcombine", 2, "Convolving...\n");
             if (!pmSubtractionConvolve(conv1, conv2, ro1, ro2, subMask, stride, maskBad, maskPoor, poorFrac,
-                                       sysError, region, kernels, true, useFFT)) {
-                psError(PS_ERR_UNKNOWN, false, "Unable to convolve image.");
+                                       kernelError, covarFrac, region, kernels, true, useFFT)) {
+                psError(psErrorCodeLast(), false, "Unable to convolve image.");
                 goto MATCH_ERROR;
             }
@@ -594,18 +862,18 @@
     variance = NULL;
 
-    if (!pmSubtractionBorder(conv1->image, conv1->variance, conv1->mask, size, maskBad)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to set border of convolved image.");
+    if (conv1 && !pmSubtractionBorder(conv1->image, conv1->variance, conv1->mask, size, maskBad)) {
+        psError(psErrorCodeLast(), false, "Unable to set border of convolved image.");
         goto MATCH_ERROR;
     }
+    if (conv2 && !pmSubtractionBorder(conv2->image, conv2->variance, conv2->mask, size, maskBad)) {
+        psError(psErrorCodeLast(), false, "Unable to set border of convolved image.");
+        goto MATCH_ERROR;
+    }
 
     memCheck("convolution");
 
-    if (conv1) {
-        psMetadataCopy(conv1->analysis, analysis);
-    }
-    if (conv2) {
-        psMetadataCopy(conv2->analysis, analysis);
-    }
+    subtractionAnalysisUpdate(conv1, conv2, analysis, header);
     psFree(analysis);
+    psFree(header);
 
 #ifdef TESTING
@@ -629,4 +897,5 @@
 MATCH_ERROR:
     psFree(analysis);
+    psFree(header);
     psFree(region);
     psFree(regionString);
@@ -797,5 +1066,5 @@
         } else {
             if (!pmSubtractionOrderStamp(ratios, mask, stamps, models, modelSums, i, bg1, bg2)) {
-                psError(PS_ERR_UNKNOWN, false, "Unable to measure PSF width for stamp %d", i);
+                psError(psErrorCodeLast(), false, "Unable to measure PSF width for stamp %d", i);
                 psFree(models);
                 psFree(modelSums);
@@ -808,5 +1077,5 @@
 
     if (!psThreadPoolWait(true)) {
-        psError(PS_ERR_UNKNOWN, false, "Error waiting for threads.");
+        psError(psErrorCodeLast(), false, "Error waiting for threads.");
         psFree(models);
         psFree(modelSums);
@@ -821,5 +1090,5 @@
     psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
     if (!psVectorStats(stats, ratios, NULL, mask, 0xff)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to calculate statistics for moments ratio.");
+        psError(psErrorCodeLast(), false, "Unable to calculate statistics for moments ratio.");
         psFree(mask);
         psFree(ratios);
@@ -842,2 +1111,215 @@
     return mode;
 }
+
+
+// Test a subtraction mode by performing a single iteration
+static bool subtractionModeTest(pmSubtractionStampList *stamps, // Stamps to use to find best mode
+                                pmSubtractionKernels *kernels, // Kernel description
+                                const char *description, // Description for trace
+                                psImage *subMask,  // Subtraction mask
+                                float rej               // Rejection threshold
+                                )
+{
+    assert(stamps);
+    assert(kernels);
+
+    psTrace("psModules.imcombine", 3, "Calculating %s normalization equation...\n", description);
+    if (!pmSubtractionCalculateEquation(stamps, kernels, SUBMODE)) {
+        psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+        return false;
+    }
+
+    psTrace("psModules.imcombine", 3, "Solving %s normalization equation...\n", description);
+    if (!pmSubtractionSolveEquation(kernels, stamps, SUBMODE)) {
+        psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+        return false;
+    }
+
+# if (SEPARATE)
+    // set USED -> CALCULATE
+    pmSubtractionStampsResetStatus (stamps);
+
+    psTrace("psModules.imcombine", 3, "Calculating %s kernel coeffs equation...\n", description);
+    if (!pmSubtractionCalculateEquation(stamps, kernels, PM_SUBTRACTION_EQUATION_KERNELS)) {
+        psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+        return false;
+    }
+
+    psTrace("psModules.imcombine", 3, "Solving %s kernel coeffs equation...\n", description);
+    if (!pmSubtractionSolveEquation(kernels, stamps, PM_SUBTRACTION_EQUATION_KERNELS)) {
+        psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+        return false;
+    }
+# endif
+
+    psTrace("psModules.imcombine", 3, "Calculate %s deviations...\n", description);
+    psVector *deviations = pmSubtractionCalculateDeviations(stamps, kernels); // Stamp deviations
+    if (!deviations) {
+        psError(psErrorCodeLast(), false, "Unable to calculate deviations.");
+        return false;
+    }
+
+    psTrace("psModules.imcombine", 3, "Rejecting %s stamps...\n", description);
+    long numRejected = pmSubtractionRejectStamps(kernels, stamps, deviations, subMask, rej);
+    if (numRejected < 0) {
+        psError(psErrorCodeLast(), false, "Unable to reject stamps.");
+        psFree(deviations);
+        return false;
+    }
+    psFree(deviations);
+
+    if (numRejected > 0) {
+        // Allow re-fit with reduced stamps set
+        psTrace("psModules.imcombine", 3, "Calculating %s normalization equation...\n", description);
+        if (!pmSubtractionCalculateEquation(stamps, kernels, PM_SUBTRACTION_EQUATION_ALL)) {
+            psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+            return false;
+        }
+
+        psTrace("psModules.imcombine", 3, "Resolving %s equation...\n", description);
+        if (!pmSubtractionSolveEquation(kernels, stamps, PM_SUBTRACTION_EQUATION_ALL)) {
+            psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+            return false;
+        }
+        psTrace("psModules.imcombine", 3, "Recalculate %s deviations...\n", description);
+
+# if (SEPARATE)
+        // set USED -> CALCULATE
+        pmSubtractionStampsResetStatus (stamps);
+
+        psTrace("psModules.imcombine", 3, "Calculating %s normalization equation...\n", description);
+        if (!pmSubtractionCalculateEquation(stamps, kernels, PM_SUBTRACTION_EQUATION_KERNELS)) {
+            psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+            return false;
+        }
+
+        psTrace("psModules.imcombine", 3, "Resolving %s equation...\n", description);
+        if (!pmSubtractionSolveEquation(kernels, stamps, PM_SUBTRACTION_EQUATION_KERNELS)) {
+            psError(psErrorCodeLast(), false, "Unable to calculate least-squares equation.");
+            return false;
+        }
+        psTrace("psModules.imcombine", 3, "Recalculate %s deviations...\n", description);
+# endif
+
+        psVector *deviations = pmSubtractionCalculateDeviations(stamps, kernels); // Stamp deviations
+        if (!deviations) {
+            psError(psErrorCodeLast(), false, "Unable to calculate deviations.");
+            return false;
+        }
+        psTrace("psModules.imcombine", 3, "Measuring %s quality...\n", description);
+        long numRejected = pmSubtractionRejectStamps(kernels, stamps, deviations, subMask, NAN);
+        if (numRejected < 0) {
+            psError(psErrorCodeLast(), false, "Unable to reject stamps.");
+            psFree(deviations);
+            return false;
+        }
+        psFree(deviations);
+    }
+
+    return true;
+}
+
+
+pmSubtractionMode pmSubtractionBestMode(pmSubtractionStampList **stamps, pmSubtractionKernels **kernels,
+                                        const psImage *subMask, float rej)
+{
+    PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(*stamps, PM_SUBTRACTION_MODE_ERR);
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(*kernels, PM_SUBTRACTION_MODE_ERR);
+
+    // Copies of the inputs
+    pmSubtractionStampList *stamps1 = pmSubtractionStampListCopy(*stamps);
+    pmSubtractionKernels *kernels1 = pmSubtractionKernelsCopy(*kernels);
+    psImage *subMask1 = psImageCopy(NULL, subMask, subMask->type.type);
+    kernels1->mode = PM_SUBTRACTION_MODE_1;
+
+    if (!subtractionModeTest(stamps1, kernels1, "convolve 1", subMask1, rej)) {
+        psError(psErrorCodeLast(), false, "Unable to test subtraction with convolution of image 1");
+        psFree(stamps1);
+        psFree(kernels1);
+        psFree(subMask1);
+        return PM_SUBTRACTION_MODE_ERR;
+    }
+    psFree(subMask1);
+
+    // Copies of the inputs
+    pmSubtractionStampList *stamps2 = pmSubtractionStampListCopy(*stamps);
+    pmSubtractionKernels *kernels2 = pmSubtractionKernelsCopy(*kernels);
+    psImage *subMask2 = psImageCopy(NULL, subMask, subMask->type.type);
+    kernels2->mode = PM_SUBTRACTION_MODE_2;
+
+    if (!subtractionModeTest(stamps2, kernels2, "convolve 2", subMask2, rej)) {
+        psError(psErrorCodeLast(), false, "Unable to test subtraction with convolution of image 2");
+        psFree(stamps2);
+        psFree(kernels2);
+        psFree(subMask2);
+        psFree(stamps1);
+        psFree(kernels1);
+        return PM_SUBTRACTION_MODE_ERR;
+    }
+    psFree(subMask2);
+
+
+    pmSubtractionStampList *bestStamps = NULL; // Best choice for stamps
+    pmSubtractionKernels *bestKernels = NULL; // Best choice for kernels
+    psLogMsg("psModules.imcombine", PS_LOG_INFO,
+             "Image 1: %f +/- %f from %d stamps\nImage 2: %f +/- %f from %d stamps\n",
+             kernels1->mean, kernels1->rms, kernels1->numStamps,
+             kernels2->mean, kernels2->rms, kernels2->numStamps);
+
+    if (kernels1->mean < kernels2->mean) {
+        bestStamps = stamps1;
+        bestKernels = kernels1;
+    } else {
+        bestStamps = stamps2;
+        bestKernels = kernels2;
+    }
+
+    psFree(*stamps);
+    psFree(*kernels);
+    *stamps = psMemIncrRefCounter(bestStamps);
+    *kernels = psMemIncrRefCounter(bestKernels);
+
+    psFree(stamps1);
+    psFree(stamps2);
+    psFree(kernels1);
+    psFree(kernels2);
+
+    return bestKernels->mode;
+}
+
+
+bool pmSubtractionParamsScale(int *kernelSize, int *stampSize, psVector *widths,
+                              float fwhm1, float fwhm2, float scaleRef, float scaleMin, float scaleMax)
+{
+    PS_ASSERT_PTR_NON_NULL(kernelSize, false);
+    PS_ASSERT_PTR_NON_NULL(stampSize, false);
+    PS_ASSERT_VECTOR_NON_NULL(widths, false);
+    PS_ASSERT_VECTOR_TYPE(widths, PS_TYPE_F32, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(fwhm1, 0.0, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(fwhm2, 0.0, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(scaleRef, 0.0, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(scaleMin, 0.0, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(scaleMax, 0.0, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(scaleMax, scaleMin, false);
+
+//    float diff = sqrtf(PS_SQR(PS_MAX(fwhm1, fwhm2)) - PS_SQR(PS_MIN(fwhm1, fwhm2))); // Difference
+    float scale = PS_MAX(fwhm1, fwhm2) / scaleRef;      // Scaling factor
+
+    if (isfinite(scaleMin) && scale < scaleMin) {
+        scale = scaleMin;
+    }
+    if (isfinite(scaleMax) && scale > scaleMax) {
+        scale = scaleMax;
+    }
+
+    for (int i = 0; i < widths->n; i++) {
+        widths->data.F32[i] *= scale;
+    }
+    *kernelSize = *kernelSize * scale + 0.5;
+    *stampSize = *stampSize * scale + 0.5;
+
+    psLogMsg("psModules.imcombine", PS_LOG_INFO,
+             "Scaling kernel parameters by %f: %d %d", scale, *kernelSize, *stampSize);
+
+    return true;
+}
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionMatch.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionMatch.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionMatch.h	(revision 27839)
@@ -39,5 +39,9 @@
                         int iter,       ///< Rejection iterations
                         float rej,      ///< Rejection threshold
-                        float sysError, ///< Relative systematic error
+                        float normFrac, ///< Fraction of flux in window for normalisation window
+                        float sysError, ///< Relative systematic error in images
+                        float skyError, ///< Relative systematic error in images
+                        float kernelError, ///< Relative systematic error in kernel
+                        float covarFrac,   ///< Fraction for kernel truncation before covariance calculation
                         psImageMaskType maskVal, ///< Value to mask for input
                         psImageMaskType maskBad, ///< Mask for output bad pixels
@@ -55,5 +59,6 @@
                                psMetadata *analysis, ///< Analysis metadata with pre-calculated kernel, region
                                int stride, ///< Size for convolution patches
-                               float sysError, ///< Relative systematic error
+                               float kernelError, ///< Relative systematic error in kernel
+                               float covarFrac,   ///< Fraction for kernel truncation before covariance calc.
                                psImageMaskType maskVal, ///< Value to mask for input
                                psImageMaskType maskBad, ///< Mask for output bad pixels
@@ -83,4 +88,26 @@
     );
 
+/// Determine best subtraction mode to use
+///
+/// Subtractions are attempted each way, and the mode with the lower residual is taken to be the best
+pmSubtractionMode pmSubtractionBestMode(
+    pmSubtractionStampList **stamps,    ///< Stamps to use for solution
+    pmSubtractionKernels **kernels,     ///< Kernels to use for solution
+    const psImage *subMask,             ///< Subtraction mask
+    float rej                           ///< Rejection threshold for stamps
+    );
+
+
+/// Scale subtraction parameters according to the FWHMs of the inputs
+bool pmSubtractionParamsScale(
+    int *kernelSize,                    ///< Half-size of the kernel
+    int *stampSize,                     ///< Half-size of the stamp (footprint)
+    psVector *widths,                   ///< ISIS widths
+    float fwhm1, float fwhm2,           ///< FWHMs for inputs
+    float scaleRef,                     ///< Reference width for scaling
+    float scaleMin,                     ///< Minimum scaling ratio, or NAN
+    float scaleMax                      ///< Maximum scaling ratio, or NAN
+    );
+
 
 #endif
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionParams.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionParams.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionParams.c	(revision 27839)
@@ -8,4 +8,5 @@
 #include <pslib.h>
 
+#include "pmErrorCodes.h"
 #include "pmSubtractionStamps.h"
 #include "pmSubtraction.h"
@@ -71,5 +72,5 @@
                             double *sumII, // Sum of I(x)^2/sigma(x)^2
                             double *sumIC, // Sum of I(x)conv(x)/sigma(x)^2
-                            const pmSubtractionStamp *stamp, // Stamp with variance
+                            const pmSubtractionStamp *stamp, // Stamp
                             const psKernel *target, // Target stamp
                             int kernelIndex, // Index for kernel component
@@ -78,13 +79,13 @@
     )
 {
-    psKernel *variance = stamp->variance;   // Variance, sigma(x)^2
+    psKernel *weight = stamp->weight;   // Weight image
     psKernel *convolution = selectConvolution(stamp, kernelIndex, mode); // Convolution of interest
 
     for (int y = -footprint; y <= footprint; y++) {
         psF32 *in = &target->kernel[y][-footprint]; // Dereference input
-        psF32 *wt = &variance->kernel[y][-footprint]; // Dereference variance
+        psF32 *wt = &weight->kernel[y][-footprint]; // Dereference weight
         psF32 *conv = &convolution->kernel[y][-footprint]; // Dereference convolution
         for (int x = -footprint; x <= footprint; x++, in++, wt++, conv++) {
-            double temp = *in / *wt; // Temporary product
+            double temp = *in * *wt; // Temporary product
             *sumI += temp;
             *sumII += *in * temp;
@@ -98,5 +99,5 @@
 static void accumulateConvolutions(double *sumC, // Sum of conv(x)/sigma(x)^2
                                    double *sumCC, // Sum of conv(x)^2/sigma(x)^2
-                                   const pmSubtractionStamp *stamp, // Stamp with input and variance
+                                   const pmSubtractionStamp *stamp, // Stamp with input and weight
                                    int kernelIndex, // Index for kernel component
                                    int footprint, // Size of region of interest
@@ -104,12 +105,12 @@
     )
 {
-    psKernel *variance = stamp->variance;   // Variance, sigma(x)^2
+    psKernel *weight = stamp->weight;   // Weight image
     psKernel *convolution = selectConvolution(stamp, kernelIndex, mode); // Convolution of interest
 
     for (int y = -footprint; y <= footprint; y++) {
-        psF32 *wt = &variance->kernel[y][-footprint]; // Dereference variance
+        psF32 *wt = &weight->kernel[y][-footprint]; // Dereference weight
         psF32 *conv = &convolution->kernel[y][-footprint]; // Dereference convolution
         for (int x = -footprint; x <= footprint; x++, wt++, conv++) {
-            double convNoise = *conv / *wt; // Temporary product
+            double convNoise = *conv * *wt; // Temporary product
             *sumC += convNoise;
             *sumCC += *conv * convNoise;
@@ -120,5 +121,5 @@
 
 static double accumulateChi2(const psKernel *target, // Target stamp
-                             pmSubtractionStamp *stamp, // Stamp with variance
+                             pmSubtractionStamp *stamp, // Stamp with weight
                              int kernelIndex, // Index for kernel component
                              double coeff, // Coefficient of convolution
@@ -129,13 +130,13 @@
 {
     double chi2 = 0.0;
-    psKernel *variance = stamp->variance;   // Variance, sigma(x)^2
+    psKernel *weight = stamp->weight;   // Weight image
     psKernel *convolution = selectConvolution(stamp, kernelIndex, mode); // Convolution of interest
 
     for (int y = -footprint; y <= footprint; y++) {
         psF32 *in = &target->kernel[y][-footprint]; // Dereference input
-        psF32 *wt = &variance->kernel[y][-footprint]; // Dereference variance
+        psF32 *wt = &weight->kernel[y][-footprint]; // Dereference weight
         psF32 *conv = &convolution->kernel[y][-footprint]; // Dereference convolution
         for (int x = -footprint; x <= footprint; x++, in++, wt++, conv++) {
-            chi2 += PS_SQR(*in - bg - coeff * *conv) / *wt;
+            chi2 += PS_SQR(*in - bg - coeff * *conv) * *wt;
         }
     }
@@ -146,10 +147,10 @@
 // Return the initial value of chi^2
 static double initialChi2(const psKernel *target, // Target stamp
-                          const pmSubtractionStamp *stamp, // Stamp with variance
+                          const pmSubtractionStamp *stamp, // Stamp
                           int footprint, // Size of convolution
                           pmSubtractionMode mode // Mode of subtraction
     )
 {
-    psKernel *variance = stamp->variance;   // Variance map
+    psKernel *weight = stamp->weight;   // Weight image
     psKernel *source;                   // Source stamp
     switch (mode) {
@@ -167,9 +168,9 @@
     for (int y = -footprint; y <= footprint; y++) {
         psF32 *in = &target->kernel[y][-footprint]; // Dereference input
-        psF32 *wt = &variance->kernel[y][-footprint]; // Dereference variance
+        psF32 *wt = &weight->kernel[y][-footprint]; // Dereference weight
         psF32 *ref = &source->kernel[y][-footprint]; // Derference reference
         for (int x = -footprint; x <= footprint; x++, in++, wt++, ref++) {
             float diff = *in - *ref;    // Temporary value
-            chi2 += PS_SQR(diff) / *wt;
+            chi2 += PS_SQR(diff) * *wt;
         }
     }
@@ -180,5 +181,5 @@
 // Subtract a convolution from the input
 static void subtractConvolution(psKernel *target, // Target stamp
-                                const pmSubtractionStamp *stamp, // Stamp with variance
+                                const pmSubtractionStamp *stamp, // Stamp
                                 int kernelIndex, // Index for kernel component
                                 float coeff, // Coefficient of subtraction
@@ -204,5 +205,6 @@
                                                       int spatialOrder, const psVector *fwhms, int maxOrder,
                                                       const pmSubtractionStampList *stamps, int footprint,
-                                                      float tolerance, float penalty, pmSubtractionMode mode)
+                                                      float tolerance, float penalty, psRegion bounds,
+                                                      pmSubtractionMode mode)
 {
     if (type != PM_SUBTRACTION_KERNEL_ISIS && type != PM_SUBTRACTION_KERNEL_GUNK) {
@@ -232,5 +234,5 @@
     psVectorInit(orders, maxOrder);
     pmSubtractionKernels *kernels = p_pmSubtractionKernelsRawISIS(size, spatialOrder, fwhms, orders,
-                                                                  penalty, mode); // Kernels
+                                                                  penalty, bounds, mode); // Kernels
     psFree(orders);
     psFree(kernels->description);
@@ -280,5 +282,5 @@
         }
         if (!pmSubtractionConvolveStamp(stamp, kernels, footprint)) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to convolve stamp %d.", i);
+            psError(psErrorCodeLast(), false, "Unable to convolve stamp %d.", i);
             psFree(targets);
             psFree(kernels);
@@ -288,13 +290,13 @@
 
         // This sum is invariant to the kernel
-        psKernel *variance = stamp->variance; // Variance map for stamp
+        psKernel *weight = stamp->weight; // Weight image
         for (int v = -footprint; v <= footprint; v++) {
-            psF32 *wt = &variance->kernel[v][-footprint]; // Dereference variance map
+            psF32 *wt = &weight->kernel[v][-footprint]; // Dereference weight
             for (int u = -footprint; u <= footprint; u++, wt++) {
-                sum1 += 1.0 / *wt;
+                sum1 += 1.0 * *wt;
             }
         }
         if (!isfinite(sum1)) {
-            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+            psError(PM_ERR_DATA, true,
                     "Sum of 1/sigma^2 is non-finite for stamp %d (%d,%d)\n",
                     i, (int)stamp->x, (int)stamp->y);
@@ -368,5 +370,5 @@
 
         if (bestIndex == -1) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to find best kernel component in round %d.", iter);
+            psError(PM_ERR_DATA, true, "Unable to find best kernel component in round %d.", iter);
             psFree(targets);
             psFree(sumC);
@@ -407,5 +409,5 @@
 
     if (cutIndex < 0) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to converge to tolerance %g\n", tolerance);
+        psError(PM_ERR_DATA, true, "Unable to converge to tolerance %g\n", tolerance);
         psFree(ranking);
         psFree(kernels);
@@ -482,9 +484,18 @@
     // Maintain photometric scaling
     if (type == PM_SUBTRACTION_KERNEL_ISIS) {
-        psKernel *subtract = kernels->preCalc->data[0]; // Kernel to subtract from the rest
+
+        // XXX in r26035, this code was just wrong.  we had:
+
+        // psKernel *subtract = kernels->preCalc->data[0]
+
+        // but, kernels->preCalc was an array of psArray, not an array of kernels.  It is now
+        // an array of pmSubtractionKernelPreCalc.
+
+        pmSubtractionKernelPreCalc *subtract = kernels->preCalc->data[0]; // Kernel to subtract from the rest
+
         for (int i = 1; i < newSize; i++) {
             if (kernels->u->data.S32[i] % 2 == 0 && kernels->v->data.S32[i] % 2 == 0) {
-                psKernel *kernel = kernels->preCalc->data[i]; // Kernel of interest
-                psBinaryOp(kernel->image, kernel->image, "-", subtract->image);
+                pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[i]; // Kernel of interest
+                psBinaryOp(preCalc->kernel->image, preCalc->kernel->image, "-", subtract->kernel->image);
             }
         }
@@ -495,6 +506,6 @@
         for (int i = 0; i < newSize; i++) {
             if (kernels->u->data.S32[i] % 2 == 0 && kernels->v->data.S32[i] % 2 == 0) {
-                psKernel *kernel = kernels->preCalc->data[i]; // Kernel of interest
-                kernel->kernel[0][0] -= 1.0;
+                pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[i]; // Kernel of interest
+                preCalc->kernel->kernel[0][0] -= 1.0;
             }
         }
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionParams.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionParams.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionParams.h	(revision 27839)
@@ -17,5 +17,6 @@
                                                       float tolerance, ///< Maximum difference in chi^2
                                                       float penalty, ///< Penalty for wideness
-                                                      pmSubtractionMode mode // Mode for subtraction
+                                                      psRegion bounds,       ///< Bounds of validity
+                                                      pmSubtractionMode mode ///< Mode for subtraction
     );
 
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionStamps.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionStamps.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionStamps.c	(revision 27839)
@@ -46,4 +46,5 @@
     psFree(list->y);
     psFree(list->flux);
+    psFree(list->window);
 }
 
@@ -54,14 +55,9 @@
     psFree(stamp->image1);
     psFree(stamp->image2);
-    psFree(stamp->variance);
+    psFree(stamp->weight);
     psFree(stamp->convolutions1);
     psFree(stamp->convolutions2);
-
-    psFree(stamp->matrix1);
-    psFree(stamp->matrix2);
-    psFree(stamp->matrixX);
-    psFree(stamp->vector1);
-    psFree(stamp->vector2);
-
+    psFree(stamp->matrix);
+    psFree(stamp->vector);
 }
 
@@ -80,5 +76,5 @@
 
 // Search a region for a suitable stamp
-bool stampSearch(int *xStamp, int *yStamp, // Coordinates of stamp, to return
+bool stampSearch(float *xStamp, float *yStamp, // Coordinates of stamp, to return
                  float *fluxStamp, // Flux of stamp, to return
                  const psImage *image1, const psImage *image2, // Images to search
@@ -93,4 +89,6 @@
     *fluxStamp = -INFINITY;             // Flux of best stamp
 
+    // fprintf (stderr, "xMin, xMax: %d, %d -> ", xMin, xMax);
+
     // Ensure we're not going to go outside the bounds of the image
     xMin = PS_MAX(border, xMin);
@@ -99,4 +97,10 @@
     yMax = PS_MIN(numRows - border - 1, yMax);
 
+    if (xMax < xMin) return false;
+    if (yMax < yMin) return false;
+
+    psAssert (xMin <= xMax, "x mismatch?");
+    psAssert (yMin <= yMax, "y mismatch?");
+
     for (int y = yMin; y <= yMax; y++) {
         for (int x = xMin; x <= xMax; x++) {
@@ -115,6 +119,6 @@
                 ((image1) ? image1->data.F32[y][x] : image2->data.F32[y][x]); // Flux at pixel
             if (flux > *fluxStamp) {
-                *xStamp = x;
-                *yStamp = y;
+                *xStamp = x + 0.5;
+                *yStamp = y + 0.5;
                 *fluxStamp = flux;
                 found = true;
@@ -180,5 +184,6 @@
 
 pmSubtractionStampList *pmSubtractionStampListAlloc(int numCols, int numRows, const psRegion *region,
-                                                    int footprint, float spacing)
+                                                    int footprint, float spacing, float normFrac,
+                                                    float sysErr, float skyErr)
 {
     pmSubtractionStampList *list = psAlloc(sizeof(pmSubtractionStampList)); // Stamp list to return
@@ -220,7 +225,77 @@
     list->y = NULL;
     list->flux = NULL;
+    list->window = NULL;
+    list->normFrac = normFrac;
+    list->normWindow = 0;
     list->footprint = footprint;
+    list->sysErr = sysErr;
+    list->skyErr = skyErr;
 
     return list;
+}
+
+pmSubtractionStampList *pmSubtractionStampListCopy(const pmSubtractionStampList *in)
+{
+    PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(in, NULL);
+
+    pmSubtractionStampList *out = psAlloc(sizeof(pmSubtractionStampList)); // Copied stamp list to return
+    psMemSetDeallocator(out, (psFreeFunc)subtractionStampListFree);
+
+    int num = out->num = in->num;       // Number of stamps
+    out->stamps = psArrayAlloc(num);
+    out->regions = psArrayAlloc(num);
+    out->x = NULL;
+    out->y = NULL;
+    out->flux = NULL;
+    out->window = psMemIncrRefCounter(in->window);
+    out->footprint = in->footprint;
+    out->normWindow = in->normWindow;
+
+    for (int i = 0; i < num; i++) {
+        psRegion *inRegion = in->regions->data[i]; // Input region
+        out->regions->data[i] = psRegionAlloc(inRegion->x0, inRegion->x1, inRegion->y0, inRegion->y1);
+
+        pmSubtractionStamp *inStamp = in->stamps->data[i]; // Input stamp
+        pmSubtractionStamp *outStamp = psAlloc(sizeof(pmSubtractionStamp));
+        psMemSetDeallocator(outStamp, (psFreeFunc)subtractionStampFree);
+        outStamp->x = inStamp->x;
+        outStamp->y = inStamp->y;
+        outStamp->flux = inStamp->flux;
+        outStamp->xNorm = inStamp->xNorm;
+        outStamp->yNorm = inStamp->yNorm;
+        outStamp->status = inStamp->status;
+
+        outStamp->image1 = inStamp->image1 ? psKernelCopy(inStamp->image1) : NULL;
+        outStamp->image2 = inStamp->image2 ? psKernelCopy(inStamp->image2) : NULL;
+        outStamp->weight = inStamp->weight ? psKernelCopy(inStamp->weight) : NULL;
+
+        if (inStamp->convolutions1) {
+            int size = inStamp->convolutions1->n; // Size of array
+            outStamp->convolutions1 = psArrayAlloc(size);
+            for (int j = 0; j < size; j++) {
+                psKernel *conv = inStamp->convolutions1->data[j]; // Convolution
+                outStamp->convolutions1->data[j] = conv ? psKernelCopy(conv) : NULL;
+            }
+        } else {
+            outStamp->convolutions1 = NULL;
+        }
+        if (inStamp->convolutions2) {
+            int size = inStamp->convolutions2->n; // Size of array
+            outStamp->convolutions2 = psArrayAlloc(size);
+            for (int j = 0; j < size; j++) {
+                psKernel *conv = inStamp->convolutions2->data[j]; // Convolution
+                outStamp->convolutions2->data[j] = conv ? psKernelCopy(conv) : NULL;
+            }
+        } else {
+            outStamp->convolutions2 = NULL;
+        }
+
+        outStamp->matrix = inStamp->matrix ? psImageCopy(NULL, inStamp->matrix, PS_TYPE_F64) : NULL;
+        outStamp->vector = inStamp->vector ? psVectorCopy(NULL, inStamp->vector, PS_TYPE_F64) : NULL;
+
+        out->stamps->data[i] = outStamp;
+    }
+
+    return out;
 }
 
@@ -239,13 +314,11 @@
     stamp->image1 = NULL;
     stamp->image2 = NULL;
-    stamp->variance = NULL;
+    stamp->weight = NULL;
     stamp->convolutions1 = NULL;
     stamp->convolutions2 = NULL;
 
-    stamp->matrix1 = NULL;
-    stamp->matrix2 = NULL;
-    stamp->matrixX = NULL;
-    stamp->vector1 = NULL;
-    stamp->vector2 = NULL;
+    stamp->matrix = NULL;
+    stamp->vector = NULL;
+    stamp->norm = NAN;
 
     return stamp;
@@ -256,6 +329,6 @@
                                                 const psImage *image2, const psImage *subMask,
                                                 const psRegion *region, float thresh1, float thresh2,
-                                                int size, int footprint, float spacing,
-                                                pmSubtractionMode mode)
+                                                int size, int footprint, float spacing, float normFrac,
+                                                float sysErr, float skyErr, pmSubtractionMode mode)
 {
     if (!image1 && !image2) {
@@ -296,5 +369,5 @@
         if (psRegionIsNaN(*region)) {
             psString string = psRegionToString(*region);
-            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Input region (%s) contains NAN values", string);
+            psError(PM_ERR_PROG, true, "Input region (%s) contains NAN values", string);
             psFree(string);
             return false;
@@ -303,5 +376,5 @@
             region->y0 < 0 || region->y1 > numRows) {
             psString string = psRegionToString(*region);
-            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Input region (%s) does not fit in image (%dx%d)",
+            psError(PM_ERR_PROG, true, "Input region (%s) does not fit in image (%dx%d)",
                     string, numCols, numRows);
             psFree(string);
@@ -313,5 +386,6 @@
 
     if (!stamps) {
-        stamps = pmSubtractionStampListAlloc(numCols, numRows, region, footprint, spacing);
+        stamps = pmSubtractionStampListAlloc(numCols, numRows, region, footprint, spacing,
+                                             normFrac, sysErr, skyErr);
     }
 
@@ -335,5 +409,5 @@
             numSearch++;
 
-            int xStamp = 0, yStamp = 0; // Coordinates of stamp
+            float xStamp = 0.0, yStamp = 0.0; // Coordinates of stamp
             float fluxStamp = -INFINITY; // Flux of stamp
             bool goodStamp = false;     // Found a good stamp?
@@ -347,6 +421,4 @@
                 // Take stamps off the top of the (sorted) list
                 for (int j = xList->n - 1; j >= 0 && !goodStamp; j--) {
-                    int xCentre = xList->data.F32[j] + 0.5, yCentre = yList->data.F32[j] + 0.5;// Stamp centre
-
                     // Chop off the top of the list
                     xList->n = j;
@@ -354,9 +426,12 @@
                     fluxList->n = j;
 
+#if 0
                     // Fish around a bit to see if we can find a pixel that isn't masked
+                    // This is not a good idea if we're using the window feature
                     psTrace("psModules.imcombine", 7, "Searching for stamp %d around %d,%d\n",
                             i, xCentre, yCentre);
 
                     // Search bounds
+                    int xCentre = xList->data.F32[j] - 0.5, yCentre = yList->data.F32[j] - 0.5;// Stamp centre
                     int search = footprint - size; // Search radius
                     int xMin = PS_MAX(border, xCentre - search);
@@ -367,4 +442,16 @@
                     goodStamp = stampSearch(&xStamp, &yStamp, &fluxStamp, image1, image2, thresh1, thresh2,
                                             subMask, xMin, xMax, yMin, yMax, numCols, numRows, border);
+                    // fprintf (stderr, "find: %d %d ==> %5.1f %5.1f (\n", xCentre, yCentre, xStamp, yStamp);
+#else
+                    // Only search the exact centre pixel
+                    goodStamp = stampSearch(&xStamp, &yStamp, &fluxStamp, image1, image2, thresh1, thresh2,
+                                            subMask, xList->data.F32[j], xList->data.F32[j],
+                                            yList->data.F32[j], yList->data.F32[j], numCols, numRows, border);
+#endif
+                    if (0 && goodStamp) {
+                        fprintf (stderr, "find: %6.1f < %6.1f < %6.1f, %6.1f < %6.1f %6.1f\n",
+                                 region->x0 + size, xStamp, region->x1 - size,
+                                 region->y0 + size, yStamp, region->y1 - size);
+                    }
                 }
             } else {
@@ -386,8 +473,8 @@
                 psFree(stamp->image1);
                 psFree(stamp->image2);
-                psFree(stamp->variance);
+                psFree(stamp->weight);
                 psFree(stamp->convolutions1);
                 psFree(stamp->convolutions2);
-                stamp->image1 = stamp->image2 = stamp->variance = NULL;
+                stamp->image1 = stamp->image2 = stamp->weight = NULL;
                 stamp->convolutions1 = stamp->convolutions2 = NULL;
 
@@ -421,5 +508,6 @@
                                                const psImage *image, const psImage *subMask,
                                                const psRegion *region, int size, int footprint,
-                                               float spacing, pmSubtractionMode mode)
+                                               float spacing, float normFrac, float sysErr, float skyErr,
+                                               pmSubtractionMode mode)
 
 {
@@ -441,5 +529,6 @@
     int numStars = x->n;                // Number of stars
     pmSubtractionStampList *stamps = pmSubtractionStampListAlloc(subMask->numCols, subMask->numRows,
-                                                                 region, footprint, spacing); // Stamp list
+                                                                 region, footprint, spacing,
+                                                                 normFrac, sysErr, skyErr); // Stamp list
     int numStamps = stamps->num;        // Number of stamps
 
@@ -448,4 +537,5 @@
     psStringAppend(&ds9name, "stamps_all_%d.ds9", ds9num);
     FILE *ds9 = pmSubtractionStampsFile(stamps, ds9name, "all stamps"); // ds9 region file
+    psFree(ds9name);
     ds9num++;
 
@@ -463,5 +553,5 @@
     for (int i = 0; i < numStars; i++) {
         float xStamp = x->data.F32[i], yStamp = y->data.F32[i]; // Coordinates of stamp
-        int xPix = xStamp + 0.5, yPix = yStamp + 0.5; // Pixel coordinate of stamp
+        int xPix = xStamp - 0.5, yPix = yStamp - 0.5; // Pixel coordinate of stamp
         if (!checkStampRegion(xPix, yPix, region)) {
             // It's not in the big region
@@ -471,4 +561,6 @@
             continue;
         }
+
+        // fprintf (stderr, "stamp: %5.1f %5.1f == %d %d\n", xStamp, yStamp, xPix, yPix);
 
         bool found = false;
@@ -539,6 +631,128 @@
 
 
+bool pmSubtractionStampsGetWindow(pmSubtractionStampList *stamps, int kernelSize)
+{
+    PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, false);
+    PS_ASSERT_INT_NONNEGATIVE(kernelSize, false);
+
+    int size = stamps->footprint; // Size of postage stamps
+
+    psFree (stamps->window);
+    stamps->window = psKernelAlloc(-size, size, -size, size);
+    psImageInit(stamps->window->image, 0.0);
+
+    // generate normalizations for each stamp
+    psVector *norm1 = psVectorAlloc(stamps->num, PS_TYPE_F32);
+    psVector *norm2 = psVectorAlloc(stamps->num, PS_TYPE_F32);
+    for (int i = 0; i < stamps->num; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        if (!stamp) continue;
+        if (!stamp->image1) continue;
+        if (!stamp->image2) continue;
+
+        float sum1 = 0.0;
+        float sum2 = 0.0;
+        for (int y = -size; y <= size; y++) {
+            for (int x = -size; x <= size; x++) {
+                sum1 += stamp->image1->kernel[y][x];
+                sum2 += stamp->image2->kernel[y][x];
+            }
+        }
+        norm1->data.F32[i] = sum1;
+        norm2->data.F32[i] = sum2;
+    }
+
+    // storage vector for flux data
+    psStats *stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    psVector *flux1 = psVectorAllocEmpty(2*stamps->num, PS_TYPE_F32);
+    psVector *flux2 = psVectorAllocEmpty(2*stamps->num, PS_TYPE_F32);
+
+    // generate the window pixels
+    double sum = 0.0;                   // Sum inside the window
+    float maxValue = 0.0;               // Maximum value, for normalisation
+    for (int y = -size; y <= size; y++) {
+        for (int x = -size; x <= size; x++) {
+
+            flux1->n = 0;
+            flux2->n = 0;
+            for (int i = 0; i < stamps->num; i++) {
+                pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+                if (!stamp) continue;
+                if (!stamp->image1) continue;
+                if (!stamp->image2) continue;
+
+                psVectorAppend(flux1, stamp->image1->kernel[y][x] / norm1->data.F32[i]);
+                psVectorAppend(flux2, stamp->image2->kernel[y][x] / norm2->data.F32[i]);
+            }
+
+            psStatsInit (stats);
+            if (!psVectorStats (stats, flux1, NULL, NULL, 0)) {
+                psAbort ("failed to generate stats");
+            }
+            float f1 = stats->robustMedian;
+            psStatsInit (stats);
+            if (!psVectorStats (stats, flux2, NULL, NULL, 0)) {
+                psAbort ("failed to generate stats");
+            }
+            float f2 = stats->robustMedian;
+
+            stamps->window->kernel[y][x] = f1 + f2;
+            if (PS_SQR(x) + PS_SQR(y) <= PS_SQR(size)) {
+                sum += stamps->window->kernel[y][x];
+            }
+            maxValue = PS_MAX(maxValue, stamps->window->kernel[y][x]);
+        }
+    }
+
+    psTrace("psModules.imcombine", 3, "Window total: %f, threshold: %f\n",
+            sum, (1.0 - stamps->normFrac) * sum);
+    bool done = false;
+    for (int radius = 1; radius <= size && !done; radius++) {
+        double within = 0.0;
+        for (int y = -radius; y <= radius; y++) {
+            for (int x = -radius; x <= radius; x++) {
+                if (PS_SQR(x) + PS_SQR(y) <= PS_SQR(radius)) {
+                    within += stamps->window->kernel[y][x];
+                }
+            }
+        }
+        psTrace("psModules.imcombine", 5, "Radius %d: %f\n", radius, within);
+        if (within > (1.0 - stamps->normFrac) * sum) {
+            stamps->normWindow = radius;
+            done = true;
+        }
+    }
+
+    psTrace("psModules.imcombine", 3, "Normalisation window radius set to %d\n", stamps->normWindow);
+    if (stamps->normWindow == 0 || stamps->normWindow >= size) {
+        psError(PM_ERR_STAMPS, true, "Unable to determine normalisation window size.");
+        return false;
+    }
+
+    // re-normalize so chisquare values are sensible
+    for (int y = -size; y <= size; y++) {
+        for (int x = -size; x <= size; x++) {
+            stamps->window->kernel[y][x] /= maxValue;
+        }
+    }
+
+#if 0
+    {
+        psFits *fits = psFitsOpen ("window.fits", "w");
+        psFitsWriteImage (fits, NULL, stamps->window->image, 0, NULL);
+        psFitsClose (fits);
+    }
+#endif
+
+    psFree (stats);
+    psFree (flux1);
+    psFree(flux2);
+    psFree (norm1);
+    psFree (norm2);
+    return true;
+}
+
 bool pmSubtractionStampsExtract(pmSubtractionStampList *stamps, psImage *image1, psImage *image2,
-                                psImage *variance, int kernelSize)
+                                psImage *variance, int kernelSize, psRegion bounds)
 {
     PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, false);
@@ -550,10 +764,11 @@
         PS_ASSERT_IMAGE_TYPE(image2, PS_TYPE_F32, false);
     }
-    PS_ASSERT_IMAGE_NON_NULL(variance, false);
-    PS_ASSERT_IMAGES_SIZE_EQUAL(variance, image1, false);
-    PS_ASSERT_IMAGE_TYPE(variance, PS_TYPE_F32, false);
-    PS_ASSERT_INT_NONNEGATIVE(kernelSize, false);
-
-    int numCols = image1->numCols, numRows = image1->numRows; // Size of images
+    if (variance) {
+        PS_ASSERT_IMAGE_NON_NULL(variance, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(variance, image1, false);
+        PS_ASSERT_IMAGE_TYPE(variance, PS_TYPE_F32, false);
+        PS_ASSERT_INT_NONNEGATIVE(kernelSize, false);
+    }
+
     int size = kernelSize + stamps->footprint; // Size of postage stamps
 
@@ -564,14 +779,12 @@
         }
 
-        if (isnan(stamp->xNorm)) {
-            stamp->xNorm = 2.0 * (stamp->x - (float)numCols/2.0) / (float)numCols;
-        }
-        if (isnan(stamp->yNorm)) {
-            stamp->yNorm = 2.0 * (stamp->y - (float)numRows/2.0) / (float)numRows;
-        }
-
-        int x = stamp->x + 0.5, y = stamp->y + 0.5; // Stamp coordinates
-        if (x < size || x > numCols - size || y < size || y > numRows - size) {
-            psError(PS_ERR_UNKNOWN, false, "Stamp %d (%d,%d) is within the image border.\n", i, x, y);
+        p_pmSubtractionPolynomialNormCoords(&stamp->xNorm, &stamp->yNorm, stamp->x, stamp->y,
+                                            bounds.x0, bounds.x1, bounds.y0, bounds.y1);
+
+        int x = stamp->x - 0.5, y = stamp->y - 0.5; // Stamp coordinates
+        // fprintf (stderr, "stamp: %5.1f %5.1f == %d %d (size: %d)\n", stamp->x, stamp->y, x, y, size);
+
+        if (x < bounds.x0 + size || x > bounds.x1 - size || y < bounds.y0 + size || y > bounds.y1 - size) {
+            psError(PM_ERR_PROG, false, "Stamp %d (%d,%d) is within the region border.\n", i, x, y);
             return false;
         }
@@ -580,5 +793,5 @@
         assert(stamp->image1 == NULL);
         assert(stamp->image2 == NULL);
-        assert(stamp->variance == NULL);
+        assert(stamp->weight == NULL);
 
         psRegion region = psRegionSet(x - size, x + size + 1, y - size, y + size + 1); // Region of interest
@@ -594,7 +807,32 @@
         }
 
-        psImage *wtSub = psImageSubset(variance, region); // Subimage with stamp
-        stamp->variance = psKernelAllocFromImage(wtSub, size, size);
-        psFree(wtSub);                  // Drop reference
+        psKernel *weight = stamp->weight = psKernelAlloc(-size, size, -size, size); // Weight = 1/variance
+        if (variance) {
+            psImage *varSub = psImageSubset(variance, region); // Subimage with stamp
+            psKernel *var = psKernelAllocFromImage(varSub, size, size); // Variance postage stamp
+
+            if ((isfinite(stamps->skyErr) && (stamps->skyErr > 0)) ||
+                (isfinite(stamps->sysErr) && (stamps->sysErr > 0))) {
+                float sysErr = 0.25 * PS_SQR(stamps->sysErr); // Systematic error
+                float skyErr = stamps->skyErr;
+                psKernel *image1 = stamp->image1, *image2 = stamp->image2; // Input images
+                for (int y = -size; y <= size; y++) {
+                    for (int x = -size; x <= size; x++) {
+                        float additional = image1->kernel[y][x] + image2->kernel[y][x];
+                        weight->kernel[y][x] = 1.0 / (skyErr + var->kernel[y][x] + sysErr * PS_SQR(additional));
+                    }
+                }
+            } else {
+                for (int y = -size; y <= size; y++) {
+                    for (int x = -size; x <= size; x++) {
+                        weight->kernel[y][x] = 1.0 / var->kernel[y][x];
+                    }
+                }
+            }
+            psFree(var);
+            psFree(varSub);
+        } else {
+            psImageInit(weight->image, 1.0);
+        }
 
         stamp->status = PM_SUBTRACTION_STAMP_CALCULATE;
@@ -607,4 +845,5 @@
                                                           const psImage *subMask, const psRegion *region,
                                                           int size, int footprint, float spacing,
+                                                          float normFrac, float sysErr, float skyErr,
                                                           pmSubtractionMode mode)
 {
@@ -630,4 +869,5 @@
             y->data.F32[numOut] = source->peak->yf;
         }
+        // fprintf (stderr, "stamp: %5.1f %5.1f\n", x->data.F32[numOut], y->data.F32[numOut]);
         numOut++;
     }
@@ -636,10 +876,11 @@
 
     pmSubtractionStampList *stamps = pmSubtractionStampsSet(x, y, image, subMask, region, size,
-                                                            footprint, spacing, mode); // Stamps to return
+                                                            footprint, spacing, normFrac,
+                                                            sysErr, skyErr, mode); // Stamps
     psFree(x);
     psFree(y);
 
     if (!stamps) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to set stamps from sources.");
+        psError(psErrorCodeLast(), false, "Unable to set stamps from sources.");
     }
 
@@ -650,6 +891,6 @@
 pmSubtractionStampList *pmSubtractionStampsSetFromFile(const char *filename, const psImage *image,
                                                        const psImage *subMask, const psRegion *region,
-                                                       int size, int footprint, float spacing,
-                                                       pmSubtractionMode mode)
+                                                       int size, int footprint, float spacing, float normFrac,
+                                                       float sysErr, float skyErr, pmSubtractionMode mode)
 {
     PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
@@ -658,5 +899,5 @@
     psArray *data = psVectorsReadFromFile(filename, "%f %f");
     if (!data) {
-        psError(PS_ERR_IO, false, "Unable to read stamps file %s", filename);
+        psError(psErrorCodeLast(), false, "Unable to read stamps file %s", filename);
         return NULL;
     }
@@ -668,5 +909,5 @@
 
     pmSubtractionStampList *stamps = pmSubtractionStampsSet(x, y, image, subMask, region, size, footprint,
-                                                            spacing, mode);
+                                                            spacing, normFrac, sysErr, skyErr, mode);
     psFree(data);
 
@@ -674,2 +915,15 @@
 
 }
+
+
+bool pmSubtractionStampsResetStatus (pmSubtractionStampList *stamps) {
+
+    for (int i = 0; i < stamps->num; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        if (!stamp) continue;
+        if (stamp->status != PM_SUBTRACTION_STAMP_USED) continue;
+        stamp->status = PM_SUBTRACTION_STAMP_CALCULATE;
+    }
+    return true;
+}
+
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionStamps.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionStamps.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionStamps.h	(revision 27839)
@@ -24,12 +24,21 @@
     psArray *flux;                      ///< Fluxes for possible stamps (or NULL)
     int footprint;                      ///< Half-size of stamps
+    psKernel *window;                   ///< window function generated from ensemble of stamps
+    float normFrac;                     ///< Fraction of flux in window for normalisation window
+    int normWindow;                     ///< Size of window for measuring normalisation
+    float sysErr;                       ///< Systematic error
+    float skyErr;                       ///< increase effective readnoise
 } pmSubtractionStampList;
 
 /// Allocate a list of stamps
-pmSubtractionStampList *pmSubtractionStampListAlloc(int numCols, // Number of columns in image
-                                                    int numRows, // Number of rows in image
-                                                    const psRegion *region, // Region for stamps, or NULL
-                                                    int footprint, // Half-size of stamps
-                                                    float spacing // Rough average spacing between stamps
+pmSubtractionStampList *pmSubtractionStampListAlloc(
+    int numCols, // Number of columns in image
+    int numRows, // Number of rows in image
+    const psRegion *region, // Region for stamps, or NULL
+    int footprint, // Half-size of stamps
+    float spacing, // Rough average spacing between stamps
+    float normFrac, // Fraction of flux in window for normalisation window
+    float sysErr,  // Relative systematic error or NAN
+    float skyErr  // Relative systematic error or NAN
     );
 
@@ -53,4 +62,12 @@
 }
 
+/// Copy a list of stamps
+///
+/// A deep copy is performed of the stamp list and the component stamps
+pmSubtractionStampList *pmSubtractionStampListCopy(
+    const pmSubtractionStampList *in    // Stamp list to copy
+    );
+
+
 /// A stamp for image subtraction
 typedef struct {
@@ -60,10 +77,10 @@
     psKernel *image1;                   ///< Reference image postage stamp
     psKernel *image2;                   ///< Input image postage stamp
-    psKernel *variance;                 ///< Variance image postage stamp, or NULL
+    psKernel *weight;                   ///< Weight image (1/variance) postage stamp, or NULL
     psArray *convolutions1;             ///< Convolutions of image 1 for each kernel component, or NULL
     psArray *convolutions2;             ///< Convolutions of image 2 for each kernel component, or NULL
-    psImage *matrix1, *matrix2;         ///< Least-squares matrices for each image, or NULL
-    psImage *matrixX;                   ///< Cross-matrix (for mode DUAL), or NULL
-    psVector *vector1, *vector2;        ///< Least-squares vectors for each image, or NULL
+    psImage *matrix;                    ///< Least-squares matrix, or NULL
+    psVector *vector;                   ///< Least-squares vector, or NULL
+    double norm;                        ///< Normalisation difference
     pmSubtractionStampStatus status;    ///< Status of stamp
 } pmSubtractionStamp;
@@ -73,27 +90,35 @@
 
 /// Find stamps on an image
-pmSubtractionStampList *pmSubtractionStampsFind(pmSubtractionStampList *stamps, ///< Output stamps, or NULL
-                                                const psImage *image1, ///< Image for which to find stamps
-                                                const psImage *image2, ///< Image for which to find stamps
-                                                const psImage *mask, ///< Mask, or NULL
-                                                const psRegion *region, ///< Region to search, or NULL
-                                                float thresh1, ///< Threshold for stamps in image 1
-                                                float thresh2, ///< Threshold for stamps in image 2
-                                                int size, ///< Kernel half-size
-                                                int footprint, ///< Half-size for stamps
-                                                float spacing, ///< Rough spacing for stamps
-                                                pmSubtractionMode mode ///< Mode for subtraction
+pmSubtractionStampList *pmSubtractionStampsFind(
+    pmSubtractionStampList *stamps, ///< Output stamps, or NULL
+    const psImage *image1, ///< Image for which to find stamps
+    const psImage *image2, ///< Image for which to find stamps
+    const psImage *mask, ///< Mask, or NULL
+    const psRegion *region, ///< Region to search, or NULL
+    float thresh1, ///< Threshold for stamps in image 1
+    float thresh2, ///< Threshold for stamps in image 2
+    int size, ///< Kernel half-size
+    int footprint, ///< Half-size for stamps
+    float spacing, ///< Rough spacing for stamps
+    float normFrac, // Fraction of flux in window for normalisation window
+    float sysErr,  ///< Relative systematic error in images
+    float skyErr,  ///< Relative systematic error in images
+    pmSubtractionMode mode ///< Mode for subtraction
     );
 
 /// Set stamps based on a list of x,y
-pmSubtractionStampList *pmSubtractionStampsSet(const psVector *x, ///< x coordinates for each stamp
-                                               const psVector *y, ///< y coordinates for each stamp
-                                               const psImage *image, ///< Image for flux of stamp
-                                               const psImage *mask, ///< Mask, or NULL
-                                               const psRegion *region, ///< Region to search, or NULL
-                                               int size, ///< Kernel half-size
-                                               int footprint, ///< Half-size for stamps
-                                               float spacing, ///< Rough spacing for stamps
-                                               pmSubtractionMode mode ///< Mode for subtraction
+pmSubtractionStampList *pmSubtractionStampsSet(
+    const psVector *x, ///< x coordinates for each stamp
+    const psVector *y, ///< y coordinates for each stamp
+    const psImage *image, ///< Image for flux of stamp
+    const psImage *mask, ///< Mask, or NULL
+    const psRegion *region, ///< Region to search, or NULL
+    int size, ///< Kernel half-size
+    int footprint, ///< Half-size for stamps
+    float spacing, ///< Rough spacing for stamps
+    float normFrac, // Fraction of flux in window for normalisation window
+    float sysErr,  ///< Systematic error in images
+    float skyErr,  ///< Systematic error in images
+    pmSubtractionMode mode ///< Mode for subtraction
     );
 
@@ -107,4 +132,7 @@
     int footprint,                      ///< Half-size for stamps
     float spacing,                      ///< Rough spacing for stamps
+    float normFrac, // Fraction of flux in window for normalisation window
+    float sysErr,                       ///< Systematic error in images
+    float skyErr,                       ///< Systematic error in images
     pmSubtractionMode mode              ///< Mode for subtraction
     );
@@ -119,5 +147,14 @@
     int footprint,                      ///< Half-size for stamps
     float spacing,                      ///< Rough spacing for stamps
+    float normFrac, // Fraction of flux in window for normalisation window
+    float sysErr,                       ///< Systematic error in images
+    float skyErr,                       ///< Systematic error in images
     pmSubtractionMode mode              ///< Mode for subtraction
+    );
+
+/// Calculate the window and normalisation window from the stamps
+bool pmSubtractionStampsGetWindow(
+    pmSubtractionStampList *stamps,     ///< List of stamps
+    int kernelSize                      ///< Half-size of kernel
     );
 
@@ -127,5 +164,6 @@
                                 psImage *image2, ///< Input image (or NULL)
                                 psImage *variance, ///< Variance map
-                                int kernelSize ///< Kernel half-size
+                                int kernelSize, ///< Kernel half-size
+                                psRegion bounds ///< Bounds of validity
     );
 
@@ -154,3 +192,6 @@
     );
 
+
+bool pmSubtractionStampsResetStatus (pmSubtractionStampList *stamps);
+
 #endif
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionThreads.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionThreads.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionThreads.c	(revision 27839)
@@ -17,38 +17,5 @@
 }
 
-// Initialise a mutex in each of the input
-static void subtractionMutexInit(pmReadout *ro)
-{
-    if (!ro) {
-        return;
-    }
-
-    // XXX if (ro->image) {
-    // XXX     psMutexInit(ro->image);
-    // XXX }
-    // XXX if (ro->variance) {
-    // XXX     psMutexInit(ro->variance);
-    // XXX }
-
-    return;
-}
-
-static void subtractionMutexDestroy(pmReadout *ro)
-{
-    if (!ro) {
-        return;
-    }
-
-    // XXX if (ro->image) {
-    // XXX     psMutexDestroy(ro->image);
-    // XXX }
-    // XXX if (ro->variance) {
-    // XXX     psMutexDestroy(ro->variance);
-    // XXX }
-
-    return;
-}
-
-void pmSubtractionThreadsInit(pmReadout *in1, pmReadout *in2)
+void pmSubtractionThreadsInit(void)
 {
     if (threaded) {
@@ -57,7 +24,4 @@
 
     threaded = true;
-
-    subtractionMutexInit(in1);
-    subtractionMutexInit(in2);
 
     {
@@ -69,5 +33,5 @@
 
     {
-        psThreadTask *task = psThreadTaskAlloc("PSMODULES_SUBTRACTION_CALCULATE_EQUATION", 3);
+        psThreadTask *task = psThreadTaskAlloc("PSMODULES_SUBTRACTION_CALCULATE_EQUATION", 4);
         task->function = &pmSubtractionCalculateEquationThread;
         psThreadTaskAdd(task);
@@ -86,5 +50,5 @@
 
 
-void pmSubtractionThreadsFinalize(pmReadout *in1, pmReadout *in2)
+void pmSubtractionThreadsFinalize(void)
 {
     if (!threaded) {
@@ -97,6 +61,4 @@
     psThreadTaskRemove("PSMODULES_SUBTRACTION_CONVOLVE");
 
-    subtractionMutexDestroy(in1);
-    subtractionMutexDestroy(in1);
     return;
 }
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionThreads.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionThreads.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionThreads.h	(revision 27839)
@@ -7,13 +7,12 @@
 /// Set up threading for image matching
 ///
-/// Sets up thread tasks, and initialises mutexes in readouts
-void pmSubtractionThreadsInit(pmReadout *in1, pmReadout *in2 // Input readouts
-    );
+/// Sets up thread tasks
+void pmSubtractionThreadsInit(void);
 
 
 /// Take down threading for image matching
 ///
-/// Destroys thread tasks, and initialises mutexes in readouts
-void pmSubtractionThreadsFinalize(pmReadout *in1, pmReadout *in2);
+/// Destroys thread tasks
+void pmSubtractionThreadsFinalize(void);
 
 #endif
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionVisual.c
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionVisual.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionVisual.c	(revision 27839)
@@ -16,5 +16,7 @@
 
 #include "pmKapaPlots.h"
+#include "pmSubtraction.h"
 #include "pmSubtractionStamps.h"
+#include "pmSubtractionEquation.h"
 
 #include "pmVisual.h"
@@ -32,7 +34,9 @@
 static bool plotLeastSquares     = true;
 static bool plotImage            = true;
+
 // variables to store plotting window indices
-static int kapa  = -1;
+static int kapa1 = -1;
 static int kapa2 = -1;
+static int kapa3 = -1;
 
 /** function prototypes*/
@@ -46,8 +50,6 @@
 bool pmSubtractionVisualClose(void)
 {
-    if(kapa != -1)
-        KiiClose(kapa);
-    if(kapa2 != -1)
-        KiiClose(kapa2);
+    if(kapa1 != -1) KiiClose(kapa1);
+    if(kapa2 != -1) KiiClose(kapa2);
     return true;
 }
@@ -60,8 +62,8 @@
 bool pmSubtractionVisualPlotConvKernels(psImage *convKernels) {
     if (!pmVisualIsVisual() || !plotConvKernels) return true;
-    if (!pmVisualInitWindow(&kapa, "ppSub:Images")) {
-        return false;
-    }
-    pmVisualScaleImage(kapa, convKernels, "Convolution_Kernels", 0, true);
+    if (!pmVisualInitWindow(&kapa1, "ppSub:Images")) {
+        return false;
+    }
+    pmVisualScaleImage(kapa1, convKernels, "Convolution_Kernels", 0, true);
     pmVisualAskUser(&plotConvKernels);
     return true;
@@ -74,5 +76,5 @@
 bool pmSubtractionVisualPlotStamps(pmSubtractionStampList *stamps, pmReadout *ro) {
     if (!pmVisualIsVisual() || !plotStamps) return true;
-    if (!pmVisualInitWindow (&kapa, "ppSub:Images")) {
+    if (!pmVisualInitWindow (&kapa1, "ppSub:Images")) {
         return false;
     }
@@ -134,5 +136,5 @@
         stampNum++;
     }
-    pmVisualScaleImage(kapa, canvas, "Subtraction_Stamps", 0, true);
+    pmVisualScaleImage(kapa1, canvas, "Subtraction_Stamps", 0, true);
 
     pmVisualAskUser(&plotStamps);
@@ -144,5 +146,5 @@
 
     if (!pmVisualIsVisual() || !plotLeastSquares) return true;
-    if (!pmVisualInitWindow (&kapa, "PPSub:Images")) {
+    if (!pmVisualInitWindow (&kapa1, "PPSub:Images")) {
         return false;
     }
@@ -157,7 +159,5 @@
         if (stamp == NULL) continue;
 
-        psImage *im = stamp->matrix1;
-        if (im == NULL) im = stamp->matrix2;
-        if (im == NULL) im = stamp->matrixX;
+        psImage *im = stamp->matrix;
         if (im == NULL) continue;
 
@@ -187,7 +187,5 @@
         if (stamp == NULL) continue;
 
-        psImage *im = stamp->matrix1;
-        if (im == NULL) im = stamp->matrix2;
-        if (im == NULL) im = stamp->matrixX;
+        psImage *im = stamp->matrix;
         if (im == NULL) continue;
 
@@ -197,5 +195,5 @@
 
     psImage *canvas32 = pmVisualImageToFloat(canvas);
-    pmVisualScaleImage(kapa, canvas32, "Least_Squares", 0, true);
+    pmVisualScaleImage(kapa1, canvas32, "Least_Squares", 0, true);
 
     pmVisualAskUser(&plotLeastSquares);
@@ -207,11 +205,163 @@
 bool pmSubtractionVisualShowSubtraction(psImage *image, psImage *ref, psImage *sub) {
     if (!pmVisualIsVisual() || !plotImage) return true;
-    if (!pmVisualInitWindow (&kapa, "PPSub:Images")) {
-        return false;
-    }
-
-    pmVisualScaleImage(kapa, image, "Image", 0, true);
-    pmVisualScaleImage(kapa, ref, "Reference", 1, true);
-    pmVisualScaleImage(kapa, sub, "Subtraction", 2, true);
+    if (!pmVisualInitWindow (&kapa1, "PPSub:Images")) {
+        return false;
+    }
+
+    pmVisualScaleImage(kapa1, image, "Image", 0, true);
+    pmVisualScaleImage(kapa1, ref, "Reference", 1, true);
+    pmVisualScaleImage(kapa1, sub, "Subtraction", 2, true);
+    pmVisualAskUser(&plotImage);
+    return true;
+}
+
+bool pmSubtractionVisualShowKernels(pmSubtractionKernels *kernels) {
+
+    if (!pmVisualIsVisual()) return true;
+    if (!pmVisualInitWindow (&kapa1, "PPSub:Images")) {
+        return false;
+    }
+
+    // get the kernel sizes
+    int footprint = kernels->size;
+
+    // output image is a grid of NXsub by NYsub sub-images
+    int NXsub = sqrt(kernels->num);
+    int NYsub = kernels->num / NXsub;
+    if (kernels->num % NXsub) NYsub++;
+
+    int NXpix = NXsub * (2*footprint + 1 + 3);
+    int NYpix = NYsub * (2*footprint + 1 + 3);
+
+    psImage *output = psImageAlloc(NXpix, NYpix, PS_TYPE_F32);
+    psImageInit (output, 0.0);
+
+    for (int i = 0; i < kernels->num; i++) {
+	pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[i];
+	psKernel *kernel = preCalc->kernel;
+
+	int xSub = i % NXsub;
+	int ySub = i / NXsub;
+
+	int xPix = xSub * (2*footprint + 1 + 3) + footprint;
+	int yPix = ySub * (2*footprint + 1 + 3) + footprint;
+
+	double sum = 0.0;
+	for (int y = -footprint; y <= footprint; y++) {
+	    for (int x = -footprint; x <= footprint; x++) {
+		output->data.F32[y + yPix][x + xPix] = kernel->kernel[y][x];
+		sum += kernel->kernel[y][x];
+	    }
+	}
+	fprintf (stderr, "kernel %d, sum %f\n", i, sum);
+    }							 
+	
+    pmVisualScaleImage(kapa1, output, "Image", 0, true);
+    pmVisualAskUser(&plotImage);
+    return true;
+}
+
+bool pmSubtractionVisualShowBasis(pmSubtractionStampList *stamps) {
+
+    if (!pmVisualIsVisual()) return true;
+    if (!pmVisualInitWindow (&kapa2, "ppSub:StampMasterImage")) {
+        return false;
+    }
+
+    // get the kernel sizes
+    int footprint = stamps->footprint;
+
+    // choose the brightest stamp
+    pmSubtractionStamp *maxStamp = NULL;
+    float maxFlux = NAN;
+    for (int i = 0; i < stamps->num; i++) {
+	pmSubtractionStamp *stamp = stamps->stamps->data[i];
+	if (!isfinite(stamp->flux)) continue;
+	if (!stamp->convolutions1 && !stamp->convolutions2) continue;
+	if (!maxStamp) {
+	    maxFlux = stamp->flux;
+	    maxStamp = stamp;
+	    continue;
+	}
+	if (stamp->flux > maxFlux) {
+	    maxFlux = stamp->flux;
+	    maxStamp = stamp;
+	}
+    }
+
+    if (!isfinite(maxStamp->flux)) {
+	fprintf (stderr, "no valid stamps?\n");
+    }
+
+    int nKernels = 0;
+
+    if (maxStamp->convolutions1) {
+	// output image is a grid of NXsub by NYsub sub-images
+	nKernels = maxStamp->convolutions1->n;
+	int NXsub = sqrt(nKernels);
+	int NYsub = nKernels / NXsub;
+	if (nKernels % NXsub) NYsub++;
+
+	int NXpix = NXsub * (2*footprint + 1 + 3);
+	int NYpix = NYsub * (2*footprint + 1 + 3);
+
+	psImage *output = psImageAlloc(NXpix, NYpix, PS_TYPE_F32);
+	psImageInit (output, 0.0);
+
+	for (int i = 0; i < nKernels; i++) {
+            psKernel *kernel = maxStamp->convolutions1->data[i];
+	    
+	    int xSub = i % NXsub;
+	    int ySub = i / NXsub;
+	    
+	    int xPix = xSub * (2*footprint + 1 + 3) + footprint;
+	    int yPix = ySub * (2*footprint + 1 + 3) + footprint;
+	    
+	    double sum = 0.0;
+	    for (int y = -footprint; y <= footprint; y++) {
+		for (int x = -footprint; x <= footprint; x++) {
+		    output->data.F32[y + yPix][x + xPix] = kernel->kernel[y][x];
+		    sum += kernel->kernel[y][x];
+		}
+	    }
+	    fprintf (stderr, "kernel %d, sum %f\n", i, sum);
+	}		
+	pmVisualScaleImage(kapa2, output, "Image", 0, true);
+    }					 
+	
+    if (maxStamp->convolutions2) {
+	// output image is a grid of NXsub by NYsub sub-images
+	nKernels = maxStamp->convolutions2->n;
+	int NXsub = sqrt(nKernels);
+	int NYsub = nKernels / NXsub;
+	if (nKernels % NXsub) NYsub++;
+
+	int NXpix = NXsub * (2*footprint + 1 + 3);
+	int NYpix = NYsub * (2*footprint + 1 + 3);
+
+	psImage *output = psImageAlloc(NXpix, NYpix, PS_TYPE_F32);
+	psImageInit (output, 0.0);
+
+	for (int i = 0; i < nKernels; i++) {
+            psKernel *kernel = maxStamp->convolutions2->data[i];
+	    
+	    int xSub = i % NXsub;
+	    int ySub = i / NXsub;
+	    
+	    int xPix = xSub * (2*footprint + 1 + 3) + footprint;
+	    int yPix = ySub * (2*footprint + 1 + 3) + footprint;
+	    
+	    double sum = 0.0;
+	    for (int y = -footprint; y <= footprint; y++) {
+		for (int x = -footprint; x <= footprint; x++) {
+		    output->data.F32[y + yPix][x + xPix] = kernel->kernel[y][x];
+		    sum += kernel->kernel[y][x];
+		}
+	    }
+	    fprintf (stderr, "kernel %d, sum %f\n", i, sum);
+	}		
+	pmVisualScaleImage(kapa2, output, "Image", 1, true);
+    }					 
+	
     pmVisualAskUser(&plotImage);
     return true;
@@ -240,4 +390,12 @@
 
         overlay[Noverlay].type = KII_OVERLAY_BOX;
+	if ((stamp->x < 1.0) && (stamp->y < 1.0)) {
+	    // fprintf (stderr, "stamp zero: %f %f\n", stamp->x, stamp->y);
+	    continue;
+	}
+	if (!isfinite(stamp->x) && !isfinite(stamp->y)) {
+	    // fprintf (stderr, "stamp nan: %f %f\n", stamp->x, stamp->y);
+	    continue;
+	}
         overlay[Noverlay].x = stamp->x;
         overlay[Noverlay].y = stamp->y;
@@ -255,4 +413,252 @@
 }
 
+static int footprint = 0;
+static int NX = 0;
+static int NY = 0;
+static psImage *sourceImage      = NULL;
+static psImage *targetImage      = NULL;
+static psImage *residualImage    = NULL;
+static psImage *fresidualImage   = NULL;
+static psImage *differenceImage  = NULL;
+static psImage *convolutionImage = NULL;
+
+bool pmSubtractionVisualShowFitInit(pmSubtractionStampList *stamps) {
+
+    if (!pmVisualIsVisual()) return true;
+
+    // generate 4 storage images large enough to hold the N stamps:
+
+    footprint = stamps->footprint;
+
+    float NXf = sqrt(stamps->num);
+    NX = (int) NXf == NXf ? NXf : NXf + 1.0;
+    
+    float NYf = stamps->num / NX;
+    NY = (int) NYf == NY ? NYf : NYf + 1.0;
+
+    int NXpix = (2*footprint + 1) * NX;
+    NXpix += (NX > 1) ? 3 * NX : 0;
+
+    int NYpix = (2*footprint + 1) * NY;
+    NYpix += (NY > 1) ? 3 * NY : 0;
+
+    sourceImage      = psImageAlloc (NXpix, NYpix, PS_TYPE_F32);
+    targetImage      = psImageAlloc (NXpix, NYpix, PS_TYPE_F32);
+    residualImage    = psImageAlloc (NXpix, NYpix, PS_TYPE_F32);
+    fresidualImage   = psImageAlloc (NXpix, NYpix, PS_TYPE_F32);
+    differenceImage  = psImageAlloc (NXpix, NYpix, PS_TYPE_F32);
+    convolutionImage = psImageAlloc (NXpix, NYpix, PS_TYPE_F32);
+    
+    psImageInit (sourceImage,      0.0);
+    psImageInit (targetImage,      0.0);
+    psImageInit (residualImage,    0.0);
+    psImageInit (fresidualImage,   0.0);
+    psImageInit (differenceImage,  0.0);
+    psImageInit (convolutionImage, 0.0);
+
+    return true;
+}
+
+bool pmSubtractionVisualShowFitAddStamp(psKernel *target, psKernel *source, psKernel *convolution, double background, double norm, int index) {
+
+    if (!pmVisualIsVisual()) return true;
+
+    double sum;
+
+    int NXoff = index % NX;
+    int NYoff = index / NX;
+
+    int NXpix = NXoff * (2*footprint + 1 + 3) + footprint;
+    int NYpix = NYoff * (2*footprint + 1 + 3) + footprint;
+
+    // insert the (target) kernel into the (target) image:
+    sum = 0.0;
+    for (int y = -footprint; y <= footprint; y++) {
+	for (int x = -footprint; x <= footprint; x++) {
+	    targetImage->data.F32[y + NYpix][x + NXpix] = target->kernel[y][x];
+	    sum += targetImage->data.F32[y + NYpix][x + NXpix];
+	}
+    }
+    targetImage->data.F32[footprint + 1 + NYpix][NXpix] = sum;
+
+    // insert the (source) kernel into the (source) image:
+    sum = 0.0;
+    for (int y = -footprint; y <= footprint; y++) {
+	for (int x = -footprint; x <= footprint; x++) {
+	    sourceImage->data.F32[y + NYpix][x + NXpix] = source->kernel[y][x];
+	    sum += sourceImage->data.F32[y + NYpix][x + NXpix];
+	}
+    }
+    sourceImage->data.F32[footprint + 1 + NYpix][NXpix] = sum;
+
+    // insert the (convolution) kernel into the (convolution) image:
+    sum = 0.0;
+    for (int y = -footprint; y <= footprint; y++) {
+	for (int x = -footprint; x <= footprint; x++) {
+	    convolutionImage->data.F32[y + NYpix][x + NXpix] = -convolution->kernel[y][x];
+	    sum += convolutionImage->data.F32[y + NYpix][x + NXpix];
+	}
+    }
+    convolutionImage->data.F32[footprint + 1 + NYpix][NXpix] = sum;
+    
+    // insert the (difference) kernel into the (difference) image:
+    sum = 0.0;
+    for (int y = -footprint; y <= footprint; y++) {
+	for (int x = -footprint; x <= footprint; x++) {
+	    differenceImage->data.F32[y + NYpix][x + NXpix] = target->kernel[y][x] - background - source->kernel[y][x] * norm;
+	    sum += differenceImage->data.F32[y + NYpix][x + NXpix];
+	}
+    }
+    differenceImage->data.F32[footprint + 1 + NYpix][NXpix] = sum;
+
+    // insert the (residual) kernel into the (residual) image:
+    sum = 0.0;
+    for (int y = -footprint; y <= footprint; y++) {
+	for (int x = -footprint; x <= footprint; x++) {
+	    residualImage->data.F32[y + NYpix][x + NXpix] = target->kernel[y][x] - background - source->kernel[y][x] * norm - convolution->kernel[y][x];
+	    sum += residualImage->data.F32[y + NYpix][x + NXpix];
+	}
+    }
+    residualImage->data.F32[footprint + 1 + NYpix][NXpix] = sum;
+
+    // insert the (fresidual) kernel into the (fresidual) image:
+    for (int y = -footprint; y <= footprint; y++) {
+	for (int x = -footprint; x <= footprint; x++) {
+	    fresidualImage->data.F32[y + NYpix][x + NXpix] = residualImage->data.F32[y + NYpix][x + NXpix] / sqrt(PS_MAX(target->kernel[y][x], 100.0));
+	}
+    }
+    return true;
+}
+
+bool pmSubtractionVisualShowFit(double norm) {
+
+    // for testing, dump the residual image and exit
+    if (0) {
+	psMetadata *header = psMetadataAlloc();
+        psMetadataAddF32 (header, PS_LIST_TAIL, "NORM", 0, "Normalization", norm);
+	psFits *fits = psFitsOpen("resid.fits", "w");
+	psFitsWriteImage(fits, header, residualImage, 0, NULL);
+	psFitsClose(fits);
+	// exit (0);
+    }
+
+    if (!pmVisualIsVisual()) return true;
+    if (!pmVisualInitWindow(&kapa1, "ppSub:Images")) return false;
+    if (!pmVisualInitWindow(&kapa2, "ppSub:Misc")) return false;
+
+    KiiEraseOverlay (kapa1, "red");
+    KiiEraseOverlay (kapa2, "red");
+
+    pmVisualScaleImage(kapa1, targetImage, "Target Stamps", 0, true);
+    pmVisualScaleImage(kapa1, sourceImage, "Source Stamps", 1, true);
+    pmVisualScaleImage(kapa1, convolutionImage, "Convolution Stamps", 2, true);
+    KiiCenter (kapa1, 0.5*targetImage->numCols, 0.5*targetImage->numRows, 1);
+
+    pmVisualScaleImage(kapa2, fresidualImage, "Frac Residual Stamps", 2, true);
+    pmVisualScaleImage(kapa2, differenceImage, "Difference Stamps", 0, true);
+
+    if (1) {
+	KiiImage image;
+	KapaImageData data;
+	Coords coords;
+	strcpy (coords.ctype, "RA---TAN");
+
+	image.data2d = residualImage->data.F32;
+	image.Nx = residualImage->numCols;
+	image.Ny = residualImage->numRows;
+	strcpy (data.name, "Residual Stamps");
+	strcpy (data.file, "Residual Stamps");
+
+	data.zero  = -300.0;
+	data.range = +600.0;
+	data.logflux = 0;
+
+	KiiSetChannel (kapa2, 1);
+	KiiNewPicture2D (kapa2, &image, &data, &coords);
+    } else {
+	pmVisualScaleImage(kapa2, residualImage, "Residual Stamps", 1, true);
+    }
+
+    KiiCenter (kapa2, 0.5*residualImage->numCols, 0.5*residualImage->numRows, 1);
+
+    pmVisualAskUser(NULL);
+
+    psFree(targetImage);
+    psFree(sourceImage);
+    psFree(convolutionImage);
+    psFree(differenceImage);
+    psFree(residualImage);
+    psFree(fresidualImage);
+
+    targetImage = NULL;
+    sourceImage = NULL;
+    convolutionImage = NULL;
+    differenceImage = NULL;
+    residualImage = NULL;
+    fresidualImage = NULL;
+
+    return true;
+}
+
+bool pmSubtractionVisualPlotFit(const pmSubtractionKernels *kernels) {
+
+    Graphdata graphdata;
+
+    if (!pmVisualIsVisual()) return true;
+    if (!pmVisualInitWindow(&kapa3, "ppSub:plots")) return false;
+
+    KapaClearSections (kapa3);
+    KapaInitGraph (&graphdata);
+
+    psVector *x = psVectorAllocEmpty (kernels->num, PS_TYPE_F32);
+    psVector *y = psVectorAllocEmpty (kernels->num, PS_TYPE_F32);
+
+    graphdata.xmin = -1.0;
+    graphdata.xmax = kernels->num + 1.0;
+    graphdata.ymin = +32.0;
+    graphdata.ymax = -32.0;
+
+    psImage *polyValues = p_pmSubtractionPolynomial(NULL, kernels->spatialOrder, 0.0, 0.0);
+
+    // construct the plot vectors
+    for (int i = 0; i < kernels->num; i++) {
+        x->data.F32[i] = i;
+	y->data.F32[i] = p_pmSubtractionSolutionCoeff(kernels, polyValues, i, false);
+        graphdata.ymin = PS_MIN(graphdata.ymin, y->data.F32[i]);
+        graphdata.ymax = PS_MAX(graphdata.ymax, y->data.F32[i]);
+    }
+    x->n = y->n = kernels->num;
+
+    float range;
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    KapaSetLimits (kapa3, &graphdata);
+
+    KapaSetFont (kapa3, "helvetica", 14);
+    KapaBox (kapa3, &graphdata);
+    KapaSendLabel (kapa3, "kernel number", KAPA_LABEL_XM);
+    KapaSendLabel (kapa3, "coeff", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+
+    KapaPrepPlot   (kapa3, x->n, &graphdata);
+    KapaPlotVector (kapa3, x->n, x->data.F32, "x");
+    KapaPlotVector (kapa3, x->n, y->data.F32, "y");
+
+    psFree (x);
+    psFree (y);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
 #else
 bool pmSubtractionVisualClose(void) {return true;}
@@ -261,3 +667,7 @@
 bool pmSubtractionVisualPlotLeastSquares(pmSubtractionStampList *stamps) {return true;}
 bool pmSubtractionVisualShowSubtraction(psImage *image, psImage *ref, psImage *sub) {return true;}
+bool pmSubtractionVisualShowFitInit(pmSubtractionStampList *stamps) {return true;}
+bool pmSubtractionVisualShowFitAddStamp(psKernel *target, psKernel *source, psKernel *convolution, double background, double norm, int index) {return true;}
+bool pmSubtractionVisualShowFit() {return true;}
+bool pmSubtractionVisualPlotFit(const pmSubtractionKernels *kernels);
 #endif
Index: branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionVisual.h
===================================================================
--- branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionVisual.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/imcombine/pmSubtractionVisual.h	(revision 27839)
@@ -7,4 +7,10 @@
 bool pmSubtractionVisualPlotLeastSquares(pmSubtractionStampList *stamps);
 bool pmSubtractionVisualShowSubtraction(psImage *image, psImage *ref, psImage *sub);
+bool pmSubtractionVisualShowFitInit(pmSubtractionStampList *stamps);
+bool pmSubtractionVisualShowFitAddStamp(psKernel *target, psKernel *source, psKernel *convolution, double background, double norm, int index);
+bool pmSubtractionVisualShowFit(double norm);
+bool pmSubtractionVisualPlotFit(const pmSubtractionKernels *kernels);
+bool pmSubtractionVisualShowKernels(pmSubtractionKernels *kernels);
+bool pmSubtractionVisualShowBasis(pmSubtractionStampList *stamps);
 
 #endif
Index: branches/simmosaic_branches/psModules/src/objects/Makefile.am
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/Makefile.am	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/Makefile.am	(revision 27839)
@@ -10,5 +10,7 @@
 	pmFootprintArraysMerge.c \
 	pmFootprintAssignPeaks.c \
+	pmFootprintCullPeaks.c \
 	pmFootprintFind.c \
+	pmFootprintSpans.c \
 	pmFootprintFindAtPoint.c \
 	pmFootprintIDs.c \
@@ -19,6 +21,8 @@
 	pmModelUtils.c \
 	pmSource.c \
+	pmPhotObj.c \
 	pmSourceMasks.c \
 	pmSourceMoments.c \
+	pmSourceDiffStats.c \
 	pmSourceExtendedPars.c \
 	pmSourceUtils.c \
@@ -39,4 +43,5 @@
 	pmSourceIO_CMF_PS1_V1.c \
 	pmSourceIO_CMF_PS1_V2.c \
+	pmSourceIO_CMF_PS1_DV1.c \
 	pmSourceIO_MatchedRefs.c \
 	pmSourcePlots.c \
@@ -49,14 +54,19 @@
 	pmPSF_IO.c \
 	pmPSFtry.c \
+	pmPSFtryModel.c \
+	pmPSFtryFitEXT.c \
+	pmPSFtryMakePSF.c \
+	pmPSFtryFitPSF.c \
+	pmPSFtryMetric.c \
 	pmTrend2D.c \
 	pmGrowthCurveGenerate.c \
 	pmGrowthCurve.c \
-	pmSourceMatch.c
-
-EXTRA_DIST = \
+	pmSourceMatch.c \
+	pmDetEff.c \
+	pmSourceGroups.c \
 	models/pmModel_GAUSS.c \
 	models/pmModel_PGAUSS.c \
+	models/pmModel_PS1_V1.c \
 	models/pmModel_QGAUSS.c \
-	models/pmModel_SGAUSS.c \
 	models/pmModel_RGAUSS.c \
 	models/pmModel_SERSIC.c
@@ -66,4 +76,5 @@
 	pmSpan.h \
 	pmFootprint.h \
+	pmFootprintSpans.h \
 	pmPeaks.h \
 	pmMoments.h \
@@ -72,5 +83,7 @@
 	pmModelUtils.h \
 	pmSource.h \
+	pmPhotObj.h \
 	pmSourceMasks.h \
+	pmSourceDiffStats.h \
 	pmSourceExtendedPars.h \
 	pmSourceUtils.h \
@@ -89,5 +102,13 @@
 	pmTrend2D.h \
 	pmGrowthCurve.h \
-	pmSourceMatch.h
+	pmSourceMatch.h \
+	pmDetEff.h \
+	pmSourceGroups.h \
+	models/pmModel_GAUSS.h \
+	models/pmModel_PGAUSS.h \
+	models/pmModel_PS1_V1.h \
+	models/pmModel_QGAUSS.h \
+	models/pmModel_RGAUSS.h \
+	models/pmModel_SERSIC.h
 
 CLEANFILES = *~
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_GAUSS.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_GAUSS.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/models/pmModel_GAUSS.c	(revision 27839)
@@ -1,8 +1,8 @@
 /******************************************************************************
  * this file defines the GAUSS source shape model.  Note that these model functions are loaded
- * by pmModelGroup.c using 'include', and thus need no 'include' statements of their own.  The
+ * by pmModelClass.c using 'include', and thus need no 'include' statements of their own.  The
  * models use a psVector to represent the set of parameters, with the sequence used to specify
  * the meaning of the parameter.  The meaning of the parameters may thus vary depending on the
- * specifics of the model.  All models which are used a PSF representations share a few
+ * specifics of the model.  All models which are used as a PSF representations share a few
  * parameters, for which # define names are listed in pmModel.h:
 
@@ -19,4 +19,13 @@
  *****************************************************************************/
 
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmMoments.h"
+#include "pmPeaks.h"
+#include "pmSource.h"
+#include "pmModel.h"
+#include "pmModel_GAUSS.h"
+
 # define PM_MODEL_FUNC            pmModelFunc_GAUSS
 # define PM_MODEL_FLUX            pmModelFlux_GAUSS
@@ -27,6 +36,28 @@
 # define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_GAUSS
 # define PM_MODEL_FIT_STATUS      pmModelFitStatus_GAUSS
+# define PM_MODEL_SET_LIMITS      pmModelSetLimits_GAUSS
+
+// Lax parameter limits
+static float paramsMinLax[] = { -1.0e3, 1.0e-2, -100, -100, 0.5, 0.5, -1.0 };
+static float paramsMaxLax[] = { 1.0e5, 1.0e8, 1.0e4, 1.0e4, 100, 100, 1.0 };
+
+// Moderate parameter limits
+static float *paramsMinModerate = paramsMinLax;
+static float *paramsMaxModerate = paramsMaxLax;
+
+// Strict parameter limits
+static float *paramsMinStrict = paramsMinLax;
+static float *paramsMaxStrict = paramsMaxLax;
+
+// Parameter limits to use
+static float *paramsMinUse = paramsMinLax;
+static float *paramsMaxUse = paramsMaxLax;
+static float betaUse[] = { 1000, 3e6, 5, 5, 2.0, 2.0, 0.5 };
+
+static bool limitsApply = true;         // Apply limits?
 
 // the model is a function of the pixel coordinate (pixcoord[0,1] = x,y)
+// 0.5 PIX: the parameters are defined in terms of pixel coords, so the incoming pixcoords
+// values need to be pixel coords
 psF32 PM_MODEL_FUNC(psVector *deriv,
                     const psVector *params,
@@ -68,118 +99,66 @@
 bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
 {
-    float beta_lim = 0, params_min = 0, params_max = 0;
-    float f1 = 0, f2 = 0, q1 = 0, q2 = 0;
+    if (!limitsApply) {
+        return true;
+    }
+    psAssert(nParam >= 0 && nParam <= PM_PAR_7, "Parameter index is out of bounds");
 
     // we need to calculate the limits for SXY specially
+    float q2 = NAN;
     if (nParam == PM_PAR_SXY) {
-        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
-        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
-        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
-        q1 = PS_MAX (0.0, q1);
+        float f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        float f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        float q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        q1 = (q1 < 0.0) ? 0.0 : q1;
         // if q1 < 0.0, f2 ~ f1, we have a very large axis ratio near 45deg..  Saturate at that
         // angle and let f2,f1 fight it out
-        q2  = 0.5*sqrt (q1);
+        q2 = 0.5*sqrtf(q1);
     }
 
     switch (mode) {
-    case PS_MINIMIZE_BETA_LIMIT:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            beta_lim = 1000;
-            break;
-        case PM_PAR_I0:
-            beta_lim = 3e6;
-            break;
-        case PM_PAR_XPOS:
-            beta_lim = 5;
-            break;
-        case PM_PAR_YPOS:
-            beta_lim = 5;
-            break;
-        case PM_PAR_SXX:
-            beta_lim = 2.0;
-            break;
-        case PM_PAR_SYY:
-            beta_lim = 2.0;
-            break;
-        case PM_PAR_SXY:
-            beta_lim =  0.5*q2;
-            break;
-        default:
-            psAbort("invalid parameter %d for beta test", nParam);
-        }
-        if (fabs(beta[nParam]) > fabs(beta_lim)) {
-            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
-            psTrace ("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
-                     nParam, beta[nParam], beta_lim);
-            return false;
-        }
-        return true;
-    case PS_MINIMIZE_PARAM_MIN:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            params_min = -1000;
-            break;
-        case PM_PAR_I0:
-            params_min =   0.01;
-            break;
-        case PM_PAR_XPOS:
-            params_min =  -100;
-            break;
-        case PM_PAR_YPOS:
-            params_min =  -100;
-            break;
-        case PM_PAR_SXX:
-            params_min =   0.5;
-            break;
-        case PM_PAR_SYY:
-            params_min =   0.5;
-            break;
-        case PM_PAR_SXY:
-            params_min =   -q2;
-            break;
-        default:
-            psAbort("invalid parameter %d for param min test", nParam);
-        }
-        if (params[nParam] < params_min) {
-            params[nParam] = params_min;
-            psTrace ("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
-                     nParam, params[nParam], params_min);
-            return false;
-        }
-        return true;
-    case PS_MINIMIZE_PARAM_MAX:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            params_max =   1e5;
-            break;
-        case PM_PAR_I0:
-            params_max =   1e8;
-            break;
-        case PM_PAR_XPOS:
-            params_max =   1e4;
-            break;
-        case PM_PAR_YPOS:
-            params_max =   1e4;
-            break;
-        case PM_PAR_SXX:
-            params_max =   100;
-            break;
-        case PM_PAR_SYY:
-            params_max =   100;
-            break;
-        case PM_PAR_SXY:
-            params_max =   +q2;
-            break;
-        default:
-            psAbort("invalid parameter %d for param max test", nParam);
-        }
-        if (params[nParam] > params_max) {
-            params[nParam] = params_max;
-            psTrace ("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
-                     nParam, params[nParam], params_max);
-            return false;
-        }
-        return true;
+      case PS_MINIMIZE_BETA_LIMIT: {
+          psAssert(beta, "Require beta to limit beta");
+          float limit = betaUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (fabs(beta[nParam]) > fabs(limit)) {
+              beta[nParam] = (beta[nParam] > 0) ? fabs(limit) : -fabs(limit);
+              psTrace("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
+                      nParam, beta[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      case PS_MINIMIZE_PARAM_MIN: {
+          psAssert(params, "Require parameters to limit parameters");
+          psAssert(paramsMinUse, "Require parameter limits to limit parameters");
+          float limit = paramsMinUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (params[nParam] < limit) {
+              params[nParam] = limit;
+              psTrace("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
+                      nParam, params[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      case PS_MINIMIZE_PARAM_MAX: {
+          psAssert(params, "Require parameters to limit parameters");
+          psAssert(paramsMaxUse, "Require parameter limits to limit parameters");
+          float limit = paramsMaxUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (params[nParam] > limit) {
+              params[nParam] = limit;
+              psTrace("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
+                      nParam, params[nParam], limit);
+              return false;
+          }
+          return true;
+      }
     default:
         psAbort("invalid choice for limits");
@@ -190,4 +169,5 @@
 
 // make an initial guess for parameters
+// 0.5 PIX: moments and peaks are in pixel coords, thus so are model parameters
 bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
 {
@@ -205,5 +185,5 @@
     psEllipseShape shape = psEllipseAxesToShape (axes);
 
-    PAR[PM_PAR_SKY]  = moments->Sky;
+    PAR[PM_PAR_SKY]  = 0.0;
     PAR[PM_PAR_I0]   = peak->flux;
     PAR[PM_PAR_XPOS] = peak->xf;
@@ -257,4 +237,6 @@
     psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
     psF64 radius = axes.major * sqrt (2.0 * log(PAR[PM_PAR_I0] / flux));
+    psAssert (isfinite(radius), "fix this code: radius should not be nan for %f", PAR[PM_PAR_I0]);
+
     return (radius);
 }
@@ -367,5 +349,4 @@
 bool PM_MODEL_FIT_STATUS (pmModel *model)
 {
-    psF32 dP;
     bool  status;
 
@@ -373,17 +354,43 @@
     psF32 *dPAR = model->dparams->data.F32;
 
-    dP = 0;
-    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
-    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
-    dP = sqrt (dP);
-
     status = true;
-    status &= (dP < 0.5);
     status &= (PAR[PM_PAR_I0] > 0);
     status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
 
-    if (status)
-        return true;
-    return false;
+    return status;
+}
+
+void PM_MODEL_SET_LIMITS(pmModelLimitsType type)
+{
+    switch (type) {
+      case PM_MODEL_LIMITS_NONE:
+        paramsMinUse = NULL;
+        paramsMaxUse = NULL;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_IGNORE:
+        paramsMinUse = NULL;
+        paramsMaxUse = NULL;
+        limitsApply = false;
+        break;
+      case PM_MODEL_LIMITS_LAX:
+        paramsMinUse = paramsMinLax;
+        paramsMaxUse = paramsMaxLax;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_MODERATE:
+        paramsMinUse = paramsMinModerate;
+        paramsMaxUse = paramsMaxModerate;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_STRICT:
+        paramsMinUse = paramsMinStrict;
+        paramsMaxUse = paramsMaxStrict;
+        limitsApply = true;
+        break;
+      default:
+        psAbort("Unrecognised model limits type: %x", type);
+    }
+    return;
 }
 
@@ -396,2 +403,3 @@
 # undef PM_MODEL_PARAMS_FROM_PSF
 # undef PM_MODEL_FIT_STATUS
+# undef PM_MODEL_SET_LIMITS
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_GAUSS.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_GAUSS.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/models/pmModel_GAUSS.h	(revision 27839)
@@ -0,0 +1,15 @@
+#ifndef PM_MODEL_GAUSS_H
+
+#include "pmModel.h"
+
+psF32 pmModelFunc_GAUSS(psVector *deriv, const psVector *params, const psVector *pixcoord);
+bool pmModelLimits_GAUSS(psMinConstraintMode mode, int nParam, float *params, float *beta);
+bool pmModelGuess_GAUSS(pmModel *model, pmSource *source);
+psF64 pmModelFlux_GAUSS(const psVector *params);
+psF64 pmModelRadius_GAUSS(const psVector *params, psF64 flux);
+bool pmModelFromPSF_GAUSS(pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf);
+bool  pmModelParamsFromPSF_GAUSS(pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io);
+bool pmModelFitStatus_GAUSS(pmModel *model);
+void pmModelSetLimits_GAUSS(pmModelLimitsType type);
+
+#endif
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_PGAUSS.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_PGAUSS.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/models/pmModel_PGAUSS.c	(revision 27839)
@@ -1,8 +1,8 @@
 /******************************************************************************
  * this file defines the PGAUSS source shape model.  Note that these model functions are loaded
- * by pmModelGroup.c using 'include', and thus need no 'include' statements of their own.  The
+ * by pmModelClass.c using 'include', and thus need no 'include' statements of their own.  The
  * models use a psVector to represent the set of parameters, with the sequence used to specify
  * the meaning of the parameter.  The meaning of the parameters may thus vary depending on the
- * specifics of the model.  All models which are used a PSF representations share a few
+ * specifics of the model.  All models which are used as a PSF representations share a few
  * parameters, for which # define names are listed in pmModel.h:
 
@@ -19,4 +19,13 @@
  *****************************************************************************/
 
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmMoments.h"
+#include "pmPeaks.h"
+#include "pmSource.h"
+#include "pmModel.h"
+#include "pmModel_PGAUSS.h"
+
 # define PM_MODEL_FUNC            pmModelFunc_PGAUSS
 # define PM_MODEL_FLUX            pmModelFlux_PGAUSS
@@ -27,6 +36,28 @@
 # define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_PGAUSS
 # define PM_MODEL_FIT_STATUS      pmModelFitStatus_PGAUSS
+# define PM_MODEL_SET_LIMITS      pmModelSetLimits_PGAUSS
+
+// Lax parameter limits
+static float paramsMinLax[] = { -1.0e3, 1.0e-2, -100, -100, 0.5, 0.5, -1.0 };
+static float paramsMaxLax[] = { 1.0e5, 1.0e8, 1.0e4, 1.0e4, 100, 100, 1.0 };
+
+// Moderate parameter limits
+static float *paramsMinModerate = paramsMinLax;
+static float *paramsMaxModerate = paramsMaxLax;
+
+// Strict parameter limits
+static float *paramsMinStrict = paramsMinLax;
+static float *paramsMaxStrict = paramsMaxLax;
+
+// Parameter limits to use
+static float *paramsMinUse = paramsMinLax;
+static float *paramsMaxUse = paramsMaxLax;
+static float betaUse[] = { 1000, 3e6, 5, 5, 2.0, 2.0, 0.5 };
+
+static bool limitsApply = true;         // Apply limits?
 
 // the model is a function of the pixel coordinate (pixcoord[0,1] = x,y)
+// 0.5 PIX: the parameters are defined in terms of pixel coords, so the incoming pixcoords
+// values need to be pixel coords
 psF32 PM_MODEL_FUNC(psVector *deriv,
                     const psVector *params,
@@ -69,119 +100,67 @@
 bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
 {
-    float beta_lim = 0, params_min = 0, params_max = 0;
-    float f1 = 0, f2 = 0, q1 = 0, q2 = 0;
+    if (!limitsApply) {
+        return true;
+    }
+    psAssert(nParam >= 0 && nParam <= PM_PAR_7, "Parameter index is out of bounds");
 
     // we need to calculate the limits for SXY specially
+    float q2 = NAN;
     if (nParam == PM_PAR_SXY) {
-        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
-        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
-        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
-        q1 = PS_MAX (0.0, q1);
+        float f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        float f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        float q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        q1 = (q1 < 0.0) ? 0.0 : q1;
         // if q1 < 0.0, f2 ~ f1, we have a very large axis ratio near 45deg..  Saturate at that
         // angle and let f2,f1 fight it out
-        q2  = 0.5*sqrt (q1);
+        q2 = 0.5*sqrtf(q1);
     }
 
     switch (mode) {
-    case PS_MINIMIZE_BETA_LIMIT:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            beta_lim = 1000;
-            break;
-        case PM_PAR_I0:
-            beta_lim = 3e6;
-            break;
-        case PM_PAR_XPOS:
-            beta_lim = 5;
-            break;
-        case PM_PAR_YPOS:
-            beta_lim = 5;
-            break;
-        case PM_PAR_SXX:
-            beta_lim = 2.0;
-            break;
-        case PM_PAR_SYY:
-            beta_lim = 2.0;
-            break;
-        case PM_PAR_SXY:
-            beta_lim =  0.5*q2;
-            break;
-        default:
-            psAbort("invalid parameter %d for beta test", nParam);
-        }
-        if (fabs(beta[nParam]) > fabs(beta_lim)) {
-            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
-            psTrace ("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
-                     nParam, beta[nParam], beta_lim);
-            return false;
-        }
-        return true;
-    case PS_MINIMIZE_PARAM_MIN:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            params_min = -1000;
-            break;
-        case PM_PAR_I0:
-            params_min =  0.01;
-            break;
-        case PM_PAR_XPOS:
-            params_min =  -100;
-            break;
-        case PM_PAR_YPOS:
-            params_min =  -100;
-            break;
-        case PM_PAR_SXX:
-            params_min =   0.5;
-            break;
-        case PM_PAR_SYY:
-            params_min =   0.5;
-            break;
-        case PM_PAR_SXY:
-            params_min =   -q2;
-            break;
-        default:
-            psAbort("invalid parameter %d for param min test", nParam);
-        }
-        if (params[nParam] < params_min) {
-            params[nParam] = params_min;
-            psTrace ("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
-                     nParam, params[nParam], params_min);
-            return false;
-        }
-        return true;
-    case PS_MINIMIZE_PARAM_MAX:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            params_max =   1e5;
-            break;
-        case PM_PAR_I0:
-            params_max =   1e8;
-            break;
-        case PM_PAR_XPOS:
-            params_max =   1e4;
-            break;
-        case PM_PAR_YPOS:
-            params_max =   1e4;
-            break;
-        case PM_PAR_SXX:
-            params_max =   100;
-            break;
-        case PM_PAR_SYY:
-            params_max =   100;
-            break;
-        case PM_PAR_SXY:
-            params_max =   +q2;
-            break;
-        default:
-            psAbort("invalid parameter %d for param max test", nParam);
-        }
-        if (params[nParam] > params_max) {
-            params[nParam] = params_max;
-            psTrace ("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
-                     nParam, params[nParam], params_max);
-            return false;
-        }
-        return true;
-    default:
+      case PS_MINIMIZE_BETA_LIMIT: {
+          psAssert(beta, "Require beta to limit beta");
+          float limit = betaUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (fabs(beta[nParam]) > fabs(limit)) {
+              beta[nParam] = (beta[nParam] > 0) ? fabs(limit) : -fabs(limit);
+              psTrace("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
+                      nParam, beta[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      case PS_MINIMIZE_PARAM_MIN: {
+          psAssert(params, "Require parameters to limit parameters");
+          psAssert(paramsMinUse, "Require parameter limits to limit parameters");
+          float limit = paramsMinUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (params[nParam] < limit) {
+              params[nParam] = limit;
+              psTrace("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
+                      nParam, params[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      case PS_MINIMIZE_PARAM_MAX: {
+          psAssert(params, "Require parameters to limit parameters");
+          psAssert(paramsMaxUse, "Require parameter limits to limit parameters");
+          float limit = paramsMaxUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (params[nParam] > limit) {
+              params[nParam] = limit;
+              psTrace("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
+                      nParam, params[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      default:
         psAbort("invalid choice for limits");
     }
@@ -190,5 +169,7 @@
 }
 
+
 // make an initial guess for parameters
+// 0.5 PIX: moments and peaks are in pixel coords, thus so are model parameters
 bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
 {
@@ -205,5 +186,5 @@
     psEllipseShape shape = psEllipseAxesToShape (axes);
 
-    PAR[PM_PAR_SKY]  = moments->Sky;
+    PAR[PM_PAR_SKY]  = 0.0;
     PAR[PM_PAR_I0]   = peak->flux;
     PAR[PM_PAR_XPOS] = peak->xf;
@@ -255,5 +236,5 @@
 psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
 {
-    psF64 z, f;
+    psF64 z;
     int Nstep = 0;
     psEllipseShape shape;
@@ -284,23 +265,29 @@
     // choose a z value guaranteed to be beyond our limit
     float z0 = pow((1.0 / limit), (1.0 / 3.0));
+    psAssert (isfinite(z0), "fix this code: z0 should not be nan for %f", PAR[PM_PAR_I0]);
     float z1 = (1.0 / limit);
+    psAssert (isfinite(z1), "fix this code: z1 should not be nan for %f", PAR[PM_PAR_I0]);
     z1 = PS_MAX (z0, z1);
     z0 = 0.0;
 
-    // perform a type of bisection to find the value
-    float f0 = 1.0 / (1 + z0 + z0*z0/2.0 + z0*z0*z0/6.0);
-    float f1 = 1.0 / (1 + z1 + z1*z1/2.0 + z1*z1*z1/6.0);
-    while ((Nstep < 10) && (fabs(z1 - z0) > 0.5)) {
-        z = 0.5*(z0 + z1);
-        f = 1.0 / (1 + z + z*z/2.0 + z*z*z/6.0);
-        if (f > limit) {
-            z0 = z;
-            f0 = f;
-        } else {
-            z1 = z;
-            f1 = f;
-        }
-        Nstep ++;
-    }
+    // starting guess:
+    z = 0.5*(z0 + z1);
+    float dz = 1.0;
+
+    for (int i = 0; (i < 10) && (fabs(dz) > 0.0001); i++) {
+	// use Newton-Raphson to minimize f(z) - limit = 0
+	float dqdz = (1.0 + z + z*z/2.0);
+	float q = (dqdz + z*z*z/6.0);
+
+	float f = 1.0 / q;
+	float dfdz = -dqdz * f / q;
+
+	dz = (f - limit) / dfdz;
+
+	// fprintf (stderr, "%f %f %f : %f %f\n", f, z, dz, dfdz, q);
+	z -= dz;
+	z = PS_MAX(z, 0.0);
+    }
+
     psF64 radius = sigma * sqrt (2.0 * z);
 
@@ -415,5 +402,4 @@
 bool PM_MODEL_FIT_STATUS (pmModel *model)
 {
-    psF32 dP;
     bool  status;
 
@@ -421,15 +407,44 @@
     psF32 *dPAR = model->dparams->data.F32;
 
-    dP = 0;
-    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
-    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
-    dP = sqrt (dP);
-
     status = true;
-    status &= (dP < 0.5);
     status &= (PAR[PM_PAR_I0] > 0);
     status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
 
     return status;
+}
+
+
+void PM_MODEL_SET_LIMITS(pmModelLimitsType type)
+{
+    switch (type) {
+      case PM_MODEL_LIMITS_NONE:
+        paramsMinUse = NULL;
+        paramsMaxUse = NULL;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_IGNORE:
+        paramsMinUse = NULL;
+        paramsMaxUse = NULL;
+        limitsApply = false;
+        break;
+      case PM_MODEL_LIMITS_LAX:
+        paramsMinUse = paramsMinLax;
+        paramsMaxUse = paramsMaxLax;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_MODERATE:
+        paramsMinUse = paramsMinModerate;
+        paramsMaxUse = paramsMaxModerate;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_STRICT:
+        paramsMinUse = paramsMinStrict;
+        paramsMaxUse = paramsMaxStrict;
+        limitsApply = true;
+        break;
+      default:
+        psAbort("Unrecognised model limits type: %x", type);
+    }
+    return;
 }
 
@@ -442,2 +457,3 @@
 # undef PM_MODEL_PARAMS_FROM_PSF
 # undef PM_MODEL_FIT_STATUS
+# undef PM_MODEL_SET_LIMITS
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_PGAUSS.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_PGAUSS.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/models/pmModel_PGAUSS.h	(revision 27839)
@@ -0,0 +1,15 @@
+#ifndef PM_MODEL_PGAUSS_H
+
+#include "pmModel.h"
+
+psF32 pmModelFunc_PGAUSS(psVector *deriv, const psVector *params, const psVector *pixcoord);
+bool pmModelLimits_PGAUSS(psMinConstraintMode mode, int nParam, float *params, float *beta);
+bool pmModelGuess_PGAUSS(pmModel *model, pmSource *source);
+psF64 pmModelFlux_PGAUSS(const psVector *params);
+psF64 pmModelRadius_PGAUSS(const psVector *params, psF64 flux);
+bool pmModelFromPSF_PGAUSS(pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf);
+bool  pmModelParamsFromPSF_PGAUSS(pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io);
+bool pmModelFitStatus_PGAUSS(pmModel *model);
+void pmModelSetLimits_PGAUSS(pmModelLimitsType type);
+
+#endif
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_PS1_V1.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_PS1_V1.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/models/pmModel_PS1_V1.c	(revision 27839)
@@ -1,9 +1,9 @@
 /******************************************************************************
- * this file defines the PS1_V1 source shape model (XXX need a better name!).  Note that these
- * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
- * statements of their own.  The models use a psVector to represent the set of parameters, with
- * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
- * may thus vary depending on the specifics of the model.  All models which are used a PSF
- * representations share a few parameters, for which # define names are listed in pmModel.h:
+ * this file defines the PS1_V1 source shape model.  Note that these model functions are loaded
+ * by pmModelClass.c using 'include', and thus need no 'include' statements of their own.  The
+ * models use a psVector to represent the set of parameters, with the sequence used to specify
+ * the meaning of the parameter.  The meaning of the parameters may thus vary depending on the
+ * specifics of the model.  All models which are used as a PSF representations share a few
+ * parameters, for which # define names are listed in pmModel.h:
 
    power-law with fitted linear term
@@ -20,4 +20,13 @@
    *****************************************************************************/
 
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmMoments.h"
+#include "pmPeaks.h"
+#include "pmSource.h"
+#include "pmModel.h"
+#include "pmModel_PS1_V1.h"
+
 # define PM_MODEL_FUNC            pmModelFunc_PS1_V1
 # define PM_MODEL_FLUX            pmModelFlux_PS1_V1
@@ -28,7 +37,33 @@
 # define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_PS1_V1
 # define PM_MODEL_FIT_STATUS      pmModelFitStatus_PS1_V1
+# define PM_MODEL_SET_LIMITS      pmModelSetLimits_PS1_V1
 
 # define ALPHA   1.666
 # define ALPHA_M 0.666
+
+// the model is a function of the pixel coordinate (pixcoord[0,1] = x,y)
+// 0.5 PIX: the parameters are defined in terms of pixel coords, so the incoming pixcoords
+// values need to be pixel coords
+
+// Lax parameter limits
+static float paramsMinLax[] = { -1.0e3, 1.0e-2, -100, -100, 0.5, 0.5, -1.0, -1.0 };
+static float paramsMaxLax[] = { 1.0e5, 1.0e8, 1.0e4, 1.0e4, 100, 100, 1.0, 20.0 };
+
+// Moderate parameter limits
+// Tolerate a small divot (k < 0)
+static float paramsMinModerate[] = { -1.0e3, 1.0e-2, -100, -100, 0.5, 0.5, -1.0, -0.05 };
+static float paramsMaxModerate[] = { 1.0e5, 1.0e8, 1.0e4, 1.0e4, 100, 100, 1.0, 20.0 };
+
+// Strict parameter limits
+// k = PAR_7 < 0 is very undesirable (big divot in the middle)
+static float paramsMinStrict[] = { -1.0e3, 1.0e-2, -100, -100, 0.5, 0.5, -1.0, 0.0 };
+static float paramsMaxStrict[] = { 1.0e5, 1.0e8, 1.0e4, 1.0e4, 100, 100, 1.0, 20.0 };
+
+// Parameter limits to use
+static float *paramsMinUse = paramsMinLax;
+static float *paramsMaxUse = paramsMaxLax;
+static float betaUse[] = { 1000, 3e6, 5, 5, 1.0, 1.0, 0.5, 2.0 };
+
+static bool limitsApply = true;         // Apply limits?
 
 psF32 PM_MODEL_FUNC (psVector *deriv,
@@ -84,128 +119,67 @@
 bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
 {
-    float beta_lim = 0, params_min = 0, params_max = 0;
-    float f1 = 0, f2 = 0, q1 = 0, q2 = 0;
+    if (!limitsApply) {
+        return true;
+    }
+    psAssert(nParam >= 0 && nParam <= PM_PAR_7, "Parameter index is out of bounds");
 
     // we need to calculate the limits for SXY specially
+    float q2 = NAN;
     if (nParam == PM_PAR_SXY) {
-        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
-        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
-        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        float f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        float f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        float q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
         q1 = (q1 < 0.0) ? 0.0 : q1;
         // if q1 < 0.0, f2 ~ f1, we have a very large axis ratio near 45deg..  Saturate at that
         // angle and let f2,f1 fight it out
-        q2  = 0.5*sqrt (q1);
+        q2 = 0.5*sqrtf(q1);
     }
 
     switch (mode) {
-    case PS_MINIMIZE_BETA_LIMIT:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            beta_lim = 1000;
-            break;
-        case PM_PAR_I0:
-            beta_lim = 3e6;
-            break;
-        case PM_PAR_XPOS:
-            beta_lim = 5;
-            break;
-        case PM_PAR_YPOS:
-            beta_lim = 5;
-            break;
-        case PM_PAR_SXX:
-            beta_lim = 1.0;
-            break;
-        case PM_PAR_SYY:
-            beta_lim = 1.0;
-            break;
-        case PM_PAR_SXY:
-            beta_lim =  0.5*q2;
-            break;
-        case PM_PAR_7:
-            beta_lim = 2.0;
-            break;
-        default:
-            psAbort("invalid parameter %d for beta test", nParam);
-        }
-        if (fabs(beta[nParam]) > fabs(beta_lim)) {
-            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
-            psTrace ("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
-                     nParam, beta[nParam], beta_lim);
-            return false;
-        }
-        return true;
-    case PS_MINIMIZE_PARAM_MIN:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            params_min = -1000;
-            break;
-        case PM_PAR_I0:
-            params_min =   0.01;
-            break;
-        case PM_PAR_XPOS:
-            params_min =  -100;
-            break;
-        case PM_PAR_YPOS:
-            params_min =  -100;
-            break;
-        case PM_PAR_SXX:
-            params_min =   0.5;
-            break;
-        case PM_PAR_SYY:
-            params_min =   0.5;
-            break;
-        case PM_PAR_SXY:
-            params_min =  -q2;
-            break;
-        case PM_PAR_7:
-            params_min =  -1.0;
-            break;
-        default:
-            psAbort("invalid parameter %d for param min test", nParam);
-        }
-        if (params[nParam] < params_min) {
-            params[nParam] = params_min;
-            psTrace ("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
-                     nParam, params[nParam], params_min);
-            return false;
-        }
-        return true;
-    case PS_MINIMIZE_PARAM_MAX:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            params_max =   1e5;
-            break;
-        case PM_PAR_I0:
-            params_max =   1e8;
-            break;
-        case PM_PAR_XPOS:
-            params_max =   1e4;
-            break;
-        case PM_PAR_YPOS:
-            params_max =   1e4;
-            break;
-        case PM_PAR_SXX:
-            params_max =   100;
-            break;
-        case PM_PAR_SYY:
-            params_max =   100;
-            break;
-        case PM_PAR_SXY:
-            params_max =  +q2;
-            break;
-        case PM_PAR_7:
-            params_max =  20.0;
-            break;
-        default:
-            psAbort("invalid parameter %d for param max test", nParam);
-        }
-        if (params[nParam] > params_max) {
-            params[nParam] = params_max;
-            psTrace ("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
-                     nParam, params[nParam], params_max);
-            return false;
-        }
-        return true;
-    default:
+      case PS_MINIMIZE_BETA_LIMIT: {
+          psAssert(beta, "Require beta to limit beta");
+          float limit = betaUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (fabs(beta[nParam]) > fabs(limit)) {
+              beta[nParam] = (beta[nParam] > 0) ? fabs(limit) : -fabs(limit);
+              psTrace("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
+                      nParam, beta[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      case PS_MINIMIZE_PARAM_MIN: {
+          psAssert(params, "Require parameters to limit parameters");
+          psAssert(paramsMinUse, "Require parameter limits to limit parameters");
+          float limit = paramsMinUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (params[nParam] < limit) {
+              params[nParam] = limit;
+              psTrace("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
+                      nParam, params[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      case PS_MINIMIZE_PARAM_MAX: {
+          psAssert(params, "Require parameters to limit parameters");
+          psAssert(paramsMaxUse, "Require parameter limits to limit parameters");
+          float limit = paramsMaxUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (params[nParam] > limit) {
+              params[nParam] = limit;
+              psTrace("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
+                      nParam, params[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      default:
         psAbort("invalid choice for limits");
     }
@@ -216,4 +190,5 @@
 
 // make an initial guess for parameters
+// 0.5 PIX: moments and peaks are in pixel coords, thus so are model parameters
 bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
 {
@@ -240,5 +215,4 @@
     if (!isfinite(shape.sxy)) return false;
 
-    // XXX turn this off here for now PAR[PM_PAR_SKY]  = moments->Sky;
     PAR[PM_PAR_SKY]  = 0.0;
     PAR[PM_PAR_I0]   = peak->flux;
@@ -293,5 +267,5 @@
 psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
 {
-    psF64 z, f;
+    psF64 z;
     int Nstep = 0;
     psEllipseShape shape;
@@ -299,10 +273,8 @@
     psF32 *PAR = params->data.F32;
 
-    if (flux <= 0)
-        return (1.0);
-    if (PAR[PM_PAR_I0] <= 0)
-        return (1.0);
-    if (flux >= PAR[PM_PAR_I0])
-        return (1.0);
+    if (flux <= 0) return 1.0;
+    if (PAR[PM_PAR_I0] <= 0) return 1.0;
+    if (flux >= PAR[PM_PAR_I0]) return 1.0;
+    if (PAR[PM_PAR_7] == 0.0) return powf(PAR[PM_PAR_I0] / flux - 1.0, 1.0 / ALPHA);
 
     shape.sx  = PAR[PM_PAR_SXX] / M_SQRT2;
@@ -320,23 +292,26 @@
 
     // choose a z value guaranteed to be beyond our limit
-    float z0 = pow((1.0 / limit), (1.0 / ALPHA));
-    float z1 = (1.0 / limit) / PAR[PM_PAR_7];
-    z1 = PS_MAX (z0, z1);
-    z0 = 0.0;
-
-    // perform a type of bisection to find the value
-    float f0 = 1.0 / (1 + PAR[PM_PAR_7]*z0 + pow(z0, ALPHA));
-    float f1 = 1.0 / (1 + PAR[PM_PAR_7]*z1 + pow(z1, ALPHA));
-    while ((Nstep < 10) && (fabs(z1 - z0) > 0.5)) {
-        z = 0.5*(z0 + z1);
-        f = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, ALPHA));
-        if (f > limit) {
-            z0 = z;
-            f0 = f;
-        } else {
-            z1 = z;
-            f1 = f;
-        }
-        Nstep ++;
+    float z0 = 0.0;
+    float z1 = pow((1.0 / limit), (1.0 / ALPHA));
+    psAssert (isfinite(z1), "fix this code: z1 should not be nan for %f", PAR[PM_PAR_7]);
+    if (PAR[PM_PAR_7] < 0.0) z1 *= 2.0;
+
+    // starting guess:
+    z = 0.5*(z0 + z1);
+    float dz = 1.0;
+
+    // use Newton-Raphson to minimize f(z) - limit = 0
+    for (int i = 0; (i < 10) && (fabs(dz) > 0.0001); i++) {
+	float q = (1.0 + PAR[PM_PAR_7]*z + pow(z, ALPHA));
+	float dqdz = (PAR[PM_PAR_7] + ALPHA*pow(z, ALPHA - 1.0));
+
+	float f = 1.0 / q;
+	float dfdz = -dqdz * f / q;
+
+	dz = (f - limit) / dfdz;
+
+	// fprintf (stderr, "%f %f %f : %f %f\n", f, z, dz, dfdz, q);
+	z -= dz;
+	z = PS_MAX(z, 0.0);
     }
     psF64 radius = sigma * sqrt (2.0 * z);
@@ -370,5 +345,4 @@
     // convert to shape terms (SXX,SYY,SXY)
     if (!pmPSF_FitToModel (out, 0.1)) {
-        // psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)", in[PM_PAR_YPOS], in[PM_PAR_XPOS]);
         psTrace("psModules.objects", 5, "Failed to fit object at (r,c) = (%.1f,%.1f)", in[PM_PAR_YPOS], in[PM_PAR_XPOS]);
         return false;
@@ -448,6 +422,4 @@
 bool PM_MODEL_FIT_STATUS (pmModel *model)
 {
-
-    psF32 dP;
     bool  status;
 
@@ -455,15 +427,44 @@
     psF32 *dPAR = model->dparams->data.F32;
 
-    dP = 0;
-    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
-    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
-    dP = sqrt (dP);
-
     status = true;
-//    status &= (dP < 0.5);
     status &= (PAR[PM_PAR_I0] > 0);
     status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
 
     return status;
+}
+
+
+void PM_MODEL_SET_LIMITS(pmModelLimitsType type)
+{
+    switch (type) {
+      case PM_MODEL_LIMITS_NONE:
+        paramsMinUse = NULL;
+        paramsMaxUse = NULL;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_IGNORE:
+        paramsMinUse = NULL;
+        paramsMaxUse = NULL;
+        limitsApply = false;
+        break;
+      case PM_MODEL_LIMITS_LAX:
+        paramsMinUse = paramsMinLax;
+        paramsMaxUse = paramsMaxLax;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_MODERATE:
+        paramsMinUse = paramsMinModerate;
+        paramsMaxUse = paramsMaxModerate;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_STRICT:
+        paramsMinUse = paramsMinStrict;
+        paramsMaxUse = paramsMaxStrict;
+        limitsApply = true;
+        break;
+      default:
+        psAbort("Unrecognised model limits type: %x", type);
+    }
+    return;
 }
 
@@ -476,4 +477,5 @@
 # undef PM_MODEL_PARAMS_FROM_PSF
 # undef PM_MODEL_FIT_STATUS
+# undef PM_MODEL_SET_LIMITS
 # undef ALPHA
 # undef ALPHA_M
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_PS1_V1.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_PS1_V1.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/models/pmModel_PS1_V1.h	(revision 27839)
@@ -0,0 +1,15 @@
+#ifndef PM_MODEL_PS1_V1_H
+
+#include "pmModel.h"
+
+psF32 pmModelFunc_PS1_V1(psVector *deriv, const psVector *params, const psVector *pixcoord);
+bool pmModelLimits_PS1_V1(psMinConstraintMode mode, int nParam, float *params, float *beta);
+bool pmModelGuess_PS1_V1(pmModel *model, pmSource *source);
+psF64 pmModelFlux_PS1_V1(const psVector *params);
+psF64 pmModelRadius_PS1_V1(const psVector *params, psF64 flux);
+bool pmModelFromPSF_PS1_V1(pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf);
+bool  pmModelParamsFromPSF_PS1_V1(pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io);
+bool pmModelFitStatus_PS1_V1(pmModel *model);
+void pmModelSetLimits_PS1_V1(pmModelLimitsType type);
+
+#endif
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_QGAUSS.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_QGAUSS.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/models/pmModel_QGAUSS.c	(revision 27839)
@@ -1,5 +1,5 @@
 /******************************************************************************
  * this file defines the QGAUSS source shape model (XXX need a better name!).  Note that these
- * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
+ * model functions are loaded by pmModelClass.c using 'include', and thus need no 'include'
  * statements of their own.  The models use a psVector to represent the set of parameters, with
  * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
@@ -20,4 +20,13 @@
    *****************************************************************************/
 
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmMoments.h"
+#include "pmPeaks.h"
+#include "pmSource.h"
+#include "pmModel.h"
+#include "pmModel_QGAUSS.h"
+
 # define PM_MODEL_FUNC            pmModelFunc_QGAUSS
 # define PM_MODEL_FLUX            pmModelFlux_QGAUSS
@@ -28,4 +37,33 @@
 # define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_QGAUSS
 # define PM_MODEL_FIT_STATUS      pmModelFitStatus_QGAUSS
+# define PM_MODEL_SET_LIMITS      pmModelSetLimits_QGAUSS
+
+# define ALPHA   2.250
+# define ALPHA_M 1.250
+
+// the model is a function of the pixel coordinate (pixcoord[0,1] = x,y)
+// 0.5 PIX: the parameters are defined in terms of pixel coords, so the incoming pixcoords
+// values need to be pixel coords
+
+// Lax parameter limits
+static float paramsMinLax[] = { -1.0e3, 1.0e-2, -100, -100, 0.5, 0.5, -1.0, -1.0 };
+static float paramsMaxLax[] = { 1.0e5, 1.0e8, 1.0e4, 1.0e4, 100, 100, 1.0, 20.0 };
+
+// Moderate parameter limits
+// Tolerate a small divot (k < 0)
+static float paramsMinModerate[] = { -1.0e3, 1.0e-2, -100, -100, 0.5, 0.5, -1.0, -0.05 };
+static float paramsMaxModerate[] = { 1.0e5, 1.0e8, 1.0e4, 1.0e4, 100, 100, 1.0, 20.0 };
+
+// Strict parameter limits
+// k = PAR_7 < 0 is very undesirable (big divot in the middle)
+static float paramsMinStrict[] = { -1.0e3, 1.0e-2, -100, -100, 0.5, 0.5, -1.0, 0.0 };
+static float paramsMaxStrict[] = { 1.0e5, 1.0e8, 1.0e4, 1.0e4, 100, 100, 1.0, 20.0 };
+
+// Parameter limits to use
+static float *paramsMinUse = paramsMinLax;
+static float *paramsMaxUse = paramsMaxLax;
+static float betaUse[] = { 1000, 3e6, 5, 5, 1.0, 1.0, 0.5, 2.0 };
+
+static bool limitsApply = true;         // Apply limits?
 
 psF32 PM_MODEL_FUNC (psVector *deriv,
@@ -48,5 +86,5 @@
     assert (z >= 0);
 
-    psF32 zp = pow(z,1.25);
+    psF32 zp = pow(z,ALPHA_M);
     psF32 r  = 1.0 / (1 + PAR[PM_PAR_7]*z + z*zp);
 
@@ -59,5 +97,5 @@
         // note difference from a pure gaussian: q = params->data.F32[PM_PAR_I0]*r
         psF32 t = r1*r;
-        psF32 q = t*(PAR[PM_PAR_7] + 2.25*zp);
+        psF32 q = t*(PAR[PM_PAR_7] + ALPHA*zp);
 
         dPAR[PM_PAR_SKY]  = +1.0;
@@ -79,129 +117,69 @@
 # define AR_MAX 20.0
 # define AR_RATIO 0.99
+
 bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
 {
-    float beta_lim = 0, params_min = 0, params_max = 0;
-    float f1 = 0, f2 = 0, q1 = 0, q2 = 0;
+    if (!limitsApply) {
+        return true;
+    }
+    psAssert(nParam >= 0 && nParam <= PM_PAR_7, "Parameter index is out of bounds");
 
     // we need to calculate the limits for SXY specially
+    float q2 = NAN;
     if (nParam == PM_PAR_SXY) {
-        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
-        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
-        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        float f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        float f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        float q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
         q1 = (q1 < 0.0) ? 0.0 : q1;
         // if q1 < 0.0, f2 ~ f1, we have a very large axis ratio near 45deg..  Saturate at that
         // angle and let f2,f1 fight it out
-        q2  = 0.5*sqrt (q1);
+        q2 = 0.5*sqrtf(q1);
     }
 
     switch (mode) {
-    case PS_MINIMIZE_BETA_LIMIT:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            beta_lim = 1000;
-            break;
-        case PM_PAR_I0:
-            beta_lim = 3e6;
-            break;
-        case PM_PAR_XPOS:
-            beta_lim = 5;
-            break;
-        case PM_PAR_YPOS:
-            beta_lim = 5;
-            break;
-        case PM_PAR_SXX:
-            beta_lim = 1.0;
-            break;
-        case PM_PAR_SYY:
-            beta_lim = 1.0;
-            break;
-        case PM_PAR_SXY:
-            beta_lim =  0.5*q2;
-            break;
-        case PM_PAR_7:
-            beta_lim = 2.0;
-            break;
-        default:
-            psAbort("invalid parameter %d for beta test", nParam);
-        }
-        if (fabs(beta[nParam]) > fabs(beta_lim)) {
-            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
-            psTrace ("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
-                     nParam, beta[nParam], beta_lim);
-            return false;
-        }
-        return true;
-    case PS_MINIMIZE_PARAM_MIN:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            params_min = -1000;
-            break;
-        case PM_PAR_I0:
-            params_min =   0.01;
-            break;
-        case PM_PAR_XPOS:
-            params_min =  -100;
-            break;
-        case PM_PAR_YPOS:
-            params_min =  -100;
-            break;
-        case PM_PAR_SXX:
-            params_min =   0.5;
-            break;
-        case PM_PAR_SYY:
-            params_min =   0.5;
-            break;
-        case PM_PAR_SXY:
-            params_min =  -q2;
-            break;
-        case PM_PAR_7:
-            params_min =   0.1;
-            break;
-        default:
-            psAbort("invalid parameter %d for param min test", nParam);
-        }
-        if (params[nParam] < params_min) {
-            params[nParam] = params_min;
-            psTrace ("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
-                     nParam, params[nParam], params_min);
-            return false;
-        }
-        return true;
-    case PS_MINIMIZE_PARAM_MAX:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            params_max =   1e5;
-            break;
-        case PM_PAR_I0:
-            params_max =   1e8;
-            break;
-        case PM_PAR_XPOS:
-            params_max =   1e4;
-            break;
-        case PM_PAR_YPOS:
-            params_max =   1e4;
-            break;
-        case PM_PAR_SXX:
-            params_max =   100;
-            break;
-        case PM_PAR_SYY:
-            params_max =   100;
-            break;
-        case PM_PAR_SXY:
-            params_max =  +q2;
-            break;
-        case PM_PAR_7:
-            params_max =  20.0;
-            break;
-        default:
-            psAbort("invalid parameter %d for param max test", nParam);
-        }
-        if (params[nParam] > params_max) {
-            params[nParam] = params_max;
-            psTrace ("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
-                     nParam, params[nParam], params_max);
-            return false;
-        }
-        return true;
+      case PS_MINIMIZE_BETA_LIMIT: {
+          psAssert(beta, "Require beta to limit beta");
+          float limit = betaUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (fabs(beta[nParam]) > fabs(limit)) {
+              beta[nParam] = (beta[nParam] > 0) ? fabs(limit) : -fabs(limit);
+              psTrace("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
+                      nParam, beta[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      case PS_MINIMIZE_PARAM_MIN: {
+          psAssert(params, "Require parameters to limit parameters");
+          psAssert(paramsMinUse, "Require parameter limits to limit parameters");
+          float limit = paramsMinUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (params[nParam] < limit) {
+              params[nParam] = limit;
+              psTrace("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
+                      nParam, params[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      case PS_MINIMIZE_PARAM_MAX: {
+          psAssert(params, "Require parameters to limit parameters");
+          psAssert(paramsMaxUse, "Require parameter limits to limit parameters");
+          float limit = paramsMaxUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (params[nParam] > limit) {
+              params[nParam] = limit;
+              psTrace("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
+                      nParam, params[nParam], limit);
+              return false;
+          }
+          return true;
+      }
     default:
         psAbort("invalid choice for limits");
@@ -213,4 +191,5 @@
 
 // make an initial guess for parameters
+// 0.5 PIX: moments and peaks are in pixel coords, thus so are model parameters
 bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
 {
@@ -237,5 +216,4 @@
     if (!isfinite(shape.sxy)) return false;
 
-    // XXX turn this off here for now PAR[PM_PAR_SKY]  = moments->Sky;
     PAR[PM_PAR_SKY]  = 0.0;
     PAR[PM_PAR_I0]   = peak->flux;
@@ -273,7 +251,7 @@
     float f1, f2;
     for (z = DZ; z < 50; z += DZ) {
-        f1 = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, 2.25));
+        f1 = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, ALPHA));
         z += DZ;
-        f2 = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, 2.25));
+        f2 = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, ALPHA));
         norm += f0 + 4*f1 + f2;
         f0 = f2;
@@ -290,5 +268,5 @@
 psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
 {
-    psF64 z, f;
+    psF64 z;
     int Nstep = 0;
     psEllipseShape shape;
@@ -296,10 +274,8 @@
     psF32 *PAR = params->data.F32;
 
-    if (flux <= 0)
-        return (1.0);
-    if (PAR[PM_PAR_I0] <= 0)
-        return (1.0);
-    if (flux >= PAR[PM_PAR_I0])
-        return (1.0);
+    if (flux <= 0) return 1.0;
+    if (PAR[PM_PAR_I0] <= 0) return 1.0;
+    if (flux >= PAR[PM_PAR_I0]) return 1.0;
+    if (PAR[PM_PAR_7] == 0.0) return powf(PAR[PM_PAR_I0] / flux - 1.0, 1.0 / ALPHA);
 
     shape.sx  = PAR[PM_PAR_SXX] / M_SQRT2;
@@ -317,23 +293,26 @@
 
     // choose a z value guaranteed to be beyond our limit
-    float z0 = pow((1.0 / limit), (1.0 / 2.25));
-    float z1 = (1.0 / limit) / PAR[PM_PAR_7];
-    z1 = PS_MAX (z0, z1);
-    z0 = 0.0;
-
-    // perform a type of bisection to find the value
-    float f0 = 1.0 / (1 + PAR[PM_PAR_7]*z0 + pow(z0, 2.25));
-    float f1 = 1.0 / (1 + PAR[PM_PAR_7]*z1 + pow(z1, 2.25));
-    while ((Nstep < 10) && (fabs(z1 - z0) > 0.5)) {
-        z = 0.5*(z0 + z1);
-        f = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, 2.25));
-        if (f > limit) {
-            z0 = z;
-            f0 = f;
-        } else {
-            z1 = z;
-            f1 = f;
-        }
-        Nstep ++;
+    float z0 = 0.0;
+    float z1 = pow((1.0 / limit), (1.0 / ALPHA));
+    psAssert (isfinite(z1), "fix this code: z1 should not be nan for %f", PAR[PM_PAR_7]);
+    if (PAR[PM_PAR_7] < 0.0) z1 *= 2.0;
+
+    // starting guess:
+    z = 0.5*(z0 + z1);
+    float dz = 1.0;
+
+    // use Newton-Raphson to minimize f(z) - limit = 0
+    for (int i = 0; (i < 10) && (fabs(dz) > 0.0001); i++) {
+	float q = (1.0 + PAR[PM_PAR_7]*z + pow(z, ALPHA));
+	float dqdz = (PAR[PM_PAR_7] + ALPHA*pow(z, ALPHA - 1.0));
+
+	float f = 1.0 / q;
+	float dfdz = -dqdz * f / q;
+
+	dz = (f - limit) / dfdz;
+
+	// fprintf (stderr, "%f %f %f : %f %f\n", f, z, dz, dfdz, q);
+	z -= dz;
+	z = PS_MAX(z, 0.0);
     }
     psF64 radius = sigma * sqrt (2.0 * z);
@@ -444,6 +423,4 @@
 bool PM_MODEL_FIT_STATUS (pmModel *model)
 {
-
-    psF32 dP;
     bool  status;
 
@@ -451,15 +428,44 @@
     psF32 *dPAR = model->dparams->data.F32;
 
-    dP = 0;
-    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
-    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
-    dP = sqrt (dP);
-
     status = true;
-//    status &= (dP < 0.5);
     status &= (PAR[PM_PAR_I0] > 0);
     status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
 
     return status;
+}
+
+
+void PM_MODEL_SET_LIMITS(pmModelLimitsType type)
+{
+    switch (type) {
+      case PM_MODEL_LIMITS_NONE:
+        paramsMinUse = NULL;
+        paramsMaxUse = NULL;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_IGNORE:
+        paramsMinUse = NULL;
+        paramsMaxUse = NULL;
+        limitsApply = false;
+        break;
+      case PM_MODEL_LIMITS_LAX:
+        paramsMinUse = paramsMinLax;
+        paramsMaxUse = paramsMaxLax;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_MODERATE:
+        paramsMinUse = paramsMinModerate;
+        paramsMaxUse = paramsMaxModerate;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_STRICT:
+        paramsMinUse = paramsMinStrict;
+        paramsMaxUse = paramsMaxStrict;
+        limitsApply = true;
+        break;
+      default:
+        psAbort("Unrecognised model limits type: %x", type);
+    }
+    return;
 }
 
@@ -472,2 +478,5 @@
 # undef PM_MODEL_PARAMS_FROM_PSF
 # undef PM_MODEL_FIT_STATUS
+# undef PM_MODEL_SET_LIMITS
+# undef ALPHA
+# undef ALPHA_M
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_QGAUSS.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_QGAUSS.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/models/pmModel_QGAUSS.h	(revision 27839)
@@ -0,0 +1,15 @@
+#ifndef PM_MODEL_QGAUSS_H
+
+#include "pmModel.h"
+
+psF32 pmModelFunc_QGAUSS(psVector *deriv, const psVector *params, const psVector *pixcoord);
+bool pmModelLimits_QGAUSS(psMinConstraintMode mode, int nParam, float *params, float *beta);
+bool pmModelGuess_QGAUSS(pmModel *model, pmSource *source);
+psF64 pmModelFlux_QGAUSS(const psVector *params);
+psF64 pmModelRadius_QGAUSS(const psVector *params, psF64 flux);
+bool pmModelFromPSF_QGAUSS(pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf);
+bool  pmModelParamsFromPSF_QGAUSS(pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io);
+bool pmModelFitStatus_QGAUSS(pmModel *model);
+void pmModelSetLimits_QGAUSS(pmModelLimitsType type);
+
+#endif
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_RGAUSS.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_RGAUSS.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/models/pmModel_RGAUSS.c	(revision 27839)
@@ -1,8 +1,8 @@
 /******************************************************************************
  * this file defines the RGAUSS source shape model (XXX need a better name!).  Note that these
- * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
+ * model functions are loaded by pmModelClass.c using 'include', and thus need no 'include'
  * statements of their own.  The models use a psVector to represent the set of parameters, with
  * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
- * may thus vary depending on the specifics of the model.  All models which are used a PSF
+ * may thus vary depending on the specifics of the model.  All models which are used as a PSF
  * representations share a few parameters, for which # define names are listed in pmModel.h:
 
@@ -20,4 +20,13 @@
  *****************************************************************************/
 
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmMoments.h"
+#include "pmPeaks.h"
+#include "pmSource.h"
+#include "pmModel.h"
+#include "pmModel_RGAUSS.h"
+
 # define PM_MODEL_FUNC            pmModelFunc_RGAUSS
 # define PM_MODEL_FLUX            pmModelFlux_RGAUSS
@@ -28,4 +37,28 @@
 # define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_RGAUSS
 # define PM_MODEL_FIT_STATUS      pmModelFitStatus_RGAUSS
+# define PM_MODEL_SET_LIMITS      pmModelSetLimits_RGAUSS
+
+// the model is a function of the pixel coordinate (pixcoord[0,1] = x,y)
+// 0.5 PIX: the parameters are defined in terms of pixel coords, so the incoming pixcoords
+// values need to be pixel coords
+
+// Lax parameter limits
+static float paramsMinLax[] = { -1.0e3, 1.0e-2, -100, -100, 0.5, 0.5, -1.0, 1.25 };
+static float paramsMaxLax[] = { 1.0e5, 1.0e8, 1.0e4, 1.0e4, 100, 100, 1.0, 4.0 };
+
+// Moderate parameter limits
+static float *paramsMinModerate = paramsMinLax;
+static float *paramsMaxModerate = paramsMaxLax;
+
+// Strict parameter limits
+static float *paramsMinStrict = paramsMinLax;
+static float *paramsMaxStrict = paramsMaxLax;
+
+// Parameter limits to use
+static float *paramsMinUse = paramsMinLax;
+static float *paramsMaxUse = paramsMaxLax;
+static float betaUse[] = { 1000, 3e6, 5, 5, 0.5, 0.5, 0.5, 0.5 };
+
+static bool limitsApply = true;         // Apply limits?
 
 psF32 PM_MODEL_FUNC (psVector *deriv,
@@ -62,5 +95,5 @@
         dPAR[PM_PAR_SXY] = -q*X*Y;
 
-        // this model derivative is undefined at z = 0.0, but is actually 0.0
+        // this model derivative is undefined at z = 0.0, but the limit is zero as z -> 0.0
         dPAR[PM_PAR_7] = (z == 0.0) ? 0.0 : -5.0*t*log(z)*p*z;
     }
@@ -73,130 +106,70 @@
 # define AR_MAX 20.0
 # define AR_RATIO 0.99
+
 bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
 {
-    float beta_lim = 0, params_min = 0, params_max = 0;
-    float f1 = 0, f2 = 0, q1 = 0, q2 = 0;
+    if (!limitsApply) {
+        return true;
+    }
+    psAssert(nParam >= 0 && nParam <= PM_PAR_7, "Parameter index is out of bounds");
 
     // we need to calculate the limits for SXY specially
+    float q2 = NAN;
     if (nParam == PM_PAR_SXY) {
-        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
-        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
-        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        float f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        float f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        float q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
         q1 = (q1 < 0.0) ? 0.0 : q1;
         // if q1 < 0.0, f2 ~ f1, we have a very large axis ratio near 45deg..  Saturate at that
         // angle and let f2,f1 fight it out
-        q2  = 0.5*sqrt (q1);
+        q2 = 0.5*sqrtf(q1);
     }
 
     switch (mode) {
-    case PS_MINIMIZE_BETA_LIMIT:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            beta_lim = 1000;
-            break;
-        case PM_PAR_I0:
-            beta_lim = 3e6;
-            break;
-        case PM_PAR_XPOS:
-            beta_lim = 5;
-            break;
-        case PM_PAR_YPOS:
-            beta_lim = 5;
-            break;
-        case PM_PAR_SXX:
-            beta_lim = 0.5;
-            break;
-        case PM_PAR_SYY:
-            beta_lim = 0.5;
-            break;
-        case PM_PAR_SXY:
-            beta_lim =  0.5*q2;
-            break;
-        case PM_PAR_7:
-            beta_lim = 0.5;
-            break;
-        default:
-            psAbort("invalid parameter %d for beta test", nParam);
-        }
-        if (fabs(beta[nParam]) > fabs(beta_lim)) {
-            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
-            psTrace ("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
-                     nParam, beta[nParam], beta_lim);
-            return false;
-        }
-        return true;
-    case PS_MINIMIZE_PARAM_MIN:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            params_min = -1000;
-            break;
-        case PM_PAR_I0:
-            params_min =   0.01;
-            break;
-        case PM_PAR_XPOS:
-            params_min =  -100;
-            break;
-        case PM_PAR_YPOS:
-            params_min =  -100;
-            break;
-        case PM_PAR_SXX:
-            params_min =   0.5;
-            break;
-        case PM_PAR_SYY:
-            params_min =   0.5;
-            break;
-        case PM_PAR_SXY:
-            params_min =  -q2;
-            break;
-        case PM_PAR_7:
-            params_min =   1.25;
-            break;
-        default:
-            psAbort("invalid parameter %d for param min test", nParam);
-        }
-        if (params[nParam] < params_min) {
-            params[nParam] = params_min;
-            psTrace ("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
-                     nParam, params[nParam], params_min);
-            return false;
-        }
-        return true;
-    case PS_MINIMIZE_PARAM_MAX:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            params_max =   1e5;
-            break;
-        case PM_PAR_I0:
-            params_max =   1e8;
-            break;
-        case PM_PAR_XPOS:
-            params_max =   1e4;
-            break;
-        case PM_PAR_YPOS:
-            params_max =   1e4;
-            break;
-        case PM_PAR_SXX:
-            params_max =   100;
-            break;
-        case PM_PAR_SYY:
-            params_max =   100;
-            break;
-        case PM_PAR_SXY:
-            params_max =  +q2;
-            break;
-        case PM_PAR_7:
-            params_max =  4.0;
-            break;
-        default:
-            psAbort("invalid parameter %d for param max test", nParam);
-        }
-        if (params[nParam] > params_max) {
-            params[nParam] = params_max;
-            psTrace ("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
-                     nParam, params[nParam], params_max);
-            return false;
-        }
-        return true;
-    default:
+      case PS_MINIMIZE_BETA_LIMIT: {
+          psAssert(beta, "Require beta to limit beta");
+          float limit = betaUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (fabs(beta[nParam]) > fabs(limit)) {
+              beta[nParam] = (beta[nParam] > 0) ? fabs(limit) : -fabs(limit);
+              psTrace("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
+                      nParam, beta[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      case PS_MINIMIZE_PARAM_MIN: {
+          psAssert(params, "Require parameters to limit parameters");
+          psAssert(paramsMinUse, "Require parameter limits to limit parameters");
+          float limit = paramsMinUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (params[nParam] < limit) {
+              params[nParam] = limit;
+              psTrace("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
+                      nParam, params[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      case PS_MINIMIZE_PARAM_MAX: {
+          psAssert(params, "Require parameters to limit parameters");
+          psAssert(paramsMaxUse, "Require parameter limits to limit parameters");
+          float limit = paramsMaxUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (params[nParam] > limit) {
+              params[nParam] = limit;
+              psTrace("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
+                      nParam, params[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      default:
         psAbort("invalid choice for limits");
     }
@@ -205,5 +178,7 @@
 }
 
+
 // make an initial guess for parameters
+// 0.5 PIX: moments and peaks are in pixel coords, thus so are model parameters
 bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
 {
@@ -230,5 +205,5 @@
     if (!isfinite(shape.sxy)) return false;
 
-    PAR[PM_PAR_SKY]  = moments->Sky;
+    PAR[PM_PAR_SKY]  = 0.0;
     PAR[PM_PAR_I0]   = peak->flux;
     PAR[PM_PAR_XPOS] = peak->xf;
@@ -282,5 +257,5 @@
 psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
 {
-    psF64 z, f;
+    psF64 z;
     int Nstep = 0;
     psEllipseShape shape;
@@ -310,23 +285,29 @@
     // choose a z value guaranteed to be beyond our limit
     float z0 = pow((1.0 / limit), (1.0 / PAR[PM_PAR_7]));
+    psAssert (isfinite(z0), "fix this code: z0 should not be nan for %f", PAR[PM_PAR_7]);
     float z1 = (1.0 / limit);
+    psAssert (isfinite(z1), "fix this code: z1 should not be nan for %f", PAR[PM_PAR_7]);
     z1 = PS_MAX (z0, z1);
     z0 = 0.0;
 
-    // perform a type of bisection to find the value
-    float f0 = 1.0 / (1 + z0 + pow(z0, PAR[PM_PAR_7]));
-    float f1 = 1.0 / (1 + z1 + pow(z1, PAR[PM_PAR_7]));
-    while ((Nstep < 10) && (fabs(z1 - z0) > 0.5)) {
-        z = 0.5*(z0 + z1);
-        f = 1.0 / (1 + z + pow(z, PAR[PM_PAR_7]));
-        if (f > limit) {
-            z0 = z;
-            f0 = f;
-        } else {
-            z1 = z;
-            f1 = f;
-        }
-        Nstep ++;
-    }
+    // starting guess:
+    z = 0.5*(z0 + z1);
+    float dz = 1.0;
+
+    for (int i = 0; (i < 10) && (fabs(dz) > 0.0001); i++) {
+	// use Newton-Raphson to minimize f(z) - limit = 0
+	float q = (1 + z + pow(z,PAR[PM_PAR_7]));
+	float dqdz = (1.0 + PAR[PM_PAR_7]*pow(z,PAR[PM_PAR_7] - 1.0));
+
+	float f = 1.0 / q;
+	float dfdz = -dqdz * f / q;
+
+	dz = (f - limit) / dfdz;
+
+	// fprintf (stderr, "%f %f %f : %f %f\n", f, z, dz, dfdz, q);
+	z -= dz;
+	z = PS_MAX(z, 0.0);
+    }
+
     psF64 radius = sigma * sqrt (2.0 * z);
 
@@ -436,6 +417,4 @@
 bool PM_MODEL_FIT_STATUS (pmModel *model)
 {
-
-    psF32 dP;
     bool  status;
 
@@ -443,15 +422,44 @@
     psF32 *dPAR = model->dparams->data.F32;
 
-    dP = 0;
-    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
-    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
-    dP = sqrt (dP);
-
     status = true;
-    status &= (dP < 0.5);
     status &= (PAR[PM_PAR_I0] > 0);
     status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
 
     return status;
+}
+
+
+void PM_MODEL_SET_LIMITS(pmModelLimitsType type)
+{
+    switch (type) {
+      case PM_MODEL_LIMITS_NONE:
+        paramsMinUse = NULL;
+        paramsMaxUse = NULL;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_IGNORE:
+        paramsMinUse = NULL;
+        paramsMaxUse = NULL;
+        limitsApply = false;
+        break;
+      case PM_MODEL_LIMITS_LAX:
+        paramsMinUse = paramsMinLax;
+        paramsMaxUse = paramsMaxLax;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_MODERATE:
+        paramsMinUse = paramsMinModerate;
+        paramsMaxUse = paramsMaxModerate;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_STRICT:
+        paramsMinUse = paramsMinStrict;
+        paramsMaxUse = paramsMaxStrict;
+        limitsApply = true;
+        break;
+      default:
+        psAbort("Unrecognised model limits type: %x", type);
+    }
+    return;
 }
 
@@ -464,2 +472,3 @@
 # undef PM_MODEL_PARAMS_FROM_PSF
 # undef PM_MODEL_FIT_STATUS
+# undef PM_MODEL_SET_LIMITS
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_RGAUSS.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_RGAUSS.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/models/pmModel_RGAUSS.h	(revision 27839)
@@ -0,0 +1,15 @@
+#ifndef PM_MODEL_RGAUSS_H
+
+#include "pmModel.h"
+
+psF32 pmModelFunc_RGAUSS(psVector *deriv, const psVector *params, const psVector *pixcoord);
+bool pmModelLimits_RGAUSS(psMinConstraintMode mode, int nParam, float *params, float *beta);
+bool pmModelGuess_RGAUSS(pmModel *model, pmSource *source);
+psF64 pmModelFlux_RGAUSS(const psVector *params);
+psF64 pmModelRadius_RGAUSS(const psVector *params, psF64 flux);
+bool pmModelFromPSF_RGAUSS(pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf);
+bool  pmModelParamsFromPSF_RGAUSS(pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io);
+bool pmModelFitStatus_RGAUSS(pmModel *model);
+void pmModelSetLimits_RGAUSS(pmModelLimitsType type);
+
+#endif
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_SERSIC.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_SERSIC.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/models/pmModel_SERSIC.c	(revision 27839)
@@ -1,8 +1,8 @@
 /******************************************************************************
  * this file defines the SERSIC source shape model.  Note that these model functions are loaded
- * by pmModelGroup.c using 'include', and thus need no 'include' statements of their own.  The
+ * by pmModelClass.c using 'include', and thus need no 'include' statements of their own.  The
  * models use a psVector to represent the set of parameters, with the sequence used to specify
  * the meaning of the parameter.  The meaning of the parameters may thus vary depending on the
- * specifics of the model.  All models which are used a PSF representations share a few
+ * specifics of the model.  All models which are used as a PSF representations share a few
  * parameters, for which # define names are listed in pmModel.h:
 
@@ -23,4 +23,13 @@
    *****************************************************************************/
 
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmMoments.h"
+#include "pmPeaks.h"
+#include "pmSource.h"
+#include "pmModel.h"
+#include "pmModel_SERSIC.h"
+
 # define PM_MODEL_FUNC            pmModelFunc_SERSIC
 # define PM_MODEL_FLUX            pmModelFlux_SERSIC
@@ -31,4 +40,28 @@
 # define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_SERSIC
 # define PM_MODEL_FIT_STATUS      pmModelFitStatus_SERSIC
+# define PM_MODEL_SET_LIMITS      pmModelSetLimits_SERSIC
+
+// the model is a function of the pixel coordinate (pixcoord[0,1] = x,y)
+// 0.5 PIX: the parameters are defined in terms of pixel coords, so the incoming pixcoords
+// values need to be pixel coords
+
+// Lax parameter limits
+static float paramsMinLax[] = { -1.0e3, 1.0e-2, -100, -100, 0.05, 0.05, -1.0, 0.05 };
+static float paramsMaxLax[] = { 1.0e5, 1.0e8, 1.0e4, 1.0e4, 100, 100, 1.0, 4.0 };
+
+// Moderate parameter limits
+static float *paramsMinModerate = paramsMinLax;
+static float *paramsMaxModerate = paramsMaxLax;
+
+// Strict parameter limits
+static float *paramsMinStrict = paramsMinLax;
+static float *paramsMaxStrict = paramsMaxLax;
+
+// Parameter limits to use
+static float *paramsMinUse = paramsMinLax;
+static float *paramsMaxUse = paramsMaxLax;
+static float betaUse[] = { 1000, 3e6, 5, 5, 1.0, 1.0, 0.5, 2.0 };
+
+static bool limitsApply = true;         // Apply limits?
 
 psF32 PM_MODEL_FUNC (psVector *deriv,
@@ -91,128 +124,67 @@
 bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
 {
-    float beta_lim = 0, params_min = 0, params_max = 0;
-    float f1 = 0, f2 = 0, q1 = 0, q2 = 0;
+    if (!limitsApply) {
+        return true;
+    }
+    psAssert(nParam >= 0 && nParam <= PM_PAR_7, "Parameter index is out of bounds");
 
     // we need to calculate the limits for SXY specially
+    float q2 = NAN;
     if (nParam == PM_PAR_SXY) {
-        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
-        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
-        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        float f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        float f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        float q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
         q1 = (q1 < 0.0) ? 0.0 : q1;
         // if q1 < 0.0, f2 ~ f1, we have a very large axis ratio near 45deg..  Saturate at that
         // angle and let f2,f1 fight it out
-        q2  = 0.5*sqrt (q1);
+        q2 = 0.5*sqrtf(q1);
     }
 
     switch (mode) {
-    case PS_MINIMIZE_BETA_LIMIT:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            beta_lim = 1000;
-            break;
-        case PM_PAR_I0:
-            beta_lim = 3e6;
-            break;
-        case PM_PAR_XPOS:
-            beta_lim = 5;
-            break;
-        case PM_PAR_YPOS:
-            beta_lim = 5;
-            break;
-        case PM_PAR_SXX:
-            beta_lim = 1.0;
-            break;
-        case PM_PAR_SYY:
-            beta_lim = 1.0;
-            break;
-        case PM_PAR_SXY:
-            beta_lim =  0.5*q2;
-            break;
-        case PM_PAR_7:
-            beta_lim = 2.0;
-            break;
-        default:
-            psAbort("invalid parameter %d for beta test", nParam);
-        }
-        if (fabs(beta[nParam]) > fabs(beta_lim)) {
-            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
-            psTrace ("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
-                     nParam, beta[nParam], beta_lim);
-            return false;
-        }
-        return true;
-    case PS_MINIMIZE_PARAM_MIN:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            params_min = -1000;
-            break;
-        case PM_PAR_I0:
-            params_min =     0.01;
-            break;
-        case PM_PAR_XPOS:
-            params_min =  -100;
-            break;
-        case PM_PAR_YPOS:
-            params_min =  -100;
-            break;
-        case PM_PAR_SXX:
-            params_min =   0.05;
-            break;
-        case PM_PAR_SYY:
-            params_min =   0.05;
-            break;
-        case PM_PAR_SXY:
-            params_min =  -q2;
-            break;
-        case PM_PAR_7:
-            params_min =   0.05;
-            break;
-        default:
-            psAbort("invalid parameter %d for param min test", nParam);
-        }
-        if (params[nParam] < params_min) {
-            params[nParam] = params_min;
-            psTrace ("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
-                     nParam, params[nParam], params_min);
-            return false;
-        }
-        return true;
-    case PS_MINIMIZE_PARAM_MAX:
-        switch (nParam) {
-        case PM_PAR_SKY:
-            params_max =   1e5;
-            break;
-        case PM_PAR_I0:
-            params_max =   1e8;
-            break;
-        case PM_PAR_XPOS:
-            params_max =   1e4;
-            break;
-        case PM_PAR_YPOS:
-            params_max =   1e4;
-            break;
-        case PM_PAR_SXX:
-            params_max =   100;
-            break;
-        case PM_PAR_SYY:
-            params_max =   100;
-            break;
-        case PM_PAR_SXY:
-            params_max =  +q2;
-            break;
-        case PM_PAR_7:
-            params_max =   4.0;
-            break;
-        default:
-            psAbort("invalid parameter %d for param max test", nParam);
-        }
-        if (params[nParam] > params_max) {
-            params[nParam] = params_max;
-            psTrace ("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
-                     nParam, params[nParam], params_max);
-            return false;
-        }
-        return true;
-    default:
+      case PS_MINIMIZE_BETA_LIMIT: {
+          psAssert(beta, "Require beta to limit beta");
+          float limit = betaUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (fabs(beta[nParam]) > fabs(limit)) {
+              beta[nParam] = (beta[nParam] > 0) ? fabs(limit) : -fabs(limit);
+              psTrace("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
+                      nParam, beta[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      case PS_MINIMIZE_PARAM_MIN: {
+          psAssert(params, "Require parameters to limit parameters");
+          psAssert(paramsMinUse, "Require parameter limits to limit parameters");
+          float limit = paramsMinUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (params[nParam] < limit) {
+              params[nParam] = limit;
+              psTrace("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
+                      nParam, params[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      case PS_MINIMIZE_PARAM_MAX: {
+          psAssert(params, "Require parameters to limit parameters");
+          psAssert(paramsMaxUse, "Require parameter limits to limit parameters");
+          float limit = paramsMaxUse[nParam];
+          if (nParam == PM_PAR_SXY) {
+              limit *= q2;
+          }
+          if (params[nParam] > limit) {
+              params[nParam] = limit;
+              psTrace("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
+                      nParam, params[nParam], limit);
+              return false;
+          }
+          return true;
+      }
+      default:
         psAbort("invalid choice for limits");
     }
@@ -221,6 +193,6 @@
 }
 
-
 // make an initial guess for parameters
+// 0.5 PIX: moments and peaks are in pixel coords, thus so are model parameters
 bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
 {
@@ -247,5 +219,4 @@
     if (!isfinite(shape.sxy)) return false;
 
-    // XXX PAR[PM_PAR_SKY]  = moments->Sky;
     PAR[PM_PAR_SKY]  = 0.0;
     PAR[PM_PAR_I0]   = peak->flux;
@@ -321,6 +292,8 @@
 
     psF64 z = pow (-log(limit), (1.0 / PAR[PM_PAR_7]));
+    psAssert (isfinite(z), "fix this code: z should not be nan for %f", PAR[PM_PAR_7]);
 
     psF64 radius = sigma * sqrt (2.0 * z);
+    psAssert (isfinite(radius), "fix this code: radius should not be nan for %f, %f", PAR[PM_PAR_7], sigma);
 
     if (isnan(radius))
@@ -429,6 +402,4 @@
 bool PM_MODEL_FIT_STATUS (pmModel *model)
 {
-
-    psF32 dP;
     bool  status;
 
@@ -436,18 +407,44 @@
     psF32 *dPAR = model->dparams->data.F32;
 
-    dP = 0;
-    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
-    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
-    dP = sqrt (dP);
-
     status = true;
-//    status &= (dP < 0.5);
     status &= (PAR[PM_PAR_I0] > 0);
     status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
 
-    fprintf (stderr, "SERSIC status pars: dP: %f, I0: %f, S/N: %f\n",
-	     dP, PAR[PM_PAR_I0], (dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]));
-
     return status;
+}
+
+
+void PM_MODEL_SET_LIMITS(pmModelLimitsType type)
+{
+    switch (type) {
+      case PM_MODEL_LIMITS_NONE:
+        paramsMinUse = NULL;
+        paramsMaxUse = NULL;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_IGNORE:
+        paramsMinUse = NULL;
+        paramsMaxUse = NULL;
+        limitsApply = false;
+        break;
+      case PM_MODEL_LIMITS_LAX:
+        paramsMinUse = paramsMinLax;
+        paramsMaxUse = paramsMaxLax;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_MODERATE:
+        paramsMinUse = paramsMinModerate;
+        paramsMaxUse = paramsMaxModerate;
+        limitsApply = true;
+        break;
+      case PM_MODEL_LIMITS_STRICT:
+        paramsMinUse = paramsMinStrict;
+        paramsMaxUse = paramsMaxStrict;
+        limitsApply = true;
+        break;
+      default:
+        psAbort("Unrecognised model limits type: %x", type);
+    }
+    return;
 }
 
@@ -460,2 +457,3 @@
 # undef PM_MODEL_PARAMS_FROM_PSF
 # undef PM_MODEL_FIT_STATUS
+# undef PM_MODEL_SET_LIMITS
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_SERSIC.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_SERSIC.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/models/pmModel_SERSIC.h	(revision 27839)
@@ -0,0 +1,15 @@
+#ifndef PM_MODEL_SERSIC_H
+
+#include "pmModel.h"
+
+psF32 pmModelFunc_SERSIC(psVector *deriv, const psVector *params, const psVector *pixcoord);
+bool pmModelLimits_SERSIC(psMinConstraintMode mode, int nParam, float *params, float *beta);
+bool pmModelGuess_SERSIC(pmModel *model, pmSource *source);
+psF64 pmModelFlux_SERSIC(const psVector *params);
+psF64 pmModelRadius_SERSIC(const psVector *params, psF64 flux);
+bool pmModelFromPSF_SERSIC(pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf);
+bool  pmModelParamsFromPSF_SERSIC(pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io);
+bool pmModelFitStatus_SERSIC(pmModel *model);
+void pmModelSetLimits_SERSIC(pmModelLimitsType type);
+
+#endif
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_SGAUSS.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_SGAUSS.c	(revision 25094)
+++ 	(revision )
@@ -1,389 +1,0 @@
-/******************************************************************************
- * this file defines the SGAUSS source shape model (XXX need a better name!).  Note that these
- * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
- * statements of their own.  The models use a psVector to represent the set of parameters, with
- * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
- * may thus vary depending on the specifics of the model.  All models which are used a PSF
- * representations share a few parameters, for which # define names are listed in pmModel.h:
-
-   power-law with fitted slope and outer tidal radius
-   1 / (1 + z^N + kz^4)
-
-   * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
-   * PM_PAR_I0 1    - central intensity
-   * PM_PAR_XPOS 2  - X center of object
-   * PM_PAR_YPOS 3  - Y center of object
-   * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) / SigmaX)
-   * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) / SigmaY)
-   * PM_PAR_SXY 6   - X*Y term of elliptical contour
-   * PM_PAR_7   7   - slope of power-law component (N)
-   * PM_PAR_8   8   - amplitude of the tidal cutoff (k)
-   *****************************************************************************/
-
-/***
-    XXXX the model in this file needs to be tested more carefully.
-    the code for guessing the power-law slope based on the radial profile
-    is either too slow or does not work well.
-    fix up the code to follow conventions in the other model function files.
-***/
-
-XXX broken code
-
-# define PM_MODEL_FUNC       pmModelFunc_SGAUSS
-# define PM_MODEL_FLUX       pmModelFlux_SGAUSS
-# define PM_MODEL_GUESS      pmModelGuess_SGAUSS
-# define PM_MODEL_LIMITS     pmModelLimits_SGAUSS
-# define PM_MODEL_RADIUS     pmModelRadius_SGAUSS
-# define PM_MODEL_FROM_PSF   pmModelFromPSF_SGAUSS
-# define PM_MODEL_FIT_STATUS pmModelFitStatus_SGAUSS
-
-psF64 psImageEllipseContour (psEllipseAxes axes, double xc, double yc, psImage *image);
-
-psF32 PM_MODEL_FUNC (psVector *deriv,
-                     const psVector *params,
-                     const psVector *x)
-{
-    psF32 *PAR = params->data.F32;
-
-    psF32 X  = x->data.F32[0] - PAR[2];
-    psF32 Y  = x->data.F32[1] - PAR[3];
-    psF32 px = PAR[4]*X;
-    psF32 py = PAR[5]*Y;
-    psF32 z  = PS_MAX((0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y), 1e-8);
-    // note that if z -> 0, dPAR[7] -> -inf
-    // also z^(PAR[7]-1) -> Inf
-
-    psF32 pr = z*PAR[8];
-    psF32 pr3 = pr*pr*pr;
-    psF32 p  = pow(z, PAR[7] - 1.0);
-    psF32 r  = 1.0 / (1 + z*p + pr*pr3);
-    psF32 f  = PAR[1]*r + PAR[0];
-
-    if (deriv != NULL) {
-        // note difference from a pure gaussian: q = params->data.F32[1]*r
-        psF32 t = PAR[1]*r*r;
-        psF32 q = t*(PAR[7]*p + 4*PAR[8]*pr3);
-
-        deriv->data.F32[0] = +1.0;
-        deriv->data.F32[1] = +r;
-        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
-        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
-        deriv->data.F32[4] = -2.0*q*px*X;
-        deriv->data.F32[5] = -2.0*q*py*Y;
-        deriv->data.F32[6] = -q*X*Y;
-        deriv->data.F32[7] = -2*t*log(z)*z*p;
-        deriv->data.F32[8] = -2*t*4*z*pr3;
-    }
-    return(f);
-}
-
-bool PM_MODEL_LIMITS  (psVector **beta_lim, psVector **params_min, psVector **params_max)
-{
-
-    *beta_lim   = psVectorAlloc (9, PS_TYPE_F32);
-    *params_min = psVectorAlloc (9, PS_TYPE_F32);
-    *params_max = psVectorAlloc (9, PS_TYPE_F32);
-
-    beta_lim[0][0].data.F32[0] = 1000;
-    beta_lim[0][0].data.F32[1] = 10000;
-    beta_lim[0][0].data.F32[2] = 5;
-    beta_lim[0][0].data.F32[3] = 5;
-    beta_lim[0][0].data.F32[4] = 0.5;
-    beta_lim[0][0].data.F32[5] = 0.5;
-    beta_lim[0][0].data.F32[6] = 0.5;
-    beta_lim[0][0].data.F32[7] = 0.5;
-    beta_lim[0][0].data.F32[8] = 0.05;
-
-    params_min[0][0].data.F32[0] = -1000;
-    params_min[0][0].data.F32[1] = 0;
-    params_min[0][0].data.F32[2] = -100;
-    params_min[0][0].data.F32[3] = -100;
-    params_min[0][0].data.F32[4] = 0.01;
-    params_min[0][0].data.F32[5] = 0.01;
-    params_min[0][0].data.F32[6] = -5.0;
-    params_min[0][0].data.F32[7] = 0.5;
-    params_min[0][0].data.F32[8] = 0.001;
-
-    params_max[0][0].data.F32[0] = 1e5;
-    params_max[0][0].data.F32[1] = 1e6;
-    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
-    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
-    params_max[0][0].data.F32[4] = 2.0;
-    params_max[0][0].data.F32[5] = 2.0;
-    params_max[0][0].data.F32[6] = +3.0;
-    params_max[0][0].data.F32[7] = 5.0;
-    params_max[0][0].data.F32[8] = 0.5;
-
-    return (TRUE);
-}
-
-bool PM_MODEL_GUESS  (pmModel *model, pmSource *source)
-{
-
-    pmMoments *sMoments = source->moments;
-    pmPeak    *peak     = source->peak;
-    psF32     *params   = model->params->data.F32;
-
-    psEllipseAxes axes;
-    psEllipseShape shape;
-    psEllipseMoments moments;
-
-    moments.x2 = PS_SQR(sMoments->Sx);
-    moments.y2 = PS_SQR(sMoments->Sy);
-    moments.xy = sMoments->Sxy;
-
-    // solve the math to go from Moments To Shape
-    axes = psEllipseMomentsToAxes(moments);
-    shape = psEllipseAxesToShape(axes);
-
-    params[0] = sMoments->Sky;
-    params[1] = sMoments->Peak - sMoments->Sky;
-    params[2] = peak->x;
-    params[3] = peak->y;
-    params[4] = 1.0 / shape.sx;
-    params[5] = 1.0 / shape.sy;
-    params[6] = shape.sxy;
-
-    # if (0)
-
-        f1 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
-    axes.major *= 2.0;
-    axes.minor *= 2.0;
-    f2 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
-
-    if (f1 > f2) {
-        params[7] = PS_MIN (3.0, PS_MAX (0.5, log(2.0*(f1/f2) - 1.0) / log(2.0)));
-    } else {
-        params[7] = 0.5;
-    }
-    # endif
-
-    params[7] = 1.8;
-    params[8] = 0.1;
-
-
-    return(true);
-}
-
-psF64 PM_MODEL_FLUX (const psVector *params)
-{
-    float f, norm, z;
-
-    psF32 *PAR = params->data.F32;
-
-    psF64 A1   = PS_SQR(PAR[4]);
-    psF64 A2   = PS_SQR(PAR[5]);
-    psF64 A3   = PS_SQR(PAR[6]);
-    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
-    // Area is equivalent to 2 pi sigma^2
-
-    // the area needs to be multiplied by the integral of f(z)
-    norm = 0.0;
-    for (z = 0.005; z < 50; z += 0.01) {
-        psF32 pr = PAR[8]*z;
-        f = 1.0 / (1 + pow(z, PAR[7]) + PS_SQR(PS_SQR(pr)));
-        norm += f;
-    }
-    norm *= 0.01;
-
-    psF64 Flux = PAR[1] * Area * norm;
-
-    return(Flux);
-}
-
-// XXX need to define the radius along the major axis
-// define this function so it never returns Inf or NaN
-// return the radius which yields the requested flux
-psF64 PM_MODEL_RADIUS   (const psVector *params, psF64 flux)
-{
-    psF64 r, z = 0.0, pr, f;
-    psF32 *PAR = params->data.F32;
-
-    psEllipseAxes axes;
-    psEllipseShape shape;
-
-    if (flux <= 0)
-        return (1.0);
-    if (PAR[1] <= 0)
-        return (1.0);
-    if (flux >= PAR[1])
-        return (1.0);
-
-    // convert Sx,Sy,Sxy to major/minor axes
-    shape.sx = 1.0 / PAR[4];
-    shape.sy = 1.0 / PAR[5];
-    shape.sxy = PAR[6];
-
-    axes = psEllipseShapeToAxes (shape);
-    psF64 dr = 1.0 / axes.major;
-    psF64 limit = flux / PAR[1];
-
-    // XXX : we can do this faster with an intelligent starting choice
-    for (r = 0.0; r < 20.0; r += dr) {
-        z = PS_SQR(r);
-        pr = PAR[8]*z;
-        f = 1.0 / (1 + pow(z, PAR[7]) + PS_SQR(PS_SQR(pr)));
-        if (f < limit)
-            break;
-    }
-    psF64 radius = 2.0 * axes.major * sqrt (z);
-    if (isnan(radius)) {
-        fprintf (stderr, "error in code\n");
-    }
-    return (radius);
-}
-
-bool PM_MODEL_FROM_PSF  (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
-{
-
-    psF32 *out = modelPSF->params->data.F32;
-    psF32 *in  = modelFLT->params->data.F32;
-
-    out[0] = in[0];
-    out[1] = in[1];
-    out[2] = in[2];
-    out[3] = in[3];
-
-    for (int i = 4; i < 9; i++) {
-        psPolynomial2D *poly = psf->params->data[i-4];
-        // XXX: Verify this (from EAM change)
-        //out[i] = Polynomial2DEval_EAM(poly, out[2], out[3]);
-        out[i] = psPolynomial2DEval(poly, out[2], out[3]);
-    }
-    return(true);
-}
-
-bool PM_MODEL_FIT_STATUS  (pmModel *model)
-{
-
-    psF32 dP;
-    bool  status;
-    psEllipseAxes axes;
-    psEllipseShape shape;
-
-    psF32 *PAR  = model->params->data.F32;
-    psF32 *dPAR = model->dparams->data.F32;
-
-    shape.sx = 1.0 / PAR[4];
-    shape.sy = 1.0 / PAR[5];
-    shape.sxy = PAR[6];
-
-    axes = psEllipseShapeToAxes (shape);
-
-    dP = 0;
-    dP += PS_SQR(dPAR[4] / PAR[4]);
-    dP += PS_SQR(dPAR[5] / PAR[5]);
-    dP += PS_SQR(dPAR[7] / PAR[7]);
-    dP = sqrt (dP);
-
-    status = true;
-    status &= (dP < 0.5);
-    status &= (PAR[1] > 0);
-    status &= ((dPAR[1]/PAR[1]) < 0.5);
-    status &= (fabs(PAR[8]) < 0.5);
-    status &= (dPAR[8] < 0.1);
-    status &= (axes.major > 1.41);
-    status &= (axes.minor > 1.41);
-    status &= ((axes.major / axes.minor) < 5.0);
-    status &= (PAR[7] > 0.5);
-
-    if (status)
-        return true;
-    return false;
-}
-
-// measure the flux for the elliptical contour
-psF64 psImageEllipseContour (psEllipseAxes axes, double xc, double yc, psImage *image)
-{
-
-    double t, dt, ct, st, xo, yo, value;
-    int N, Nt, x, y;
-
-    // choose dt to uniformly divide contour, with ~1 pix spacing at most
-    dt = asin (1 / axes.minor);
-    Nt = (int)(2*M_PI / dt) + 1;
-    dt = 2*M_PI / Nt;
-
-    ct = cos(axes.theta);
-    st = sin(axes.theta);
-    xo = xc - image->col0;
-    yo = yc - image->row0;
-
-    psVector *contour = psVectorAlloc (Nt, PS_TYPE_F32);
-    contour->n = contour->nalloc;
-    for (t = 0, N = 0; (t < 2*M_PI) && (N < Nt); t += dt) {
-        x = ct*axes.major*cos(t) + st*axes.minor*sin(t) + xo;
-        y = ct*axes.minor*sin(t) + st*axes.major*cos(t) + yo;
-        value = p_psImageGetElementF64(image, x, y);
-        if (isfinite(value)) {
-            contour->data.F32[N] = value;
-            N++;
-        }
-    }
-    contour->n = N;
-    // accept every pixel: double counting is not so problematic here...
-
-    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
-    if (!psVectorStats (stats, contour, NULL, NULL, 0)) {
-	psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
-	return false;
-    }
-    value = stats->sampleMedian;
-
-    psFree (stats);
-    psFree (contour);
-
-    return (value);
-}
-
-// XXX EAM : test version using flux contours to guess slope
-bool PM_MODEL_GUESS_HARD (pmModel *model, pmSource *source)
-{
-
-    pmMoments *sMoments = source->moments;
-    pmPeak    *peak     = source->peak;
-    psF32     *params   = model->params->data.F32;
-    float f1, f2;
-
-    psEllipseAxes axes;
-    psEllipseShape shape;
-    psEllipseMoments moments;
-
-    moments.x2 = PS_SQR(sMoments->Sx);
-    moments.y2 = PS_SQR(sMoments->Sy);
-    moments.xy = sMoments->Sxy;
-
-    // solve the math to go from Moments To Shape
-    axes = psEllipseMomentsToAxes(moments);
-    shape = psEllipseAxesToShape(axes);
-
-    params[0] = sMoments->Sky;
-    params[1] = sMoments->Peak - sMoments->Sky;
-    params[2] = peak->x;
-    params[3] = peak->y;
-    params[4] = 1.0 / shape.sx;
-    params[5] = 1.0 / shape.sy;
-    params[6] = shape.sxy;
-
-    f1 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
-    axes.major *= 2.0;
-    axes.minor *= 2.0;
-    f2 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
-
-    if (f1 > f2) {
-        params[7] = PS_MIN (3.0, PS_MAX (0.5, log(2.0*(f1/f2) - 1.0) / log(2.0)));
-    } else {
-        params[7] = 0.5;
-    }
-    params[8] = 0.1;
-
-    return(true);
-}
-
-# undef PM_MODEL_FUNC
-# undef PM_MODEL_FLUX
-# undef PM_MODEL_GUESS
-# undef PM_MODEL_LIMITS
-# undef PM_MODEL_RADIUS
-# undef PM_MODEL_FROM_PSF
-# undef PM_MODEL_FIT_STATUS
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_TGAUSS.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_TGAUSS.c	(revision 25094)
+++ 	(revision )
@@ -1,202 +1,0 @@
-/******************************************************************************
- * this file defines the TGAUSS source shape model (XXX need a better name!).  Note that these
- * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
- * statements of their own.  The models use a psVector to represent the set of parameters, with
- * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
- * may thus vary depending on the specifics of the model.  All models which are used a PSF
- * representations share a few parameters, for which # define names are listed in pmModel.h:
-
-   power-law with fixed slope and fitted amplitude
-   1 / (1 + z + kz^2.2)
-
-   * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
-   * PM_PAR_I0 1    - central intensity
-   * PM_PAR_XPOS 2  - X center of object
-   * PM_PAR_YPOS 3  - Y center of object
-   * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) / SigmaX)
-   * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) / SigmaY)
-   * PM_PAR_SXY 6   - X*Y term of elliptical contour
-   * PM_PAR_7   7   - amplitude of high-order component (k)
-   *****************************************************************************/
-
-/***
-    XXXX the model in this file needs to be tested more carefully.
-    fix up the code to follow conventions in the other model function files.
-***/
-
-XXX broken code
-
-# define PM_MODEL_FUNC       pmModelFunc_TGAUSS
-# define PM_MODEL_FLUX       pmModelFlux_TGAUSS
-# define PM_MODEL_GUESS      pmModelGuess_TGAUSS
-# define PM_MODEL_LIMITS     pmModelLimits_TGAUSS
-# define PM_MODEL_RADIUS     pmModelRadius_TGAUSS
-# define PM_MODEL_FROM_PSF   pmModelFromPSF_TGAUSS
-# define PM_MODEL_FIT_STATUS pmModelFitStatus_TGAUSS
-
-psF64 PS_MODEL_FUNC (psVector *deriv,
-                     const psVector *params,
-                     const psVector *x)
-{
-    psF32 *PAR = params->data.F32;
-
-    psF32 X  = x->data.F32[0] - PAR[2];
-    psF32 Y  = x->data.F32[1] - PAR[3];
-    psF32 px = PAR[4]*X;
-    psF32 py = PAR[5]*Y;
-    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
-
-    psF32 p  = pow(z, 1.2);
-    psF32 r  = 1.0 / (1 + z + PAR[7]*z*p);
-    psF32 f  = PAR[1]*r + PAR[0];
-
-    if (deriv != NULL) {
-        // note difference from a pure gaussian: q = params->data.F32[1]*r
-        psF32 t = PAR[1]*r*r;
-        psF32 q = t*(1 + PAR[7]*2.2*p);
-
-        deriv->data.F32[0] = +1.0;
-        deriv->data.F32[1] = +r;
-        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
-        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
-        deriv->data.F32[4] = -2.0*q*px*X;
-        deriv->data.F32[5] = -2.0*q*py*Y;
-        deriv->data.F32[6] = -q*X*Y;
-        deriv->data.F32[7] = -t*z*p;
-    }
-    return(f);
-}
-
-bool PM_MODEL_LIMITS  (psVector **beta_lim, psVector **params_min, psVector **params_max)
-{
-
-    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
-    *params_min = psVectorAlloc (8, PS_TYPE_F32);
-    *params_max = psVectorAlloc (8, PS_TYPE_F32);
-
-    beta_lim[0][0].data.F32[PM_PAR_SKY] = 1000;
-    beta_lim[0][0].data.F32[PM_PAR_I0] = 3e6;
-    beta_lim[0][0].data.F32[PM_PAR_XPOS] = 5;
-    beta_lim[0][0].data.F32[PM_PAR_YPOS] = 5;
-    beta_lim[0][0].data.F32[PM_PAR_SXX] = 0.5;
-    beta_lim[0][0].data.F32[PM_PAR_SYY] = 0.5;
-    beta_lim[0][0].data.F32[PM_PAR_SXY] = 0.5;
-    beta_lim[0][0].data.F32[PM_PAR_7] = 0.5;
-
-    params_min[0][0].data.F32[PM_PAR_SKY] = -1000;
-    params_min[0][0].data.F32[PM_PAR_I0] = 0;
-    params_min[0][0].data.F32[PM_PAR_XPOS] = -100;
-    params_min[0][0].data.F32[PM_PAR_YPOS] = -100;
-    params_min[0][0].data.F32[PM_PAR_SXX] = 0.5;
-    params_min[0][0].data.F32[PM_PAR_SYY] = 0.5;
-    params_min[0][0].data.F32[PM_PAR_SXY] = -5.0;
-    params_min[0][0].data.F32[PM_PAR_7] = 0.1;
-
-    params_max[0][0].data.F32[PM_PAR_SKY] = 1e5;
-    params_max[0][0].data.F32[PM_PAR_I0] = 1e8;
-    params_max[0][0].data.F32[PM_PAR_XPOS] = 1e4;  // this should be set by image dimensions!
-    params_max[0][0].data.F32[PM_PAR_YPOS] = 1e4;  // this should be set by image dimensions!
-    params_max[0][0].data.F32[PM_PAR_SXX] = 100.0;
-    params_max[0][0].data.F32[PM_PAR_SYY] = 100.0;
-    params_max[0][0].data.F32[PM_PAR_SXY] = +5.0;
-    params_max[0][0].data.F32[PM_PAR_7] = 10.0;
-
-    return (TRUE);
-}
-
-bool PS_MODEL_GUESS  (psModel *model, psSource *source)
-{
-
-    psVector *params = model->params;
-
-    params->data.F32[0] = source->moments->Sky;
-    params->data.F32[1] = source->peak->counts - source->moments->Sky;
-    params->data.F32[2] = source->moments->x;
-    params->data.F32[3] = source->moments->y;
-    params->data.F32[4] = 1.0/source->moments->Sx;
-    params->data.F32[5] = 1.0/source->moments->Sy;
-    // params->data.F32[6] = source->moments->Sxy;
-    params->data.F32[6] = 0.0;
-    params->data.F32[7] = 5.0;
-    return(true);
-}
-
-psF64 PS_MODEL_FLUX (const psVector *params)
-{
-    psF64 A1   = 1 / PS_SQR(params->data.F32[4]);
-    psF64 A2   = 1 / PS_SQR(params->data.F32[5]);
-    psF64 A3   = params->data.F32[6];
-    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - PS_SQR(A3));
-    // Area is equivalent to 2 pi sigma^2
-
-    psF64 Flux = params->data.F32[1] * Area;
-
-    return(Flux);
-}
-
-// define this function so it never returns Inf or NaN
-// return the radius which yields the requested flux
-psF64 PS_MODEL_RADIUS   (const psVector *params, psF64 flux)
-{
-    if (flux <= 0)
-        return (1.0);
-    if (params->data.F32[1] <= 0)
-        return (1.0);
-    if (flux >= params->data.F32[1])
-        return (1.0);
-
-    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
-    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
-    if (isnan(radius)) {
-        fprintf (stderr, "error in code\n");
-    }
-    return (radius);
-}
-
-bool PS_MODEL_FROM_PSF  (psModel *modelPSF, psModel *modelFLT, const pmPSF *psf)
-{
-
-    psF32 *out = modelPSF->params->data.F32;
-    psF32 *in  = modelFLT->params->data.F32;
-
-    out[0] = in[0];
-    out[1] = in[1];
-    out[2] = in[2];
-    out[3] = in[3];
-
-    for (int i = 4; i < 8; i++) {
-        psPolynomial2D *poly = psf->params->data[i-4];
-        out[i] = Polynomial2DEval (poly, out[2], out[3]);
-    }
-    return(true);
-}
-
-bool PM_MODEL_FIT_STATUS (pmModel *model)
-{
-
-    psF32 dP;
-    bool  status;
-
-    psF32 *PAR  = model->params->data.F32;
-    psF32 *dPAR = model->dparams->data.F32;
-
-    dP = 0;
-    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
-    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
-    dP = sqrt (dP);
-
-    status = true;
-    status &= (dP < 0.5);
-    status &= (PAR[PM_PAR_I0] > 0);
-    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
-
-    return status;
-}
-
-# undef PM_MODEL_FUNC
-# undef PM_MODEL_FLUX
-# undef PM_MODEL_GUESS
-# undef PM_MODEL_LIMITS
-# undef PM_MODEL_RADIUS
-# undef PM_MODEL_FROM_PSF
-# undef PM_MODEL_FIT_STATUS
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_WAUSS.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_WAUSS.c	(revision 25094)
+++ 	(revision )
@@ -1,198 +1,0 @@
-/******************************************************************************
- * this file defines the WAUSS source shape model (XXX need a better name!).  Note that these
- * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
- * statements of their own.  The models use a psVector to represent the set of parameters, with
- * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
- * may thus vary depending on the specifics of the model.  All models which are used a PSF
- * representations share a few parameters, for which # define names are listed in pmModel.h:
-
-   power-law with fitted linear term
-   1 / (1 + Az + Bz^2 + z^3/6)
-
-   * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
-   * PM_PAR_I0 1    - central intensity
-   * PM_PAR_XPOS 2  - X center of object
-   * PM_PAR_YPOS 3  - Y center of object
-   * PM_PAR_SXX 4   - X^2 term of elliptical contour (SigmaX / sqrt(2))
-   * PM_PAR_SYY 5   - Y^2 term of elliptical contour (SigmaY / sqrt(2))
-   * PM_PAR_SXY 6   - X*Y term of elliptical contour
-   * PM_PAR_7   7   - amplitude of the linear component (A)
-   * PM_PAR_8   8   - amplitude of the quadratic component (B)
-   *****************************************************************************/
-
-/***
-    XXXX the model in this file needs to be tested more carefully.
-    fix up the code to follow conventions in the other model function files.
-***/
-
-XXX broken code
-
-# define PM_MODEL_FUNC       pmModelFunc_WAUSS
-# define PM_MODEL_FLUX       pmModelFlux_WAUSS
-# define PM_MODEL_GUESS      pmModelGuess_WAUSS
-# define PM_MODEL_LIMITS     pmModelLimits_WAUSS
-# define PM_MODEL_RADIUS     pmModelRadius_WAUSS
-# define PM_MODEL_FROM_PSF   pmModelFromPSF_WAUSS
-# define PM_MODEL_FIT_STATUS pmModelFitStatus_WAUSS
-
-psF64 PS_MODEL_FUNC (psVector *deriv,
-                     const psVector *params,
-                     const psVector *x)
-{
-    psF32 X = x->data.F32[0] - params->data.F32[2];
-    psF32 Y = x->data.F32[1] - params->data.F32[2];
-    psF32 px = params->data.F32[4]*X;
-    psF32 py = params->data.F32[5]*Y;
-    psF32 z = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + params->data.F32[6]*X*Y;
-    psF32 t = 0.5*z*z*(1.0 + params->data.F32[8]*z/3.0);
-    psF32 r = 1.0 / (1.0 + z + params->data.F32[7]*t); /* exp (-Z) */
-    psF32 f = params->data.F32[1]*r + params->data.F32[0];
-
-    if (deriv != NULL) {
-        // note difference from gaussian: q = params->data.F32[1]*r
-        psF32 q = params->data.F32[1]*r*r*(1.0 + params->data.F32[7]*z*(1.0 + params->data.F32[8]*z/2.0));
-        deriv->data.F32[0] = +1.0;
-        deriv->data.F32[1] = +r;
-        deriv->data.F32[2] = q*(2.0*px*params->data.F32[4] + params->data.F32[6]*Y);
-        deriv->data.F32[3] = q*(2.0*py*params->data.F32[5] + params->data.F32[6]*X);
-        deriv->data.F32[4] = -2.0*q*px*X;
-        deriv->data.F32[5] = -2.0*q*py*Y;
-        deriv->data.F32[6] = -q*X*Y;
-        deriv->data.F32[7] = -100.0*params->data.F32[1]*r*r*t;
-        deriv->data.F32[8] = -100.0*params->data.F32[1]*r*r*params->data.F32[7]*(z*z*z)/6.0;
-        // The values of 100 dampen the swing of params->data.F32[7,8] */
-    }
-    return(f);
-}
-
-bool PM_MODEL_LIMITS  (psVector **beta_lim, psVector **params_min, psVector **params_max)
-{
-
-    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
-    *params_min = psVectorAlloc (8, PS_TYPE_F32);
-    *params_max = psVectorAlloc (8, PS_TYPE_F32);
-
-    beta_lim[0][0].data.F32[PM_PAR_SKY] = 1000;
-    beta_lim[0][0].data.F32[PM_PAR_I0] = 3e6;
-    beta_lim[0][0].data.F32[PM_PAR_XPOS] = 5;
-    beta_lim[0][0].data.F32[PM_PAR_YPOS] = 5;
-    beta_lim[0][0].data.F32[PM_PAR_SXX] = 0.5;
-    beta_lim[0][0].data.F32[PM_PAR_SYY] = 0.5;
-    beta_lim[0][0].data.F32[PM_PAR_SXY] = 0.5;
-    beta_lim[0][0].data.F32[PM_PAR_7] = 0.5;
-
-    params_min[0][0].data.F32[PM_PAR_SKY] = -1000;
-    params_min[0][0].data.F32[PM_PAR_I0] = 0;
-    params_min[0][0].data.F32[PM_PAR_XPOS] = -100;
-    params_min[0][0].data.F32[PM_PAR_YPOS] = -100;
-    params_min[0][0].data.F32[PM_PAR_SXX] = 0.5;
-    params_min[0][0].data.F32[PM_PAR_SYY] = 0.5;
-    params_min[0][0].data.F32[PM_PAR_SXY] = -5.0;
-    params_min[0][0].data.F32[PM_PAR_7] = 0.1;
-
-    params_max[0][0].data.F32[PM_PAR_SKY] = 1e5;
-    params_max[0][0].data.F32[PM_PAR_I0] = 1e8;
-    params_max[0][0].data.F32[PM_PAR_XPOS] = 1e4;  // this should be set by image dimensions!
-    params_max[0][0].data.F32[PM_PAR_YPOS] = 1e4;  // this should be set by image dimensions!
-    params_max[0][0].data.F32[PM_PAR_SXX] = 100.0;
-    params_max[0][0].data.F32[PM_PAR_SYY] = 100.0;
-    params_max[0][0].data.F32[PM_PAR_SXY] = +5.0;
-    params_max[0][0].data.F32[PM_PAR_7] = 10.0;
-
-    return (TRUE);
-}
-
-bool PS_MODEL_GUESS  (psModel *model, psSource *source)
-{
-
-    psVector *params = model->params;
-
-    params->data.F32[0] = source->moments->Sky;
-    params->data.F32[1] = source->peak->counts - source->moments->Sky;
-    params->data.F32[2] = source->moments->x;
-    params->data.F32[3] = source->moments->y;
-    params->data.F32[4] = sqrt(2.0) / source->moments->Sx;
-    params->data.F32[5] = sqrt(2.0) / source->moments->Sy;
-    params->data.F32[6] = source->moments->Sxy;
-    // XXX: What are these?
-    // params->data.F32[7] = B2;
-    // params->data.F32[8] = B3;
-    return(true);
-}
-
-// this is probably wrong since it uses the gauss integral 2 pi sigma^2
-psF64 PS_MODEL_FLUX (const psVector *params)
-{
-    psF64 A1   = 1 / PS_SQR(params->data.F32[4]);
-    psF64 A2   = 1 / PS_SQR(params->data.F32[5]);
-    psF64 A3   = params->data.F32[6];
-    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - PS_SQR(A3));
-    // Area is equivalent to 2 pi sigma^2
-
-    psF64 Flux = params->data.F32[1] * Area;
-
-    return(Flux);
-}
-
-// return the radius which yields the requested flux
-psF64 PS_MODEL_RADIUS   (const psVector *params, psF64 flux)
-{
-    if (flux <= 0)
-        return (1.0);
-    if (params->data.F32[1] <= 0)
-        return (1.0);
-    if (flux >= params->data.F32[1] <= 0)
-        return (1.0);
-
-    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
-    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
-    return (radius);
-}
-
-bool PS_MODEL_FROM_PSF  (psModel *modelPSF, psModel *modelFLT, const pmPSF *psf)
-{
-
-    psF32 *out = modelPSF->params->data.F32;
-    psF32 *in  = modelFLT->params->data.F32;
-
-    out[0] = in[0];
-    out[1] = in[1];
-    out[2] = in[2];
-    out[3] = in[3];
-
-    for (int i = 4; i < 9; i++) {
-        psPolynomial2D *poly = psf->params->data[i-4];
-        out[i] = Polynomial2DEval (poly, out[2], out[3]);
-    }
-    return(true);
-}
-
-bool PM_MODEL_FIT_STATUS (pmModel *model)
-{
-
-    psF32 dP;
-    bool  status;
-
-    psF32 *PAR  = model->params->data.F32;
-    psF32 *dPAR = model->dparams->data.F32;
-
-    dP = 0;
-    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
-    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
-    dP = sqrt (dP);
-
-    status = true;
-    status &= (dP < 0.5);
-    status &= (PAR[PM_PAR_I0] > 0);
-    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
-
-    return status;
-}
-
-# undef PM_MODEL_FUNC
-# undef PM_MODEL_FLUX
-# undef PM_MODEL_GUESS
-# undef PM_MODEL_LIMITS
-# undef PM_MODEL_RADIUS
-# undef PM_MODEL_FROM_PSF
-# undef PM_MODEL_FIT_STATUS
Index: branches/simmosaic_branches/psModules/src/objects/models/pmModel_ZGAUSS.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/models/pmModel_ZGAUSS.c	(revision 25094)
+++ 	(revision )
@@ -1,251 +1,0 @@
-/******************************************************************************
- * this file defines the ZGAUSS source shape model (XXX need a better name!).  Note that these
- * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
- * statements of their own.  The models use a psVector to represent the set of parameters, with
- * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
- * may thus vary depending on the specifics of the model.  All models which are used a PSF
- * representations share a few parameters, for which # define names are listed in pmModel.h:
-
-   power-law with fitted slope and tidal cutoff
-   1 / (1 + z^N + (Az)^4)
-
-   * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
-   * PM_PAR_I0 1    - central intensity
-   * PM_PAR_XPOS 2  - X center of object
-   * PM_PAR_YPOS 3  - Y center of object
-   * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) / SigmaX)
-   * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) / SigmaY)
-   * PM_PAR_SXY 6   - X*Y term of elliptical contour
-   * PM_PAR_7   7   - slope of power-law component (N)
-   *****************************************************************************/
-
-/***
-    XXXX the model in this file needs to be tested more carefully.
-    fix up the code to follow conventions in the other model function files.
-***/
-
-XXX broken code
-
-# define PM_MODEL_FUNC       pmModelFunc_ZGAUSS
-# define PM_MODEL_FLUX       pmModelFlux_ZGAUSS
-# define PM_MODEL_GUESS      pmModelGuess_ZGAUSS
-# define PM_MODEL_LIMITS     pmModelLimits_ZGAUSS
-# define PM_MODEL_RADIUS     pmModelRadius_ZGAUSS
-# define PM_MODEL_FROM_PSF   pmModelFromPSF_ZGAUSS
-# define PM_MODEL_FIT_STATUS pmModelFitStatus_ZGAUSS
-
-# define PAR8 0.1
-
-psF64 PS_MODEL_FUNC (psVector *deriv,
-                     const psVector *params,
-                     const psVector *x)
-{
-    psF32 *PAR = params->data.F32;
-
-    psF32 X  = x->data.F32[0] - PAR[2];
-    psF32 Y  = x->data.F32[1] - PAR[3];
-    psF32 px = PAR[4]*X;
-    psF32 py = PAR[5]*Y;
-    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
-
-    psF32 pr = PAR8*z;
-    psF32 p  = pow(z, PAR[7] - 1.0);
-    psF32 r  = 1.0 / (1 + z*p + SQ(SQ(pr)));
-    psF32 f  = PAR[1]*r + PAR[0];
-
-    if (deriv != NULL) {
-        // note difference from a pure gaussian: q = params->data.F32[1]*r
-        psF32 t = PAR[1]*r*r;
-        psF32 q = t*(PAR[7]*p + 4*PAR8*pr*pr*pr);
-
-        deriv->data.F32[0] = +1.0;
-        deriv->data.F32[1] = +r;
-        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
-        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
-        deriv->data.F32[4] = -q*px*X;
-        deriv->data.F32[5] = -q*py*Y;
-        deriv->data.F32[6] = -q*X*Y;
-        deriv->data.F32[7] = -t*log(z)*z*p;
-    }
-    return(f);
-}
-
-bool PM_MODEL_LIMITS  (psVector **beta_lim, psVector **params_min, psVector **params_max)
-{
-
-    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
-    *params_min = psVectorAlloc (8, PS_TYPE_F32);
-    *params_max = psVectorAlloc (8, PS_TYPE_F32);
-
-    beta_lim[0][0].data.F32[PM_PAR_SKY] = 1000;
-    beta_lim[0][0].data.F32[PM_PAR_I0] = 3e6;
-    beta_lim[0][0].data.F32[PM_PAR_XPOS] = 5;
-    beta_lim[0][0].data.F32[PM_PAR_YPOS] = 5;
-    beta_lim[0][0].data.F32[PM_PAR_SXX] = 0.5;
-    beta_lim[0][0].data.F32[PM_PAR_SYY] = 0.5;
-    beta_lim[0][0].data.F32[PM_PAR_SXY] = 0.5;
-    beta_lim[0][0].data.F32[PM_PAR_7] = 0.5;
-
-    params_min[0][0].data.F32[PM_PAR_SKY] = -1000;
-    params_min[0][0].data.F32[PM_PAR_I0] = 0;
-    params_min[0][0].data.F32[PM_PAR_XPOS] = -100;
-    params_min[0][0].data.F32[PM_PAR_YPOS] = -100;
-    params_min[0][0].data.F32[PM_PAR_SXX] = 0.5;
-    params_min[0][0].data.F32[PM_PAR_SYY] = 0.5;
-    params_min[0][0].data.F32[PM_PAR_SXY] = -5.0;
-    params_min[0][0].data.F32[PM_PAR_7] = 0.1;
-
-    params_max[0][0].data.F32[PM_PAR_SKY] = 1e5;
-    params_max[0][0].data.F32[PM_PAR_I0] = 1e8;
-    params_max[0][0].data.F32[PM_PAR_XPOS] = 1e4;  // this should be set by image dimensions!
-    params_max[0][0].data.F32[PM_PAR_YPOS] = 1e4;  // this should be set by image dimensions!
-    params_max[0][0].data.F32[PM_PAR_SXX] = 100.0;
-    params_max[0][0].data.F32[PM_PAR_SYY] = 100.0;
-    params_max[0][0].data.F32[PM_PAR_SXY] = +5.0;
-    params_max[0][0].data.F32[PM_PAR_7] = 10.0;
-
-    return (TRUE);
-}
-
-bool PS_MODEL_GUESS  (psModel *model, psSource *source)
-{
-
-    psVector *params = model->params;
-
-    psEllipseAxes axes;
-    psEllipseShape shape;
-    psEllipseMoments moments;
-
-    moments.x2 = PS_SQR(source->moments->Sx);
-    moments.y2 = PS_SQR(source->moments->Sy);
-    moments.xy = source->moments->Sxy;
-
-    axes = psEllipseMomentsToAxes(moments);
-    shape = psEllipseAxesToShape(axes);
-
-    params->data.F32[0] = source->moments->Sky;
-    params->data.F32[1] = source->peak->counts - source->moments->Sky;
-    params->data.F32[2] = source->moments->x;
-    params->data.F32[3] = source->moments->y;
-    params->data.F32[4] = 1.0 / shape.sx;
-    params->data.F32[5] = 1.0 / shape.sy;
-    params->data.F32[6] = shape.sxy;
-    params->data.F32[7] = 1.9;
-    return(true);
-}
-
-psF64 PS_MODEL_FLUX (const psVector *params)
-{
-    float f, norm, z;
-
-    psF32 *PAR = params->data.F32;
-
-    psF64 A1   = PS_SQR(PAR[4]);
-    psF64 A2   = PS_SQR(PAR[5]);
-    psF64 A3   = PS_SQR(PAR[6]);
-    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
-    // Area is equivalent to 2 pi sigma^2
-
-    // the area needs to be multiplied by the integral of f(z)
-    norm = 0.0;
-    psF32 pr = PAR8*z;
-    for (z = 0.005; z < 50; z += 0.01) {
-        f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
-        norm += f;
-    }
-    norm *= 0.01;
-
-    psF64 Flux = PAR[1] * Area * norm;
-
-    return(Flux);
-}
-
-// XXX need to define the radius along the major axis
-// define this function so it never returns Inf or NaN
-// return the radius which yields the requested flux
-psF64 PS_MODEL_RADIUS   (const psVector *params, psF64 flux)
-{
-    psF64 r, z, pr, f;
-    psF32 *PAR = params->data.F32;
-
-    psEllipseAxes axes;
-    psEllipseShape shape;
-
-    if (flux <= 0)
-        return (1.0);
-    if (PAR[1] <= 0)
-        return (1.0);
-    if (flux >= PAR[1])
-        return (1.0);
-
-    // convert Sx,Sy,Sxy to major/minor axes
-    shape.sx = 1.0 / PAR[4];
-    shape.sy = 1.0 / PAR[5];
-    shape.sxy = PAR[6];
-
-    axes = psEllipseShapeToAxes (shape);
-    psF64 dr = 1.0 / axes.major;
-    psF64 limit = flux / PAR[1];
-
-    // XXX : we can do this faster with an intelligent starting choice
-    for (r = 0.0; r < 20.0; r += dr) {
-        z = SQ(r);
-        pr = PAR8*z;
-        f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
-        if (f < limit)
-            break;
-    }
-    psF64 radius = 2.0 * axes.major * sqrt (z);
-    if (isnan(radius)) {
-        fprintf (stderr, "error in code\n");
-    }
-    return (radius);
-}
-
-bool PS_MODEL_FROM_PSF  (psModel *modelPSF, psModel *modelFLT, const pmPSF *psf)
-{
-
-    psF32 *out = modelPSF->params->data.F32;
-    psF32 *in  = modelFLT->params->data.F32;
-
-    out[0] = in[0];
-    out[1] = in[1];
-    out[2] = in[2];
-    out[3] = in[3];
-
-    for (int i = 4; i < 8; i++) {
-        psPolynomial2D *poly = psf->params->data[i-4];
-        out[i] = Polynomial2DEval (poly, out[2], out[3]);
-    }
-    return(true);
-}
-
-bool PM_MODEL_FIT_STATUS (pmModel *model)
-{
-
-    psF32 dP;
-    bool  status;
-
-    psF32 *PAR  = model->params->data.F32;
-    psF32 *dPAR = model->dparams->data.F32;
-
-    dP = 0;
-    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
-    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
-    dP = sqrt (dP);
-
-    status = true;
-    status &= (dP < 0.5);
-    status &= (PAR[PM_PAR_I0] > 0);
-    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
-
-    return status;
-}
-
-# undef PM_MODEL_FUNC
-# undef PM_MODEL_FLUX
-# undef PM_MODEL_GUESS
-# undef PM_MODEL_LIMITS
-# undef PM_MODEL_RADIUS
-# undef PM_MODEL_FROM_PSF
-# undef PM_MODEL_FIT_STATUS
Index: branches/simmosaic_branches/psModules/src/objects/pmDetEff.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmDetEff.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmDetEff.c	(revision 27839)
@@ -0,0 +1,169 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+
+#include "pmDetEff.h"
+
+
+static void detEffFree(pmDetEff *de)
+{
+    psFree(de->magOffsets);
+    psFree(de->counts);
+    psFree(de->magDiffMean);
+    psFree(de->magDiffStdev);
+    psFree(de->magErrMean);
+}
+
+
+pmDetEff *pmDetEffAlloc(float magRef, int numSources, int numBins)
+{
+    pmDetEff *de = psAlloc(sizeof(pmDetEff)); // Detection efficiency, to return
+    psMemSetDeallocator(de, (psFreeFunc)detEffFree);
+
+    de->magRef = magRef;
+    de->numSources = numSources;
+    de->numBins = numBins;
+
+    de->magOffsets = NULL;
+    de->counts = NULL;
+    de->magDiffMean = NULL;
+    de->magDiffStdev = NULL;
+    de->magErrMean = NULL;
+
+    return de;
+}
+
+
+bool pmDetEffWrite(psFits *fits, pmDetEff *de, const psMetadata *header, const char *extname)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    PM_ASSERT_DETEFF_RESULTS(de, false);
+
+    psArray *table = psArrayAlloc(de->numBins); // Table to write
+    for (int i = 0; i < de->numBins; i++) {
+        psMetadata *row = psMetadataAlloc(); // Table row
+        psMetadataAddF32(row, PS_LIST_TAIL, "OFFSET", 0, "Magnitude offset from reference",
+                         de->magOffsets->data.F32[i]);
+        psMetadataAddS32(row, PS_LIST_TAIL, "COUNTS", 0, "Number of sources recovered",
+                         de->counts->data.S32[i]);
+        psMetadataAddF32(row, PS_LIST_TAIL, "DIFF.MEAN", 0, "Mean magnitude difference",
+                         de->magDiffMean->data.F32[i]);
+        psMetadataAddF32(row, PS_LIST_TAIL, "DIFF.STDEV", 0, "Stdev magnitude difference",
+                         de->magDiffStdev->data.F32[i]);
+        psMetadataAddF32(row, PS_LIST_TAIL, "ERR.MEAN", 0, "Mean magnitude error",
+                         de->magErrMean->data.F32[i]);
+        table->data[i] = row;
+    }
+
+    psMetadata *deHeader = psMetadataCopy(NULL, header); // Header for detection efficiency
+    psMetadataAddF32(deHeader, PS_LIST_TAIL, "DETEFF.MAGREF", PS_META_REPLACE, "Magnitude reference",
+                     de->magRef);
+    psMetadataAddS32(deHeader, PS_LIST_TAIL, "DETEFF.NUM", PS_META_REPLACE, "Number of fake sources injected",
+                     de->numSources);
+
+    if (!psFitsWriteTable(fits, deHeader, table, extname)) {
+        psError(PS_ERR_IO, false, "Unable to write detection efficiency table.");
+        psFree(table);
+        psFree(deHeader);
+        return false;
+    }
+
+    psFree(table);
+    psFree(deHeader);
+
+    return true;
+}
+
+bool pmReadoutWriteDetEff(psFits *fits, const pmReadout *readout,
+                          const psMetadata *header, const char *extname)
+{
+    PM_ASSERT_READOUT_NON_NULL(readout, false);
+
+    bool mdok;                          // Status of MD lookup
+    pmDetEff *de = psMetadataLookupPtr(&mdok, readout->analysis, PM_DETEFF_ANALYSIS); // Detection efficiency
+    if (!mdok || !de) {
+        // Wrote everything there was to write
+        return true;
+    }
+    return pmDetEffWrite(fits, de, header, extname);
+}
+
+
+pmDetEff *pmDetEffRead(psFits *fits, const char *extname)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_STRING_NON_EMPTY(extname, false);
+
+    if (!psFitsMoveExtNameClean(fits, extname)) {
+        // Nothing to read
+        return NULL;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, fits); // Header for table
+    if (!header) {
+        psError(PS_ERR_IO, false, "Unable to read FITS header");
+        return NULL;
+    }
+
+    int numBins = psFitsTableSize(fits);// Size of table
+    bool mdok;                          // Status of MD lookup
+    int numSources = psMetadataLookupS32(&mdok, header, "DETEFF.NUM"); // Number of fake sources injected
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find number of sources");
+        psFree(header);
+        return NULL;
+    }
+    float magRef = psMetadataLookupF32(&mdok, header, "DETEFF.MAGREF"); // Magnitude reference
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find magnitude reference");
+        psFree(header);
+        return NULL;
+    }
+    psFree(header);
+
+    pmDetEff *de = pmDetEffAlloc(magRef, numSources, numBins); // Detection efficiency
+    de->magOffsets = psVectorAlloc(numBins, PS_TYPE_F32);
+    de->counts = psVectorAlloc(numBins, PS_TYPE_S32);
+    de->magDiffMean = psVectorAlloc(numBins, PS_TYPE_F32);
+    de->magDiffStdev = psVectorAlloc(numBins, PS_TYPE_F32);
+    de->magErrMean = psVectorAlloc(numBins, PS_TYPE_F32);
+
+    psArray *table = psFitsReadTable(fits); // FITS table
+    if (!table) {
+        psError(PS_ERR_IO, false, "Unable to read detection efficiency table.");
+        psFree(de);
+        return false;
+    }
+
+    for (int i = 0; i < numBins; i++) {
+        psMetadata *row = table->data[i]; // Table row
+        de->magOffsets->data.F32[i] = psMetadataLookupF32(NULL, row, "OFFSET");
+        de->counts->data.S32[i] = psMetadataLookupS32(NULL, row, "COUNTS");
+        de->magDiffMean->data.F32[i] = psMetadataLookupF32(NULL, row, "DIFF.MEAN");
+        de->magDiffStdev->data.F32[i] = psMetadataLookupF32(NULL, row, "DIFF.STDEV");
+        de->magErrMean->data.F32[i] = psMetadataLookupF32(NULL, row, "ERR.MEAN");
+    }
+
+    psFree(table);
+    return de;
+}
+
+bool pmReadoutReadDetEff(psFits *fits, const pmReadout *readout, const char *extname)
+{
+    PM_ASSERT_READOUT_NON_NULL(readout, false);
+
+    pmDetEff *de = pmDetEffRead(fits, extname);
+    if (!de) {
+        if (psErrorCodeLast() != PS_ERR_NONE) {
+            return false;
+        }
+        return true;
+    }
+
+    bool status = psMetadataAddPtr(readout->analysis, PS_LIST_TAIL, PM_DETEFF_ANALYSIS, PS_META_REPLACE | PS_DATA_UNKNOWN, "Detection efficiency", de);
+    psFree (de);
+    return status;
+}
Index: branches/simmosaic_branches/psModules/src/objects/pmDetEff.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmDetEff.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmDetEff.h	(revision 27839)
@@ -0,0 +1,81 @@
+#ifndef PM_DET_EFF_H
+#define PM_DET_EFF_H
+
+#include <pslib.h>
+#include <string.h>
+
+#include "pmFPA.h"
+
+#define PM_DETEFF_ANALYSIS "DETEFF"     // Location of detection efficiency on pmReadout.analysis
+
+// Detection efficiency characterisation
+typedef struct {
+    float magRef;                       // Reference magnitude
+    int numSources;                     // Number of sources
+    int numBins;                        // Number of bins
+    psVector *magOffsets;               // Magnitude offsets for each bin
+    psVector *counts;                   // Counts of sources recovered for each bin
+    psVector *magDiffMean;              // Mean magnitude difference for each bin
+    psVector *magDiffStdev;             // Stdev of magnitude difference for each bin
+    psVector *magErrMean;               // Mean magnitude error for each bin
+} pmDetEff;
+
+
+/// Allocator
+pmDetEff *pmDetEffAlloc(float magRef,   // Reference magnitude
+                        int numSources, // Number of sources
+                        int numBins     // Number of bins
+                        );
+
+/// Write detection efficiency to FITS file
+bool pmDetEffWrite(psFits *fits,        // FITS file to which to write
+                   pmDetEff *deteff,    // Detection efficiency to write
+                   const psMetadata *header, // Header to write
+                   const char *extname  // Extension name
+                   );
+
+/// Write detection efficiency from a readout to a FITS file
+bool pmReadoutWriteDetEff(psFits *fits,// FITS file to which to write
+                          const pmReadout *readout, // Readout with detection efficiency
+                          const psMetadata *header, // Header to write
+                          const char *extname // Extension name
+    );
+
+/// Read detection efficiency
+pmDetEff *pmDetEffRead(psFits *fits,    // FITS file from which to read
+                       const char *extname // Extension name
+                       );
+
+/// Read detection efficiency into a readout
+bool pmReadoutReadDetEff(psFits *fits,// FITS file to which to write
+                         const pmReadout *readout, // Readout with detection efficiency
+                         const char *extname // Extension name
+    );
+
+#define PM_ASSERT_DETEFF_NON_NULL(DE, RETURN) { \
+    if (!(DE)) { \
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Detection efficiency %s is NULL", #DE); \
+        return RETURN; \
+    } \
+}
+
+#define PM_ASSERT_DETEFF_RESULTS(DE, RETURN) { \
+    PM_ASSERT_DETEFF_NON_NULL(DE, RETURN); \
+    PS_ASSERT_VECTOR_NON_NULL((DE)->magOffsets, RETURN); \
+    PS_ASSERT_VECTOR_SIZE((DE)->magOffsets, (long)(DE)->numBins, RETURN); \
+    PS_ASSERT_VECTOR_TYPE((DE)->magOffsets, PS_TYPE_F32, RETURN); \
+    PS_ASSERT_VECTOR_NON_NULL((DE)->counts, RETURN); \
+    PS_ASSERT_VECTOR_SIZE((DE)->counts, (long)(DE)->numBins, RETURN); \
+    PS_ASSERT_VECTOR_TYPE((DE)->counts, PS_TYPE_S32, RETURN); \
+    PS_ASSERT_VECTOR_NON_NULL((DE)->magDiffMean, RETURN); \
+    PS_ASSERT_VECTOR_SIZE((DE)->magDiffMean, (long)(DE)->numBins, RETURN); \
+    PS_ASSERT_VECTOR_TYPE((DE)->magDiffMean, PS_TYPE_F32, RETURN); \
+    PS_ASSERT_VECTOR_NON_NULL((DE)->magDiffStdev, RETURN); \
+    PS_ASSERT_VECTOR_SIZE((DE)->magDiffStdev, (long)(DE)->numBins, RETURN); \
+    PS_ASSERT_VECTOR_TYPE((DE)->magDiffStdev, PS_TYPE_F32, RETURN); \
+    PS_ASSERT_VECTOR_NON_NULL((DE)->magErrMean, RETURN); \
+    PS_ASSERT_VECTOR_SIZE((DE)->magErrMean, (long)(DE)->numBins, RETURN); \
+    PS_ASSERT_VECTOR_TYPE((DE)->magErrMean, PS_TYPE_F32, RETURN); \
+}
+
+#endif
Index: branches/simmosaic_branches/psModules/src/objects/pmDetections.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmDetections.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmDetections.c	(revision 27839)
@@ -26,4 +26,8 @@
   psFree (detections->peaks);
   psFree (detections->oldPeaks);
+  psFree (detections->oldFootprints);
+
+  psFree (detections->newSources);
+  psFree (detections->allSources);
   return;
 }
@@ -35,8 +39,11 @@
     psMemSetDeallocator(detections, (psFreeFunc) pmDetectionsFree);
 
-    detections->footprints = NULL;
-    detections->peaks      = NULL;
-    detections->oldPeaks   = NULL;
-    detections->last       = 0;
+    detections->footprints    = NULL;
+    detections->peaks         = NULL;
+    detections->oldPeaks      = NULL;
+    detections->oldFootprints = NULL;
+    detections->newSources    = NULL;
+    detections->allSources    = NULL;
+    detections->last          = 0;
 
     return (detections);
Index: branches/simmosaic_branches/psModules/src/objects/pmDetections.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmDetections.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmDetections.h	(revision 27839)
@@ -21,6 +21,9 @@
 typedef struct {
   psArray *footprints;        // collection of footprints in the image
+  psArray *oldFootprints;     // collection of footprints previously found
   psArray *peaks;             // collection of all peaks contained by the footprints
   psArray *oldPeaks;          // collection of all peaks previously found
+  psArray *newSources;        // collection of sources
+  psArray *allSources;        // collection of sources
   int last;
 } pmDetections;
Index: branches/simmosaic_branches/psModules/src/objects/pmFootprint.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmFootprint.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmFootprint.c	(revision 27839)
@@ -53,4 +53,5 @@
     assert(nspan >= 0);
     footprint->npix = 0;
+    footprint->nspans = 0; // we may allocate more spans than we set -- this is the number of active spans
     footprint->spans = psArrayAllocEmpty(nspan);
     footprint->peaks = psArrayAlloc(0);
@@ -73,4 +74,23 @@
 }
 
+bool pmFootprintAllocEmptySpans (pmFootprint *footprint, int nSpans) {
+
+    psArrayRealloc (footprint->spans, nSpans);
+    for (int i = 0; i < nSpans; i++) {
+	footprint->spans->data[i] = pmSpanAlloc(-1, -1, -1);
+    }
+    footprint->spans->n = nSpans;
+    return true;
+}
+
+// reset the footprint containers
+bool pmFootprintInit(pmFootprint *footprint) {
+
+    footprint->bbox.x0 = footprint->bbox.y0 = 0;
+    footprint->bbox.x1 = footprint->bbox.y1 = -1;
+    footprint->nspans = 0;
+    return true;
+}
+
 bool pmFootprintTest(const psPtr ptr) {
     return (psMemGetDeallocator(ptr) == (psFreeFunc)footprintFree);
@@ -103,8 +123,10 @@
     psArrayAdd(fp->spans, 1, sp);
     psFree(sp);
-    
+
+    fp->nspans ++;
+
     fp->npix += x1 - x0 + 1;
 
-    if (fp->spans->n == 1) {
+    if (fp->nspans == 1) {
 	fp->bbox.x0 = x0;
 	fp->bbox.x1 = x1;
@@ -121,7 +143,55 @@
 }
 
+
+// Set the next available elements of the nSpan entry in footprint->spans
+pmSpan *pmFootprintSetSpan(pmFootprint *fp,	// the footprint to add to
+			   const int y,		// row to add
+			   int x0,		// range of
+			   int x1) {		// columns
+
+    if (x1 < x0) {
+	int tmp = x0;
+	x0 = x1;
+	x1 = tmp;
+    }
+
+    int N = fp->nspans;
+    if (N == fp->spans->n) {
+	// if we need more space, extend fp->spans as needed
+	int Nalloc = fp->spans->n + 100;
+	psArrayRealloc(fp->spans, Nalloc);
+	fp->spans->n = Nalloc;
+	for (int i = N; i < fp->spans->n; i++) {
+	    fp->spans->data[i] = pmSpanAlloc(-1, -1, -1);
+	}
+    }
+
+    pmSpan *span = fp->spans->data[N];
+    span->y = y;
+    span->x0 = x0;
+    span->x1 = x1;
+
+    fp->nspans ++;
+
+    fp->npix += x1 - x0 + 1;
+
+    if (fp->nspans == 1) {
+	fp->bbox.x0 = x0;
+	fp->bbox.x1 = x1;
+	fp->bbox.y0 = y;
+	fp->bbox.y1 = y;
+    } else {
+	if (x0 < fp->bbox.x0) fp->bbox.x0 = x0;
+	if (x1 > fp->bbox.x1) fp->bbox.x1 = x1;
+	if (y < fp->bbox.y0) fp->bbox.y0 = y;
+	if (y > fp->bbox.y1) fp->bbox.y1 = y;
+    }
+
+    return span;
+}
+
 void pmFootprintSetBBox(pmFootprint *fp) {
     assert (fp != NULL);
-    if (fp->spans->n == 0) {
+    if (fp->nspans == 0) {
 	return;
     }
@@ -132,5 +202,5 @@
     int y1 = sp->y;
 
-    for (int i = 1; i < fp->spans->n; i++) {
+    for (int i = 1; i < fp->nspans; i++) {
 	sp = fp->spans->data[i];
 	
@@ -150,5 +220,5 @@
    assert (fp != NULL);
    int npix = 0;
-   for (int i = 0; i < fp->spans->n; i++) {
+   for (int i = 0; i < fp->nspans; i++) {
        pmSpan *span = fp->spans->data[i];
        npix += span->x1 - span->x0 + 1;
Index: branches/simmosaic_branches/psModules/src/objects/pmFootprint.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmFootprint.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmFootprint.h	(revision 27839)
@@ -13,10 +13,11 @@
 #include <pslib.h>
 #include "pmSpan.h"
-
+#include "pmFootprintSpans.h"
 
 typedef struct {
     const int id;                       //!< unique ID
     int npix;                           //!< number of pixels in this pmFootprint
-    psArray *spans;                     //!< the pmSpans
+    int nspans;
+    psArray *spans;                     //!< the allocated pmSpans
     psRegion bbox;                      //!< the pmFootprint's bounding box
     psArray *peaks;                     //!< the peaks lying in this footprint
@@ -26,5 +27,8 @@
 
 pmFootprint *pmFootprintAlloc(int nspan, const psImage *img);
+bool pmFootprintInit(pmFootprint *footprint);
 bool pmFootprintTest(const psPtr ptr);
+
+bool pmFootprintAllocEmptySpans (pmFootprint *footprint, int nSpans);
 
 pmFootprint *pmFootprintNormalize(pmFootprint *fp);
@@ -37,9 +41,30 @@
                            int x1);    //          columns
 
+pmSpan *pmFootprintSetSpan(pmFootprint *fp,	// the footprint to add to
+			   const int y,		// row to add
+			   int x0,		// range of
+			   int x1); 		// columns
+
 psArray *pmFootprintsFind(const psImage *img, const float threshold, const int npixMin);
-pmFootprint *pmFootprintsFindAtPoint(const psImage *img,
-                                    const float threshold,
-                                    const psArray *peaks,
-                                    int row, int col);
+
+bool pmFootprintsFindAtPoint(pmFootprint *fp,
+			     pmFootprintSpans *fpSpans,
+			     const psImage *img,     // image to search
+			     psImage *mask,
+			     const float threshold,   // Threshold
+			     const psArray *peaks, // array of peaks; finding one terminates search for footprint
+			     int row, int col);
+
+// pmFootprint *pmFootprintsFindAtPoint(const psImage *img,
+//                                     const float threshold,
+//                                     const psArray *peaks,
+//                                     int row, int col);
+
+bool pmFootprintSpansBuild(pmFootprint *fp, // the footprint that we're building
+			   pmFootprintSpans *fpSpans,
+			   const psImage *img, // the psImage we're working on
+			   psImage *mask, // the associated masks
+			   const float threshold // Threshold
+    );
 
 psArray *pmFootprintArrayGrow(const psArray *footprints, int r);
@@ -51,12 +76,16 @@
 void pmSetFootprintArrayIDsForImage(psImage *idImage,
                                     const psArray *footprints, // the footprints to insert
-                                    const bool relativeIDs); // show IDs starting at 0, not pmFootprint->id
+                                    const bool relativeIDs // show IDs starting at 0, not pmFootprint->id
+    );
 
 psErrorCode pmFootprintsAssignPeaks(psArray *footprints, const psArray *peaks);
 
-psErrorCode pmFootprintArrayCullPeaks(const psImage *img, const psImage *weight, psArray *footprints,
-                                 const float nsigma, const float threshold_min);
-psErrorCode pmFootprintCullPeaks(const psImage *img, const psImage *weight, pmFootprint *fp,
-                                 const float nsigma, const float threshold_min);
+psErrorCode pmFootprintCullPeaks(const psImage *img,       // the image wherein lives the footprint
+				 const psImage *weight,	   // corresponding variance image
+				 pmFootprint *fp,	   // Footprint containing mortal peaks
+				 const float nsigma_delta, // how many sigma above local background a peak needs to be to survive
+				 const float fPadding, // fractional padding added to stdev since bright peaks have unreasonably high significance
+				 const float min_threshold // minimum permitted coll height
+    );
 
 psArray *pmFootprintArrayToPeaks(const psArray *footprints);
Index: branches/simmosaic_branches/psModules/src/objects/pmFootprintCullPeaks.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmFootprintCullPeaks.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmFootprintCullPeaks.c	(revision 27839)
@@ -0,0 +1,181 @@
+/* @file  pmFootprint.c
+ * low-level pmFootprint functions
+ *
+ * @author RHL, Princeton & IfA; EAM, IfA
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-12-08 02:51:14 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <strings.h>
+#include <pslib.h>
+#include "pmSpan.h"
+#include "pmFootprint.h"
+#include "pmFootprintSpans.h"
+#include "pmPeaks.h"
+
+bool dumpfootprints (pmFootprint *fp, pmFootprintSpans *fpSp);
+
+ /*
+  * Examine the peaks in a pmFootprint, and throw away the ones that are not sufficiently
+  * isolated.  More precisely, for each peak find the highest coll that you'd have to traverse
+  * to reach a still higher peak --- and if that coll's more than nsigma DN below your
+  * starting point, discard the peak.
+  */
+
+# define IN_PEAK 1
+psErrorCode pmFootprintCullPeaks(const psImage *img, // the image wherein lives the footprint
+                                 const psImage *weight, // corresponding variance image
+                                 pmFootprint *fp, // Footprint containing mortal peaks
+                                 const float nsigma_delta, // how many sigma above local background a peak needs to be to survive
+                                 const float fPadding, // fractional padding added to stdev since bright peaks have unreasonably high significance
+                                 const float min_threshold) { // minimum permitted coll height
+    assert (img != NULL); assert (img->type.type == PS_TYPE_F32);
+    assert (weight != NULL); assert (weight->type.type == PS_TYPE_F32);
+    assert (img->row0 == weight->row0 && img->col0 == weight->col0);
+    assert (fp != NULL);
+
+    if (fp->peaks == NULL || fp->peaks->n == 0) { // nothing to do
+        return PS_ERR_NONE;
+    }
+
+    psRegion subRegion;                 // desired subregion; 1 larger than bounding box (grr)
+    subRegion.x0 = fp->bbox.x0; subRegion.x1 = fp->bbox.x1 + 1;
+    subRegion.y0 = fp->bbox.y0; subRegion.y1 = fp->bbox.y1 + 1;
+
+    psImage *subImg = psImageSubset((psImage *)img, subRegion);
+    psImage *subWt = psImageSubset((psImage *)weight, subRegion);
+    assert (subImg != NULL && subWt != NULL);
+
+    psImage *idImg = psImageAlloc(subImg->numCols, subImg->numRows, PS_TYPE_S32);
+
+    // We need a psArray of peaks brighter than the current peak.
+    // We reject peaks which either:
+    // 1) are below the local threshold
+    // 2) have a brighter peak within their threshold
+
+    // allocate the full-sized array.  if the final array is much smaller, we can realloc
+    // at that point.
+    psArray *brightPeaks = psArrayAllocEmpty(fp->peaks->n);
+    psArrayAdd (brightPeaks, 128, fp->peaks->data[0]);
+
+    // allocate the peakFootprint and peakFPSpans containers -- these are re-used by pmFootprintsFindAtPoint to minimize allocs in this function
+    pmFootprint *peakFootprint = pmFootprintAlloc(fp->nspans, subImg);
+    pmFootprintSpans *peakFPSpans = pmFootprintSpansAlloc(2*fp->nspans);
+
+    // allocate empty spans for the footprints
+    pmFootprintAllocEmptySpans(peakFootprint, fp->nspans);
+
+    psImage *subMask = psImageCopy(NULL, subImg, PS_TYPE_IMAGE_MASK);
+
+    // The brightest peak is always safe; go through other peaks trying to cull them
+    for (int i = 1; i < fp->peaks->n; i++) { // n.b. fp->peaks->n can change within the loop
+        const pmPeak *peak = fp->peaks->data[i];
+        int x = peak->x - subImg->col0;
+        int y = peak->y - subImg->row0;
+        //
+        // Find the level nsigma below the peak that must separate the peak
+        // from any of its friends
+        //
+        assert (x >= 0 && x < subImg->numCols && y >= 0 && y < subImg->numRows);
+
+        // const float stdev = sqrt(subWt->data.F32[y][x]);
+        // float threshold = subImg->data.F32[y][x] - nsigma_delta*stdev;
+
+        const float stdev = sqrt(subWt->data.F32[y][x]);
+        const float flux = subImg->data.F32[y][x];
+        const float fStdev = fabs(stdev/flux);
+        const float stdev_pad = fabs(flux * hypot(fStdev, fPadding));
+        // if flux is negative, careful with fStdev
+
+        float threshold = flux - nsigma_delta*stdev_pad;
+
+        if (isnan(threshold) || threshold < min_threshold) {
+            // min_threshold is assumed to be below the detection threshold,
+            // so all the peaks are pmFootprint, and this isn't the brightest
+            continue;
+        }
+
+        // XXX EAM : if stdev >= 0, i'm not sure how this can ever be true?
+        if (threshold > subImg->data.F32[y][x]) {
+            threshold = subImg->data.F32[y][x] - 10*FLT_EPSILON;
+        }
+
+	// init peakFootprint here?
+        pmFootprintsFindAtPoint(peakFootprint, peakFPSpans, subImg, subMask, threshold, brightPeaks, peak->y, peak->x);
+	if (peakFPSpans->nStartSpans > 2000) {
+	    // dumpfootprints(peakFootprint, peakFPSpans);
+	    // fprintf (stderr, "big footprint %d : %d\n", peakFootprint->nspans, peakFPSpans->nStartSpans);
+	    fprintf (stderr, "big test footprint: %f %f to %f %f (%d pix)\n", peakFootprint->bbox.x0, peakFootprint->bbox.y0, peakFootprint->bbox.x1, peakFootprint->bbox.y1, peakFootprint->npix);
+	}
+
+        // at this point brightPeaks only has the peaks brighter than the current
+
+        // we set the IDs to either 1 (in peak) or 0 (not in peak)
+        pmSetFootprintID (idImg, peakFootprint, IN_PEAK);
+
+        // If this peak has not already been assigned to a source, then we can look for any
+        // brighter peaks within its footprint. Check if any of the previous (brighter) peaks
+        // are within the footprint of this peak If so, the current peak is bogus; drop it.
+        bool keep = true;
+        for (int j = 0; keep && !peak->assigned && (j < brightPeaks->n); j++) {
+            const pmPeak *peak2 = fp->peaks->data[j];
+            int x2 = peak2->x - subImg->col0;
+            int y2 = peak2->y - subImg->row0;
+            if (idImg->data.S32[y2][x2] == IN_PEAK) {
+                // There's a brighter peak within the footprint above threshold; so cull our initial peak
+                keep = false;
+	    }
+        }
+        if (!keep) {
+	    psAssert (!peak->assigned, "logic error: trying to drop a previously-assigned peak");  // we should not drop any already assigned peaks.
+	    continue;
+	}
+
+        psArrayAdd (brightPeaks, 128, fp->peaks->data[i]);
+    }
+
+    psFree (fp->peaks);
+    fp->peaks = brightPeaks;
+
+    psFree(idImg);
+    psFree(subImg);
+    psFree(subWt);
+    psFree(subMask);
+    psFree(peakFootprint);
+    psFree(peakFPSpans);
+
+    return PS_ERR_NONE;
+}
+
+
+bool dumpfootprints (pmFootprint *fp, pmFootprintSpans *fpSp) {
+
+    FILE *f1 = fopen ("fp.dat", "w");
+    if (!f1) return false;
+
+    for (int i = 0; i < fp->nspans; i++) {
+	pmSpan *sp = fp->spans->data[i];
+	fprintf (f1, "%d - %d : %d\n", sp->x0, sp->x1, sp->y);
+    }
+    fclose (f1);
+
+    FILE *f2 = fopen ("fpSp.dat", "w");
+    if (!f2) return false;
+
+    for (int i = 0; i < fpSp->nStartSpans; i++) {
+	pmStartSpan *sp = fpSp->startspans->data[i];
+	if (!sp->span) continue;
+	fprintf (f2, "%d - %d : %d\n", sp->span->x0, sp->span->x1, sp->span->y);
+    }
+    fclose (f2);
+    return true;
+}
Index: branches/simmosaic_branches/psModules/src/objects/pmFootprintFindAtPoint.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmFootprintFindAtPoint.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmFootprintFindAtPoint.c	(revision 27839)
@@ -21,103 +21,9 @@
 #include "pmFootprint.h"
 #include "pmPeaks.h"
-
-/*
- * A data structure to hold the starting point for a search for pixels above threshold,
- * used by pmFootprintsFindAtPoint
- *
- * We don't want to find this span again --- it's already part of the footprint ---
- * so we set appropriate mask bits
- *
- * EAM : these function were confusingly using "startspan" and "spartspan"  
- * I've rationalized them all to 'startspan'
- */
-
-//
-// An enum for what we should do with a Startspan
-//
-typedef enum {PM_SSPAN_DOWN = 0,	// scan down from this span
-	      PM_SSPAN_UP,		// scan up from this span
-	      PM_SSPAN_RESTART,		// restart scanning from this span
-	      PM_SSPAN_DONE		// this span is processed
-} PM_SSPAN_DIR;				// How to continue searching
-//
-// An enum for mask's pixel values.  We're looking for pixels that are above threshold, and
-// we keep extra book-keeping information in the PM_SSPAN_STOP plane.  It's simpler to be
-// able to check for 
-//
-enum {
-    PM_SSPAN_INITIAL = 0x0,		// initial state of pixels.
-    PM_SSPAN_DETECTED = 0x1,		// we've seen this pixel
-    PM_SSPAN_STOP = 0x2			// you may stop searching when you see this pixel
-};
-//
-// The struct that remembers how to [re-]start scanning the image for pixels
-//
-typedef struct {
-    const pmSpan *span;			// save the pixel range
-    PM_SSPAN_DIR direction;		// How to continue searching
-    bool stop;				// should we stop searching?
-} Startspan;
-
-static void startspanFree(Startspan *sspan) {
-    psFree((void *)sspan->span);
-}
-
-static Startspan *
-StartspanAlloc(const pmSpan *span,	// The span in question
-	       psImage *mask,		// Pixels that we've already detected
-	       const PM_SSPAN_DIR dir	// Should we continue searching towards the top of the image?
-    ) {
-    Startspan *sspan = psAlloc(sizeof(Startspan));
-    psMemSetDeallocator(sspan, (psFreeFunc)startspanFree);
-
-    sspan->span = psMemIncrRefCounter((void *)span);
-    sspan->direction = dir;
-    sspan->stop = false;
-    
-    if (mask != NULL) {			// remember that we've detected these pixels
-	psImageMaskType *mpix = &mask->data.PS_TYPE_IMAGE_MASK_DATA[span->y - mask->row0][span->x0 - mask->col0];
-
-	for (int i = 0; i <= span->x1 - span->x0; i++) {
-	    mpix[i] |= PM_SSPAN_DETECTED;
-	    if (mpix[i] & PM_SSPAN_STOP) {
-		sspan->stop = true;
-	    }
-	}
-    }
-    
-    return sspan;
-}
-
-//
-// Add a new Startspan to an array of Startspans.  Iff we see a stop bit, return true
-//
-static bool add_startspan(psArray *startspans, // the saved Startspans
-			  const pmSpan *sp, // the span in question
-			  psImage *mask, // mask of detected/stop pixels
-			  const PM_SSPAN_DIR dir) { // the desired direction to search
-    if (dir == PM_SSPAN_RESTART) {
-	if (add_startspan(startspans, sp, mask,  PM_SSPAN_UP) ||
-	    add_startspan(startspans, sp, NULL, PM_SSPAN_DOWN)) {
-	    return true;
-	}
-    } else {
-	Startspan *sspan = StartspanAlloc(sp, mask, dir);
-	if (sspan->stop) {		// we detected a stop bit
-	    psFree(sspan);		// don't allocate new span
-
-	    return true;
-	} else {
-	    psArrayAdd(startspans, 1, sspan);
-	    psFree(sspan);		// as it's now owned by startspans
-	}
-    }
-
-    return false;
-}
+#include "pmFootprintSpans.h"
 
 /************************************************************************************************************/
 /*
- * Search the image for pixels above threshold, starting at a single Startspan.
+ * Search the image for pixels above threshold, starting at a single pmStartSpan.
  * We search the array looking for one to process; it'd be better to move the
  * ones that we're done with to the end, but it probably isn't worth it for
@@ -126,284 +32,304 @@
  * This is the guts of pmFootprintsFindAtPoint
  */
-static bool do_startspan(pmFootprint *fp, // the footprint that we're building
-			 const psImage *img, // the psImage we're working on
-			 psImage *mask, // the associated masks
-			 const float threshold,	// Threshold
-			 psArray *startspans) {	// specify which span to process next
-    bool F32 = false;			// is this an F32 image?
+bool pmFootprintSpansBuild(pmFootprint *fp, // the footprint that we're building
+			   pmFootprintSpans *fpSpans,
+			   const psImage *img, // the psImage we're working on
+			   psImage *mask, // the associated masks
+			   const float threshold // Threshold
+    ) {
+    bool F32 = false;                   // is this an F32 image?
+    if (img->type.type == PS_TYPE_F32) {
+        F32 = true;
+    } else if (img->type.type == PS_TYPE_S32) {
+        F32 = false;
+    } else {                            // N.b. You can't trivially add more cases here; F32 is just a bool
+        psError(PS_ERR_UNKNOWN, true, "Unsupported psImage type: %d", img->type.type);
+        return NULL;
+    }
+
+    psF32 *imgRowF32 = NULL;            // row pointer if F32
+    psS32 *imgRowS32 = NULL;            //  "   "   "  "  !F32
+    psImageMaskType *maskRow = NULL;            //  masks's row pointer
+
+    const int row0 = img->row0;
+    const int col0 = img->col0;
+    const int numRows = img->numRows;
+    const int numCols = img->numCols;
+
+    /********************************************************************************************************/
+
+    pmStartSpan *startspan = NULL;
+    for (int i = 0; i < fpSpans->nStartSpans; i++) {
+        startspan = fpSpans->startspans->data[i];
+        if (startspan->direction != PM_STARTSPAN_DONE) {
+            break;
+        }
+        if (startspan->stop) {
+            break;
+        }
+    }
+    if (startspan == NULL || startspan->direction == PM_STARTSPAN_DONE) { // no more pmStartSpans to process
+        return false;
+    }
+    if (startspan->stop) {                  // they don't want any more spans processed
+        return false;
+    }
+
+    /*
+     * Work
+     */
+    const PM_STARTSPAN_DIR dir = startspan->direction;
+    /*
+     * Set initial span to the startspan
+     */
+    int x0 = startspan->span->x0 - col0, x1 = startspan->span->x1 - col0;
+    /*
+     * Go through image identifying objects
+     */
+    int nx0, nx1 = -1;                  // new values of x0, x1
+    const int di = (dir == PM_STARTSPAN_UP) ? 1 : -1; // how much i changes to get to the next row
+    bool stop = false;                  // should I stop searching for spans?
+
+    for (int i = startspan->span->y - row0 + di; i < numRows && i >= 0; i += di) {
+        imgRowF32 = img->data.F32[i];   // only one of
+        imgRowS32 = img->data.S32[i];   //      these is valid!
+        maskRow = mask->data.PS_TYPE_IMAGE_MASK_DATA[i];
+        //
+        // Search left from the pixel diagonally to the left of (i - di, x0). If there's
+        // a connected span there it may need to grow up and/or down, so push it onto
+        // the stack for later consideration
+        //
+        nx0 = -1;
+        for (int j = x0 - 1; j >= -1; j--) {
+            double pixVal = (j < 0) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
+            if ((maskRow[j] & PM_STARTSPAN_DETECTED) || pixVal < threshold) {
+                if (j < x0 - 1) {       // we found some pixels above threshold
+                    nx0 = j + 1;
+                }
+                break;
+            }
+        }
+
+        if (nx0 < 0) {                  // no span to the left
+            nx1 = x0 - 1;               // we're going to resume searching at nx1 + 1
+        } else {
+            //
+            // Search right in leftmost span
+            //
+            for (int j = nx0 + 1; j <= numCols; j++) {
+                double pixVal = (j >= numCols) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
+                if ((maskRow[j] & PM_STARTSPAN_DETECTED) || pixVal < threshold) {
+                    nx1 = j - 1;
+                    break;
+                }
+            }
+
+	    pmSpan *sp = pmFootprintSetSpan(fp, i + row0, nx0 + col0, nx1 + col0);
+	    bool status = pmFootprintSpansSet(fpSpans, sp, mask, PM_STARTSPAN_RESTART);
+	    // fprintf (stderr, "set 1: %d vs %d\n", fp->nspans, fpSpans->nStartSpans);
+            if (status) {
+                stop = true;
+                break;
+            }
+        }
+        //
+        // Now look for spans connected to the old span.  The first of these we'll
+        // simply process, but others will have to be deferred for later consideration.
+        //
+        // In fact, if the span overhangs to the right we'll have to defer the overhang
+        // until later too, as it too can grow in both directions
+        //
+        // Note that column numCols exists virtually, and always ends the last span; this
+        // is why we claim below that sx1 is always set
+        //
+        bool first = false;             // is this the first new span detected?
+        for (int j = nx1 + 1; j <= x1 + 1; j++) {
+            double pixVal = (j >= numCols) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
+            if (!(maskRow[j] & PM_STARTSPAN_DETECTED) && pixVal >= threshold) {
+                int sx0 = j++;          // span that we're working on is sx0:sx1
+                int sx1 = -1;           // We know that if we got here, we'll also set sx1
+                for (; j <= numCols; j++) {
+                    double pixVal = (j >= numCols) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
+                    if ((maskRow[j] & PM_STARTSPAN_DETECTED) || pixVal < threshold) { // end of span
+                        sx1 = j;
+                        break;
+                    }
+                }
+                assert (sx1 >= 0);
+
+                pmSpan *sp;
+                if (first) {
+                    if (sx1 <= x1) {
+                        sp = pmFootprintSetSpan(fp, i + row0, sx0 + col0, sx1 + col0 - 1);
+                        bool status = pmFootprintSpansSet(fpSpans, sp, mask, PM_STARTSPAN_DONE);
+			// fprintf (stderr, "set 2: %d vs %d\n", fp->nspans, fpSpans->nStartSpans);
+                        if (status) {
+                            stop = true;
+                            break;
+                        }
+                    } else {            // overhangs to right
+                        sp = pmFootprintSetSpan(fp, i + row0, sx0 + col0, x1 + col0);
+                        bool status = pmFootprintSpansSet(fpSpans, sp, mask, PM_STARTSPAN_DONE);
+			// fprintf (stderr, "set 3: %d vs %d\n", fp->nspans, fpSpans->nStartSpans);
+			if (status) {
+                            stop = true;
+                            break;
+                        }
+                        sp = pmFootprintSetSpan(fp, i + row0, x1 + 1 + col0, sx1 + col0 - 1);
+                        status = pmFootprintSpansSet(fpSpans, sp, mask, PM_STARTSPAN_RESTART);
+			// fprintf (stderr, "set 4: %d vs %d\n", fp->nspans, fpSpans->nStartSpans);
+			if (status) {
+                            stop = true;
+                            break;
+                        }
+                    }
+                    first = false;
+                } else {
+                    sp = pmFootprintSetSpan(fp, i + row0, sx0 + col0, sx1 + col0 - 1);
+                    bool status = pmFootprintSpansSet(fpSpans, sp, mask, PM_STARTSPAN_RESTART);
+		    // fprintf (stderr, "set 5: %d vs %d\n", fp->nspans, fpSpans->nStartSpans);
+		    if (status) {
+                        stop = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (stop || first == false) {   // we're done
+            break;
+        }
+
+        x0 = nx0; x1 = nx1;
+    }
+    /*
+     * Cleanup
+     */
+
+    startspan->direction = PM_STARTSPAN_DONE;
+    return stop ? false : true;
+}
+
+/*
+ * Go through an image, starting at (row, col) and assembling all the pixels
+ * that are connected to that point (in a chess kings-move sort of way) into
+ * a pmFootprint.
+ *
+ * This is much slower than pmFootprintsFind if you want to find lots of
+ * footprints, but if you only want a small region about a given point it
+ * can be much faster
+ *
+ * N.b. The returned pmFootprint is not in "normal form"; that is the pmSpans
+ * are not sorted by increasing y, x0, x1.  If this matters to you, call
+ * pmFootprintNormalize()
+ * 
+ * The calling function must supply a footprint allocated with a reasonable amount of space
+ *
+ */
+
+bool pmFootprintsFindAtPoint(pmFootprint *fp,
+			     pmFootprintSpans *fpSpans,
+			     const psImage *img,     // image to search
+			     psImage *mask,
+			     const float threshold,   // Threshold
+			     const psArray *peaks, // array of peaks; finding one terminates search for footprint
+			     int row, int col) { // starting position (in img's parent's coordinate system)
+    psAssert(img, "image must be supplied");
+    psAssert(fp, "footprint must be supplied");
+    psAssert(fpSpans, "footprint spans must be supplied");
+
+    bool F32 = false;                    // is this an F32 image?
     if (img->type.type == PS_TYPE_F32) {
 	F32 = true;
     } else if (img->type.type == PS_TYPE_S32) {
 	F32 = false;
-    } else {				// N.b. You can't trivially add more cases here; F32 is just a bool
+    } else {                             // N.b. You can't trivially add more cases here; F32 is just a bool
 	psError(PS_ERR_UNKNOWN, true, "Unsupported psImage type: %d", img->type.type);
-	return NULL;
-    }
-
-    psF32 *imgRowF32 = NULL;		// row pointer if F32
-    psS32 *imgRowS32 = NULL;		//  "   "   "  "  !F32
-    psImageMaskType *maskRow = NULL;		//  masks's row pointer
-    
+	return false;
+    }
+    psF32 *imgRowF32 = NULL;             // row pointer if F32
+    psS32 *imgRowS32 = NULL;             //  "   "   "  "  !F32
+
     const int row0 = img->row0;
     const int col0 = img->col0;
     const int numRows = img->numRows;
     const int numCols = img->numCols;
-    
-    /********************************************************************************************************/
-    
-    Startspan *sspan = NULL;
-    for (int i = 0; i < startspans->n; i++) {
-	sspan = startspans->data[i];
-	if (sspan->direction != PM_SSPAN_DONE) {
-	    break;
+
+    /*
+     * Is point in image, and above threshold?
+     */
+    row -= row0; col -= col0;
+    if (row < 0 || row >= numRows ||
+	col < 0 || col >= numCols) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "row/col == (%d, %d) are out of bounds [%d--%d, %d--%d]",
+                row + row0, col + col0, row0, row0 + numRows - 1, col0, col0 + numCols - 1);
+	return false;
+    }
+
+    double pixVal = F32 ? img->data.F32[row][col] : img->data.S32[row][col];
+    if (pixVal < threshold) {
+	return true;
+    }
+
+    /*
+     * We need a mask for two purposes; to indicate which pixels are already detected,
+     * and to store the "stop" pixels --- those that, once reached, should stop us
+     * looking for the rest of the pmFootprint.  These are generally set from peaks.
+     */
+
+    pmFootprintInit(fp);
+    pmFootprintSpansInit(fpSpans);
+    psImageInit(mask, PM_STARTSPAN_INITIAL);
+
+    // fprintf (stderr, "init: %d vs %d\n", fp->nspans, fpSpans->nStartSpans);
+
+    //
+    // Set stop bits from peaks list
+    //
+    assert (peaks == NULL || peaks->n == 0 || psMemCheckPeak(peaks->data[0]));
+    if (peaks != NULL) {
+	for (int i = 0; i < peaks->n; i++) {
+	    pmPeak *peak = peaks->data[i];
+	    mask->data.PS_TYPE_IMAGE_MASK_DATA[peak->y - mask->row0][peak->x - mask->col0] |= PM_STARTSPAN_STOP;
 	}
-	if (sspan->stop) {
-	    break;
-	}
-    }
-    if (sspan == NULL || sspan->direction == PM_SSPAN_DONE) { // no more Startspans to process
-	return false;
-    }
-    if (sspan->stop) {			// they don't want any more spans processed
-	return false;
-    }
-    /*
-     * Work
-     */
-    const PM_SSPAN_DIR dir = sspan->direction;
-    /*
-     * Set initial span to the startspan
-     */
-    int x0 = sspan->span->x0 - col0, x1 = sspan->span->x1 - col0;
-    /*
-     * Go through image identifying objects
-     */
-    int nx0, nx1 = -1;			// new values of x0, x1
-    const int di = (dir == PM_SSPAN_UP) ? 1 : -1; // how much i changes to get to the next row
-    bool stop = false;			// should I stop searching for spans?
-
-    for (int i = sspan->span->y -row0 + di; i < numRows && i >= 0; i += di) {
-	imgRowF32 = img->data.F32[i];	// only one of
-	imgRowS32 = img->data.S32[i];	//      these is valid!
-	maskRow = mask->data.PS_TYPE_IMAGE_MASK_DATA[i];
-	//
-	// Search left from the pixel diagonally to the left of (i - di, x0). If there's
-	// a connected span there it may need to grow up and/or down, so push it onto
-	// the stack for later consideration
-	//
-	nx0 = -1;
-	for (int j = x0 - 1; j >= -1; j--) {
-	    double pixVal = (j < 0) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
-	    if ((maskRow[j] & PM_SSPAN_DETECTED) || pixVal < threshold) {
-		if (j < x0 - 1) {	// we found some pixels above threshold
-		    nx0 = j + 1;
-		}
+    }
+
+    /*
+     * Find starting span passing through (row, col)
+     */
+    imgRowF32 = img->data.F32[row];      // only one of
+    imgRowS32 = img->data.S32[row];      //      these is valid!
+    psImageMaskType *maskRow = mask->data.PS_TYPE_IMAGE_MASK_DATA[row];
+    {
+	int i;
+	for (i = col; i >= 0; i--) {
+	    pixVal = F32 ? imgRowF32[i] : imgRowS32[i];
+	    if ((maskRow[i] & PM_STARTSPAN_DETECTED) || pixVal < threshold) {
 		break;
 	    }
 	}
-
-	if (nx0 < 0) {			// no span to the left
-	    nx1 = x0 - 1;		// we're going to resume searching at nx1 + 1
-	} else {
-	    //
-	    // Search right in leftmost span
-	    //
-	    //nx1 = 0;			// make gcc happy
-	    for (int j = nx0 + 1; j <= numCols; j++) {
-		double pixVal = (j >= numCols) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
-		if ((maskRow[j] & PM_SSPAN_DETECTED) || pixVal < threshold) {
-		    nx1 = j - 1;
-		    break;
-		}
-	    }
-	    
-	    const pmSpan *sp = pmFootprintAddSpan(fp, i + row0, nx0 + col0, nx1 + col0);
-	    
-	    if (add_startspan(startspans, sp, mask, PM_SSPAN_RESTART)) {
-		stop = true;
+	int i0 = i;
+	for (i = col; i < numCols; i++) {
+	    pixVal = F32 ? imgRowF32[i] : imgRowS32[i];
+	    if ((maskRow[i] & PM_STARTSPAN_DETECTED) || pixVal < threshold) {
 		break;
 	    }
 	}
-	//
-	// Now look for spans connected to the old span.  The first of these we'll
-	// simply process, but others will have to be deferred for later consideration.
-	//
-	// In fact, if the span overhangs to the right we'll have to defer the overhang
-	// until later too, as it too can grow in both directions
-	//
-	// Note that column numCols exists virtually, and always ends the last span; this
-	// is why we claim below that sx1 is always set
-	//
-	bool first = false;		// is this the first new span detected?
-	for (int j = nx1 + 1; j <= x1 + 1; j++) {
-	    double pixVal = (j >= numCols) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
-	    if (!(maskRow[j] & PM_SSPAN_DETECTED) && pixVal >= threshold) {
-		int sx0 = j++;		// span that we're working on is sx0:sx1
-		int sx1 = -1;		// We know that if we got here, we'll also set sx1
-		for (; j <= numCols; j++) {
-		    double pixVal = (j >= numCols) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
-		    if ((maskRow[j] & PM_SSPAN_DETECTED) || pixVal < threshold) { // end of span
-			sx1 = j;
-			break;
-		    }
-		}
-		assert (sx1 >= 0);
-
-		const pmSpan *sp;
-		if (first) {
-		    if (sx1 <= x1) {
-			sp = pmFootprintAddSpan(fp, i + row0, sx0 + col0, sx1 + col0 - 1);
-			if (add_startspan(startspans, sp, mask, PM_SSPAN_DONE)) {
-			    stop = true;
-			    break;
-			}
-		    } else {		// overhangs to right
-			sp = pmFootprintAddSpan(fp, i + row0, sx0 + col0, x1 + col0);
-			if (add_startspan(startspans, sp, mask, PM_SSPAN_DONE)) {
-			    stop = true;
-			    break;
-			}
-			sp = pmFootprintAddSpan(fp, i + row0, x1 + 1 + col0, sx1 + col0 - 1);
-			if (add_startspan(startspans, sp, mask, PM_SSPAN_RESTART)) {
-			    stop = true;
-			    break;
-			}
-		    }
-		    first = false;
-		} else {
-		    sp = pmFootprintAddSpan(fp, i + row0, sx0 + col0, sx1 + col0 - 1);
-		    if (add_startspan(startspans, sp, mask, PM_SSPAN_RESTART)) {
-			stop = true;
-			break;
-		    }
-		}
-	    }
-	}
-
-	if (stop || first == false) {	// we're done
-	    break;
-	}
-
-	x0 = nx0; x1 = nx1;
-    }
+	int i1 = i;
+	pmSpan *sp = pmFootprintSetSpan(fp, row + row0, i0 + col0 + 1, i1 + col0 - 1);
+	pmFootprintSpansSet(fpSpans, sp, mask, PM_STARTSPAN_RESTART);
+	// fprintf (stderr, "first: %d vs %d\n", fp->nspans, fpSpans->nStartSpans);
+    }
+    /*
+     * Now workout from those pmStartSpans, searching for pixels above threshold
+     */
+    while (pmFootprintSpansBuild(fp, fpSpans, img, mask, threshold)) continue;
     /*
      * Cleanup
      */
-
-    sspan->direction = PM_SSPAN_DONE;
-    return stop ? false : true;
+    // psFree(mask);
+    // psFree(startspans);                  // restores the image pixel
+
+    return fp;                           // pmFootprint really
 }
-
-/*
- * Go through an image, starting at (row, col) and assembling all the pixels
- * that are connected to that point (in a chess kings-move sort of way) into
- * a pmFootprint.
- *
- * This is much slower than pmFootprintsFind if you want to find lots of
- * footprints, but if you only want a small region about a given point it
- * can be much faster
- *
- * N.b. The returned pmFootprint is not in "normal form"; that is the pmSpans
- * are not sorted by increasing y, x0, x1.  If this matters to you, call
- * pmFootprintNormalize()
- */
-pmFootprint *
-pmFootprintsFindAtPoint(const psImage *img,	// image to search
-		       const float threshold,	// Threshold
-		       const psArray *peaks, // array of peaks; finding one terminates search for footprint
-		       int row, int col) { // starting position (in img's parent's coordinate system)
-   assert(img != NULL);
-
-   bool F32 = false;			// is this an F32 image?
-   if (img->type.type == PS_TYPE_F32) {
-       F32 = true;
-   } else if (img->type.type == PS_TYPE_S32) {
-       F32 = false;
-   } else {				// N.b. You can't trivially add more cases here; F32 is just a bool
-       psError(PS_ERR_UNKNOWN, true, "Unsupported psImage type: %d", img->type.type);
-       return NULL;
-   }
-   psF32 *imgRowF32 = NULL;		// row pointer if F32
-   psS32 *imgRowS32 = NULL;		//  "   "   "  "  !F32
-   
-   const int row0 = img->row0;
-   const int col0 = img->col0;
-   const int numRows = img->numRows;
-   const int numCols = img->numCols;
-/*
- * Is point in image, and above threshold?
- */
-   row -= row0; col -= col0;
-   if (row < 0 || row >= numRows ||
-       col < 0 || col >= numCols) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
-                "row/col == (%d, %d) are out of bounds [%d--%d, %d--%d]",
-		row + row0, col + col0, row0, row0 + numRows - 1, col0, col0 + numCols - 1);
-       return NULL;
-   }
-
-   double pixVal = F32 ? img->data.F32[row][col] : img->data.S32[row][col];
-   if (pixVal < threshold) {
-       return pmFootprintAlloc(0, img);
-   }
-   
-   pmFootprint *fp = pmFootprintAlloc(1 + img->numRows/10, img);
-/*
- * We need a mask for two purposes; to indicate which pixels are already detected,
- * and to store the "stop" pixels --- those that, once reached, should stop us
- * looking for the rest of the pmFootprint.  These are generally set from peaks.
- */
-   psImage *mask = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK);
-   P_PSIMAGE_SET_ROW0(mask, row0);
-   P_PSIMAGE_SET_COL0(mask, col0);
-   psImageInit(mask, PM_SSPAN_INITIAL);
-   //
-   // Set stop bits from peaks list
-   //
-   assert (peaks == NULL || peaks->n == 0 || psMemCheckPeak(peaks->data[0]));
-   if (peaks != NULL) {
-       for (int i = 0; i < peaks->n; i++) {
-	   pmPeak *peak = peaks->data[i];
-	   mask->data.PS_TYPE_IMAGE_MASK_DATA[peak->y - mask->row0][peak->x - mask->col0] |= PM_SSPAN_STOP;
-       }
-   }
-/*
- * Find starting span passing through (row, col)
- */
-   psArray *startspans = psArrayAllocEmpty(1); // spans where we have to restart the search
-   
-   imgRowF32 = img->data.F32[row];	// only one of
-   imgRowS32 = img->data.S32[row];	//      these is valid!
-   psImageMaskType *maskRow = mask->data.PS_TYPE_IMAGE_MASK_DATA[row];
-   {
-       int i;
-       for (i = col; i >= 0; i--) {
-	   pixVal = F32 ? imgRowF32[i] : imgRowS32[i];
-	   if ((maskRow[i] & PM_SSPAN_DETECTED) || pixVal < threshold) {
-	       break;
-	   }
-       }
-       int i0 = i;
-       for (i = col; i < numCols; i++) {
-	   pixVal = F32 ? imgRowF32[i] : imgRowS32[i];
-	   if ((maskRow[i] & PM_SSPAN_DETECTED) || pixVal < threshold) {
-	       break;
-	   }
-       }
-       int i1 = i;
-       const pmSpan *sp = pmFootprintAddSpan(fp, row + row0, i0 + col0 + 1, i1 + col0 - 1);
-
-       (void)add_startspan(startspans, sp, mask, PM_SSPAN_RESTART);
-   }
-   /*
-    * Now workout from those Startspans, searching for pixels above threshold
-    */
-   while (do_startspan(fp, img, mask, threshold, startspans)) continue;
-   /*
-    * Cleanup
-    */
-   psFree(mask);
-   psFree(startspans);			// restores the image pixel
-
-   return fp;				// pmFootprint really
-}
Index: branches/simmosaic_branches/psModules/src/objects/pmFootprintIDs.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmFootprintIDs.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmFootprintIDs.c	(revision 27839)
@@ -32,5 +32,5 @@
    const int row0 = fp->region.y0;
 
-   for (int j = 0; j < fp->spans->n; j++) {
+   for (int j = 0; j < fp->nspans; j++) {
        const pmSpan *span = fp->spans->data[j];
        psS32 *imgRow = idImage->data.S32[span->y - row0];
Index: branches/simmosaic_branches/psModules/src/objects/pmFootprintSpans.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmFootprintSpans.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmFootprintSpans.c	(revision 27839)
@@ -0,0 +1,136 @@
+/* @file  pmFootprintFindAtPoint.c
+ * find footprints in a small image relative to a reference point
+ *
+ * @author RHL, Princeton & IfA; EAM, IfA
+ *
+ * @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2009-01-27 06:39:38 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <strings.h>
+#include <pslib.h>
+#include "pmSpan.h"
+#include "pmFootprint.h"
+#include "pmPeaks.h"
+#include "pmFootprintSpans.h"
+
+static void pmStartSpanFree(pmStartSpan *sspan) {
+    return;
+}
+
+// Allocate an un-assigned pmStartSpan
+pmStartSpan *pmStartSpanAlloc() {
+    pmStartSpan *sspan = psAlloc(sizeof(pmStartSpan));
+    psMemSetDeallocator(sspan, (psFreeFunc)pmStartSpanFree);
+    
+    sspan->span = NULL;
+    sspan->direction = PM_STARTSPAN_NONE;
+    sspan->stop = false;
+    
+    return sspan;
+}
+
+// Assign a pmSpan to this pmStartSpan
+bool pmStartSpanSet(pmStartSpan *sspan,
+		    pmSpan *span,      // The span in question
+		    psImage *mask,           // Pixels that we've already detected
+		    const PM_STARTSPAN_DIR dir   // Should we continue searching towards the top of the image?
+    ) {
+    sspan->span = span; // view on the span (we do not own this memory)
+    sspan->direction = dir;
+    sspan->stop = false;
+    
+    if (mask) {                 // remember that we've detected these pixels
+        psImageMaskType *mpix = &mask->data.PS_TYPE_IMAGE_MASK_DATA[span->y - mask->row0][span->x0 - mask->col0];
+
+        for (int i = 0; i <= span->x1 - span->x0; i++) {
+            mpix[i] |= PM_STARTSPAN_DETECTED;
+            if (mpix[i] & PM_STARTSPAN_STOP) {
+                sspan->stop = true;
+            }
+        }
+    }
+
+    return true;
+}
+
+// remove a span assignment
+bool pmStartSpanUnset(pmStartSpan *sspan) {
+    if (!sspan) return false;
+    sspan->span = NULL;
+    sspan->direction = PM_STARTSPAN_NONE;
+    sspan->stop = false;
+    return true;
+}
+
+static void pmFootprintSpansFree(pmFootprintSpans *fp) {
+    psFree(fp->startspans);
+    return;
+}
+
+// Allocate a pmFootprintSpans structure with unassigned spans
+pmFootprintSpans *pmFootprintSpansAlloc(int nSpans) {
+    pmFootprintSpans *fpSpans = psAlloc(sizeof(pmFootprintSpans));
+    psMemSetDeallocator(fpSpans, (psFreeFunc)pmFootprintSpansFree);
+    
+    // create an footprint with allocated pmStartSpans, but none yet assigned
+    fpSpans->nStartSpans = 0;
+    fpSpans->startspans = psArrayAlloc(nSpans);
+
+    for (int i = 0; i < nSpans; i++) {
+	fpSpans->startspans->data[i] = pmStartSpanAlloc();
+    }
+    return fpSpans;
+}
+
+// Allocate a pmFootprintSpans structure with unassigned spans
+bool pmFootprintSpansInit(pmFootprintSpans *fpSpans) {
+    fpSpans->nStartSpans = 0;
+    for (int i = 0; i < fpSpans->startspans->n; i++) {
+	pmStartSpanUnset(fpSpans->startspans->data[i]);
+    }
+    return true;
+}
+
+// Add a new pmSpan to a pmFootprintSpans.  Iff we see a stop bit, return true
+bool pmFootprintSpansSet(pmFootprintSpans *fpSpans, // the saved pmStartSpans
+			 pmSpan *sp, // the span in question
+			 psImage *mask, // mask of detected/stop pixels
+			 const PM_STARTSPAN_DIR dir) { // the desired direction to search
+    if (dir == PM_STARTSPAN_RESTART) {
+        if (pmFootprintSpansSet(fpSpans, sp, mask, PM_STARTSPAN_UP)) return true;
+	if (pmFootprintSpansSet(fpSpans, sp, NULL, PM_STARTSPAN_DOWN)) return true;
+    } else {
+	int N = fpSpans->nStartSpans;
+	if (N == fpSpans->startspans->n) {
+	    // if we need more space, extend fpSpans->startspans as needed
+	    int Nalloc = fpSpans->startspans->n + 100;
+	    psArrayRealloc(fpSpans->startspans, Nalloc);
+	    fpSpans->startspans->n = Nalloc;
+	    for (int i = N; i < fpSpans->startspans->n; i++) {
+		fpSpans->startspans->data[i] = pmStartSpanAlloc();
+	    }
+	}
+	pmStartSpan *startspan = fpSpans->startspans->data[N];
+	
+	pmStartSpanSet (startspan, sp, mask, dir);
+
+        if (startspan->stop) {              // we detected a stop bit
+            pmStartSpanUnset(startspan);    // don't allocate new span
+            return true;
+        } else {
+	    fpSpans->nStartSpans ++;
+	    return false;
+        }
+    }
+    return false;
+}
+
Index: branches/simmosaic_branches/psModules/src/objects/pmFootprintSpans.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmFootprintSpans.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmFootprintSpans.h	(revision 27839)
@@ -0,0 +1,86 @@
+/* @file  pmFootprintSpans.h
+ *
+ * @author RHL, Princeton & IfA; EAM, IfA
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-12-09 21:16:09 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FOOTPRINT_SPANS_H
+#define PM_FOOTPRINT_SPANS_H
+
+#include <pslib.h>
+#include "pmSpan.h"
+
+/* We define two helper structures used in building the pmFootprints:
+ *
+ * pmStartSpan      : a smart span which knows how to (re-)start pixel scanning
+ * pmFootprintSpans : a collection of pmStartSpans which can define a footprint
+ *
+ * pmFootprintSpans allows us to allocate the memory for the spans before actually defining them
+ *
+ */
+
+/* pmStartSpan
+ *
+ * A data structure to hold the starting point for a search for pixels above threshold,
+ * used by pmFootprintsFindAtPoint
+ *
+ * We don't want to find this span again --- it's already part of the footprint ---
+ * so we set appropriate mask bits
+ *
+ */
+
+//
+// An enum for what we should do with a pmStartSpan
+//
+typedef enum {PM_STARTSPAN_NONE = 0,	// span is not defined
+	      PM_STARTSPAN_DOWN,	// scan down from this span
+              PM_STARTSPAN_UP,		// scan up from this span
+              PM_STARTSPAN_RESTART,	// restart scanning from this span
+              PM_STARTSPAN_DONE		// this span is processed
+} PM_STARTSPAN_DIR;			// How to continue searching
+//
+// An enum for mask's pixel values.  We're looking for pixels that are above threshold, and
+// we keep extra book-keeping information in the PM_STARTSPAN_STOP plane.  It's simpler to be
+// able to check for
+//
+enum {
+    PM_STARTSPAN_INITIAL = 0x0,             // initial state of pixels.
+    PM_STARTSPAN_DETECTED = 0x1,            // we've seen this pixel
+    PM_STARTSPAN_STOP = 0x2                 // you may stop searching when you see this pixel
+};
+//
+// The struct that remembers how to [re-]start scanning the image for pixels
+//
+typedef struct {
+    pmSpan *span;			// view on the real span (on a pmFootprint->spans array)
+    PM_STARTSPAN_DIR direction;		// How to continue searching
+    bool stop;                          // should we stop searching?
+} pmStartSpan;
+
+typedef struct {
+    psArray *startspans;
+    int nStartSpans;
+} pmFootprintSpans;
+
+pmStartSpan *pmStartSpanAlloc();
+
+bool pmStartSpanSet(pmStartSpan *sspan,
+		    pmSpan *span,      // The span in question
+		    psImage *mask,           // Pixels that we've already detected
+		    const PM_STARTSPAN_DIR dir   // Should we continue searching towards the top of the image?
+    );
+
+pmFootprintSpans *pmFootprintSpansAlloc(int nSpans);
+
+bool pmFootprintSpansInit(pmFootprintSpans *fpSpans);
+
+bool pmFootprintSpansSet(pmFootprintSpans *fpSpans, // the saved pmStartSpans
+			 pmSpan *sp, // the span in question
+			 psImage *mask, // mask of detected/stop pixels
+			 const PM_STARTSPAN_DIR dir); // the desired direction to search
+
+/// @}
+# endif /* PM_FOOTPRINT_SPANS_H */
Index: branches/simmosaic_branches/psModules/src/objects/pmGrowthCurveGenerate.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmGrowthCurveGenerate.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmGrowthCurveGenerate.c	(revision 27839)
@@ -65,4 +65,5 @@
 
 	    // use the center of the center pixel of the image
+	    // 0.5 PIX: is this offset needed? probably -- the psf model uses 0.5,0.5 as the center, double check
 	    float xc = (int)(ix*readout->image->numCols + 0.5*readout->image->numCols) + readout->image->col0 + 0.5;
 	    float yc = (int)(iy*readout->image->numRows + 0.5*readout->image->numRows) + readout->image->row0 + 0.5;
@@ -156,5 +157,5 @@
 
     // measure the fitMag for this model
-    pmSourcePhotometryModel (&fitMag, model);
+    pmSourcePhotometryModel (&fitMag, NULL, model);
     growth->fitMag = fitMag;
 
@@ -195,5 +196,5 @@
 	    return NULL;
         }
-        psImageKeepCircle (mask, xc, yc, radius, "AND", PS_NOT_IMAGE_MASK(markVal));
+	psImageMaskPixels (mask, "AND", PS_NOT_IMAGE_MASK(markVal)); // clear the circular mask
 
         // the 'ignore' mode is for testing
Index: branches/simmosaic_branches/psModules/src/objects/pmModel.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmModel.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmModel.c	(revision 27839)
@@ -56,10 +56,11 @@
 
     tmp->type = type;
-    tmp->chisq = 0.0;
-    tmp->chisqNorm = 0.0;
+    tmp->mag = NAN;
+    tmp->chisq = NAN;
+    tmp->chisqNorm = NAN;
     tmp->nDOF  = 0;
     tmp->nPix  = 0;
     tmp->nIter = 0;
-    tmp->radiusFit = 0;
+    tmp->fitRadius = 0;
     tmp->flags = PM_MODEL_STATUS_NONE;
     tmp->residuals = NULL;              // XXX should the model own this memory?
@@ -86,4 +87,5 @@
     tmp->modelParamsFromPSF = class->modelParamsFromPSF;
     tmp->modelFitStatus     = class->modelFitStatus;
+    tmp->modelSetLimits     = class->modelSetLimits;
 
     psTrace("psModules.objects", 10, "---- %s() end ----\n", __func__);
@@ -108,5 +110,5 @@
     new->nIter     = model->nIter;
     new->flags     = model->flags;
-    new->radiusFit = model->radiusFit;
+    new->fitRadius = model->fitRadius;
 
     for (int i = 0; i < new->params->n; i++) {
@@ -189,7 +191,7 @@
     psVector *params = model->params;
 
-    psS32 imageCol;
-    psS32 imageRow;
-    psF32 pixelValue;
+    float imageCol;
+    float imageRow;
+    float pixelValue;
 
     // save original values; restore before returning
@@ -232,5 +234,5 @@
     psF32 **Rx = NULL;
     psF32 **Ry = NULL;
-    psImageMaskType **Rm = NULL;
+    pmResidMaskType **Rm = NULL;
 
     if (model->residuals) {
@@ -240,5 +242,5 @@
 	Rx = (model->residuals->Rx)   ? model->residuals->Rx->data.F32 : NULL;
 	Ry = (model->residuals->Ry)   ? model->residuals->Ry->data.F32 : NULL;
-	Rm = (model->residuals->mask) ? model->residuals->mask->data.PS_TYPE_IMAGE_MASK_DATA : NULL;
+	Rm = (model->residuals->mask) ? model->residuals->mask->data.PM_TYPE_RESID_MASK_DATA : NULL;
 	if (Ro) {
 	    NX = model->residuals->Ro->numCols;
@@ -255,11 +257,11 @@
                 continue;
 
-            // XXX should we use using 0.5 pixel offset?
-	    // Convert to coordinate in parent image, with offset (dx,dy)
-            imageCol = ix + image->col0 - dx;
-            imageRow = iy + image->row0 - dy;
-
-            x->data.F32[0] = (float) imageCol;
-            x->data.F32[1] = (float) imageRow;
+            // Convert to coordinate in parent image, with offset (dx,dy)
+	    // 0.5 PIX: the model take pixel coordinates so convert the pixel index here
+            imageCol = ix + 0.5 + image->col0 - dx;
+            imageRow = iy + 0.5 + image->row0 - dy;
+
+            x->data.F32[0] = imageCol;
+            x->data.F32[1] = imageRow;
 
             pixelValue = 0.0;
@@ -276,56 +278,56 @@
                 float rx = xBin*ix + DX;
 
-		int rx0 = rx - 0.5;
-		int rx1 = rx + 0.5;
-		int ry0 = ry - 0.5;
-		int ry1 = ry + 0.5;
-
-		if (rx0 < 0) goto skip;
-		if (ry0 < 0) goto skip;
-		if (rx1 >= NX) goto skip;
-		if (ry1 >= NY) goto skip;
-
-		// these go from 0.0 to 1.0 between the centers of the pixels 
-		float fx = rx - 0.5 - rx0;
-		float Fx = 1.0 - fx;
-		float fy = ry - 0.5 - ry0;
-		float Fy = 1.0 - fy;
-
-		// check the residual image mask (if set). give up if any of the 4 pixels are masked.
-		if (Rm) {
-		    if (Rm[ry0][rx0]) goto skip;
-		    if (Rm[ry0][rx1]) goto skip;
-		    if (Rm[ry1][rx0]) goto skip;
-		    if (Rm[ry1][rx1]) goto skip;
-		}
-
-		// a possible further optimization if we re-use these values
-		// XXX allow for masked pixels, and add pixel weights
-		float V0 = (Ro[ry0][rx0]*Fx + Ro[ry0][rx1]*fx);
-		float V1 = (Ro[ry1][rx0]*Fx + Ro[ry1][rx1]*fx);
-		float Vo = V0*Fy + V1*fy;
-		if (!isfinite(Vo)) goto skip;
-
-		float Vx = 0.0;
-		float Vy = 0.0;
-
-		// skip Rx,Ry if Ro is masked
-		if (Rx && Ry && (mode & PM_MODEL_OP_RES1)) {
-		    V0 = (Rx[ry0][rx0]*Fx + Rx[ry0][rx1]*fx);
-		    V1 = (Rx[ry1][rx0]*Fx + Rx[ry1][rx1]*fx);
-		    Vx = V0*Fy + V1*fy;
-
-		    V0 = (Ry[ry0][rx0]*Fx + Ry[ry0][rx1]*fx);
-		    V1 = (Ry[ry1][rx0]*Fx + Ry[ry1][rx1]*fx);
-		    Vy = V0*Fy + V1*fy;
-		}
-		if (!isfinite(Vx)) goto skip;
-		if (!isfinite(Vy)) goto skip;
-
-		// 2D residual variations are set for the true source position
-		pixelValue += Io*(Vo + XoSave*Vx + XoSave*Vy);
+                int rx0 = rx - 0.5;
+                int rx1 = rx + 0.5;
+                int ry0 = ry - 0.5;
+                int ry1 = ry + 0.5;
+
+                if (rx0 < 0) goto skip;
+                if (ry0 < 0) goto skip;
+                if (rx1 >= NX) goto skip;
+                if (ry1 >= NY) goto skip;
+
+                // these go from 0.0 to 1.0 between the centers of the pixels
+                float fx = rx - 0.5 - rx0;
+                float Fx = 1.0 - fx;
+                float fy = ry - 0.5 - ry0;
+                float Fy = 1.0 - fy;
+
+                // check the residual image mask (if set). give up if any of the 4 pixels are masked.
+                if (Rm) {
+                    if (Rm[ry0][rx0]) goto skip;
+                    if (Rm[ry0][rx1]) goto skip;
+                    if (Rm[ry1][rx0]) goto skip;
+                    if (Rm[ry1][rx1]) goto skip;
+                }
+
+                // a possible further optimization if we re-use these values
+                // XXX allow for masked pixels, and add pixel weights
+                float V0 = (Ro[ry0][rx0]*Fx + Ro[ry0][rx1]*fx);
+                float V1 = (Ro[ry1][rx0]*Fx + Ro[ry1][rx1]*fx);
+                float Vo = V0*Fy + V1*fy;
+                if (!isfinite(Vo)) goto skip;
+
+                float Vx = 0.0;
+                float Vy = 0.0;
+
+                // skip Rx,Ry if Ro is masked
+                if (Rx && Ry && (mode & PM_MODEL_OP_RES1)) {
+                    V0 = (Rx[ry0][rx0]*Fx + Rx[ry0][rx1]*fx);
+                    V1 = (Rx[ry1][rx0]*Fx + Rx[ry1][rx1]*fx);
+                    Vx = V0*Fy + V1*fy;
+
+                    V0 = (Ry[ry0][rx0]*Fx + Ry[ry0][rx1]*fx);
+                    V1 = (Ry[ry1][rx0]*Fx + Ry[ry1][rx1]*fx);
+                    Vy = V0*Fy + V1*fy;
+                }
+                if (!isfinite(Vx)) goto skip;
+                if (!isfinite(Vy)) goto skip;
+
+                // 2D residual variations are set for the true source position
+                pixelValue += Io*(Vo + XoSave*Vx + XoSave*Vy);
             }
 
-	skip:
+        skip:
             // add or subtract the value
             if (add) {
Index: branches/simmosaic_branches/psModules/src/objects/pmModel.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmModel.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmModel.h	(revision 27839)
@@ -44,4 +44,13 @@
 } pmModelOpMode;
 
+/// Parameter limit types
+typedef enum {
+    PM_MODEL_LIMITS_NONE,               ///< Apply no limits: suitable for debugging
+    PM_MODEL_LIMITS_IGNORE,             ///< Ignore all limits: fit can go to town
+    PM_MODEL_LIMITS_LAX,                ///< Lax limits: attempting to reproduce even bad data
+    PM_MODEL_LIMITS_MODERATE,           ///< Moderate limits: cope with mildly bad data
+    PM_MODEL_LIMITS_STRICT,             ///< Strict limits: insist on good quality data
+} pmModelLimitsType;
+
 typedef struct pmModel pmModel;
 typedef struct pmSource pmSource;
@@ -74,4 +83,7 @@
 //  This function returns the success / failure status of the given model fit
 typedef bool (*pmModelFitStatusFunc)(pmModel *model);
+
+//  This function sets the parameter limits for the given model
+typedef bool (*pmModelSetLimitsFunc)(pmModelLimitsType type);
 
 /** pmModel data structure
@@ -96,5 +108,5 @@
     int nIter;                          ///< number of iterations to reach min
     pmModelStatus flags;                ///< model status flags
-    float radiusFit;                    ///< fit radius actually used
+    float fitRadius;                    ///< fit radius actually used
     pmResiduals *residuals;             ///< normalized PSF residuals
 
@@ -108,4 +120,5 @@
     pmModelParamsFromPSF modelParamsFromPSF;
     pmModelFitStatusFunc modelFitStatus;
+    pmModelSetLimitsFunc modelSetLimits;
 };
 
@@ -151,5 +164,5 @@
     pmModel *model,                     ///< The input pmModel
     pmModelOpMode mode,                 ///< mode to control how the model is added into the image
-    psImageMaskType maskVal		///< Value to mask
+    psImageMaskType maskVal             ///< Value to mask
 );
 
@@ -169,5 +182,5 @@
     pmModel *model,                     ///< The input pmModel
     pmModelOpMode mode,                 ///< mode to control how the model is added into the image
-    psImageMaskType maskVal		///< Value to mask
+    psImageMaskType maskVal             ///< Value to mask
 );
 
@@ -202,4 +215,14 @@
 );
 
+
+/// Set the model parameter limits for the given model
+///
+/// Wraps the model-specific pmModelSetLimitsFunc function.
+bool pmModelSetLimits(
+    const pmModel *model,               ///< Model of interest
+    pmModelLimits type                  ///< Type of limits
+    );
+
+
 /// @}
 # endif /* PM_MODEL_H */
Index: branches/simmosaic_branches/psModules/src/objects/pmModelClass.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmModelClass.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmModelClass.c	(revision 27839)
@@ -36,22 +36,22 @@
 #include "pmErrorCodes.h"
 
-// XXX shouldn't these be defined for us in pslib.h ???
+// XXX shouldn't these be defined for us in math.h ???
 double hypot(double x, double y);
 double sqrt (double x);
 
-# include "models/pmModel_GAUSS.c"
-# include "models/pmModel_PGAUSS.c"
-# include "models/pmModel_QGAUSS.c"
-# include "models/pmModel_PS1_V1.c"
-# include "models/pmModel_RGAUSS.c"
-# include "models/pmModel_SERSIC.c"
+# include "models/pmModel_GAUSS.h"
+# include "models/pmModel_PGAUSS.h"
+# include "models/pmModel_QGAUSS.h"
+# include "models/pmModel_PS1_V1.h"
+# include "models/pmModel_RGAUSS.h"
+# include "models/pmModel_SERSIC.h"
 
 static pmModelClass defaultModels[] = {
-    {"PS_MODEL_GAUSS",        7, pmModelFunc_GAUSS,   pmModelFlux_GAUSS,   pmModelRadius_GAUSS,   pmModelLimits_GAUSS,   pmModelGuess_GAUSS,  pmModelFromPSF_GAUSS,  pmModelParamsFromPSF_GAUSS,  pmModelFitStatus_GAUSS},
-    {"PS_MODEL_PGAUSS",       7, pmModelFunc_PGAUSS,  pmModelFlux_PGAUSS,  pmModelRadius_PGAUSS,  pmModelLimits_PGAUSS,  pmModelGuess_PGAUSS, pmModelFromPSF_PGAUSS, pmModelParamsFromPSF_PGAUSS, pmModelFitStatus_PGAUSS},
-    {"PS_MODEL_QGAUSS",       8, pmModelFunc_QGAUSS,  pmModelFlux_QGAUSS,  pmModelRadius_QGAUSS,  pmModelLimits_QGAUSS,  pmModelGuess_QGAUSS, pmModelFromPSF_QGAUSS, pmModelParamsFromPSF_QGAUSS, pmModelFitStatus_QGAUSS},
-    {"PS_MODEL_PS1_V1",       8, pmModelFunc_PS1_V1,  pmModelFlux_PS1_V1,  pmModelRadius_PS1_V1,  pmModelLimits_PS1_V1,  pmModelGuess_PS1_V1, pmModelFromPSF_PS1_V1, pmModelParamsFromPSF_PS1_V1, pmModelFitStatus_PS1_V1},
-    {"PS_MODEL_RGAUSS",       8, pmModelFunc_RGAUSS,  pmModelFlux_RGAUSS,  pmModelRadius_RGAUSS,  pmModelLimits_RGAUSS,  pmModelGuess_RGAUSS, pmModelFromPSF_RGAUSS, pmModelParamsFromPSF_RGAUSS, pmModelFitStatus_RGAUSS},
-    {"PS_MODEL_SERSIC",       8, pmModelFunc_SERSIC,  pmModelFlux_SERSIC,  pmModelRadius_SERSIC,  pmModelLimits_SERSIC,  pmModelGuess_SERSIC, pmModelFromPSF_SERSIC, pmModelParamsFromPSF_SERSIC, pmModelFitStatus_SERSIC}
+    {"PS_MODEL_GAUSS",        7, (pmModelFunc)pmModelFunc_GAUSS,   (pmModelFlux)pmModelFlux_GAUSS,   (pmModelRadius)pmModelRadius_GAUSS,   (pmModelLimits)pmModelLimits_GAUSS,   (pmModelGuessFunc)pmModelGuess_GAUSS,  (pmModelFromPSFFunc)pmModelFromPSF_GAUSS,  (pmModelParamsFromPSF)pmModelParamsFromPSF_GAUSS,  (pmModelFitStatusFunc)pmModelFitStatus_GAUSS,  (pmModelSetLimitsFunc)pmModelSetLimits_GAUSS  },
+    {"PS_MODEL_PGAUSS",       7, (pmModelFunc)pmModelFunc_PGAUSS,  (pmModelFlux)pmModelFlux_PGAUSS,  (pmModelRadius)pmModelRadius_PGAUSS,  (pmModelLimits)pmModelLimits_PGAUSS,  (pmModelGuessFunc)pmModelGuess_PGAUSS, (pmModelFromPSFFunc)pmModelFromPSF_PGAUSS, (pmModelParamsFromPSF)pmModelParamsFromPSF_PGAUSS, (pmModelFitStatusFunc)pmModelFitStatus_PGAUSS, (pmModelSetLimitsFunc)pmModelSetLimits_PGAUSS },
+    {"PS_MODEL_QGAUSS",       8, (pmModelFunc)pmModelFunc_QGAUSS,  (pmModelFlux)pmModelFlux_QGAUSS,  (pmModelRadius)pmModelRadius_QGAUSS,  (pmModelLimits)pmModelLimits_QGAUSS,  (pmModelGuessFunc)pmModelGuess_QGAUSS, (pmModelFromPSFFunc)pmModelFromPSF_QGAUSS, (pmModelParamsFromPSF)pmModelParamsFromPSF_QGAUSS, (pmModelFitStatusFunc)pmModelFitStatus_QGAUSS, (pmModelSetLimitsFunc)pmModelSetLimits_QGAUSS },
+    {"PS_MODEL_PS1_V1",       8, (pmModelFunc)pmModelFunc_PS1_V1,  (pmModelFlux)pmModelFlux_PS1_V1,  (pmModelRadius)pmModelRadius_PS1_V1,  (pmModelLimits)pmModelLimits_PS1_V1,  (pmModelGuessFunc)pmModelGuess_PS1_V1, (pmModelFromPSFFunc)pmModelFromPSF_PS1_V1, (pmModelParamsFromPSF)pmModelParamsFromPSF_PS1_V1, (pmModelFitStatusFunc)pmModelFitStatus_PS1_V1, (pmModelSetLimitsFunc)pmModelSetLimits_PS1_V1 },
+    {"PS_MODEL_RGAUSS",       8, (pmModelFunc)pmModelFunc_RGAUSS,  (pmModelFlux)pmModelFlux_RGAUSS,  (pmModelRadius)pmModelRadius_RGAUSS,  (pmModelLimits)pmModelLimits_RGAUSS,  (pmModelGuessFunc)pmModelGuess_RGAUSS, (pmModelFromPSFFunc)pmModelFromPSF_RGAUSS, (pmModelParamsFromPSF)pmModelParamsFromPSF_RGAUSS, (pmModelFitStatusFunc)pmModelFitStatus_RGAUSS, (pmModelSetLimitsFunc)pmModelSetLimits_RGAUSS },
+    {"PS_MODEL_SERSIC",       8, (pmModelFunc)pmModelFunc_SERSIC,  (pmModelFlux)pmModelFlux_SERSIC,  (pmModelRadius)pmModelRadius_SERSIC,  (pmModelLimits)pmModelLimits_SERSIC,  (pmModelGuessFunc)pmModelGuess_SERSIC, (pmModelFromPSFFunc)pmModelFromPSF_SERSIC, (pmModelParamsFromPSF)pmModelParamsFromPSF_SERSIC, (pmModelFitStatusFunc)pmModelFitStatus_SERSIC, (pmModelSetLimitsFunc)pmModelSetLimits_SERSIC }
 };
 
@@ -168,2 +168,18 @@
     return (models[type].name);
 }
+
+
+void pmModelClassSetLimits(pmModelLimitsType type)
+{
+    if (!models) {
+        pmModelClassInit();
+    }
+
+    for (int i = 0; i < Nmodels; i++) {
+        if (models[i].modelSetLimits) {
+            models[i].modelSetLimits(type);
+        }
+    }
+
+}
+
Index: branches/simmosaic_branches/psModules/src/objects/pmModelClass.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmModelClass.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmModelClass.h	(revision 27839)
@@ -29,9 +29,10 @@
 # define PM_MODEL_CLASS_H
 
+#include <pmModel.h>
+
 /// @addtogroup Objects Object Detection / Analysis Functions
 /// @{
 
-typedef struct
-{
+typedef struct {
     char *name;
     int nParams;
@@ -44,4 +45,5 @@
     pmModelParamsFromPSF modelParamsFromPSF;
     pmModelFitStatusFunc modelFitStatus;
+    pmModelSetLimitsFunc modelSetLimits;
 } pmModelClass;
 
@@ -73,4 +75,8 @@
 pmModelType pmModelClassGetType (const char *name);
 
+/// Set parameter limits for all models
+void pmModelClassSetLimits(pmModelLimitsType type);
+
+
 /// @}
 # endif /* PM_MODEL_CLASS_H */
Index: branches/simmosaic_branches/psModules/src/objects/pmModelGroup.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmModelGroup.c	(revision 25094)
+++ 	(revision )
@@ -1,204 +1,0 @@
-/** @file  pmModelGroup.c
- *
- *  Functions to define and manipulate object model attributes
- *
- *  @author GLG, MHPCC
- *  @author EAM, IfA
- *
- *  @version $Revision: 1.20 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2008-12-08 02:51:14 $
- *
- *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <math.h>
-#include <string.h>
-#include <pslib.h>
-#include "pmHDU.h"
-#include "pmFPA.h"
-#include "pmSpan.h"
-#include "pmFootprint.h"
-#include "pmPeaks.h"
-#include "pmMoments.h"
-#include "pmGrowthCurve.h"
-#include "pmResiduals.h"
-#include "pmModel.h"
-#include "pmModelGroup.h"
-#include "pmErrorCodes.h"
-
-// XXX shouldn't these be defined for us in pslib.h ???
-double hypot(double x, double y);
-double sqrt (double x);
-
-# include "models/pmModel_GAUSS.c"
-# include "models/pmModel_PGAUSS.c"
-# include "models/pmModel_QGAUSS.c"
-# include "models/pmModel_RGAUSS.c"
-# include "models/pmModel_SERSIC.c"
-
-static pmModelGroup defaultModels[] = {
-                                          {"PS_MODEL_GAUSS",        7, pmModelFunc_GAUSS,   pmModelFlux_GAUSS,   pmModelRadius_GAUSS,   pmModelLimits_GAUSS,   pmModelGuess_GAUSS,  pmModelFromPSF_GAUSS,  pmModelFitStatus_GAUSS},
-                                          {"PS_MODEL_PGAUSS",       7, pmModelFunc_PGAUSS,  pmModelFlux_PGAUSS,  pmModelRadius_PGAUSS,  pmModelLimits_PGAUSS,  pmModelGuess_PGAUSS, pmModelFromPSF_PGAUSS, pmModelFitStatus_PGAUSS},
-                                          {"PS_MODEL_QGAUSS",       8, pmModelFunc_QGAUSS,  pmModelFlux_QGAUSS,  pmModelRadius_QGAUSS,  pmModelLimits_QGAUSS,  pmModelGuess_QGAUSS, pmModelFromPSF_QGAUSS, pmModelFitStatus_QGAUSS},
-                                          {"PS_MODEL_RGAUSS",       8, pmModelFunc_RGAUSS,  pmModelFlux_RGAUSS,  pmModelRadius_RGAUSS,  pmModelLimits_RGAUSS,  pmModelGuess_RGAUSS, pmModelFromPSF_RGAUSS, pmModelFitStatus_RGAUSS},
-                                          {"PS_MODEL_SERSIC",       8, pmModelFunc_SERSIC,  pmModelFlux_SERSIC,  pmModelRadius_SERSIC,  pmModelLimits_SERSIC,  pmModelGuess_SERSIC, pmModelFromPSF_SERSIC, pmModelFitStatus_SERSIC}
-                                      };
-
-static pmModelGroup *models = NULL;
-static int Nmodels = 0;
-
-static void ModelGroupFree (pmModelGroup *modelGroup)
-{
-
-    if (modelGroup == NULL)
-        return;
-    return;
-}
-
-pmModelGroup *pmModelGroupAlloc (int nModels)
-{
-
-    pmModelGroup *modelGroup = (pmModelGroup *) psAlloc (nModels * sizeof(pmModelGroup));
-    psMemSetDeallocator(modelGroup, (psFreeFunc) ModelGroupFree);
-    return (modelGroup);
-}
-
-bool psMemCheckModelGroup(psPtr ptr)
-{
-    PS_ASSERT_PTR(ptr, false);
-    return ( psMemGetDeallocator(ptr) == (psFreeFunc) ModelGroupFree);
-}
-
-void pmModelGroupAdd (pmModelGroup *model)
-{
-
-    if (models == NULL) {
-        pmModelGroupInit ();
-    }
-
-    Nmodels ++;
-    models = (pmModelGroup *) psRealloc (models, Nmodels*sizeof(pmModelGroup));
-    models[Nmodels-1] = model[0];
-    return;
-}
-
-bool pmModelGroupInit (void)
-{
-
-    // if we do not need to init, return false;
-    if (models != NULL)
-        return false;
-
-    int Nnew = sizeof (defaultModels) / sizeof (pmModelGroup);
-
-    models = pmModelGroupAlloc (Nnew);
-    for (int i = 0; i < Nnew; i++) {
-        models[i] = defaultModels[i];
-    }
-    Nmodels = Nnew;
-    return true;
-}
-
-void pmModelGroupCleanup (void)
-{
-    psFree (models);
-    models = NULL;
-    return;
-}
-
-pmModelFunc pmModelFunc_GetFunction (pmModelType type)
-{
-    if ((type < 0) || (type >= Nmodels)) {
-        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
-        return (NULL);
-    }
-    return (models[type].modelFunc);
-}
-
-pmModelFlux pmModelFlux_GetFunction (pmModelType type)
-{
-    if ((type < 0) || (type >= Nmodels)) {
-        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
-        return (NULL);
-    }
-    return (models[type].modelFlux);
-}
-
-pmModelRadius pmModelRadius_GetFunction (pmModelType type)
-{
-    if ((type < 0) || (type >= Nmodels)) {
-        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
-        return (NULL);
-    }
-    return (models[type].modelRadius);
-}
-
-pmModelLimits pmModelLimits_GetFunction (pmModelType type)
-{
-    if ((type < 0) || (type >= Nmodels)) {
-        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
-        return (NULL);
-    }
-    return (models[type].modelLimits);
-}
-
-pmModelGuessFunc pmModelGuessFunc_GetFunction (pmModelType type)
-{
-    if ((type < 0) || (type >= Nmodels)) {
-        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
-        return (NULL);
-    }
-    return (models[type].modelGuessFunc);
-}
-
-pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction (pmModelType type)
-{
-    if ((type < 0) || (type >= Nmodels)) {
-        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
-        return (NULL);
-    }
-    return (models[type].modelFitStatusFunc);
-}
-
-pmModelFromPSFFunc pmModelFromPSFFunc_GetFunction (pmModelType type)
-{
-    if ((type < 0) || (type >= Nmodels)) {
-        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
-        return (NULL);
-    }
-    return (models[type].modelFromPSFFunc);
-}
-
-psS32 pmModelParameterCount (pmModelType type)
-{
-    if ((type < 0) || (type >= Nmodels)) {
-        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
-        return (0);
-    }
-    return (models[type].nParams);
-}
-
-psS32 pmModelSetType (char *name)
-{
-    for (int i = 0; i < Nmodels; i++) {
-        if (!strcmp(models[i].name, name)) {
-            return (i);
-        }
-    }
-    return (-1);
-}
-
-char *pmModelGetType (pmModelType type)
-{
-    if ((type < 0) || (type >= Nmodels)) {
-        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
-        return (NULL);
-    }
-    return (models[type].name);
-}
Index: branches/simmosaic_branches/psModules/src/objects/pmModelGroup.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmModelGroup.h	(revision 25094)
+++ 	(revision )
@@ -1,201 +1,0 @@
-/* @file  pmModelGroup.h
- *
- * The object model function types are desined to allow for the flexible addition of new object
- * models. Every object model, with parameters represented by pmModel, has an associated set of
- * functions which provide necessary support operations. A set of abstract functions allow the
- * programmer to select the approriate function or property for a specific named object model.
- *
- * Every model instance belongs to a class of models, defined by the value of
- * the pmModelType type entry. Various functions need access to information about
- * each of the models. Some of this information varies from model to model, and
- * may depend on the current parameter values or other data quantities. In order
- * to keep the code from requiring the information about each model to be coded
- * into the low-level fitting routines, we define a collection of functions which
- * allow us to abstract this type of model-dependent information. These generic
- * functions take the model type and return the corresponding function pointer
- * for the specified model. Each model is defined by creating this collection of
- * specific functions, and placing them in a single file for each model. We
- * define the following structure to carry the collection of information about
- * the models.
- *
- * @author EAM, IfA
- *
- * @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
- * @date $Date: 2007-11-10 01:09:20 $
- * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
- */
-
-# ifndef PM_MODEL_GROUP_H
-# define PM_MODEL_GROUP_H
-
-/// @addtogroup Objects Object Detection / Analysis Functions
-/// @{
-
-//  This function is the model chi-square minimization function for this model.
-typedef psMinimizeLMChi2Func pmModelFunc;
-
-//  This function sets the parameter limits for this model.
-typedef psMinimizeLMLimitFunc pmModelLimits;
-
-// This function returns the integrated flux for the given model parameters.
-typedef psF64 (*pmModelFlux)(const psVector *params);
-
-// This function returns the radius at which the given model and parameters
-// achieves the given flux.
-typedef psF64 (*pmModelRadius)(const psVector *params, double flux);
-
-//  This function provides the model guess parameters based on the details of
-//  the given source.
-typedef bool (*pmModelGuessFunc)(pmModel *model, pmSource *source);
-
-//  This function constructs the PSF model for the given source based on the
-//  supplied psf and the EXT model for the object.
-typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelEXT, pmPSF *psf);
-
-//  This function sets the model parameters based on the PSF for a given coordinate and central
-//  intensity
-typedef bool (*pmModelParamsFromPSF)(pmModel *model, pmPSF *psf, float Xo, float Yo, float Io);
-
-//  This function returns the success / failure status of the given model fit
-typedef bool (*pmModelFitStatusFunc)(pmModel *model);
-
-typedef struct
-{
-    char *name;
-    int nParams;
-    pmModelFunc          modelFunc;
-    pmModelFlux          modelFlux;
-    pmModelRadius        modelRadius;
-    pmModelLimits        modelLimits;
-    pmModelGuessFunc     modelGuessFunc;
-    pmModelFromPSFFunc   modelFromPSFFunc;
-    pmModelParamsFromPSF modelParamsFromPSF;
-    pmModelFitStatusFunc modelFitStatusFunc;
-}
-pmModelGroup;
-
-// allocate a pmModelGroup to hold nModels entries
-pmModelGroup *pmModelGroupAlloc (int nModels);
-
-bool psMemCheckModelGroup(psPtr ptr);
-
-// initialize the internal (static) model group with the default models
-bool pmModelGroupInit (void);
-
-// free the internal (static) model group
-void pmModelGroupCleanup (void);
-
-// add a new model to the internal (static) model group
-void pmModelGroupAdd (pmModelGroup *model);
-
-/* This function returns the number of parameters used by the listed function.
- */
-int pmModelParameterCount(
-    pmModelType type                    ///< Add comment.
-);
-
-
-/* This function returns the user-space model names for the specified model type.
- */
-char *pmModelGetType(
-    pmModelType type                    ///< Add comment.
-);
-
-
-/**
- * 
- * This function returns the internal model type code for the user-space model names.
- * 
- */
-pmModelType pmModelSetType(
-    char *name                          ///< Add comment.
-);
-
-/**
- * 
- *  Each of the function types above has a corresponding function which returns
- *  the function given the model type:
- * 
- */
-
-/**
- * 
- * pmModelFunc is the function used to determine the value of the model at a
- * specific coordinate, and is the one used by psMinimizeLMChi2.
- * 
- */
-pmModelFunc          pmModelFunc_GetFunction (pmModelType type);
-
-
-/**
- * 
- * pmModelFlux returns the total integrated flux for the given input parameters.
- * 
- */
-pmModelFlux          pmModelFlux_GetFunction (pmModelType type);
-
-
-/**
- * 
- * pmModelRadius returns the scaling radius at which the flux of the model
- * matches the specified flux. This presumes that the model is a function of an
- * elliptical contour.
- * 
- */
-pmModelRadius        pmModelRadius_GetFunction (pmModelType type);
-
-
-/**
- * 
- * pmModelLimits sets the parameter limit vectors for the function.
- * 
- */
-pmModelLimits        pmModelLimits_GetFunction (pmModelType type);
-
-
-/**
- * 
- * pmModelGuessFunc generates an initial guess for the model based on the
- * provided source statistics (moments and pixel values as needed).
- * 
- */
-pmModelGuessFunc     pmModelGuessFunc_GetFunction (pmModelType type);
-
-
-/**
- * 
- * pmModelFromPSFFunc takes as input a representation of the psf and a value
- * for the model and fills in the PSF parameters of the model. The input
- * primarily relies upon the centroid coordinates of the input model, though the
- * normalization may potentially be used.
- * 
- */
-pmModelFromPSFFunc   pmModelFromPSFFunc_GetFunction (pmModelType type);
-
-
-/**
- * 
- * pmModelFitStatusFunc returns a true or false values based on the success or
- * failure of a model fit.  The success is determined by quantities such as the
- * chisq or the signal-to-noise.
- * 
- */
-pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction (pmModelType type);
-
-
-/** pmSourceModelGuess()
- *
- * Convert available data to an initial guess for the given model. This
- * function allocates a pmModel entry for the pmSource structure based on the
- * provided model selection. The method of defining the model parameter guesses
- * are specified for each model below. The guess values are placed in the model
- * parameters. The function returns TRUE on success or FALSE on failure.
- *
- */
-pmModel *pmSourceModelGuess(
-    pmSource *source,   ///< The input pmSource
-    pmModelType model   ///< The type of model to be created.
-);
-
-/// @}
-# endif /* PM_MODEL_GROUP_H */
Index: branches/simmosaic_branches/psModules/src/objects/pmModelUtils.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmModelUtils.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmModelUtils.c	(revision 27839)
@@ -48,5 +48,5 @@
         return NULL;
     }
-    // XXX note that model->residuals is just a reference
+    // note that model->residuals is just a reference
     modelPSF->residuals = psf->residuals;
 
@@ -65,11 +65,9 @@
     // set model parameters for this source based on PSF information
     if (!modelPSF->modelParamsFromPSF (modelPSF, psf, Xo, Yo, Io)) {
-        // XXX we do not want to raise an error here, just note that we failed
-	// psError(PM_ERR_PSF, false, "Failed to set model params from PSF");
         psFree(modelPSF);
         return NULL;
     }
 
-    // XXX note that model->residuals is just a reference
+    // note that model->residuals is just a reference
     modelPSF->residuals = psf->residuals;
 
Index: branches/simmosaic_branches/psModules/src/objects/pmObjects.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmObjects.c	(revision 25094)
+++ 	(revision )
@@ -1,25 +1,0 @@
-/** @file  pmObjects.c
- *
- *  This file will ...
- *
- *  @author GLG, MHPCC
- *  @author EAM, IfA: significant modifications.
- *
- *  @version $Revision: 1.13 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2006-09-15 09:49:01 $
- *
- *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <math.h>
-#include <string.h>
-#include <pslib.h>
-#include "pmObjects.h"
-#include "pmModelGroup.h"
-
Index: branches/simmosaic_branches/psModules/src/objects/pmObjects.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmObjects.h	(revision 25094)
+++ 	(revision )
@@ -1,68 +1,0 @@
-/* @file  pmObjects.h
- *
- * The process of finding, measuring, and classifying astronomical sources on
- * images is one of the critical tasks of the IPP or any astronomical software
- * system. This file will define structures and functions related to the task
- * of source detection and measurement. The elements defined in this section
- * are generally low-level components which can be connected together to
- * construct a complete object measurement suite.
- *
- * @author GLG, MHPCC
- *
- * @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
- * @date $Date: 2007-01-24 02:54:15 $
- * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
- */
-
-#ifndef PM_OBJECTS_H
-#define PM_OBJECTS_H
-
-/// @addtogroup Objects Object Detection / Analysis Functions
-/// @{
-
-#include <stdio.h>
-#include <math.h>
-#include <pslib.h>
-
-/**
- *
- * This function converts the source classification into the closest available
- * approximation to the Dophot classification scheme:
- * XXX EAM : fix this to use current source classification scheme
- *
- * PM_SOURCE_DEFECT: 8
- * PM_SOURCE_SATURATED: 8
- * PM_SOURCE_SATSTAR: 10
- * PM_SOURCE_PSFSTAR: 1
- * PM_SOURCE_GOODSTAR: 1
- * PM_SOURCE_POOR_FIT_PSF: 7
- * PM_SOURCE_FAIL_FIT_PSF: 4
- * PM_SOURCE_FAINTSTAR: 4
- * PM_SOURCE_GALAXY: 2
- * PM_SOURCE_FAINT_GALAXY: 2
- * PM_SOURCE_DROP_GALAXY: 2
- * PM_SOURCE_FAIL_FIT_GAL: 2
- * PM_SOURCE_POOR_FIT_GAL: 2
- * PM_SOURCE_OTHER: ?
- *
- */
-int pmSourceDophotType(
-    pmSource *source                    ///< Add comment.
-);
-
-
-/** pmSourceSextractType()
- *
- * This function converts the source classification into the closest available
- * approximation to the Sextractor classification scheme. the correspondence is
- * not yet defined (TBD) .
- *
- * XXX: Must code this.
- *
- */
-int pmSourceSextractType(
-    pmSource *source                    ///< Add comment.
-);
-
-/// @}
-#endif
Index: branches/simmosaic_branches/psModules/src/objects/pmPSF.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPSF.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmPSF.c	(revision 27839)
@@ -42,4 +42,7 @@
 #include "pmErrorCodes.h"
 
+
+#define MAX_AXIS_RATIO 20.0             // Maximum axis ratio for PSF model
+
 /*****************************************************************************/
 /* FUNCTION IMPLEMENTATION - PUBLIC                                          */
@@ -405,2 +408,23 @@
     return psf;
 }
+
+
+float pmPSFtoFWHM(const pmPSF *psf, float x, float y)
+{
+    PS_ASSERT_PTR_NON_NULL(psf, NAN);
+
+    pmModel *model = pmModelFromPSFforXY(psf, x, y, 1.0); // Model of source
+    if (!model) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine PSF model at %f,%f\n", x, y);
+        return NAN;
+    }
+    psF32 *params = model->params->data.F32; // Model parameters
+    psEllipseAxes axes = pmPSF_ModelToAxes(params, MAX_AXIS_RATIO); // Ellipse axes
+
+    // Curiously, the minor axis can be larger than the major axis, so need to check.
+    float fwhm = 2.355 * PS_MAX(axes.minor, axes.major); // FWHM, converted from sigma
+
+    psFree(model);
+
+    return fwhm;
+}
Index: branches/simmosaic_branches/psModules/src/objects/pmPSF.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPSF.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmPSF.h	(revision 27839)
@@ -38,6 +38,5 @@
  *
  */
-typedef struct
-{
+typedef struct {
     pmModelType type;                   ///< PSF Model in use
     psArray *params;                    ///< Model parameters (psPolynomial2D)
@@ -65,6 +64,5 @@
     pmGrowthCurve *growth;              ///< apMag vs Radius
     pmResiduals *residuals;             ///< normalized residual image (no spatial variation)
-}
-pmPSF;
+} pmPSF;
 
 typedef struct {
@@ -81,5 +79,6 @@
     bool          poissonErrorsPhotLin; ///< use poission errors for linear model fitting
     bool          poissonErrorsParams; ///< use poission errors for model parameter fitting
-    float         radius;
+    float         fitRadius;
+    float         apRadius;
     bool          chiFluxTrend;         // Fit a trend in Chi2 as a function of flux?
 } pmPSFOptions;
@@ -112,4 +111,11 @@
 psEllipseAxes pmPSF_ModelToAxes (psF32 *modelPar, double maxAR);
 
+/// Calculate FWHM value from a PSF
+float pmPSFtoFWHM(
+    const pmPSF *psf,                   // PSF of interest
+    float x, float y                    // Position of interest
+    );
+
+
 /// @}
 # endif
Index: branches/simmosaic_branches/psModules/src/objects/pmPSF_IO.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPSF_IO.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmPSF_IO.c	(revision 27839)
@@ -28,4 +28,5 @@
 #include "pmConfig.h"
 #include "pmDetrendDB.h"
+#include "pmErrorCodes.h"
 
 #include "pmHDU.h"
@@ -49,4 +50,5 @@
 #include "pmSource.h"
 #include "pmModelClass.h"
+#include "pmModelUtils.h"
 #include "pmSourceIO.h"
 
@@ -121,5 +123,5 @@
     if (view->chip == -1) {
         if (!pmPSFmodelWriteFPA(fpa, view, file, config)) {
-            psError(PS_ERR_IO, false, "Failed to write PSF for fpa");
+            psError(psErrorCodeLast(), false, "Failed to write PSF for fpa");
             return false;
         }
@@ -134,5 +136,5 @@
     if (view->cell == -1) {
         if (!pmPSFmodelWriteChip (chip, view, file, config)) {
-            psError(PS_ERR_IO, false, "Failed to write PSF for chip");
+            psError(psErrorCodeLast(), false, "Failed to write PSF for chip");
             return false;
         }
@@ -140,5 +142,5 @@
     }
 
-    psError(PS_ERR_IO, false, "PSF must be written at the chip level");
+    psError(PM_ERR_CONFIG, true, "PSF must be written at the chip level");
     return false;
 }
@@ -157,5 +159,5 @@
         thisView->chip = i;
         if (!pmPSFmodelWriteChip (chip, thisView, file, config)) {
-            psError(PS_ERR_IO, false, "Failed to write PSF for %dth chip", i);
+            psError(psErrorCodeLast(), false, "Failed to write PSF for %dth chip", i);
             psFree(thisView);
             return false;
@@ -172,6 +174,6 @@
     PS_ASSERT_PTR_NON_NULL(chip, false);
 
-    if (!pmPSFmodelWrite (chip->analysis, view, file, config)) {
-        psError(PS_ERR_IO, false, "Failed to write PSF for chip");
+    if (!pmPSFmodelWrite(chip->analysis, view, file, config)) {
+        psError(psErrorCodeLast(), false, "Failed to write PSF for chip");
         return false;
     }
@@ -196,10 +198,13 @@
     char *headName, *tableName, *residName;
 
-    if (!analysis) return false;
+    if (!analysis) {
+        psError(PM_ERR_PROG, true, "No analysis metadata for chip.");
+        return false;
+    }
 
     // select the current recipe
     psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, "PSPHOT");
     if (!recipe) {
-        psError(PS_ERR_UNKNOWN, false, "missing recipe %s", "PSPHOT");
+        psError(PM_ERR_CONFIG, false, "missing recipe %s", "PSPHOT");
         return false;
     }
@@ -212,8 +217,12 @@
     // get the current header
     pmFPA *fpa = pmFPAfileSuitableFPA(file, view, config, false); // Suitable FPA for writing
+    if (!fpa) {
+        psError(psErrorCodeLast(), false, "Unable to get FPA for writing.");
+        return false;
+    }
     pmHDU *hdu = psMemIncrRefCounter(pmFPAviewThisHDU(view, fpa));
     psFree(fpa);
     if (!hdu) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to find HDU");
+        psError(PM_ERR_CONFIG, false, "Unable to find HDU");
         return false;
     }
@@ -233,5 +242,5 @@
         psMetadata *menu = psMetadataLookupMetadata(&status, file->camera, "EXTNAME.RULES");
         if (!menu) {
-            psError(PS_ERR_UNKNOWN, true, "missing EXTNAME.RULES in camera.config");
+            psError(PM_ERR_CONFIG, true, "missing EXTNAME.RULES in camera.config");
             psFree(hdu);
             return false;
@@ -241,5 +250,5 @@
         rule = psMetadataLookupStr(&status, menu, "PSF.HEAD");
         if (!rule) {
-            psError(PS_ERR_UNKNOWN, false, "missing entry for PSF.HEAD in EXTNAME.RULES in camera.config");
+            psError(PM_ERR_CONFIG, false, "missing entry for PSF.HEAD in EXTNAME.RULES in camera.config");
             psFree(hdu);
             return false;
@@ -250,5 +259,5 @@
         rule = psMetadataLookupStr(&status, menu, "PSF.TABLE");
         if (!rule) {
-            psError(PS_ERR_UNKNOWN, false, "missing entry for PSF.TABLE in EXTNAME.RULES in camera.config");
+            psError(PM_ERR_CONFIG, false, "missing entry for PSF.TABLE in EXTNAME.RULES in camera.config");
             psFree (headName);
             psFree(hdu);
@@ -260,5 +269,5 @@
         rule = psMetadataLookupStr(&status, menu, "PSF.RESID");
         if (!rule) {
-            psError(PS_ERR_UNKNOWN, false, "missing entry for PSF.RESID in EXTNAME.RULES in camera.config");
+            psError(PM_ERR_CONFIG, false, "missing entry for PSF.RESID in EXTNAME.RULES in camera.config");
             psFree (headName);
             psFree (tableName);
@@ -267,4 +276,15 @@
         }
         residName = pmFPAfileNameFromRule (rule, file, view);
+
+        // EXTNAME for psf image
+        // rule = psMetadataLookupStr(&status, menu, "PSF.RESID");
+        // if (!rule) {
+        //     psError(PS_ERR_UNKNOWN, false, "missing entry for PSF.RESID in EXTNAME.RULES in camera.config");
+        //     psFree (headName);
+        //     psFree (tableName);
+        //     psFree(hdu);
+        //     return false;
+        // }
+        // residName = pmFPAfileNameFromRule (rule, file, view);
     }
 
@@ -282,5 +302,9 @@
         }
 
-        psFitsWriteBlank (file->fits, hdu->header, headName);
+        if (!psFitsWriteBlank(file->fits, hdu->header, headName)) {
+            psError(psErrorCodeLast(), false, "Unable to write PSF PHU.");
+            psFree(hdu);
+            return false;
+        }
         psTrace ("pmFPAfile", 5, "wrote ext head %s (type: %d)\n", file->filename, file->type);
         file->header = hdu->header;
@@ -292,5 +316,5 @@
     pmPSF *psf = psMetadataLookupPtr (&status, analysis, "PSPHOT.PSF");
     if (!psf) {
-        psError(PS_ERR_UNKNOWN, true, "missing PSF for this analysis metadata");
+        psError(PM_ERR_PROG, true, "missing PSF for this analysis metadata");
         psFree (tableName);
         psFree (residName);
@@ -310,5 +334,5 @@
         psMetadataAddBool (header, PS_LIST_TAIL, "ERR_PAR",  0, "Use Poisson errors in fits?", psf->poissonErrorsParams);
 
-        int nPar = pmModelClassParameterCount (psf->type)    ;
+        int nPar = pmModelClassParameterCount (psf->type);
         psMetadataAdd (header, PS_LIST_TAIL, "PSF_NPAR", PS_DATA_S32, "PSF model parameter count", nPar);
 
@@ -321,27 +345,27 @@
         pmPSFClump psfClump;
 
-	// we now save clump parameters for each region : need to save all of those
-	int nRegions = psMetadataLookupS32 (&status, recipe, "PSF.CLUMP.NREGIONS");
-	psMetadataAddS32 (header, PS_LIST_TAIL, "PSF_CLN", PS_META_REPLACE, "number of psf clump regions", nRegions);
-	for (int i = 0; i < nRegions; i++) {
-	    char regionName[64];
-	    snprintf (regionName, 64, "PSF.CLUMP.REGION.%03d", i);
-	    psMetadata *regionMD = psMetadataLookupPtr (&status, recipe, regionName);
-
-	    psfClump.X  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.X");   assert (status);
-	    psfClump.Y  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.Y");   assert (status);
-	    psfClump.dX = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DX");  assert (status);
-	    psfClump.dY = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DY");  assert (status);
-
-	    char key[16];
-	    snprintf (key, 9, "CLX_%03d", i);
-	    psMetadataAddF32 (header, PS_LIST_TAIL, key, PS_META_REPLACE, "psf clump center", psfClump.X);
-	    snprintf (key, 9, "CLY_%03d", i);
-	    psMetadataAddF32 (header, PS_LIST_TAIL, key, PS_META_REPLACE, "psf clump center", psfClump.Y);
-	    snprintf (key, 9, "CLDX_%03d", i);
-	    psMetadataAddF32 (header, PS_LIST_TAIL, key, PS_META_REPLACE, "psf clump size", psfClump.dX);
-	    snprintf (key, 9, "CLDY_%03d", i);
-	    psMetadataAddF32 (header, PS_LIST_TAIL, key, PS_META_REPLACE, "psf clump size", psfClump.dY);
-	}
+        // we now save clump parameters for each region : need to save all of those
+        int nRegions = psMetadataLookupS32 (&status, analysis, "PSF.CLUMP.NREGIONS");
+        psMetadataAddS32 (header, PS_LIST_TAIL, "PSF_CLN", PS_META_REPLACE, "number of psf clump regions", nRegions);
+        for (int i = 0; i < nRegions; i++) {
+            char regionName[64];
+            snprintf (regionName, 64, "PSF.CLUMP.REGION.%03d", i);
+            psMetadata *regionMD = psMetadataLookupPtr (&status, analysis, regionName);
+
+            psfClump.X  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.X");   assert (status);
+            psfClump.Y  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.Y");   assert (status);
+            psfClump.dX = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DX");  assert (status);
+            psfClump.dY = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DY");  assert (status);
+
+            char key[16];
+            snprintf (key, 9, "CLX_%03d", i);
+            psMetadataAddF32 (header, PS_LIST_TAIL, key, PS_META_REPLACE, "psf clump center", psfClump.X);
+            snprintf (key, 9, "CLY_%03d", i);
+            psMetadataAddF32 (header, PS_LIST_TAIL, key, PS_META_REPLACE, "psf clump center", psfClump.Y);
+            snprintf (key, 9, "CLDX_%03d", i);
+            psMetadataAddF32 (header, PS_LIST_TAIL, key, PS_META_REPLACE, "psf clump size", psfClump.dX);
+            snprintf (key, 9, "CLDY_%03d", i);
+            psMetadataAddF32 (header, PS_LIST_TAIL, key, PS_META_REPLACE, "psf clump size", psfClump.dY);
+        }
 
         // save the dimensions of each parameter
@@ -424,10 +448,14 @@
         // write an empty FITS segment if we have no PSF information
         if (psfTable->n == 0) {
-            // XXX this is probably an error (if we have a PSF, how do we have no data?)
-            psFitsWriteBlank (file->fits, header, tableName);
+            psError(PM_ERR_PROG, true, "No PSF data to write.");
+            psFree(tableName);
+            psFree(residName);
+            psFree(psfTable);
+            psFree(header);
+            return false;
         } else {
             psTrace ("pmFPAfile", 5, "writing psf data %s\n", tableName);
-            if (!psFitsWriteTable (file->fits, header, psfTable, tableName)) {
-                psError(PS_ERR_IO, false, "writing psf table data %s\n", tableName);
+            if (!psFitsWriteTable(file->fits, header, psfTable, tableName)) {
+                psError(psErrorCodeLast(), false, "Error writing psf table data %s\n", tableName);
                 psFree (tableName);
                 psFree (residName);
@@ -447,5 +475,10 @@
         if (psf->residuals == NULL) {
             // set some header keywords to make it clear there are no residuals?
-            psFitsWriteBlank (file->fits, header, residName);
+            if (!psFitsWriteBlank(file->fits, header, residName)) {
+                psError(psErrorCodeLast(), false, "Unable to write blank PSF residual image.");
+                psFree(residName);
+                psFree(header);
+                return false;
+            }
             psFree (residName);
             psFree (header);
@@ -458,20 +491,109 @@
         psMetadataAddS32 (header, PS_LIST_TAIL, "YCENTER", 0, "", psf->residuals->yCenter);
 
-        // write the residuals as three planes of the image
-        // this call creates an extension with NAXIS3 = 3
+        // write the residuals as planes of the image
+	psArray *images = psArrayAllocEmpty (1);
+	psArrayAdd (images, 1, psf->residuals->Ro);  // z = 0 is Ro
+
         if (psf->residuals->Rx) {
-            // this call creates an extension with NAXIS3 = 3
-            psArray *images = psArrayAllocEmpty (3);
-            psArrayAdd (images, 1, psf->residuals->Ro);
             psArrayAdd (images, 1, psf->residuals->Rx);
             psArrayAdd (images, 1, psf->residuals->Ry);
-
-            psFitsWriteImageCube (file->fits, header, images, residName);
-            psFree (images);
-        } else {
-            // this call creates an extension with NAXIS3 = 1
-            psFitsWriteImage(file->fits, header, psf->residuals->Ro, 0, residName);
-        }
+	}
+
+	// note that all N plane are implicitly of the same type, so we convert the mask
+	if (psf->residuals->mask) {
+	    psImage *mask = psImageCopy (NULL, psf->residuals->mask, psf->residuals->Ro->type.type);
+	    psArrayAdd (images, 1, mask);
+	    psFree (mask);
+	}
+
+	// psFitsWriteImageCube (file->fits, header, images, residName);
+	// psFree (images);
+
+	if (!psFitsWriteImageCube (file->fits, header, images, residName)) {
+            psError(psErrorCodeLast(), false, "Unable to write PSF residuals.");
+            psFree(images);
+            psFree(residName);
+            psFree(header);
+            return false;
+        }
+        psFree (images);
         psFree (residName);
+        psFree (header);
+    }
+
+    // write a representation of the psf model
+    {
+        psMetadata *header = psMetadataAlloc ();
+
+        if (0) {
+            // set some header keywords to make it clear there are no residuals?
+            if (!psFitsWriteBlank (file->fits, header, residName)) {
+                psError(psErrorCodeLast(), false, "Unable to write blank PSF residuals.");
+                psFree(residName);
+                psFree(header);
+                return false;
+            }
+            psFree (residName);
+            psFree (header);
+            return true;
+        }
+
+        int DX = 65;
+        int DY = 65;
+
+        psImage *psfMosaic = psImageAlloc (DX, DY, PS_TYPE_F32);
+        psImageInit (psfMosaic, 0.0);
+
+        pmModel *modelRef = pmModelAlloc(psf->type);
+
+        // use the center of the center pixel of the image
+        float xc = 0.5*psf->fieldNx;
+        float yc = 0.5*psf->fieldNy;
+
+        // assign the x and y coords to the image center
+        // create an object with center intensity of 1000
+        modelRef->params->data.F32[PM_PAR_SKY] = 0;
+        modelRef->params->data.F32[PM_PAR_I0] = 1.000;
+        modelRef->params->data.F32[PM_PAR_XPOS] = xc;
+        modelRef->params->data.F32[PM_PAR_YPOS] = yc;
+
+        // create modelPSF from this model
+        pmModel *model = pmModelFromPSF (modelRef, psf);
+        if (model) {
+            // place the reference object in the image center
+            pmModelAddWithOffset (psfMosaic, NULL, model, PM_MODEL_OP_FULL | PM_MODEL_OP_CENTER, 0, 0.0, 0.0);
+            psFree (model);
+
+            if (false) {
+                // this call creates an extension with NAXIS3 = 3
+                psArray *images = psArrayAllocEmpty (3);
+                psArrayAdd (images, 1, psfMosaic);
+                // psArrayAdd (images, 1, psfModel);
+                // psArrayAdd (images, 1, psfModel);
+
+                if (!psFitsWriteImageCube (file->fits, header, images, "PSF_MODEL")) {
+                    psError(psErrorCodeLast(), false, "Unable to write PSF representation.");
+                    psFree(images);
+                    psFree(psfMosaic);
+                    psFree(modelRef);
+                    psFree(header);
+                    return false;
+                }
+                psFree (images);
+            } else {
+                // this call creates an extension with NAXIS3 = 1
+                // XXX need to replace PSF_MODEL with rule-based name like residName
+                if (!psFitsWriteImage(file->fits, header, psfMosaic, 0, "PSF_MODEL")) {
+                    psError(psErrorCodeLast(), false, "Unable to write PSF representation.");
+                    psFree(psfMosaic);
+                    psFree(modelRef);
+                    psFree(header);
+                    return false;
+                }
+            }
+        }
+
+        psFree (psfMosaic);
+        psFree (modelRef);
         psFree (header);
     }
@@ -523,4 +645,8 @@
     // find the FPA phu
     pmFPA *fpa = pmFPAfileSuitableFPA(file, view, config, false); // Suitable FPA for writing
+    if (!fpa) {
+        psError(psErrorCodeLast(), false, "Unable to build FPA to write.");
+        return false;
+    }
     pmHDU *phu = psMemIncrRefCounter(pmFPAviewThisPHU(view, fpa));
     psFree(fpa);
@@ -533,10 +659,18 @@
         psMetadataCopy (outhead, phu->header);
     } else {
-        pmConfigConformHeader (outhead, file->format);
+        if (!pmConfigConformHeader (outhead, file->format)) {
+            psError(psErrorCodeLast(), false, "Unable to conform header of PSF PHU.");
+            psFree(phu);
+            return false;
+        }
     }
     psFree(phu);
 
     psMetadataAddBool (outhead, PS_LIST_TAIL, "EXTEND", PS_META_REPLACE, "this file has extensions", true);
-    psFitsWriteBlank (file->fits, outhead, "");
+    if (!psFitsWriteBlank (file->fits, outhead, "")) {
+        psError(psErrorCodeLast(), false, "Unable to write PHU for PSF.");
+        psFree(outhead);
+        return false;
+    }
     file->wrote_phu = true;
 
@@ -568,5 +702,5 @@
     }
 
-    psError(PS_ERR_IO, false, "PSF must be read at the chip level");
+    psError(PM_ERR_CONFIG, true, "PSF must be read at the chip level");
     return false;
 }
@@ -595,5 +729,5 @@
 
     if (!pmPSFmodelRead (chip->analysis, view, file, config)) {
-        psError(PS_ERR_IO, false, "Failed to write PSF for chip");
+        psError(psErrorCodeLast(), false, "Failed to write PSF for chip");
         return false;
     }
@@ -618,5 +752,5 @@
     psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, "PSPHOT");
     if (!recipe) {
-        psError(PS_ERR_UNKNOWN, false, "missing recipe %s", "PSPHOT");
+        psError(PM_ERR_CONFIG, false, "missing recipe %s", "PSPHOT");
         return false;
     }
@@ -625,5 +759,5 @@
     psMetadata *menu = psMetadataLookupMetadata(&status, file->camera, "EXTNAME.RULES");
     if (!menu) {
-        psError(PS_ERR_UNKNOWN, true, "missing EXTNAME.RULES in camera.config");
+        psError(PM_ERR_CONFIG, true, "missing EXTNAME.RULES in camera.config");
         return false;
     }
@@ -631,5 +765,5 @@
     rule = psMetadataLookupStr(&status, menu, "PSF.TABLE");
     if (!rule) {
-        psError(PS_ERR_UNKNOWN, true, "missing entry for PSF.TABLE in EXTNAME.RULES in camera.config");
+        psError(PM_ERR_CONFIG, true, "missing entry for PSF.TABLE in EXTNAME.RULES in camera.config");
         return false;
     }
@@ -638,5 +772,5 @@
     rule = psMetadataLookupStr(&status, menu, "PSF.RESID");
     if (!rule) {
-        psError(PS_ERR_UNKNOWN, true, "missing entry for PSF.RESID in EXTNAME.RULES in camera.config");
+        psError(PM_ERR_CONFIG, true, "missing entry for PSF.RESID in EXTNAME.RULES in camera.config");
         return false;
     }
@@ -646,11 +780,15 @@
     // advance to the table data extension
     // since we have read the IMAGE header, the TABLE header should exist
-    if (!psFitsMoveExtName (file->fits, tableName)) {
-        psAbort("cannot find data extension %s in %s", tableName, file->filename);
+    if (!psFitsMoveExtName(file->fits, tableName)) {
+        psError(psErrorCodeLast(), false, "cannot find data extension %s in %s", tableName, file->filename);
+        return false;
     }
 
     // load the PSF model table header
     header = psFitsReadHeader (NULL, file->fits);
-    if (!header) psAbort("cannot read table header");
+    if (!header) {
+        psError(psErrorCodeLast(), false, "Cannot read PSF table header.");
+        return false;
+    }
 
     pmPSFOptions *options = pmPSFOptionsAlloc();
@@ -667,61 +805,61 @@
     int nRegions = psMetadataLookupS32 (&status, header, "PSF_CLN");
     if (!status) {
-	// read old-style psf clump data
-
-	char regionName[64];
-	snprintf (regionName, 64, "PSF.CLUMP.REGION.000");
-	psMetadataAddS32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.NREGIONS",  PS_META_REPLACE, "psf clump regions", 1);
-
-	psMetadata *regionMD = psMetadataLookupPtr (&status, recipe, regionName);
-	if (!regionMD) {
-	    regionMD = psMetadataAlloc();
-	    psMetadataAddMetadata (recipe, PS_LIST_TAIL, regionName, PS_META_REPLACE, "psf clump region", regionMD);
-	    psFree (regionMD);
-	}
-
-	// psf clump data
-	pmPSFClump psfClump;
-
-	psfClump.X  = psMetadataLookupF32 (&status, header, "PSF_CLX" );  assert(status);
-	psfClump.Y  = psMetadataLookupF32 (&status, header, "PSF_CLY" );  assert(status);
-	psfClump.dX = psMetadataLookupF32 (&status, header, "PSF_CLDX");  assert(status);
-	psfClump.dY = psMetadataLookupF32 (&status, header, "PSF_CLDY");  assert(status);
-
-	psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.X",  PS_META_REPLACE, "psf clump center", psfClump.X);
-	psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.Y",  PS_META_REPLACE, "psf clump center", psfClump.Y);
-	psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DX", PS_META_REPLACE, "psf clump center", psfClump.dX);
-	psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DY", PS_META_REPLACE, "psf clump center", psfClump.dY);
+        // read old-style psf clump data
+
+        char regionName[64];
+        snprintf (regionName, 64, "PSF.CLUMP.REGION.000");
+        psMetadataAddS32 (analysis, PS_LIST_TAIL, "PSF.CLUMP.NREGIONS",  PS_META_REPLACE, "psf clump regions", 1);
+
+        psMetadata *regionMD = psMetadataLookupPtr (&status, analysis, regionName);
+        if (!regionMD) {
+            regionMD = psMetadataAlloc();
+            psMetadataAddMetadata (analysis, PS_LIST_TAIL, regionName, PS_META_REPLACE, "psf clump region", regionMD);
+            psFree (regionMD);
+        }
+
+        // psf clump data
+        pmPSFClump psfClump;
+
+        psfClump.X  = psMetadataLookupF32 (&status, header, "PSF_CLX" );  assert(status);
+        psfClump.Y  = psMetadataLookupF32 (&status, header, "PSF_CLY" );  assert(status);
+        psfClump.dX = psMetadataLookupF32 (&status, header, "PSF_CLDX");  assert(status);
+        psfClump.dY = psMetadataLookupF32 (&status, header, "PSF_CLDY");  assert(status);
+
+        psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.X",  PS_META_REPLACE, "psf clump center", psfClump.X);
+        psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.Y",  PS_META_REPLACE, "psf clump center", psfClump.Y);
+        psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DX", PS_META_REPLACE, "psf clump center", psfClump.dX);
+        psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DY", PS_META_REPLACE, "psf clump center", psfClump.dY);
     } else {
-	psMetadataAddS32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.NREGIONS",  PS_META_REPLACE, "psf clump regions", nRegions);
-
-	for (int i = 0; i < nRegions; i++) {
-	    char key[10];
-	    char regionName[64];
-	    snprintf (regionName, 64, "PSF.CLUMP.REGION.%03d", i);
-
-	    psMetadata *regionMD = psMetadataLookupPtr (&status, recipe, regionName);
-	    if (!regionMD) {
-		regionMD = psMetadataAlloc();
-		psMetadataAddMetadata (recipe, PS_LIST_TAIL, regionName, PS_META_REPLACE, "psf clump region", regionMD);
-		psFree (regionMD);
-	    }
-
-	    // psf clump data
-	    pmPSFClump psfClump;
-
-	    snprintf (key, 9, "CLX_%03d", i);
-	    psfClump.X  = psMetadataLookupF32 (&status, header, key);  assert(status);
-	    snprintf (key, 9, "CLY_%03d", i);
-	    psfClump.Y  = psMetadataLookupF32 (&status, header, key);  assert(status);
-	    snprintf (key, 9, "CLDX_%03d", i);
-	    psfClump.dX = psMetadataLookupF32 (&status, header, key);  assert(status);
-	    snprintf (key, 9, "CLDY_%03d", i);
-	    psfClump.dY = psMetadataLookupF32 (&status, header, key);  assert(status);
-
-	    psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.X",  PS_META_REPLACE, "psf clump center", psfClump.X);
-	    psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.Y",  PS_META_REPLACE, "psf clump center", psfClump.Y);
-	    psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DX", PS_META_REPLACE, "psf clump center", psfClump.dX);
-	    psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DY", PS_META_REPLACE, "psf clump center", psfClump.dY);
-	}
+        psMetadataAddS32 (analysis, PS_LIST_TAIL, "PSF.CLUMP.NREGIONS",  PS_META_REPLACE, "psf clump regions", nRegions);
+
+        for (int i = 0; i < nRegions; i++) {
+            char key[10];
+            char regionName[64];
+            snprintf (regionName, 64, "PSF.CLUMP.REGION.%03d", i);
+
+            psMetadata *regionMD = psMetadataLookupPtr (&status, analysis, regionName);
+            if (!regionMD) {
+                regionMD = psMetadataAlloc();
+                psMetadataAddMetadata (analysis, PS_LIST_TAIL, regionName, PS_META_REPLACE, "psf clump region", regionMD);
+                psFree (regionMD);
+            }
+
+            // psf clump data
+            pmPSFClump psfClump;
+
+            snprintf (key, 9, "CLX_%03d", i);
+            psfClump.X  = psMetadataLookupF32 (&status, header, key);  assert(status);
+            snprintf (key, 9, "CLY_%03d", i);
+            psfClump.Y  = psMetadataLookupF32 (&status, header, key);  assert(status);
+            snprintf (key, 9, "CLDX_%03d", i);
+            psfClump.dX = psMetadataLookupF32 (&status, header, key);  assert(status);
+            snprintf (key, 9, "CLDY_%03d", i);
+            psfClump.dY = psMetadataLookupF32 (&status, header, key);  assert(status);
+
+            psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.X",  PS_META_REPLACE, "psf clump center", psfClump.X);
+            psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.Y",  PS_META_REPLACE, "psf clump center", psfClump.Y);
+            psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DX", PS_META_REPLACE, "psf clump center", psfClump.dX);
+            psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DY", PS_META_REPLACE, "psf clump center", psfClump.dY);
+        }
     }
 
@@ -764,5 +902,5 @@
         char *modeName = psMetadataLookupStr (&status, header, name);
         if (!status) {
-            psError(PS_ERR_UNKNOWN, true, "inconsistent PSF header: NX & NY defined for PAR %d, but not MD", i);
+            psError(PM_ERR_PROG, true, "inconsistent PSF header: NX & NY defined for PAR %d, but not MD", i);
             return false;
         }
@@ -798,4 +936,9 @@
     // read the raw table data
     psArray *table = psFitsReadTable (file->fits);
+    if (!table) {
+        psError(psErrorCodeLast(), false, "Unable to read PSF table.");
+        psFree(header);
+        return false;
+    }
 
     // fill in the matching psf->params entries
@@ -842,8 +985,14 @@
     // since we have read the IMAGE header, the TABLE header should exist
     if (!psFitsMoveExtName (file->fits, imageName)) {
-        psAbort("cannot find data extension %s in %s", imageName, file->filename);
+        psError(psErrorCodeLast(), false, "Cannot find PSF data extension %s in %s",
+                imageName, file->filename);
+        return false;
     }
 
     header = psFitsReadHeader (NULL, file->fits);
+    if (!header) {
+        psError(psErrorCodeLast(), false, "Unable to read PSF header.");
+        return false;
+    }
     int Naxis = psMetadataLookupS32 (&status, header, "NAXIS");
     if (Naxis != 0) {
@@ -865,11 +1014,51 @@
 
         psRegion fullImage = {0, 0, 0, 0};
-        psFitsReadImageBuffer(psf->residuals->Ro, file->fits, fullImage, 0); // Desired pixels
-        if (Nz > 1) {
-            assert (Nz == 3);
-            psFitsReadImageBuffer(psf->residuals->Rx, file->fits, fullImage, 1); // Desired pixels
-            psFitsReadImageBuffer(psf->residuals->Ry, file->fits, fullImage, 2); // Desired pixels
-        }
-	// XXX notice that we are not saving the resid->mask
+        if (!psFitsReadImageBuffer(psf->residuals->Ro, file->fits, fullImage, 0)) {
+            psError(psErrorCodeLast(), false, "Unable to read PSF residual image.");
+            return false;
+        }
+
+	// note that all N plane are implicitly of the same type, so we convert the mask
+	psImage *mask = psImageCopy(NULL, psf->residuals->mask, psf->residuals->Ro->type.type);
+	psImageInit (psf->residuals->mask, 0);
+	psImageInit (psf->residuals->Rx, 0.0);
+	psImageInit (psf->residuals->Ry, 0.0);
+	switch (Nz) {
+	  case 1: // Ro only
+	    break;
+	  case 2: // Ro and mask
+            if (!psFitsReadImageBuffer(mask, file->fits, fullImage, 1)) {
+                psError(psErrorCodeLast(), false, "Unable to read PSF residual image.");
+                return false;
+            }
+	    psImageCopy (psf->residuals->mask, mask, PM_TYPE_RESID_MASK);
+	    break;
+	  case 3: // Ro, Rx and Ry, no mask
+            if (!psFitsReadImageBuffer(psf->residuals->Rx, file->fits, fullImage, 1)) {
+                psError(psErrorCodeLast(), false, "Unable to read PSF residual image.");
+                return false;
+            }
+            if (!psFitsReadImageBuffer(psf->residuals->Ry, file->fits, fullImage, 2)) {
+                psError(psErrorCodeLast(), false, "Unable to read PSF residual image.");
+                return false;
+            }
+	    break;
+	  case 4: // Ro, Rx, Ry, and mask:
+            if (!psFitsReadImageBuffer(psf->residuals->Rx, file->fits, fullImage, 1)) {
+                psError(psErrorCodeLast(), false, "Unable to read PSF residual image.");
+                return false;
+            }
+            if (!psFitsReadImageBuffer(psf->residuals->Ry, file->fits, fullImage, 2)) {
+                psError(psErrorCodeLast(), false, "Unable to read PSF residual image.");
+                return false;
+            }
+            if (!psFitsReadImageBuffer(mask, file->fits, fullImage, 3)) {
+                psError(psErrorCodeLast(), false, "Unable to read PSF residual image.");
+                return false;
+            }
+	    psImageCopy (psf->residuals->mask, mask, PM_TYPE_RESID_MASK);
+	    break;
+        }
+	psFree (mask);
     }
 
Index: branches/simmosaic_branches/psModules/src/objects/pmPSFtry.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPSFtry.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmPSFtry.c	(revision 27839)
@@ -37,51 +37,4 @@
 #include "pmSourceVisual.h"
 
-bool printTrendMap (pmTrend2D *trend) {
-
-    if (!trend->map) return false;
-    if (!trend->map->map) return false;
-
-    for (int j = 0; j < trend->map->map->numRows; j++) {
-        for (int i = 0; i < trend->map->map->numCols; i++) {
-            fprintf (stderr, "%5.2f  ", trend->map->map->data.F32[j][i]);
-        }
-        fprintf (stderr, "\t\t\t");
-        for (int i = 0; i < trend->map->map->numCols; i++) {
-            fprintf (stderr, "%5.2f  ", trend->map->error->data.F32[j][i]);
-        }
-        fprintf (stderr, "\n");
-    }
-    return true;
-}
-
-bool psImageMapCleanup (psImageMap *map) {
-
-    if ((map->map->numRows == 1) && (map->map->numCols == 1)) return true;
-
-    // find the weighted average of all pixels
-    float Sum = 0.0;
-    float Wt = 0.0;
-    for (int j = 0; j < map->map->numRows; j++) {
-        for (int i = 0; i < map->map->numCols; i++) {
-            if (!isfinite(map->map->data.F32[j][i])) continue;
-            Sum += map->map->data.F32[j][i] * map->error->data.F32[j][i];
-            Wt += map->error->data.F32[j][i];
-        }
-    }
-
-    float Mean = Sum / Wt;
-
-    // do any of the pixels in the map need to be repaired?
-    // XXX for now, we are just replacing bad pixels with the Mean
-    for (int j = 0; j < map->map->numRows; j++) {
-        for (int i = 0; i < map->map->numCols; i++) {
-            if (isfinite(map->map->data.F32[j][i]) &&
-                (map->error->data.F32[j][i] > 0.0)) continue;
-            map->map->data.F32[j][i] = Mean;
-        }
-    }
-    return true;
-}
-
 // ********  pmPSFtry functions  **************************************************
 // * pmPSFtry holds a single pmPSF model test, with the input sources, the freely
@@ -110,5 +63,5 @@
     psMemSetDeallocator(test, (psFreeFunc) pmPSFtryFree);
 
-    test->psf       = pmPSFAlloc (options);
+    test->psf       = NULL; 
     test->metric    = psVectorAlloc (sources->n, PS_TYPE_F32);
     test->metricErr = psVectorAlloc (sources->n, PS_TYPE_F32);
@@ -136,934 +89,98 @@
 }
 
+float psVectorSystematicError (psVector *residuals, psVector *errors, float clipFraction) {
 
-// build a pmPSFtry for the given model:
-// - fit each source with the free-floating model
-// - construct the pmPSF from the collection of models
-// - fit each source with the PSF-parameter models
-// - measure the pmPSF quality metric (dApResid)
+    psAssert(residuals, "residuals cannot be NULL");
+    psAssert(errors, "errors cannot be NULL");
+    psAssert(residuals->n == errors->n, "residuals and errors must be the same length");
 
-// sources used in for pmPSFtry may be masked by the analysis
-// mask values indicate the reason the source was rejected:
+    // given a vector of residuals and their formal errors, calculated the necessary systematic
+    // error needed to yield a reduced chisq of 1.0, after first tossing out the clipFraction
+    // highest chi-square contributors (allowed outliers)
 
-// generate a pmPSFtry with a copy of the test PSF sources
-pmPSFtry *pmPSFtryModel (const psArray *sources, const char *modelName, pmPSFOptions *options, psImageMaskType maskVal, psImageMaskType markVal)
-{
-    bool status;
-    int Next = 0;
-    int Npsf = 0;
+    psVector *mask  = psVectorAlloc(residuals->n, PS_TYPE_VECTOR_MASK);
+    psVector *chisq = psVectorAlloc(residuals->n, PS_TYPE_F32);
 
-    // validate the requested model name
-    options->type = pmModelClassGetType (modelName);
-    if (options->type == -1) {
-        psError (PS_ERR_UNKNOWN, true, "invalid model name %s", modelName);
-        return NULL;
+    // calculate the chisq vector:
+    int Ngood = 0;
+    for (int i = 0; i < residuals->n; i++) {
+	chisq->data.F32[i] = PS_MAX_F32;
+	if (!isfinite(residuals->data.F32[i])) continue;
+	if (!isfinite(errors->data.F32[i])) continue;
+	if (errors->data.F32[i] <= 0.0) continue;
+	chisq->data.F32[i] = PS_SQR(residuals->data.F32[i] / errors->data.F32[i]);
+	Ngood ++;
     }
 
-    pmPSFtry *psfTry = pmPSFtryAlloc (sources, options);
-    if (psfTry == NULL) {
-        psError (PS_ERR_UNKNOWN, false, "failed to allocate psf model");
-        return NULL;
+    psVector *index = psVectorSortIndex(NULL, chisq);
+
+    // toss out the clipFraction highest chisq values
+    for (int i = 0; i < residuals->n; i++) {
+	int n = index->data.S32[i];
+	if (i < (1.0 - clipFraction)*Ngood) {
+	    mask->data.PS_TYPE_VECTOR_MASK_DATA[n] = 0;
+	} else {
+	    mask->data.PS_TYPE_VECTOR_MASK_DATA[n] = 1;
+	}
     }
 
-    // maskVal is used to test for rejected pixels, and must include markVal
-    maskVal |= markVal;
+    // Ndof ~= Ngood
+    // Chisq_Ndof = sum(residuals_i^2 / error_i^2) / Ndof
+    // choose S2 such than Chisq^sys_Ndof = sum(residuals_i^2 / (error_i^2 + S2)) / Ndof = 1.0
+    
+    // use Newton-Raphson to solve for S2:
 
-    // stage 1:  fit an EXT model to all candidates PSF sources -- this is independent of the modeled 2D variations in the PSF
-    psTimerStart ("psf.fit");
-    for (int i = 0; i < psfTry->sources->n; i++) {
+    // use median sigma to calculate the initial guess for S2:
+    psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
+    psVectorStats (stats, errors, NULL, mask, 1);
+    float errorMedian = stats->sampleMedian;
+    
+    float nPts = 0.0;
+    float res2mean = 0.0;
+    float ChiSq = 0.0;
+    for (int i = 0; i < residuals->n; i++) {
+	int n = index->data.S32[i];
+	if (mask->data.PS_TYPE_VECTOR_MASK_DATA[n]) continue;
+	res2mean += PS_SQR(residuals->data.F32[n]);
+	ChiSq += PS_SQR(residuals->data.F32[n]) / PS_SQR(errors->data.F32[n]);
+	nPts += 1.0;
+    }
+    res2mean /= nPts;
+    ChiSq /= nPts;
+    
+    float S2guess = res2mean - PS_SQR(errorMedian);
 
-        pmSource *source = psfTry->sources->data[i];
-        if (!source->moments) {
-            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_EXT_FAIL;
-            continue;
-        }
-        if (!source->moments->nPixels) {
-            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_EXT_FAIL;
-            continue;
-        }
+    psLogMsg ("psModules", 10, "ChiSquare: %f, Ntotal: %ld, Ngood: %d, Nkeep: %.0f, S2 guess: %f\n", 
+	      ChiSq, residuals->n, Ngood, nPts, S2guess);
 
-        source->modelEXT = pmSourceModelGuess (source, psfTry->psf->type);
-        if (source->modelEXT == NULL) {
-            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_EXT_FAIL;
-            psTrace ("psModules.objects", 4, "masking %d (%d,%d) : failed to generate model guess\n", i, source->peak->x, source->peak->y);
-            continue;
-        }
+    for (int iter = 0; iter < 10; iter++) {
 
-        // set object mask to define valid pixels
-        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->radius, "OR", markVal);
+	ChiSq = 0.0;
+	float dRdS = 0.0;
+	for (int i = 0; i < residuals->n; i++) {
+	    int n = index->data.S32[i];
+	    if (mask->data.PS_TYPE_VECTOR_MASK_DATA[n]) continue;
+	    float error2 = PS_SQR(errors->data.F32[n]) + S2guess;
+	    ChiSq += PS_SQR(residuals->data.F32[n]) / error2;
+	    dRdS += PS_SQR(residuals->data.F32[n]) / PS_SQR(error2);
+	}
+	ChiSq /= nPts;
+	dRdS /= nPts;
 
-        // fit model as EXT, not PSF
-        status = pmSourceFitModel (source, source->modelEXT, PM_SOURCE_FIT_EXT, maskVal);
+	// Note the sign on dS: dRdS above is -1 * dR/dS formally
+	float dS = (ChiSq - 1.0) / dRdS;
+	S2guess += dS;
+	S2guess = PS_MAX(0.0, S2guess);
 
-        // clear object mask to define valid pixels
-        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->radius, "AND", PS_NOT_IMAGE_MASK(markVal));
-
-        // exclude the poor fits
-        if (!status) {
-            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_EXT_FAIL;
-            psTrace ("psModules.objects", 4, "masking %d (%d,%d) : status is poor\n", i, source->peak->x, source->peak->y);
-            continue;
-        }
-        Next ++;
-    }
-    psLogMsg ("psphot.psftry", PS_LOG_MINUTIA, "fit ext:   %f sec for %d of %ld sources\n", psTimerMark ("psf.fit"), Next, sources->n);
-    psTrace ("psModules.object", 3, "keeping %d of %ld PSF candidates (EXT)\n", Next, sources->n);
-
-    if (Next == 0) {
-        psError(PS_ERR_UNKNOWN, false, "No sources with good extended fits from which to determine PSF.");
-        psFree(psfTry);
-        return NULL;
+	psLogMsg ("psModules", 10, "ChiSquare: %f, dS: %f, S2 guess: %f\n", ChiSq, dS, S2guess);
     }
 
-    // stage 2: construct a psf (pmPSF) from this collection of model fits, including the 2D variation
-    if (!pmPSFFromPSFtry (psfTry)) {
-        psError(PS_ERR_UNKNOWN, false, "failed to construct a psf model from collection of sources");
-        psFree(psfTry);
-        return NULL;
-    }
+    // free local allocations
+    psFree (mask);
+    psFree (chisq);
+    psFree (stats);
+    psFree (index);
 
-    // stage 3: refit with fixed shape parameters
-    psTimerStart ("psf.fit");
-    for (int i = 0; i < psfTry->sources->n; i++) {
-
-        pmSource *source = psfTry->sources->data[i];
-
-        // masked for: bad model fit, outlier in parameters
-        if (psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PSFTRY_MASK_ALL) {
-            psTrace ("psModules.objects", 4, "dropping %d (%d,%d) : source is masked\n", i, source->peak->x, source->peak->y);
-            continue;
-        }
-
-        // set shape for this model based on PSF
-        source->modelPSF = pmModelFromPSF (source->modelEXT, psfTry->psf);
-        if (source->modelPSF == NULL) {
-            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_BAD_MODEL;
-            abort();
-            continue;
-        }
-        source->modelPSF->radiusFit = options->radius;
-
-        // set object mask to define valid pixels
-        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->radius, "OR", markVal);
-
-        // fit the PSF model to the source
-        status = pmSourceFitModel (source, source->modelPSF, PM_SOURCE_FIT_PSF, maskVal);
-
-        // skip poor fits
-        if (!status) {
-            psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->radius, "AND", PS_NOT_IMAGE_MASK(markVal));
-            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_PSF_FAIL;
-            psTrace ("psModules.objects", 4, "dropping %d (%d,%d) : failed PSF fit\n", i, source->peak->x, source->peak->y);
-            continue;
-        }
-
-        status = pmSourceMagnitudes (source, psfTry->psf, PM_SOURCE_PHOT_INTERP, maskVal);
-        if (!status || isnan(source->apMag)) {
-            psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->radius, "AND", PS_NOT_IMAGE_MASK(markVal));
-            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_BAD_PHOT;
-            psTrace ("psModules.objects", 4, "dropping %d (%d,%d) : poor photometry\n", i, source->peak->x, source->peak->y);
-            continue;
-        }
-
-        // clear object mask to define valid pixels
-        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->radius, "AND", PS_NOT_IMAGE_MASK(markVal));
-
-        psfTry->fitMag->data.F32[i] = source->psfMag;
-        psfTry->metric->data.F32[i] = source->apMag - source->psfMag;
-        psfTry->metricErr->data.F32[i] = source->errMag;
-
-        psTrace ("psModules.object", 6, "keeping source %d (%d) of %ld\n", i, Npsf, psfTry->sources->n);
-        Npsf ++;
-    }
-    psfTry->psf->nPSFstars = Npsf;
-
-    psLogMsg ("psphot.psftry", PS_LOG_MINUTIA, "fit psf:   %f sec for %d of %ld sources\n", psTimerMark ("psf.fit"), Npsf, sources->n);
-    psTrace ("psModules.object", 3, "keeping %d of %ld PSF candidates (PSF)\n", Npsf, sources->n);
-
-    if (Npsf == 0) {
-        psError(PS_ERR_UNKNOWN, false, "No sources with good PSF fits after model is built.");
-        psFree(psfTry);
-        return NULL;
-    }
-
-    // measure the chi-square trend as a function of flux (PAR[PM_PAR_I0])
-    // this should be linear for Poisson errors and quadratic for constant sky errors
-    psVector *flux  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
-    psVector *chisq = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
-    psVector *mask  = psVectorAlloc (psfTry->sources->n, PS_TYPE_VECTOR_MASK);
-
-    // generate the x and y vectors, and mask missing models
-    for (int i = 0; i < psfTry->sources->n; i++) {
-        pmSource *source = psfTry->sources->data[i];
-        if (source->modelPSF == NULL) {
-            flux->data.F32[i] = 0.0;
-            chisq->data.F32[i] = 0.0;
-            mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = 0xff;
-        } else {
-            flux->data.F32[i] = source->modelPSF->params->data.F32[PM_PAR_I0];
-            chisq->data.F32[i] = source->modelPSF->chisq / source->modelPSF->nDOF;
-            mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = 0;
-        }
-    }
-
-    // use 3hi/3lo sigma clipping on the chisq fit
-    psStats *stats = options->stats;
-
-    // linear clipped fit of chisq trend vs flux
-    if (options->chiFluxTrend) {
-        bool result = psVectorClipFitPolynomial1D(psfTry->psf->ChiTrend, options->stats,
-                                                  mask, 0xff, chisq, NULL, flux);
-        psStatsOptions meanStat = psStatsMeanOption(options->stats->options); // Statistic for mean
-        psStatsOptions stdevStat = psStatsStdevOption(options->stats->options); // Statistic for stdev
-
-        psLogMsg ("pmPSFtry", 4, "chisq vs flux fit: %f +/- %f\n",
-                  psStatsGetValue(stats, meanStat), psStatsGetValue(stats, stdevStat));
-
-        psFree(flux);
-        psFree(mask);
-        psFree(chisq);
-
-        if (!result) {
-            psError(PS_ERR_UNKNOWN, false, "Failed to fit psf->ChiTrend");
-            psFree(psfTry);
-            return NULL;
-        }
-    }
-
-    for (int i = 0; i < psfTry->psf->ChiTrend->nX + 1; i++) {
-        psLogMsg ("pmPSFtry", 4, "chisq vs flux fit term %d: %f +/- %f\n", i,
-                  psfTry->psf->ChiTrend->coeff[i]*pow(10000, i),
-                  psfTry->psf->ChiTrend->coeffErr[i]*pow(10000,i));
-    }
-
-    // XXX this function wants aperture radius for pmSourcePhotometry
-    if (!pmPSFtryMetric (psfTry, options)) {
-        psError(PS_ERR_UNKNOWN, false, "Attempt to fit PSF with model %s failed.", modelName);
-        psFree (psfTry);
-        return NULL;
-    }
-
-    psLogMsg ("psphot.pspsf", 3, "try model %s, ap-fit: %f +/- %f : sky bias: %f\n",
-              modelName, psfTry->psf->ApResid, psfTry->psf->dApResid, psfTry->psf->skyBias);
-
-    return (psfTry);
+    return (sqrt(S2guess));
 }
 
-bool pmPSFtryMetric (pmPSFtry *psfTry, pmPSFOptions *options)
-{
-    PS_ASSERT_PTR_NON_NULL(psfTry, false);
-    PS_ASSERT_PTR_NON_NULL(options, false);
-    PS_ASSERT_PTR_NON_NULL(psfTry->sources, false);
-
-    float RADIUS = options->radius;
-
-    // the measured (aperture - fit) magnitudes (dA == psfTry->metric)
-    //   depend on both the true ap-fit (dAo) and the bias in the sky measurement:
-    //     dA = dAo + dsky/flux
-    //   where flux is the flux of the star
-    // we fit this trend to find the infinite flux aperture correction (dAo),
-    //   the nominal sky bias (dsky), and the error on dAo
-    // the values of dA are contaminated by stars with close neighbors in the aperture
-    //   we use an outlier rejection to avoid this bias
-
-    // r2rflux = radius^2 * ten(0.4*fitMag);
-    psVector *r2rflux = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
-
-    for (int i = 0; i < psfTry->sources->n; i++) {
-        if (psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PSFTRY_MASK_ALL)
-            continue;
-        r2rflux->data.F32[i] = PS_SQR(RADIUS) * pow(10.0, 0.4*psfTry->fitMag->data.F32[i]);
-    }
-
-    // XXX test dump of aperture residual data
-    if (psTraceGetLevel("psModules.objects") >= 5) {
-        FILE *f = fopen ("apresid.dat", "w");
-        for (int i = 0; i < psfTry->sources->n; i++) {
-            int keep = (psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PSFTRY_MASK_ALL);
-
-            pmSource *source = psfTry->sources->data[i];
-            float x = source->peak->x;
-            float y = source->peak->y;
-
-            fprintf (f, "%d  %d, %f %f %f  %f %f %f \n",
-                     i, keep, x, y,
-                     psfTry->fitMag->data.F32[i],
-                     r2rflux->data.F32[i],
-                     psfTry->metric->data.F32[i],
-                     psfTry->metricErr->data.F32[i]);
-        }
-        fclose (f);
-    }
-
-    // This analysis of the apResid statistics is only approximate.  The fitted magnitudes
-    // measured at this point (in the PSF fit) use Poisson errors, and are thus biased as a
-    // function of magnitude.  We re-measure the apResid statistics later in psphot using the
-    // linear, constant-error fitting.  Do not reject outliers with excessive vigor here.
-
-    // fit ApTrend only to r2rflux, ignore x,y,flux variations for now
-    // linear clipped fit of ApResid to r2rflux
-    psPolynomial1D *poly = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 1);
-    poly->coeffMask[1] = PS_POLY_MASK_SET; // fit only a constant offset (no SKYBIAS)
-
-    // XXX replace this with a psVectorStats call?  since we are not fitting the trend
-    bool result = psVectorClipFitPolynomial1D(poly, options->stats, psfTry->mask, PSFTRY_MASK_ALL,
-                                              psfTry->metric, psfTry->metricErr, r2rflux);
-    if (!result) {
-        psError(PS_ERR_UNKNOWN, false, "Failed to fit clipped poly");
-
-        psFree(poly);
-        psFree(r2rflux);
-
-        return false;
-    }
-    psStatsOptions stdevStat = psStatsStdevOption(options->stats->options); // Statistic for stdev
-    psLogMsg ("pmPSFtryMetric", 4, "apresid: %f +/- %f; from statistics of %ld psf stars\n", poly->coeff[0],
-              psStatsGetValue(options->stats, stdevStat), psfTry->sources->n);
-
-
-
-    // XXX test dump of fitted model (dump when tracing?)
-    if (psTraceGetLevel("psModules.objects") >= 4) {
-        FILE *f = fopen ("resid.dat", "w");
-        psVector *apfit = psPolynomial1DEvalVector (poly, r2rflux);
-        for (int i = 0; i < psfTry->sources->n; i++) {
-            int keep = (psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PSFTRY_MASK_ALL);
-
-            pmSource *source = psfTry->sources->data[i];
-            float x = source->peak->x;
-            float y = source->peak->y;
-
-            fprintf (f, "%d  %d, %f %f %f  %f %f %f  %f\n",
-                     i, keep, x, y,
-                     psfTry->fitMag->data.F32[i],
-                     r2rflux->data.F32[i],
-                     psfTry->metric->data.F32[i],
-                     psfTry->metricErr->data.F32[i],
-                     apfit->data.F32[i]);
-        }
-        fclose (f);
-        psFree (apfit);
-    }
-
-    // XXX drop the skyBias value, or include above??
-    psfTry->psf->skyBias  = poly->coeff[1];
-    psfTry->psf->ApResid  = poly->coeff[0];
-    psfTry->psf->dApResid = psStatsGetValue(options->stats, stdevStat);
-
-    psFree (r2rflux);
-    psFree (poly);
-
-    return true;
-}
-
-/*
-  (aprMag' - fitMag) = rflux*skyBias + ApTrend(x,y)
-  (aprMag - rflux*skyBias) - fitMag = ApTrend(x,y)
-  (aprMag - rflux*skyBias) = fitMag + ApTrend(x,y)
-*/
-
-/*****************************************************************************
-pmPSFFromPSFtry (psfTry): build a PSF model from a collection of
-source->modelEXT entries.  The PSF ignores the first 4 (independent) model
-parameters and constructs a polynomial fit to the remaining as a function of
-image coordinate.
-    input: psfTry with fitted source->modelEXT collection, pre-allocated psf
-Note: some of the array entries may be NULL (failed fits); ignore them.
- *****************************************************************************/
-bool pmPSFFromPSFtry (pmPSFtry *psfTry)
-{
-    PS_ASSERT_PTR_NON_NULL(psfTry, false);
-    PS_ASSERT_PTR_NON_NULL(psfTry->sources, false);
-
-    pmPSF *psf = psfTry->psf;
-
-    // construct the fit vectors from the collection of objects
-    psVector *x  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
-    psVector *y  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
-    psVector *z  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
-
-    // construct the x,y terms
-    for (int i = 0; i < psfTry->sources->n; i++) {
-        pmSource *source = psfTry->sources->data[i];
-        if (source->modelEXT == NULL)
-            continue;
-
-        x->data.F32[i] = source->modelEXT->params->data.F32[PM_PAR_XPOS];
-        y->data.F32[i] = source->modelEXT->params->data.F32[PM_PAR_YPOS];
-    }
-
-    if (!pmPSFFitShapeParams (psf, psfTry->sources, x, y, psfTry->mask)) {
-        psFree(x);
-        psFree(y);
-        psFree(z);
-        return false;
-    }
-
-    // skip the unfitted parameters (X, Y, Io, Sky) and the shape parameters (SXX, SYY, SXY)
-    // apply the values of Nx, Ny determined above for E0,E1,E2 to the remaining parameters
-    for (int i = 0; i < psf->params->n; i++) {
-        switch (i) {
-          case PM_PAR_SKY:
-          case PM_PAR_I0:
-          case PM_PAR_XPOS:
-          case PM_PAR_YPOS:
-          case PM_PAR_SXX:
-          case PM_PAR_SYY:
-          case PM_PAR_SXY:
-            continue;
-          default:
-            break;
-        }
-
-        // select the per-object fitted data for this parameter
-        for (int j = 0; j < psfTry->sources->n; j++) {
-            pmSource *source = psfTry->sources->data[j];
-            if (source->modelEXT == NULL) continue;
-            z->data.F32[j] = source->modelEXT->params->data.F32[i];
-        }
-
-        psImageBinning *binning = psImageBinningAlloc();
-        binning->nXruff = psf->trendNx;
-        binning->nYruff = psf->trendNy;
-        binning->nXfine = psf->fieldNx;
-        binning->nYfine = psf->fieldNy;
-
-        if (psf->psfTrendMode == PM_TREND_MAP) {
-            psImageBinningSetScale (binning, PS_IMAGE_BINNING_CENTER);
-            psImageBinningSetSkipByOffset (binning, psf->fieldXo, psf->fieldYo);
-        }
-
-        // free existing trend, re-alloc
-        psFree (psf->params->data[i]);
-        psf->params->data[i] = pmTrend2DNoImageAlloc (psf->psfTrendMode, binning, psf->psfTrendStats);
-        psFree (binning);
-
-        // fit the collection of measured parameters to the PSF 2D model
-        // the mask is carried from previous steps and updated with this operation
-        // the weight is either the flux error or NULL, depending on 'psf->poissonErrorParams'
-        if (!pmTrend2DFit (psf->params->data[i], psfTry->mask, 0xff, x, y, z, NULL)) {
-            psError(PS_ERR_UNKNOWN, false, "failed to build psf model for parameter %d", i);
-            psFree(x);
-            psFree(y);
-            psFree(z);
-            return false;
-        }
-    }
-
-    // test dump of star parameters vs position (compare with fitted values)
-    if (psTraceGetLevel("psModules.objects") >= 4) {
-        FILE *f = fopen ("params.dat", "w");
-
-        for (int j = 0; j < psfTry->sources->n; j++) {
-            pmSource *source = psfTry->sources->data[j];
-            if (source == NULL) continue;
-            if (source->modelEXT == NULL) continue;
-
-            pmModel *modelPSF = pmModelFromPSF (source->modelEXT, psf);
-
-            fprintf (f, "%f %f : ", source->modelEXT->params->data.F32[PM_PAR_XPOS], source->modelEXT->params->data.F32[PM_PAR_YPOS]);
-
-            for (int i = 0; i < psf->params->n; i++) {
-                if (psf->params->data[i] == NULL) continue;
-                fprintf (f, "%f %f : ", source->modelEXT->params->data.F32[i], modelPSF->params->data.F32[i]);
-            }
-            fprintf (f, "%f %d\n", source->modelEXT->chisq, source->modelEXT->nIter);
-
-            psFree(modelPSF);
-        }
-        fclose (f);
-    }
-
-    psFree (x);
-    psFree (y);
-    psFree (z);
-    return true;
-}
-
-
-bool pmPSFFitShapeParams (pmPSF *psf, psArray *sources, psVector *x, psVector *y, psVector *srcMask) {
-
-    // we are doing a robust fit.  after each pass, we drop points which are more deviant than
-    // three sigma.  mask is currently updated for each pass.
-
-    // The shape parameters (SXX, SXY, SYY) are strongly coupled.  We have to handle them very
-    // carefully.  First, we convert them to the Ellipse Polarization terms (E0, E1, E2) for
-    // each source and fit this set of parameters.  These values are less tightly coupled, but
-    // are still inter-related.  The fitted values do a good job of constraining the major axis
-    // and the position angle, but the minor axis is weakly measured.  When we apply the PSF
-    // model to construct a source model, we convert the fitted values of E0,E1,E2 to the shape
-    // parameters, with the constraint that the minor axis must be greater than a minimum
-    // threshold.
-
-    // convert the measured source shape paramters to polarization terms
-    psVector *e0   = psVectorAlloc (sources->n, PS_TYPE_F32);
-    psVector *e1   = psVectorAlloc (sources->n, PS_TYPE_F32);
-    psVector *e2   = psVectorAlloc (sources->n, PS_TYPE_F32);
-    psVector *mag  = psVectorAlloc (sources->n, PS_TYPE_F32);
-
-    for (int i = 0; i < sources->n; i++) {
-        // skip any masked sources (failed to fit one of the model steps or get a magnitude)
-        if (srcMask->data.PS_TYPE_VECTOR_MASK_DATA[i]) continue;
-
-        pmSource *source = sources->data[i];
-        assert (source->modelEXT); // all unmasked sources should have modelEXT
-
-        psEllipsePol pol = pmPSF_ModelToFit (source->modelEXT->params->data.F32);
-
-        e0->data.F32[i] = pol.e0;
-        e1->data.F32[i] = pol.e1;
-        e2->data.F32[i] = pol.e2;
-
-        float flux = source->modelEXT->params->data.F32[PM_PAR_I0];
-        mag->data.F32[i] = (flux > 0.0) ? -2.5*log(flux) : -100.0;
-    }
-
-    if (psf->psfTrendMode == PM_TREND_MAP) {
-        float scatterTotal = 0.0;
-        float scatterTotalMin = FLT_MAX;
-        int entryMin = -1;
-
-        psVector *dz = NULL;
-        psVector *mask = psVectorAlloc (sources->n, PS_TYPE_VECTOR_MASK);
-
-        // check the fit residuals and increase Nx,Ny until the error is minimized
-        // pmPSFParamTrend increases the number along the longer of x or y
-        for (int i = 1; i <= PS_MAX (psf->trendNx, psf->trendNy); i++) {
-
-            // copy srcMask to mask (we do not want the mask values set in pmPSFFitShapeParamsMap to be sticky)
-            for (int i = 0; i < mask->n; i++) {
-                mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = srcMask->data.PS_TYPE_VECTOR_MASK_DATA[i];
-            }
-            if (!pmPSFFitShapeParamsMap (psf, i, &scatterTotal, mask, x, y, mag, e0, e1, e2, dz)) {
-                break;
-            }
-
-            // store the resulting scatterTotal values and the scales, redo the best
-            if (scatterTotal < scatterTotalMin) {
-                scatterTotalMin = scatterTotal;
-                entryMin = i;
-            }
-        }
-        if (entryMin == -1) {
-            psError (PS_ERR_UNKNOWN, false, "failed to find image map for shape params");
-            return false;
-        }
-
-        // copy srcMask to mask (we do not want the mask values set in pmPSFFitShapeParamsMap to be sticky)
-        for (int i = 0; i < mask->n; i++) {
-            mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = srcMask->data.PS_TYPE_VECTOR_MASK_DATA[i];
-        }
-        if (!pmPSFFitShapeParamsMap (psf, entryMin, &scatterTotal, mask, x, y, mag, e0, e1, e2, dz)) {
-            psAbort ("failed pmPSFFitShapeParamsMap on second pass?");
-        }
-
-        pmTrend2D *trend = psf->params->data[PM_PAR_E0];
-        psf->trendNx = trend->map->map->numCols;
-        psf->trendNy = trend->map->map->numRows;
-
-        // copy mask back to srcMask
-        for (int i = 0; i < mask->n; i++) {
-            srcMask->data.PS_TYPE_VECTOR_MASK_DATA[i] = mask->data.PS_TYPE_VECTOR_MASK_DATA[i];
-        }
-
-        psFree (mask);
-        psFree (dz);
-    } else {
-
-        // XXX iterate Nx, Ny based on scatter?
-        // XXX we force the x & y order to be the same
-        // modify the order to correspond to the actual number of matched stars:
-        int order = PS_MAX (psf->trendNx, psf->trendNy);
-        if ((sources->n < 15) && (order >= 3)) order = 2;
-        if ((sources->n < 11) && (order >= 2)) order = 1;
-        if ((sources->n <  8) && (order >= 1)) order = 0;
-        if ((sources->n <  3)) {
-            psError (PS_ERR_UNKNOWN, true, "failed to fit polynomial to shape params");
-            return false;
-        }
-        psf->trendNx = order;
-        psf->trendNy = order;
-
-        // we run 'clipIter' cycles clipping in each of x and y, with only one iteration each.
-        // This way, the parameters masked by one of the fits will be applied to the others
-        for (int i = 0; i < psf->psfTrendStats->clipIter; i++) {
-
-            psStatsOptions meanOption = psStatsMeanOption(psf->psfTrendStats->options);
-            psStatsOptions stdevOption = psStatsStdevOption(psf->psfTrendStats->options);
-
-            pmTrend2D *trend = NULL;
-            float mean, stdev;
-
-            // XXX we are using the same stats structure on each pass: do we need to re-init it?
-            bool status = true;
-
-            trend = psf->params->data[PM_PAR_E0];
-            status &= pmTrend2DFit (trend, srcMask, 0xff, x, y, e0, NULL);
-            mean = psStatsGetValue (trend->stats, meanOption);
-            stdev = psStatsGetValue (trend->stats, stdevOption);
-            psTrace ("psModules.objects", 4, "clipped E0 : %f +/- %f keeping %ld of %ld\n", mean, stdev, psf->psfTrendStats->clippedNvalues, e0->n);
-            pmSourceVisualPSFModelResid (trend, x, y, e0, srcMask);
-
-            trend = psf->params->data[PM_PAR_E1];
-            status &= pmTrend2DFit (trend, srcMask, 0xff, x, y, e1, NULL);
-            mean = psStatsGetValue (trend->stats, meanOption);
-            stdev = psStatsGetValue (trend->stats, stdevOption);
-            psTrace ("psModules.objects", 4, "clipped E1 : %f +/- %f keeping %ld of %ld\n", mean, stdev, psf->psfTrendStats->clippedNvalues, e1->n);
-            pmSourceVisualPSFModelResid (trend, x, y, e1, srcMask);
-
-            trend = psf->params->data[PM_PAR_E2];
-            status &= pmTrend2DFit (trend, srcMask, 0xff, x, y, e2, NULL);
-            mean = psStatsGetValue (trend->stats, meanOption);
-            stdev = psStatsGetValue (trend->stats, stdevOption);
-            psTrace ("psModules.objects", 4, "clipped E2 : %f +/- %f keeping %ld of %ld\n", mean, stdev, psf->psfTrendStats->clippedNvalues, e2->n);
-            pmSourceVisualPSFModelResid (trend, x, y, e2, srcMask);
-
-            if (!status) {
-                psError (PS_ERR_UNKNOWN, true, "failed to fit polynomial to shape params");
-                return false;
-            }
-        }
-    }
-
-    // test dump of the psf parameters
-    if (psTraceGetLevel("psModules.objects") >= 4) {
-        FILE *f = fopen ("pol.dat", "w");
-        fprintf (f, "# x y  :  e0obs e1obs e2obs  : e0fit e1fit e2fit : mask\n");
-        for (int i = 0; i < e0->n; i++) {
-            fprintf (f, "%f %f  :  %f %f %f  : %f %f %f  : %d\n",
-                     x->data.F32[i], y->data.F32[i],
-                     e0->data.F32[i], e1->data.F32[i], e2->data.F32[i],
-                     pmTrend2DEval (psf->params->data[PM_PAR_E0], x->data.F32[i], y->data.F32[i]),
-                     pmTrend2DEval (psf->params->data[PM_PAR_E1], x->data.F32[i], y->data.F32[i]),
-                     pmTrend2DEval (psf->params->data[PM_PAR_E2], x->data.F32[i], y->data.F32[i]),
-                     srcMask->data.PS_TYPE_VECTOR_MASK_DATA[i]);
-        }
-        fclose (f);
-    }
-
-    psFree (e0);
-    psFree (e1);
-    psFree (e2);
-    psFree (mag);
-    return true;
-}
-
-// fit the shape variations as a psImageMap for the given scale factor
-bool pmPSFFitShapeParamsMap (pmPSF *psf, int scale, float *scatterTotal, psVector *mask, psVector *x, psVector *y, psVector *mag, psVector *e0obs, psVector *e1obs, psVector *e2obs, psVector *dz) {
-
-    int Nx, Ny;
-
-    // set the map scale to match the aspect ratio : for a scale of 1, we guarantee
-    // that we have a single cell
-    if (psf->fieldNx > psf->fieldNy) {
-        Nx = scale;
-        float AR = psf->fieldNy / (float) psf->fieldNx;
-        Ny = (int) (Nx * AR + 0.5);
-        Ny = PS_MAX (1, Ny);
-    } else {
-        Ny = scale;
-        float AR = psf->fieldNx / (float) psf->fieldNy;
-        Nx = (int) (Ny * AR + 0.5);
-        Nx = PS_MAX (1, Nx);
-    }
-
-    // do we have enough sources for this fine of a grid?
-    if (x->n < 10*Nx*Ny) {
-        return false;
-    }
-
-    // XXX check this against the exising type
-    pmTrend2DMode psfTrendMode = PM_TREND_MAP;
-
-    psImageBinning *binning = psImageBinningAlloc();
-    binning->nXruff = Nx;
-    binning->nYruff = Ny;
-    binning->nXfine = psf->fieldNx;
-    binning->nYfine = psf->fieldNy;
-    psImageBinningSetScale (binning, PS_IMAGE_BINNING_CENTER);
-    psImageBinningSetSkipByOffset (binning, psf->fieldXo, psf->fieldYo);
-
-    psFree (psf->params->data[PM_PAR_E0]);
-    psFree (psf->params->data[PM_PAR_E1]);
-    psFree (psf->params->data[PM_PAR_E2]);
-
-    int nIter = psf->psfTrendStats->clipIter;
-    psf->psfTrendStats->clipIter = 1;
-    psf->params->data[PM_PAR_E0] = pmTrend2DNoImageAlloc (psfTrendMode, binning, psf->psfTrendStats);
-    psf->params->data[PM_PAR_E1] = pmTrend2DNoImageAlloc (psfTrendMode, binning, psf->psfTrendStats);
-    psf->params->data[PM_PAR_E2] = pmTrend2DNoImageAlloc (psfTrendMode, binning, psf->psfTrendStats);
-    psFree (binning);
-
-    // if the map is 1x1 (a single value), we measure the resulting ensemble scatter
-
-    // if the map is more finely sampled, divide the values into two sets: measure the fit from
-    // one set and the scatter from the other set.
-    psVector *x_fit = NULL;
-    psVector *y_fit = NULL;
-    psVector *x_tst = NULL;
-    psVector *y_tst = NULL;
-
-    psVector *e0obs_fit = NULL;
-    psVector *e1obs_fit = NULL;
-    psVector *e2obs_fit = NULL;
-    psVector *e0obs_tst = NULL;
-    psVector *e1obs_tst = NULL;
-    psVector *e2obs_tst = NULL;
-
-    if (scale == 1) {
-        x_fit  = psMemIncrRefCounter (x);
-        y_fit  = psMemIncrRefCounter (y);
-        x_tst  = psMemIncrRefCounter (x);
-        y_tst  = psMemIncrRefCounter (y);
-        e0obs_fit = psMemIncrRefCounter (e0obs);
-        e1obs_fit = psMemIncrRefCounter (e1obs);
-        e2obs_fit = psMemIncrRefCounter (e2obs);
-        e0obs_tst = psMemIncrRefCounter (e0obs);
-        e1obs_tst = psMemIncrRefCounter (e1obs);
-        e2obs_tst = psMemIncrRefCounter (e2obs);
-    } else {
-        x_fit  = psVectorAlloc (e0obs->n/2, PS_TYPE_F32);
-        y_fit  = psVectorAlloc (e0obs->n/2, PS_TYPE_F32);
-        x_tst  = psVectorAlloc (e0obs->n/2, PS_TYPE_F32);
-        y_tst  = psVectorAlloc (e0obs->n/2, PS_TYPE_F32);
-        e0obs_fit = psVectorAlloc (e0obs->n/2, PS_TYPE_F32);
-        e1obs_fit = psVectorAlloc (e0obs->n/2, PS_TYPE_F32);
-        e2obs_fit = psVectorAlloc (e0obs->n/2, PS_TYPE_F32);
-        e0obs_tst = psVectorAlloc (e0obs->n/2, PS_TYPE_F32);
-        e1obs_tst = psVectorAlloc (e0obs->n/2, PS_TYPE_F32);
-        e2obs_tst = psVectorAlloc (e0obs->n/2, PS_TYPE_F32);
-        for (int i = 0; i < e0obs_fit->n; i++) {
-            // e0obs->n ==  8 or 9:
-            // i = 0, 1, 2, 3 : 2i+0 = 0, 2, 4, 6
-            // i = 0, 1, 2, 3 : 2i+1 = 1, 3, 5, 7
-            x_fit->data.F32[i] = x->data.F32[2*i+0];
-            x_tst->data.F32[i] = x->data.F32[2*i+1];
-            y_fit->data.F32[i] = y->data.F32[2*i+0];
-            y_tst->data.F32[i] = y->data.F32[2*i+1];
-
-            e0obs_fit->data.F32[i] = e0obs->data.F32[2*i+0];
-            e0obs_tst->data.F32[i] = e0obs->data.F32[2*i+1];
-            e1obs_fit->data.F32[i] = e1obs->data.F32[2*i+0];
-            e1obs_tst->data.F32[i] = e1obs->data.F32[2*i+1];
-            e2obs_fit->data.F32[i] = e2obs->data.F32[2*i+0];
-            e2obs_tst->data.F32[i] = e2obs->data.F32[2*i+1];
-        }
-    }
-
-    // the mask marks the values not used to calculate the ApTrend
-    psVector *fitMask = psVectorAlloc (x_fit->n, PS_TYPE_VECTOR_MASK);
-    // copy mask values to fitMask as a starting point
-    for (int i = 0; i < fitMask->n; i++) {
-        fitMask->data.PS_TYPE_VECTOR_MASK_DATA[i] = mask->data.PS_TYPE_VECTOR_MASK_DATA[i];
-    }
-
-    // we run 'clipIter' cycles clipping in each of x and y, with only one iteration each.
-    // This way, the parameters masked by one of the fits will be applied to the others
-    for (int i = 0; i < nIter; i++) {
-        // XXX we are using the same stats structure on each pass: do we need to re-init it?
-        psStatsOptions meanOption = psStatsMeanOption(psf->psfTrendStats->options);
-        psStatsOptions stdevOption = psStatsStdevOption(psf->psfTrendStats->options);
-
-        pmTrend2D *trend = NULL;
-        float mean, stdev;
-
-        // XXX we are using the same stats structure on each pass: do we need to re-init it?
-        bool status = true;
-
-        trend = psf->params->data[PM_PAR_E0];
-        status &= pmTrend2DFit (trend, fitMask, 0xff, x_fit, y_fit, e0obs_fit, NULL);
-        mean = psStatsGetValue (trend->stats, meanOption);
-        stdev = psStatsGetValue (trend->stats, stdevOption);
-        psTrace ("psModules.objects", 4, "clipped E0 : %f +/- %f keeping %ld of %ld\n", mean, stdev, psf->psfTrendStats->clippedNvalues, e0obs_fit->n);
-        // printTrendMap (trend);
-        psImageMapCleanup (trend->map);
-        // printTrendMap (trend);
-        pmSourceVisualPSFModelResid (trend, x, y, e0obs, mask);
-
-        trend = psf->params->data[PM_PAR_E1];
-        status &= pmTrend2DFit (trend, fitMask, 0xff, x_fit, y_fit, e1obs_fit, NULL);
-        mean = psStatsGetValue (trend->stats, meanOption);
-        stdev = psStatsGetValue (trend->stats, stdevOption);
-        psTrace ("psModules.objects", 4, "clipped E1 : %f +/- %f keeping %ld of %ld\n", mean, stdev, psf->psfTrendStats->clippedNvalues, e1obs_fit->n);
-        // printTrendMap (trend);
-        psImageMapCleanup (trend->map);
-        // printTrendMap (trend);
-        pmSourceVisualPSFModelResid (trend, x, y, e1obs, mask);
-
-        trend = psf->params->data[PM_PAR_E2];
-        status &= pmTrend2DFit (trend, fitMask, 0xff, x_fit, y_fit, e2obs_fit, NULL);
-        mean = psStatsGetValue (trend->stats, meanOption);
-        stdev = psStatsGetValue (trend->stats, stdevOption);
-        psTrace ("psModules.objects", 4, "clipped E2 : %f +/- %f keeping %ld of %ld\n", mean, stdev, psf->psfTrendStats->clippedNvalues, e2obs->n);
-        // printTrendMap (trend);
-        psImageMapCleanup (trend->map);
-        // printTrendMap (trend);
-        pmSourceVisualPSFModelResid (trend, x, y, e2obs, mask);
-    }
-    psf->psfTrendStats->clipIter = nIter; // restore default setting
-
-    // construct the fitted values and the residuals
-    psVector *e0fit = pmTrend2DEvalVector (psf->params->data[PM_PAR_E0], fitMask, 0xff, x_tst, y_tst);
-    psVector *e1fit = pmTrend2DEvalVector (psf->params->data[PM_PAR_E1], fitMask, 0xff, x_tst, y_tst);
-    psVector *e2fit = pmTrend2DEvalVector (psf->params->data[PM_PAR_E2], fitMask, 0xff, x_tst, y_tst);
-
-    psVector *e0res = (psVector *) psBinaryOp (NULL, (void *) e0obs_tst, "-", (void *) e0fit);
-    psVector *e1res = (psVector *) psBinaryOp (NULL, (void *) e1obs_tst, "-", (void *) e1fit);
-    psVector *e2res = (psVector *) psBinaryOp (NULL, (void *) e2obs_tst, "-", (void *) e2fit);
-
-    // measure scatter for the unfitted points
-    // psTraceSetLevel ("psLib.math.vectorSampleStdev", 10);
-    // psTraceSetLevel ("psLib.math.vectorClippedStats", 10);
-    pmPSFShapeParamsScatter (scatterTotal, e0res, e1res, e2res, fitMask, 0xff, psStatsStdevOption(psf->psfTrendStats->options));
-    // psTraceSetLevel ("psLib.math.vectorSampleStdev", 0);
-    // psTraceSetLevel ("psLib.math.vectorClippedStats", 0);
-
-    psLogMsg ("psphot.psftry", PS_LOG_INFO, "result of %d x %d grid\n", Nx, Ny);
-    psLogMsg ("psphot.psftry", PS_LOG_INFO, "systematic scatter: %f\n", *scatterTotal);
-
-    psFree (x_fit);
-    psFree (y_fit);
-    psFree (x_tst);
-    psFree (y_tst);
-
-    psFree (e0obs_fit);
-    psFree (e1obs_fit);
-    psFree (e2obs_fit);
-    psFree (e0obs_tst);
-    psFree (e1obs_tst);
-    psFree (e2obs_tst);
-
-    psFree (e0fit);
-    psFree (e1fit);
-    psFree (e2fit);
-
-    psFree (e0res);
-    psFree (e1res);
-    psFree (e2res);
-
-    // XXX copy fitMask values back to mask
-    for (int i = 0; i < fitMask->n; i++) {
-        mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = fitMask->data.PS_TYPE_VECTOR_MASK_DATA[i];
-    }
-    psFree (fitMask);
-
-    return true;
-}
-
-// calculate the scatter of the parameters
-bool pmPSFShapeParamsScatter(float *scatterTotal, psVector *e0res, psVector *e1res, psVector *e2res, psVector *mask, psVectorMaskType maskValue, psStatsOptions stdevOpt)
-{
-
-    // psStats *stats = psStatsAlloc(stdevOpt);
-    psStats *stats = psStatsAlloc(PS_STAT_CLIPPED_STDEV);
-
-    // calculate the root-mean-square of E0, E1, E2
-    float dEsquare = 0.0;
-    psStatsInit (stats);
-    if (!psVectorStats (stats, e0res, NULL, mask, maskValue)) {
-        psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
-        return false;
-    }
-    dEsquare += PS_SQR(psStatsGetValue(stats, stdevOpt));
-
-    psStatsInit (stats);
-    if (!psVectorStats (stats, e1res, NULL, mask, maskValue)) {
-        psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
-        return false;
-    }
-    dEsquare += PS_SQR(psStatsGetValue(stats, stdevOpt));
-
-    psStatsInit (stats);
-    if (!psVectorStats (stats, e2res, NULL, mask, maskValue)) {
-        psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
-        return false;
-    }
-    dEsquare += PS_SQR(psStatsGetValue(stats, stdevOpt));
-
-    *scatterTotal = sqrtf(dEsquare);
-
-    psFree(stats);
-    return true;
-}
-
-// calculate the minimum scatter of the parameters
-bool pmPSFShapeParamsErrors(float *errorFloor, psVector *mag, psVector *e0res, psVector *e1res,
-                            psVector *e2res, psVector *mask, int nGroup, psStatsOptions stdevOpt)
-{
-
-    psStats *statsS = psStatsAlloc(stdevOpt);
-
-    // measure the trend in bins with 10 values each; if < 10 total, use them all
-    int nBin = PS_MAX (mag->n / nGroup, 1);
-
-    // use mag to group parameters in sequence
-    psVector *index = psVectorSortIndex (NULL, mag);
-
-    // subset vectors for mag and dap values within the given range
-    psVector *dE0subset = psVectorAllocEmpty (nGroup, PS_TYPE_F32);
-    psVector *dE1subset = psVectorAllocEmpty (nGroup, PS_TYPE_F32);
-    psVector *dE2subset = psVectorAllocEmpty (nGroup, PS_TYPE_F32);
-    psVector *mkSubset  = psVectorAllocEmpty (nGroup, PS_TYPE_VECTOR_MASK);
-
-    int n = 0;
-    float min = INFINITY;               // Minimum error
-    for (int i = 0; i < nBin; i++) {
-        int j;
-        int nValid = 0;
-        for (j = 0; (j < nGroup) && (n < mag->n); j++, n++) {
-            int N = index->data.U32[n];
-            dE0subset->data.F32[j] = e0res->data.F32[N];
-            dE1subset->data.F32[j] = e1res->data.F32[N];
-            dE2subset->data.F32[j] = e2res->data.F32[N];
-
-            mkSubset->data.PS_TYPE_VECTOR_MASK_DATA[j]   = mask->data.PS_TYPE_VECTOR_MASK_DATA[N];
-            if (!mask->data.PS_TYPE_VECTOR_MASK_DATA[N]) nValid ++;
-        }
-        if (nValid < 3) continue;
-
-        dE0subset->n = j;
-        dE1subset->n = j;
-        dE2subset->n = j;
-        mkSubset->n = j;
-
-        // calculate the root-mean-square of E0, E1, E2
-        float dEsquare = 0.0;
-        psStatsInit (statsS);
-        if (!psVectorStats (statsS, dE0subset, NULL, mkSubset, 0xff)) {
-        }
-        dEsquare += PS_SQR(psStatsGetValue(statsS, stdevOpt));
-
-        psStatsInit (statsS);
-        if (!psVectorStats (statsS, dE1subset, NULL, mkSubset, 0xff)) {
-            psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
-            return false;
-        }
-        dEsquare += PS_SQR(psStatsGetValue(statsS, stdevOpt));
-
-        psStatsInit (statsS);
-        if (!psVectorStats (statsS, dE2subset, NULL, mkSubset, 0xff)) {
-            psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
-            return false;
-        }
-        dEsquare += PS_SQR(psStatsGetValue(statsS, stdevOpt));
-
-        if (isfinite(dEsquare)) {
-            float err = sqrtf(dEsquare);
-            if (err < min) {
-                min = err;
-            }
-        }
-    }
-    *errorFloor = min;
-
-    psFree (dE0subset);
-    psFree (dE1subset);
-    psFree (dE2subset);
-    psFree (mkSubset);
-
-    psFree(index);
-
-    psFree(statsS);
-
-    return true;
-}
Index: branches/simmosaic_branches/psModules/src/objects/pmPSFtry.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPSFtry.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmPSFtry.h	(revision 27839)
@@ -89,5 +89,18 @@
  *
  */
-pmPSFtry *pmPSFtryModel (const psArray *sources, const char *modelName, pmPSFOptions *options, psImageMaskType maskVal, psImageMaskType mark);
+pmPSFtry *pmPSFtryModel (
+    const psArray *sources,		///< PSF sources to use in the pmPSF model analysis
+    const char *modelName,  		///< human-readable name of desired model
+    pmPSFOptions *options, 
+    psImageMaskType maskVal, 
+    psImageMaskType mark
+    );
+
+/** fit EXT models to all possible psf sources */
+bool pmPSFtryFitEXT (pmPSFtry *psfTry, pmPSFOptions *options, psImageMaskType maskVal, psImageMaskType markVal);
+
+bool pmPSFtryMakePSF (pmPSFtry *psfTry);
+
+bool pmPSFtryFitPSF (pmPSFtry *psfTry, pmPSFOptions *options, psImageMaskType maskVal, psImageMaskType markVal);
 
 /** pmPSFtryMetric()
@@ -97,8 +110,5 @@
  *
  */
-bool pmPSFtryMetric(
-    pmPSFtry *psfTry,                  ///< Add comment.
-    pmPSFOptions *options              ///< PSF fitting options
-);
+bool pmPSFtryMetric(pmPSFtry *psfTry);
 
 /** pmPSFtryMetric_Alt()
@@ -112,4 +122,11 @@
     float RADIUS                        ///< Add comment.
 );
+
+bool pmPSFFitShapeParams (pmPSF *psf, psArray *sources, psVector *x, psVector *y, psVector *srcMask);
+
+float psVectorSystematicError (psVector *residuals, psVector *errors, float clipFraction);
+
+/// @}
+# endif
 
 /**
@@ -125,11 +142,3 @@
  *
  */
-bool pmPSFFromPSFtry (pmPSFtry *psfTry);
 
-bool pmPSFFitShapeParams (pmPSF *psf, psArray *sources, psVector *x, psVector *y, psVector *srcMask);
-bool pmPSFFitShapeParamsMap (pmPSF *psf, int scale, float *scatterTotal, psVector *mask, psVector *x, psVector *y, psVector *mag, psVector *e0obs, psVector *e1obs, psVector *e2obs, psVector *dz);
-bool pmPSFShapeParamsScatter(float *scatterTotal, psVector *e0res, psVector *e1res, psVector *e2res, psVector *mask, psVectorMaskType maskValue, psStatsOptions stdevOpt);
-bool pmPSFShapeParamsErrors (float *errorFloor, psVector *mag, psVector *e0res, psVector *e1res, psVector *e2res, psVector *mask, int nGroup, psStatsOptions stdevOpt);
-
-/// @}
-# endif
Index: branches/simmosaic_branches/psModules/src/objects/pmPSFtryFitEXT.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPSFtryFitEXT.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmPSFtryFitEXT.c	(revision 27839)
@@ -0,0 +1,97 @@
+/** @file  pmPSFtry.c
+ *
+ *  XXX: need description of file purpose
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.69 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-01-27 06:39:38 $
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmSpan.h"
+#include "pmFootprint.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceUtils.h"
+#include "pmPSFtry.h"
+#include "pmModelClass.h"
+#include "pmModelUtils.h"
+#include "pmSourceFitModel.h"
+#include "pmSourcePhotometry.h"
+#include "pmSourceVisual.h"
+
+// Fit an EXT model to all candidates PSF sources.
+// Note: this is independent of the modeled 2D variations in the PSF.
+bool pmPSFtryFitEXT (pmPSFtry *psfTry, pmPSFOptions *options, psImageMaskType maskVal, psImageMaskType markVal) {
+
+    bool status;
+
+    psTimerStart ("psf.fit");
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    int Next = 0;
+    for (int i = 0; i < psfTry->sources->n; i++) {
+
+        pmSource *source = psfTry->sources->data[i];
+        if (!source->moments) {
+            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_EXT_FAIL;
+            continue;
+        }
+        if (!source->moments->nPixels) {
+            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_EXT_FAIL;
+            continue;
+        }
+
+        source->modelEXT = pmSourceModelGuess (source, options->type);
+        if (source->modelEXT == NULL) {
+            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_EXT_FAIL;
+            psTrace ("psModules.objects", 4, "masking %d (%d,%d) : failed to generate model guess\n", i, source->peak->x, source->peak->y);
+            continue;
+        }
+
+        // set object mask to define valid pixels
+	// XXX 0.5 PIX: is the circle symmetric about the peak coordinate (given 0.5,0.5 center)?
+        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->fitRadius, "OR", markVal);
+
+        // fit model as EXT, not PSF
+        status = pmSourceFitModel (source, source->modelEXT, PM_SOURCE_FIT_EXT, maskVal);
+
+        // clear object mask to define valid pixels
+	psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); // clear the circular mask
+
+        // exclude the poor fits
+        if (!status) {
+            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_EXT_FAIL;
+            psTrace ("psModules.objects", 4, "masking %d (%d,%d) : status is poor\n", i, source->peak->x, source->peak->y);
+            continue;
+        }
+        Next ++;
+    }
+    psLogMsg ("psphot.psftry", PS_LOG_MINUTIA, "fit ext:   %f sec for %d of %ld sources\n", psTimerMark ("psf.fit"), Next, psfTry->sources->n);
+    psTrace ("psModules.object", 3, "keeping %d of %ld PSF candidates (EXT)\n", Next, psfTry->sources->n);
+
+    if (Next == 0) {
+        psError(PS_ERR_UNKNOWN, false, "No sources with good extended fits from which to determine PSF.");
+        return false;
+    }
+
+    return true;
+}
Index: branches/simmosaic_branches/psModules/src/objects/pmPSFtryFitPSF.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPSFtryFitPSF.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmPSFtryFitPSF.c	(revision 27839)
@@ -0,0 +1,148 @@
+/** @file  pmPSFtryFitPSF.c
+ *  @brief Fit the PSF model to the candidate PSF stars (part of PSF model generation)
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.69 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-01-27 06:39:38 $
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmSpan.h"
+#include "pmFootprint.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceUtils.h"
+#include "pmPSFtry.h"
+#include "pmModelClass.h"
+#include "pmModelUtils.h"
+#include "pmSourceFitModel.h"
+#include "pmSourcePhotometry.h"
+#include "pmSourceVisual.h"
+
+// stage 3: Refit with fixed shape parameters.  This function uses the LMM fitting, but could
+// be re-written to use the simultaneous linear fitting (see psphotFitSourcesLinear.c)
+bool pmPSFtryFitPSF (pmPSFtry *psfTry, pmPSFOptions *options, psImageMaskType maskVal, psImageMaskType markVal) {
+
+    bool status;
+
+    psTimerStart ("psf.fit");
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // DEBUG code: save the PSF model fit data in detail
+# ifdef DEBUG
+    char filename[64];
+    snprintf (filename, 64, "psffit.%dx%d.dat", psfTry->psf->trendNx, psfTry->psf->trendNy);
+    FILE *f = fopen (filename, "w");
+    psAssert (f, "failed open");
+# endif
+
+    int Npsf = 0;
+    for (int i = 0; i < psfTry->sources->n; i++) {
+
+        pmSource *source = psfTry->sources->data[i];
+	psAssert (source->moments, "how can a psf source not have moments?");
+
+        // masked for: bad model fit, outlier in parameters
+        if (psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PSFTRY_MASK_ALL) {
+            psTrace ("psModules.objects", 4, "dropping %d (%d,%d) : source is masked\n", i, source->peak->x, source->peak->y);
+            continue;
+        }
+
+        // set shape for this model based on PSF
+	psFree (source->modelPSF);
+        source->modelPSF = pmModelFromPSF (source->modelEXT, psfTry->psf);
+        if (source->modelPSF == NULL) {
+            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_BAD_MODEL;
+            continue;
+        }
+	// PSF fit and aperture mags use different radii
+        source->modelPSF->fitRadius = options->fitRadius;
+        source->apRadius = options->apRadius;
+
+        // set object mask to define valid pixels for PSF model fit
+        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->fitRadius, "OR", markVal);
+
+        // fit the PSF model to the source
+        status = pmSourceFitModel (source, source->modelPSF, PM_SOURCE_FIT_NORM, maskVal);
+
+        // skip poor fits
+        if (!status) {
+            psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); // clear the circular mask
+            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_PSF_FAIL;
+            psTrace ("psModules.objects", 4, "dropping %d (%d,%d) : failed PSF fit\n", i, source->peak->x, source->peak->y);
+            continue;
+        }
+
+        // set object mask to define valid pixels for APERTURE magnitude
+	if (options->fitRadius != options->apRadius) {
+            psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); // clear the circular mask
+	    psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->apRadius, "OR", markVal);
+	}
+
+	// This function calculates the psf and aperture magnitudes
+        status = pmSourceMagnitudes (source, psfTry->psf, PM_SOURCE_PHOT_INTERP, maskVal); // raw PSF mag, AP mag
+        if (!status || isnan(source->apMag)) {
+            psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); // clear the circular mask
+            psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = PSFTRY_MASK_BAD_PHOT;
+            psTrace ("psModules.objects", 4, "dropping %d (%d,%d) : poor photometry\n", i, source->peak->x, source->peak->y);
+            continue;
+        }
+
+        // clear object mask to define valid pixels
+	psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); // clear the circular mask
+
+        psfTry->fitMag->data.F32[i] = source->psfMag;
+        psfTry->metric->data.F32[i] = source->apMag - source->psfMag;
+        psfTry->metricErr->data.F32[i] = source->errMag;
+
+	// XXX this did not work: modifies shape of psf too much
+        // psfTry->metric->data.F32[i] = -2.5*log10(source->moments->Sum) - source->psfMag;
+
+# ifdef DEBUG
+	fprintf (f, "%6.1f %6.1f : %6.1f %6.1f : %8.3f %8.3f %8.3f : %f : %f %f %f : %f\n",
+		 source->peak->xf, source->peak->yf, 
+		 source->modelPSF->params->data.F32[PM_PAR_XPOS], source->modelPSF->params->data.F32[PM_PAR_YPOS], 
+		 source->psfMag, source->apMag, source->errMag,
+		 source->modelPSF->params->data.F32[PM_PAR_I0], 
+		 source->modelPSF->params->data.F32[PM_PAR_SXX], source->modelPSF->params->data.F32[PM_PAR_SXY], 
+		 source->modelPSF->params->data.F32[PM_PAR_SYY], source->modelPSF->params->data.F32[PM_PAR_7]);
+# endif
+
+        psTrace ("psModules.object", 6, "keeping source %d (%d) of %ld\n", i, Npsf, psfTry->sources->n);
+        Npsf ++;
+    }
+    psfTry->psf->nPSFstars = Npsf;
+
+# ifdef DEBUG
+    fclose (f);
+# endif
+
+    pmSourceVisualShowModelFits (psfTry->psf, psfTry->sources, maskVal);
+
+    psLogMsg ("psphot.psftry", PS_LOG_MINUTIA, "fit psf:   %f sec for %d of %ld sources\n", psTimerMark ("psf.fit"), Npsf, psfTry->sources->n);
+    psTrace ("psModules.object", 3, "keeping %d of %ld PSF candidates (PSF)\n", Npsf, psfTry->sources->n);
+
+    if (Npsf == 0) {
+        psError(PS_ERR_UNKNOWN, false, "No sources with good PSF fits after model is built.");
+        return false;
+    }
+
+    return true;
+}
Index: branches/simmosaic_branches/psModules/src/objects/pmPSFtryMakePSF.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPSFtryMakePSF.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmPSFtryMakePSF.c	(revision 27839)
@@ -0,0 +1,267 @@
+/** @file  pmPSFtry.c
+ *  @brief generate a pmPSF from a collection of EXT measurments of likely PSF stars.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.69 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-01-27 06:39:38 $
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmSpan.h"
+#include "pmFootprint.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceUtils.h"
+#include "pmPSFtry.h"
+#include "pmModelClass.h"
+#include "pmModelUtils.h"
+#include "pmSourceFitModel.h"
+#include "pmSourcePhotometry.h"
+#include "pmSourceVisual.h"
+
+/*****************************************************************************
+pmPSFFromPSFtry (psfTry): build a PSF model from a collection of source->modelEXT entries
+using the specified order in X,Y.  The PSF ignores the first 4 (independent) model
+parameters and constructs a polynomial fit to the remaining as a function of image
+coordinate.  Input: psfTry with fitted source->modelEXT collection, pre-allocated psf
+Note: some of the array entries may be NULL (failed fits); ignore them.
+ *****************************************************************************/
+bool pmPSFtryMakePSF (pmPSFtry *psfTry)
+{
+    PS_ASSERT_PTR_NON_NULL(psfTry, false);
+    PS_ASSERT_PTR_NON_NULL(psfTry->sources, false);
+
+    pmPSF *psf = psfTry->psf;
+    psVector *srcMask = psfTry->mask;
+
+    // construct the fit vectors from the collection of objects
+    psVector *x  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
+    psVector *y  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
+
+    // construct the x,y terms
+    for (int i = 0; i < psfTry->sources->n; i++) {
+        if (srcMask->data.PS_TYPE_VECTOR_MASK_DATA[i]) continue;
+
+        pmSource *source = psfTry->sources->data[i];
+        assert (source->modelEXT); // all unmasked sources should have modelEXT
+
+        x->data.F32[i] = source->modelEXT->params->data.F32[PM_PAR_XPOS];
+        y->data.F32[i] = source->modelEXT->params->data.F32[PM_PAR_YPOS];
+    }
+
+    // fit the shape parameters (SXX, SYY, SXY) as a function of position
+    if (!pmPSFFitShapeParams (psf, psfTry->sources, x, y, srcMask)) {
+        psFree(x);
+        psFree(y);
+        return false;
+    }
+
+    // vector to store the other parameter values
+    psVector *z  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
+
+    // skip the unfitted parameters (X, Y, Io, Sky) and the shape parameters (SXX, SYY, SXY);
+    // fit the remaining parameters.
+    for (int i = 0; i < psf->params->n; i++) {
+        switch (i) {
+          case PM_PAR_SKY:
+          case PM_PAR_I0:
+          case PM_PAR_XPOS:
+          case PM_PAR_YPOS:
+          case PM_PAR_SXX:
+          case PM_PAR_SYY:
+          case PM_PAR_SXY:
+            continue;
+          default:
+            break;
+        }
+
+        // select the per-object fitted data for this parameter
+        for (int j = 0; j < psfTry->sources->n; j++) {
+	    // skip any masked sources (failed to fit one of the model steps or get a magnitude)
+	    if (srcMask->data.PS_TYPE_VECTOR_MASK_DATA[j]) continue;
+
+            pmSource *source = psfTry->sources->data[j];
+	    assert (source->modelEXT); // all unmasked sources should have modelEXT
+
+            z->data.F32[j] = source->modelEXT->params->data.F32[i];
+        }
+
+	pmTrend2D *trend = psf->params->data[i];
+
+        // fit the collection of measured parameters to the PSF 2D model
+        // the mask is carried from previous steps and updated with this operation
+        // the weight is either the flux error or NULL, depending on 'psf->poissonErrorParams'
+        if (!pmTrend2DFit (trend, srcMask, 0xff, x, y, z, NULL)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to build psf model for parameter %d", i);
+            psFree(x);
+            psFree(y);
+            psFree(z);
+            return false;
+        }
+	if (trend->mode == PM_TREND_MAP) {
+	    // p_psImagePrint (2, trend->map->map, "param N Before"); // XXX TEST:
+	    psImageMapRepair (trend->map->map);
+	    // p_psImagePrint (2, trend->map->map, "param N After"); // XXX TEST:
+	}
+    }
+
+    // test dump of star parameters vs position (compare with fitted values)
+    if (psTraceGetLevel("psModules.objects") >= 4) {
+        FILE *f = fopen ("params.dat", "w");
+
+        for (int j = 0; j < psfTry->sources->n; j++) {
+            pmSource *source = psfTry->sources->data[j];
+            if (source == NULL) continue;
+            if (source->modelEXT == NULL) continue;
+
+            pmModel *modelPSF = pmModelFromPSF (source->modelEXT, psf);
+
+            fprintf (f, "%f %f : ", source->modelEXT->params->data.F32[PM_PAR_XPOS], source->modelEXT->params->data.F32[PM_PAR_YPOS]);
+
+            for (int i = 0; i < psf->params->n; i++) {
+                if (psf->params->data[i] == NULL) {
+		    psFree(modelPSF);
+		    continue;
+		}
+                fprintf (f, "%f %f : ", source->modelEXT->params->data.F32[i], modelPSF->params->data.F32[i]);
+            }
+            fprintf (f, "%f %d\n", source->modelEXT->chisq, source->modelEXT->nIter);
+
+            psFree(modelPSF);
+        }
+        fclose (f);
+    }
+
+    psFree (x);
+    psFree (y);
+    psFree (z);
+    return true;
+}
+
+// fit the shape parameters using the supplied order (pmPSF->trendNx,trendNy)
+bool pmPSFFitShapeParams (pmPSF *psf, psArray *sources, psVector *x, psVector *y, psVector *srcMask) {
+
+    // we are doing a robust fit.  after each pass, we drop points which are more deviant than
+    // three sigma.  the source mask (srcMask) is updated for each pass.  
+
+    // The shape parameters (SXX, SXY, SYY) are strongly coupled.  We have to handle them very
+    // carefully.  First, we convert them to the Ellipse Polarization terms (E0, E1, E2) for
+    // each source and fit this set of parameters.  These values are less tightly coupled, but
+    // are still inter-related.  The fitted values do a good job of constraining the major axis
+    // and the position angle, but the minor axis is weakly measured.  When we apply the PSF
+    // model to construct a source model, we convert the fitted values of E0,E1,E2 to the shape
+    // parameters, with the constraint that the minor axis must be greater than a minimum
+    // threshold.
+
+    // XXX re-read the sextractor manual on handling 'infinitely thin' sources...
+
+    // storage vectors for the polarization terms & mags
+    psVector *e0   = psVectorAlloc (sources->n, PS_TYPE_F32);
+    psVector *e1   = psVectorAlloc (sources->n, PS_TYPE_F32);
+    psVector *e2   = psVectorAlloc (sources->n, PS_TYPE_F32);
+
+    // convert the measured source shape paramters to polarization terms
+    for (int i = 0; i < sources->n; i++) {
+        // skip any masked sources (failed to fit one of the model steps or get a magnitude)
+        if (srcMask->data.PS_TYPE_VECTOR_MASK_DATA[i]) continue;
+
+        pmSource *source = sources->data[i];
+        assert (source->modelEXT); // all unmasked sources should have modelEXT
+
+        psEllipsePol pol = pmPSF_ModelToFit (source->modelEXT->params->data.F32);
+
+        e0->data.F32[i] = pol.e0;
+        e1->data.F32[i] = pol.e1;
+        e2->data.F32[i] = pol.e2;
+    }
+
+    // we run 'clipIter' cycles clipping in each of x and y, with only one iteration each.
+    // This way, the parameters masked by one of the fits will be applied to the others
+    // NOTE : trend->stats (below) points to the same data as psfTrendStats; set the value to 1
+    // to limit the iteration within the loop
+    int nIter = psf->psfTrendStats->clipIter;
+    psf->psfTrendStats->clipIter = 1;
+    for (int i = 0; i < nIter; i++) {
+	// XXX we are using the same stats structure on each pass: do we need to re-init it?
+	// XXX we hardwire this to SAMPLE stats above (psphotChoosePSF.c), hardwire here instead?
+	psStatsOptions meanOption = psStatsMeanOption(psf->psfTrendStats->options);
+	psStatsOptions stdevOption = psStatsStdevOption(psf->psfTrendStats->options);
+
+	pmTrend2D *trend = NULL;
+	float mean, stdev;
+
+	// XXX we are using the same stats structure on each pass: do we need to re-init it?
+	bool status = true;
+
+	trend = psf->params->data[PM_PAR_E0];
+	trend->stats->clipIter = 1; // in allocation, this value is set to the value of nIter, but we should use 1 here
+	status &= pmTrend2DFit (trend, srcMask, 0xff, x, y, e0, NULL);
+	mean = psStatsGetValue (trend->stats, meanOption);
+	stdev = psStatsGetValue (trend->stats, stdevOption);
+	psTrace ("psModules.objects", 4, "clipped E0 : %f +/- %f keeping %ld of %ld\n", mean, stdev, psf->psfTrendStats->clippedNvalues, e0->n);
+        if (psf->psfTrendMode == PM_TREND_MAP) psImageMapCleanup (trend->map);
+	pmSourceVisualPSFModelResid (trend, x, y, e0, srcMask);
+
+	trend = psf->params->data[PM_PAR_E1];
+	trend->stats->clipIter = 1; // in allocation, this value is set to the value of nIter, but we should use 1 here
+	status &= pmTrend2DFit (trend, srcMask, 0xff, x, y, e1, NULL);
+	mean = psStatsGetValue (trend->stats, meanOption);
+	stdev = psStatsGetValue (trend->stats, stdevOption);
+	psTrace ("psModules.objects", 4, "clipped E1 : %f +/- %f keeping %ld of %ld\n", mean, stdev, psf->psfTrendStats->clippedNvalues, e1->n);
+        if (psf->psfTrendMode == PM_TREND_MAP) psImageMapCleanup (trend->map);
+	pmSourceVisualPSFModelResid (trend, x, y, e1, srcMask);
+
+	trend = psf->params->data[PM_PAR_E2];
+	trend->stats->clipIter = 1; // in allocation, this value is set to the value of nIter, but we should use 1 here
+	status &= pmTrend2DFit (trend, srcMask, 0xff, x, y, e2, NULL);
+	mean = psStatsGetValue (trend->stats, meanOption);
+	stdev = psStatsGetValue (trend->stats, stdevOption);
+	psTrace ("psModules.objects", 4, "clipped E2 : %f +/- %f keeping %ld of %ld\n", mean, stdev, psf->psfTrendStats->clippedNvalues, e2->n);
+        if (psf->psfTrendMode == PM_TREND_MAP) psImageMapCleanup (trend->map);
+	pmSourceVisualPSFModelResid (trend, x, y, e2, srcMask);
+
+	if (!status) {
+	    psError (PS_ERR_UNKNOWN, true, "failed to fit PSF shape params");
+	    return false;
+	}
+    }
+    psf->psfTrendStats->clipIter = nIter;
+
+    // test dump of the psf parameters
+    if (psTraceGetLevel("psModules.objects") >= 4) {
+        FILE *f = fopen ("pol.dat", "w");
+        fprintf (f, "# x y  :  e0obs e1obs e2obs  : e0fit e1fit e2fit : mask\n");
+        for (int i = 0; i < e0->n; i++) {
+            fprintf (f, "%f %f  :  %f %f %f  : %f %f %f  : %d\n",
+                     x->data.F32[i], y->data.F32[i],
+                     e0->data.F32[i], e1->data.F32[i], e2->data.F32[i],
+                     pmTrend2DEval (psf->params->data[PM_PAR_E0], x->data.F32[i], y->data.F32[i]),
+                     pmTrend2DEval (psf->params->data[PM_PAR_E1], x->data.F32[i], y->data.F32[i]),
+                     pmTrend2DEval (psf->params->data[PM_PAR_E2], x->data.F32[i], y->data.F32[i]),
+                     srcMask->data.PS_TYPE_VECTOR_MASK_DATA[i]);
+        }
+        fclose (f);
+    }
+
+    psFree (e0);
+    psFree (e1);
+    psFree (e2);
+    return true;
+}
+
Index: branches/simmosaic_branches/psModules/src/objects/pmPSFtryMetric.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPSFtryMetric.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmPSFtryMetric.c	(revision 27839)
@@ -0,0 +1,85 @@
+/** @file  pmPSFtry.c
+ *  @brief: measure the systematic error in the aperture-psf magnitude
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.69 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-01-27 06:39:38 $
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmSpan.h"
+#include "pmFootprint.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceUtils.h"
+#include "pmPSFtry.h"
+#include "pmModelClass.h"
+#include "pmModelUtils.h"
+#include "pmSourceFitModel.h"
+#include "pmSourcePhotometry.h"
+#include "pmSourceVisual.h"
+
+// The quality of the PSF model is determined by the systematic scatter in the fit - aperture
+// magnitude.  We use the moments->Sum value, calculated with a Gaussian window, as a proxy for
+// the aperture magnitude.  We measure the systmatic scatter with the function
+// psVectorSystematicError which solves for the value of SysErr that needs to be added in
+// quadrature to the errors of a set of residual measurements in order to yield a ChiSq of 1.0.
+bool pmPSFtryMetric (pmPSFtry *psfTry)
+{
+    PS_ASSERT_PTR_NON_NULL(psfTry, false);
+    PS_ASSERT_PTR_NON_NULL(psfTry->sources, false);
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+
+    if (!psVectorStats (stats, psfTry->metric, NULL, psfTry->mask, PSFTRY_MASK_ALL)) {
+        psError(PS_ERR_UNKNOWN, false, "Failed to measured clipped mean");
+	psFree (stats);
+        return false;
+    }
+
+    // generate a residual vector
+    psVector *resid = psVectorAllocEmpty (psfTry->metric->n, PS_TYPE_F32);
+    psVector *error = psVectorAllocEmpty (psfTry->metric->n, PS_TYPE_F32);
+    int n = 0;
+    for (int i = 0; i < psfTry->metric->n; i++) {
+	if (psfTry->mask && (psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] && PSFTRY_MASK_ALL)) continue;
+	// if (psfTry->metricErr->data.F32[i] > 0.005) continue;
+	resid->data.F32[n] = psfTry->metric->data.F32[i] - stats->sampleMedian;
+	error->data.F32[n] = psfTry->metricErr->data.F32[i];
+	n++;
+    }
+    resid->n = error->n = n;
+
+    float psfSysErr = psVectorSystematicError (resid, error, 0.05);
+
+    psLogMsg ("pmPSFtryMetric", 4, "apresid: %f +/- %f (systematic error) from statistics of %ld psf stars (%d used)\n", 
+	      stats->sampleMedian, psfSysErr, psfTry->sources->n, n);
+
+    psfTry->psf->ApResid  = stats->sampleMedian;
+    psfTry->psf->dApResid = psfSysErr;
+
+    pmSourceVisualPlotPSFMetric (psfTry);
+    pmSourceVisualPlotPSFMetricSubpix (psfTry);
+
+    psFree (stats);
+    psFree (resid);
+    psFree (error);
+
+    return true;
+}
Index: branches/simmosaic_branches/psModules/src/objects/pmPSFtryModel.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPSFtryModel.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmPSFtryModel.c	(revision 27839)
@@ -0,0 +1,231 @@
+/** @file  pmPSFtry.c
+ *
+ *  XXX: need description of file purpose
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.69 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-01-27 06:39:38 $
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmSpan.h"
+#include "pmFootprint.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceUtils.h"
+#include "pmPSFtry.h"
+#include "pmModelClass.h"
+#include "pmModelUtils.h"
+#include "pmSourceFitModel.h"
+#include "pmSourcePhotometry.h"
+#include "pmSourceVisual.h"
+
+// build a pmPSFtry for the given model:
+// - fit each source with the free-floating model
+// - construct the pmPSF from the collection of models
+// - fit each source with the PSF-parameter models
+// - measure the pmPSF quality metric (dApResid)
+
+// sources used in for pmPSFtry may be masked by the analysis
+// mask values indicate the reason the source was rejected:
+
+// generate a pmPSFtry with a copy of the test PSF sources
+pmPSFtry *pmPSFtryModel (const psArray *sources, const char *modelName, pmPSFOptions *options, psImageMaskType maskVal, psImageMaskType markVal)
+{
+    // validate the requested model name
+    options->type = pmModelClassGetType (modelName);
+    if (options->type == -1) {
+        psError (PS_ERR_UNKNOWN, true, "invalid model name %s", modelName);
+        return NULL;
+    }
+
+    pmPSFtry *psfTry = pmPSFtryAlloc (sources, options);
+    if (psfTry == NULL) {
+        psError (PS_ERR_UNKNOWN, false, "failed to allocate psf model");
+        return NULL;
+    }
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // stage 1:  fit an EXT model to all candidates PSF sources -- this is independent of the modeled 2D variations in the PSF
+    if (!pmPSFtryFitEXT(psfTry, options, maskVal, markVal)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to fit EXT models to sources for psf model");
+        psFree(psfTry);
+        return NULL;
+    }
+
+    // hard limit on minimum number of stars
+    if ((sources->n <  3)) {
+        psError (PS_ERR_UNKNOWN, true, "failed to determine PSF parameters");
+        return NULL;
+    }
+
+    // this is a bit tricky, because we have two cases (MAP vs POLY), and they have a different
+    // definition for 'order' (order_MAP = order_POLY + 1).  in addition, we have a
+    // user-specified MAX order, which we should respect, regardless of the mode
+
+    // set the max order (0 = constant) which the number of psf stars can support:
+    // rule of thumb: require 3 stars per 'cell' (order+1)^2
+    int MaxOrderForStars = 0;
+    if (sources->n >= 12) MaxOrderForStars = 1; // 4 cells
+    if (sources->n >= 27) MaxOrderForStars = 2; // 9 cells
+    if (sources->n >= 48) MaxOrderForStars = 3; // 16 cells
+    if (sources->n >  75) MaxOrderForStars = 4; // 25 cells
+
+    int orderMax = PS_MAX (options->psfTrendNx, options->psfTrendNy);
+    int orderMin = 0;
+    if (options->psfTrendMode == PM_TREND_MAP) {
+        MaxOrderForStars ++;
+        orderMin ++;
+    }
+    orderMax = PS_MIN (orderMax, MaxOrderForStars);
+
+    // save the raw source mask (generated by pmPSFtryFitEXT)
+    psVector *srcMask = psVectorCopy (NULL, psfTry->mask, PS_TYPE_VECTOR_MASK);
+
+    // we will save the PSF with the best fit (min systematic error)
+    pmPSF *minPSF = NULL;
+    psVector *minMask = NULL;
+
+    // as we loop over orders, we need to refer to the initial selection, but we modify the
+    // option values to match the current guess: save the max values here:
+    int Nx = (options->psfTrendMode == PM_TREND_MAP) ? options->psfTrendNx : options->psfTrendNx + 1;
+    int Ny = (options->psfTrendMode == PM_TREND_MAP) ? options->psfTrendNy : options->psfTrendNy + 1;
+    for (int i = orderMin; i <= orderMax; i++) {
+
+        if (Nx > Ny) {
+            options->psfTrendNx = i;
+            options->psfTrendNy = PS_MAX (orderMin, (int)(i * (Ny / Nx) + 0.5));
+        } else {
+            options->psfTrendNy = i;
+            options->psfTrendNx = PS_MAX (orderMin, (int)(i * (Nx / Ny) + 0.5));
+        }
+
+        // free existing data, if any
+        psFree(psfTry->psf);
+        psFree(psfTry->mask);
+
+        // allocate a mask and a psf model using the current Nx,Ny order values;
+        psfTry->psf = pmPSFAlloc (options);
+        psfTry->mask = psVectorCopy (NULL, srcMask, PS_TYPE_VECTOR_MASK);
+
+        // stage 2: construct a psf (pmPSF) from this collection of model fits, including the 2D variation
+        if (!pmPSFtryMakePSF (psfTry)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to construct a psf model from collection of sources");
+            psFree(psfTry);
+            return NULL;
+        }
+
+        // stage 3: refit with fixed shape parameters, measure pmPSFtry->metric
+        if (!pmPSFtryFitPSF (psfTry, options, maskVal, markVal)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to construct a psf model from collection of sources");
+            psFree(psfTry);
+            return NULL;
+        }
+
+        // stage 4: measure systematic error in pmPSFtry->metric
+        if (!pmPSFtryMetric (psfTry)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to measure systematic error of metric");
+            psFree(psfTry);
+            return NULL;
+        }
+
+        if (!minPSF) {
+            minPSF = psMemIncrRefCounter(psfTry->psf);
+            minMask = psMemIncrRefCounter(psfTry->mask);
+        }
+
+        if (psfTry->psf->dApResid < minPSF->dApResid) {
+            psFree (minPSF);
+            psFree (minMask);
+            minPSF = psMemIncrRefCounter(psfTry->psf);
+            minMask = psMemIncrRefCounter(psfTry->mask);
+        }
+    }
+    psFree (srcMask);
+
+    // keep the ones matching the min systematic error:
+    psFree (psfTry->psf);
+    psFree (psfTry->mask);
+    psfTry->psf = minPSF;
+    psfTry->mask = minMask;
+
+    // XXXXX this is probably not used any more.  Are the chisq of the fits so bad? can we
+    // fix them by softening the errors on the brightest pixels?
+
+    // measure the chi-square trend as a function of flux (PAR[PM_PAR_I0])
+    // this should be linear for Poisson errors and quadratic for constant sky errors
+    psVector *flux  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
+    psVector *chisq = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
+    psVector *mask  = psVectorAlloc (psfTry->sources->n, PS_TYPE_VECTOR_MASK);
+
+    // generate the x and y vectors, and mask missing models
+    for (int i = 0; i < psfTry->sources->n; i++) {
+        pmSource *source = psfTry->sources->data[i];
+        if (source->modelPSF == NULL) {
+            flux->data.F32[i] = 0.0;
+            chisq->data.F32[i] = 0.0;
+            mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = 0xff;
+        } else {
+            flux->data.F32[i] = source->modelPSF->params->data.F32[PM_PAR_I0];
+            chisq->data.F32[i] = source->modelPSF->chisq / source->modelPSF->nDOF;
+            mask->data.PS_TYPE_VECTOR_MASK_DATA[i] = 0;
+        }
+    }
+
+    // use 3hi/3lo sigma clipping on the chisq fit
+    psStats *stats = options->stats;
+
+    // linear clipped fit of chisq trend vs flux
+    if (options->chiFluxTrend) {
+        bool result = psVectorClipFitPolynomial1D(psfTry->psf->ChiTrend, options->stats,
+                                                  mask, 0xff, chisq, NULL, flux);
+        psStatsOptions meanStat = psStatsMeanOption(options->stats->options); // Statistic for mean
+        psStatsOptions stdevStat = psStatsStdevOption(options->stats->options); // Statistic for stdev
+
+        psLogMsg ("pmPSFtry", 4, "chisq vs flux fit: %f +/- %f\n",
+                  psStatsGetValue(stats, meanStat), psStatsGetValue(stats, stdevStat));
+
+        if (!result) {
+            psError(PS_ERR_UNKNOWN, false, "Failed to fit psf->ChiTrend");
+            psFree(psfTry);
+            psFree(flux);
+            psFree(mask);
+            psFree(chisq);
+            return NULL;
+        }
+    }
+
+    psFree(flux);
+    psFree(mask);
+    psFree(chisq);
+
+    for (int i = 0; i < psfTry->psf->ChiTrend->nX + 1; i++) {
+        psLogMsg ("pmPSFtry", 4, "chisq vs flux fit term %d: %f +/- %f\n", i,
+                  psfTry->psf->ChiTrend->coeff[i]*pow(10000, i),
+                  psfTry->psf->ChiTrend->coeffErr[i]*pow(10000,i));
+    }
+
+    psLogMsg ("psphot.pspsf", 3, "try model %s, ap-fit: %f +/- %f : sky bias: %f\n",
+              modelName, psfTry->psf->ApResid, psfTry->psf->dApResid, psfTry->psf->skyBias);
+
+    return (psfTry);
+}
+
Index: branches/simmosaic_branches/psModules/src/objects/pmPeaks.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPeaks.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmPeaks.c	(revision 27839)
@@ -60,7 +60,8 @@
     // if min point is too deviant, use the peak value
     // XXX need to calculate dx, dy correctly
+    // 0.5 PIX: peaks are calculated using the pixel index and converted here to pixel coords
     if ((fabs(min.x) < 1.5) && (fabs(min.y) < 1.5)) {
-        peak->xf = min.x + ix + image->col0;
-        peak->yf = min.y + iy + image->row0;
+        peak->xf = min.x + ix + image->col0 + 0.5;
+        peak->yf = min.y + iy + image->row0 + 0.5;
 
 	// These errors are fractional errors, and should be scaled by the 
@@ -73,6 +74,6 @@
 	peak->yf = PS_MAX (PS_MIN (peak->yf, image->numRows - 1), image->row0);
     } else {
-        peak->xf = ix;
-        peak->yf = iy;
+        peak->xf = ix + 0.5;
+        peak->yf = iy + 0.5; 
 	peak->dx = NAN;
 	peak->dy = NAN;
@@ -374,5 +375,5 @@
     psU32 col = 0;
     psU32 row = 0;
-    psArray *list = NULL;
+    psArray *list = psArrayAllocEmpty(100);
 
     // Find peaks in row 0 only.
@@ -415,5 +416,5 @@
 
         } else {
-            psError(PS_ERR_UNKNOWN, true, "peak specified valid column range.");
+            psLogMsg ("psModules.objects", 5, "peak specified outside valid column range.");
         }
     }
@@ -500,5 +501,5 @@
                 }
             } else {
-                psError(PS_ERR_UNKNOWN, true, "peak specified outside valid column range.");
+		psLogMsg ("psModules.objects", 5, "peak specified outside valid column range.");
             }
 
@@ -544,5 +545,5 @@
             }
         } else {
-            psError(PS_ERR_UNKNOWN, true, "peak specified outside valid column range.");
+            psLogMsg ("psModules.objects", 5, "peak specified outside valid column range.");
         }
     }
Index: branches/simmosaic_branches/psModules/src/objects/pmPeaks.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPeaks.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmPeaks.h	(revision 27839)
@@ -63,5 +63,5 @@
     bool assigned;                      ///< is peak assigned to a source?
     pmPeakType type;                    ///< Description of peak.
-    pmFootprint *footprint;     ///< reference to containing footprint
+    pmFootprint *footprint;		///< reference to containing footprint
 }
 pmPeak;
Index: branches/simmosaic_branches/psModules/src/objects/pmPetrosian.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPetrosian.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmPetrosian.c	(revision 27839)
@@ -0,0 +1,107 @@
+/* @file  pmPetrosian.c
+ * low-level petrosian functions
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: $
+ * @date $Date: $
+ * Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pmPetrosian.h"
+
+static void pmPetrosianFree(pmPetrosian *petrosian)
+{
+    if (!petrosian) {
+        return;
+    }
+    psFree(petrosian->radii);
+    psFree(petrosian->fluxes);
+    psFree(petrosian->theta);
+    psFree(petrosian->isophotalRadii);
+
+    psFree(petrosian->radiusElliptical);
+    psFree(petrosian->fluxElliptical);
+
+    psFree(petrosian->binSB);
+    psFree(petrosian->binSBstdev);
+    psFree(petrosian->radialBins);
+    psFree(petrosian->area);
+}
+
+pmPetrosian *pmPetrosianAlloc()
+{
+    pmPetrosian *petrosian = (pmPetrosian *)psAlloc(sizeof(pmPetrosian));
+    psMemSetDeallocator(petrosian, (psFreeFunc) pmPetrosianFree);
+
+    petrosian->radii = NULL;
+    petrosian->fluxes = NULL;
+    petrosian->theta = NULL;
+    petrosian->isophotalRadii = NULL;
+
+    petrosian->radiusElliptical = NULL;
+    petrosian->fluxElliptical = NULL;
+
+    petrosian->radialBins = NULL;
+    petrosian->area = NULL;
+    petrosian->binSB = NULL;
+    petrosian->binSBstdev = NULL;
+
+    petrosian->petrosianRadius = NAN;
+    petrosian->petrosianFlux = NAN;
+
+    // petrosian->axes = {0.0, 0.0, 0.0};
+
+    return petrosian;
+}
+
+bool pmPetrosianFreeVectors(pmPetrosian *petrosian) {
+
+    psFree(petrosian->radii);
+    psFree(petrosian->fluxes);
+    psFree(petrosian->theta);
+    psFree(petrosian->isophotalRadii);
+
+    psFree(petrosian->radiusElliptical);
+    psFree(petrosian->fluxElliptical);
+
+    psFree(petrosian->binSB);
+    psFree(petrosian->binSBstdev);
+    psFree(petrosian->radialBins);
+    psFree(petrosian->area);
+
+    petrosian->radii = NULL;
+    petrosian->fluxes = NULL;
+    petrosian->theta = NULL;
+    petrosian->isophotalRadii = NULL;
+
+    petrosian->radiusElliptical = NULL;
+    petrosian->fluxElliptical = NULL;
+
+    petrosian->radialBins = NULL;
+    petrosian->area = NULL;
+    petrosian->binSB = NULL;
+    petrosian->binSBstdev = NULL;
+    
+    return true;
+}
+
+# define COMPARE_INDEX(A,B) (index->data.F32[A] < index->data.F32[B])
+# define SWAP_INDEX(TYPE,A,B) { \
+  float tmp; \
+  if (A != B) { \
+    tmp = index->data.F32[A]; \
+    index->data.F32[A] = index->data.F32[B]; \
+    index->data.F32[B] = tmp; \
+    tmp = extra->data.F32[A]; \
+    extra->data.F32[A] = extra->data.F32[B]; \
+    extra->data.F32[B] = tmp; \
+  } \
+}
+
+bool pmPetrosianSortPair (psVector *index, psVector *extra) {
+
+    // sort the vector set by the radius
+    PSSORT (index->n, COMPARE_INDEX, SWAP_INDEX, NONE);
+    return true;
+}
Index: branches/simmosaic_branches/psModules/src/objects/pmPetrosian.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPetrosian.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmPetrosian.h	(revision 27839)
@@ -0,0 +1,44 @@
+/* @file  pmPetrosian.h
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: $
+ * @date $Date: $
+ * Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_PETROSIAN_H
+#define PM_PETROSIAN_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+typedef struct {
+    psArray  *radii;			// radii for raw radial profiles at evenly-spaced angles
+    psArray  *fluxes;			// fluxes measured at above radii
+    psVector *theta;			// angles corresponding to above radial profiles
+    psVector *isophotalRadii;		// isophotal radius for the above angles
+
+    psVector *radiusElliptical;		// normalized radial coordinates for all relevant pixels
+    psVector *fluxElliptical;		// flux for the above radial coordinates
+
+    psVector *binSB;			// mean surface brightness within radial bins
+    psVector *binSBstdev;		// scatter of mean surface brightness within radial bins
+    psVector *radialBins;		// radii corresponding to above binnedBlux
+    psVector *area;			// differential area of the non-overlapping radial bins
+
+    psEllipseAxes axes;			// shape of elliptical contour
+
+    float petrosianRadius;
+    float petrosianFlux;
+
+} pmPetrosian;
+
+pmPetrosian *pmPetrosianAlloc();
+
+bool pmPetrosianFreeVectors(pmPetrosian *petrosian);
+bool pmPetrosianSortPair (psVector *index, psVector *extra);
+
+/// @}
+
+# endif /* PM_PETROSIAN_H */
Index: branches/simmosaic_branches/psModules/src/objects/pmPhotObj.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPhotObj.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmPhotObj.c	(revision 27839)
@@ -0,0 +1,65 @@
+/** @file  pmPhotObj.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.13 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ * Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmPhotObj.h"
+#include "pmSource.h"
+
+static void pmPhotObjFree (pmPhotObj *tmp)
+{
+    if (!tmp) return;
+    psFree(tmp->sources);
+}
+
+
+pmPhotObj *pmPhotObjAlloc() 
+{
+    static int id = 1;
+    pmPhotObj *obj = (pmPhotObj *) psAlloc(sizeof(pmPhotObj));
+    psMemSetDeallocator(obj, (psFreeFunc) pmPhotObjFree);
+
+    *(int *)&obj->id = id; // cast away the const to set the id.
+    id++;
+
+    obj->sources = NULL;
+    return obj;
+}
+
+bool pmPhotObjAddSource(pmPhotObj *object, pmSource *source) {
+
+    psAssert (source, "programming error: NULL source");
+    if (!source->peak) {
+	psError(PS_ERR_UNKNOWN, true, "source missing peak");
+	return false; 
+    }
+    if (!finite(source->peak->xf)) {
+	psError(PS_ERR_UNKNOWN, true, "NAN peak coordinate");
+	return false; 
+    }
+    if (!finite(source->peak->yf)) {
+	psError(PS_ERR_UNKNOWN, true, "NAN peak coordinate");
+	return false; 
+    }
+
+    // XXX we should probably use the fitted position if it exists
+    if (!object->sources) {
+	object->sources = psArrayAllocEmpty(1);
+	object->x = source->peak->xf;
+	object->y = source->peak->yf;
+    }
+    psArrayAdd (object->sources, 1, source);
+    return true;
+}
Index: branches/simmosaic_branches/psModules/src/objects/pmPhotObj.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmPhotObj.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmPhotObj.h	(revision 27839)
@@ -0,0 +1,48 @@
+/* @file  pmPhotObj.h
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: $
+ * @date $Date: 2009-02-16 22:30:50 $
+ * Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# ifndef PM_PHOT_OBJ_H
+# define PM_PHOT_OBJ_H
+
+#include <pslib.h>
+#include "pmPeaks.h"
+#include "pmModel.h"
+#include "pmMoments.h"
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+
+/** pmPhotObj data structure
+ *
+ *  A Photometry Object is a connected set of source measurements with a common
+ *  connection.  Each source that comprises the object represents the detection of some
+ *  astronomical object on a single image.  The object represents the related collection
+ *  of measurements.  The fits are coupled in some way.  For example, they may all have
+ *  the same position, but independent fluxes.  Or, they may have a common set of
+ *  positions and shape parameters.  Or the position in each image may be related by a
+ *  function.
+ *
+ *  XXX is thre any info that is neaded for each object beyond that carried by the sources
+ *  (besides ID)?
+ */
+typedef struct {
+    int id;                            ///< ID for output (generated on write OR set on read)
+    psArray *sources;
+    int flags;
+    float x;
+    float y;
+} pmPhotObj;
+
+bool pmPhotObjAddSource(pmPhotObj *object, pmSource *source);
+pmPhotObj *pmPhotObjAlloc(void);
+
+/// @}
+# endif /* PM_PHOT_OBJ_H */
+
Index: branches/simmosaic_branches/psModules/src/objects/pmSource.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSource.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSource.c	(revision 27839)
@@ -3,12 +3,10 @@
  *  Functions to define and manipulate sources on images
  *
- *  @author GLG, MHPCC
- *  @author EAM, IfA: significant modifications.
+ *  @author EAM, IfA
+ *  @author GLG, MHPCC (initial code base)
  *
  *  @version $Revision: 1.70 $ $Name: not supported by cvs2svn $
  *  @date $Date: 2009-02-16 22:29:59 $
- *
- *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
- *
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
  */
 
@@ -48,5 +46,5 @@
     psFree(tmp->maskView);
     psFree(tmp->modelFlux);
-    psFree(tmp->psfFlux);
+    psFree(tmp->psfImage);
     psFree(tmp->moments);
     psFree(tmp->modelPSF);
@@ -54,4 +52,6 @@
     psFree(tmp->modelFits);
     psFree(tmp->extpars);
+    psFree(tmp->moments);
+    psFree(tmp->diffStats);
     psFree(tmp->blends);
     psTrace("psModules.objects", 10, "---- end ----\n");
@@ -70,5 +70,5 @@
     psFree (source->maskView);
     psFree (source->modelFlux);
-    psFree (source->psfFlux);
+    psFree (source->psfImage);
 
     source->pixels = NULL;
@@ -77,5 +77,5 @@
     source->maskView = NULL;
     source->modelFlux = NULL;
-    source->psfFlux = NULL;
+    source->psfImage = NULL;
     return;
 }
@@ -105,5 +105,5 @@
     source->maskView = NULL;
     source->modelFlux = NULL;
-    source->psfFlux = NULL;
+    source->psfImage = NULL;
     source->moments = NULL;
     source->blends = NULL;
@@ -115,9 +115,13 @@
     source->tmpFlags = 0;
     source->extpars = NULL;
+    source->diffStats = NULL;
+
     source->region = psRegionSet(NAN, NAN, NAN, NAN);
     psMemSetDeallocator(source, (psFreeFunc) sourceFree);
 
     // default values are NAN
-    source->psfMag = NAN;
+    source->psfMag     = NAN;
+    source->psfFlux    = NAN;
+    source->psfFluxErr = NAN;
     source->extMag = NAN;
     source->errMag = NAN;
@@ -176,4 +180,5 @@
     source->type = in->type;
     source->mode = in->mode;
+    source->imageID = in->imageID;
 
     return(source);
@@ -242,5 +247,4 @@
     extend |= (mySource->maskView == NULL);
 
-    // extend = true;
     if (extend) {
         // re-create the subimage
@@ -250,5 +254,5 @@
 
         mySource->pixels   = psImageSubset(readout->image,  newRegion);
-        mySource->variance   = psImageSubset(readout->variance, newRegion);
+        mySource->variance = psImageSubset(readout->variance, newRegion);
         mySource->maskView = psImageSubset(readout->mask,   newRegion);
         mySource->region   = newRegion;
@@ -262,7 +266,7 @@
         mySource->modelFlux = NULL;
 
-        // drop the old psfFlux pixels and force the user to re-create
-        psFree (mySource->psfFlux);
-        mySource->psfFlux = NULL;
+        // drop the old psfImage pixels and force the user to re-create
+        psFree (mySource->psfImage);
+        mySource->psfImage = NULL;
     }
     return extend;
@@ -278,47 +282,22 @@
 // psphot-specific function which applies the recipe values
 // only apply selection to sources within specified region
-pmPSFClump pmSourcePSFClump(psRegion *region, psArray *sources, psMetadata *recipe)
+pmPSFClump pmSourcePSFClump(psImage **savedImage, psRegion *region, psArray *sources, float PSF_SN_LIM, float PSF_CLUMP_GRID_SCALE, psF32 SX_MAX, psF32 SY_MAX, psF32 AR_MAX)
 {
     psTrace("psModules.objects", 10, "---- begin ----\n");
 
     psArray *peaks  = NULL;
-    pmPSFClump errorClump = {-1.0, -1.0, 0.0, 0.0};
-    pmPSFClump emptyClump = {+0.0, +0.0, 0.0, 0.0};
+    pmPSFClump errorClump = {-1.0, -1.0, 0.0, 0.0, 0, 0.0};
+    pmPSFClump emptyClump = {+0.0, +0.0, 0.0, 0.0, 0, 0.0};
     pmPSFClump psfClump;
 
     PS_ASSERT_PTR_NON_NULL(sources, errorClump);
-    PS_ASSERT_PTR_NON_NULL(recipe, errorClump);
-
-    bool status = true;                 // Status of MD lookup
-    float PSF_SN_LIM = psMetadataLookupF32(&status, recipe, "PSF_SN_LIM");
-    if (!status) {
-        PSF_SN_LIM = 0;
-    }
-    float PSF_CLUMP_GRID_SCALE = psMetadataLookupF32(&status, recipe, "PSF_CLUMP_GRID_SCALE");
-    if (!status) {
-        PSF_CLUMP_GRID_SCALE = 0.1;
-    }
 
     // find the sigmaX, sigmaY clump
     {
-        psF32 SX_MAX = psMetadataLookupF32(&status, recipe, "MOMENTS_SX_MAX");
-        if (!status) {
-            psWarning("MOMENTS_SX_MAX not set in recipe");
-            SX_MAX = 10.0;
-        }
-        psF32 SY_MAX = psMetadataLookupF32(&status, recipe, "MOMENTS_SY_MAX");
-        if (!status) {
-            psWarning("MOMENTS_SY_MAX not set in recipe");
-            SY_MAX = 10.0;
-        }
-        psF32 AR_MAX = psMetadataLookupF32(&status, recipe, "MOMENTS_AR_MAX");
-        if (!status) {
-            psWarning("MOMENTS_AR_MAX not set in recipe");
-            AR_MAX =  3.0;
-        }
         psF32 AR_MIN = 1.0 / AR_MAX;
 
         // construct a sigma-plane image
-        int numCols = SX_MAX / PSF_CLUMP_GRID_SCALE, numRows = SY_MAX / PSF_CLUMP_GRID_SCALE; // Size of sigma-plane image
+        int numCols = 1 + SX_MAX / PSF_CLUMP_GRID_SCALE; // Size of sigma-plane image
+	int numRows = 1 + SY_MAX / PSF_CLUMP_GRID_SCALE; // Size of sigma-plane image
         psTrace("psModules.objects", 10, "sigma-plane dimensions: %dx%d\n", numCols, numRows);
         psImage *splane = psImageAlloc(numCols, numRows, PS_TYPE_F32); // sigma-plane image
@@ -333,12 +312,16 @@
             }
 
-            int x = src->peak->x, y = src->peak->y; // Coordinates of peak
-            if (x < region->x0 || x > region->x1 || y < region->y0 || y > region->y1) {
-                continue;
-            }
+	    if (region) {
+		int x = src->peak->x, y = src->peak->y; // Coordinates of peak
+		if (x < region->x0 || x > region->x1 || y < region->y0 || y > region->y1) {
+		    continue;
+		}
+	    }
 
             if (src->mode & PM_SOURCE_MODE_BLEND) {
                 continue;
             }
+
+            if (!src->moments->nPixels) continue;
 
             if (src->moments->SN < PSF_SN_LIM) {
@@ -385,5 +368,5 @@
 
         // find the peak in this image
-        psStats *stats = psStatsAlloc (PS_STAT_MAX);
+        psStats *stats = psStatsAlloc (PS_STAT_MAX | PS_STAT_SAMPLE_STDEV);
         if (!psImageStats (stats, splane, NULL, 0)) {
             psError(PS_ERR_UNKNOWN, false, "Unable to get image statistics.\n");
@@ -395,9 +378,8 @@
         psTrace ("psModules.objects", 2, "clump threshold is %f\n", stats[0].max/2);
 
-        const bool keep_psf_clump = psMetadataLookupBool(NULL, recipe, "KEEP_PSF_CLUMP");
-        if (keep_psf_clump)
-        {
-            psMetadataAdd(recipe, PS_LIST_TAIL,
-                          "PSF_CLUMP", PS_DATA_IMAGE, "Image of PSF coefficients", splane);
+	psfClump.nSigma = stats->sampleStdev;
+
+	if (savedImage) {
+	    *savedImage = psMemIncrRefCounter(splane);
         }
         psFree (splane);
@@ -405,5 +387,11 @@
 
         // if we failed to find a valid peak, return the empty clump (failure signal)
-        if (peaks == NULL)
+	if (peaks == NULL) {
+            psError(PS_ERR_UNKNOWN, false, "failure in peak analysis for PSF clump.\n");
+	    psFree (peaks);
+            return emptyClump;
+	}
+
+        if (peaks->n == 0)
         {
             psLogMsg ("psphot", 3, "failed to find a peak in the PSF clump image\n");
@@ -413,4 +401,5 @@
                 psLogMsg ("psphot", 3, "no significant peak\n");
             }
+	    psFree (peaks);
             return (emptyClump);
         }
@@ -431,4 +420,9 @@
         psTrace ("psModules.objects", 2, "clump is at %d, %d (%f)\n", clump->x, clump->y, clump->value);
 
+	// XXX store the mean sigma?
+	float meanSigma = psfClump.nSigma;
+	psfClump.nStars = clump->value;
+	psfClump.nSigma = clump->value / meanSigma;
+
         // define section window for clump
         minSx = clump->x * PSF_CLUMP_GRID_SCALE - 2.0*PSF_CLUMP_GRID_SCALE;
@@ -453,8 +447,10 @@
                 continue;
 
-            if (tmpSrc->peak->x < region->x0) continue;
-            if (tmpSrc->peak->x > region->x1) continue;
-            if (tmpSrc->peak->y < region->y0) continue;
-            if (tmpSrc->peak->y > region->y1) continue;
+	    if (region) {
+		if (tmpSrc->peak->x < region->x0) continue;
+		if (tmpSrc->peak->x > region->x1) continue;
+		if (tmpSrc->peak->y < region->y0) continue;
+		if (tmpSrc->peak->y > region->y1) continue;
+	    }
 
             if (tmpSrc->moments->Mxx < minSx)
@@ -512,10 +508,9 @@
 *****************************************************************************/
 
-bool pmSourceRoughClass(psRegion *region, psArray *sources, psMetadata *recipe, pmPSFClump clump, psImageMaskType maskSat)
+bool pmSourceRoughClass(psRegion *region, psArray *sources, float PSF_SN_LIM, float PSF_CLUMP_NSIGMA, pmPSFClump clump, psImageMaskType maskSat)
 {
     psTrace("psModules.objects", 10, "---- begin ----");
 
     PS_ASSERT_PTR_NON_NULL(sources, false);
-    PS_ASSERT_PTR_NON_NULL(recipe, false);
 
     int Nsat     = 0;
@@ -530,13 +525,4 @@
     psVector *starsn_peaks = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
     psVector *starsn_moments = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
-
-    // get basic parameters, or set defaults
-    bool status;
-    float PSF_SN_LIM = psMetadataLookupF32 (&status, recipe, "PSF_SN_LIM");
-    if (!status) PSF_SN_LIM = 20.0;
-    float PSF_CLUMP_NSIGMA = psMetadataLookupF32 (&status, recipe, "PSF_CLUMP_NSIGMA");
-    if (!status) PSF_CLUMP_NSIGMA = 1.5;
-
-    // float INNER_RADIUS = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
 
     pmSourceMode noMoments = PM_SOURCE_MODE_MOMENTS_FAILURE | PM_SOURCE_MODE_SKYVAR_FAILURE | PM_SOURCE_MODE_SKY_FAILURE | PM_SOURCE_MODE_BELOW_MOMENTS_SN;
@@ -894,11 +880,11 @@
 
     // if we already have a cached image, re-use that memory
-    source->psfFlux = psImageCopy (source->psfFlux, source->pixels, PS_TYPE_F32);
-    psImageInit (source->psfFlux, 0.0);
+    source->psfImage = psImageCopy (source->psfImage, source->pixels, PS_TYPE_F32);
+    psImageInit (source->psfImage, 0.0);
 
     // in some places (psphotEnsemble), we need a normalized version
     // in others, we just want the model.  which is more commonly used?
-    // psfFlux always has unity normalization (I0 = 1.0)
-    pmModelAdd (source->psfFlux, source->maskObj, source->modelPSF, PM_MODEL_OP_FULL | PM_MODEL_OP_NORM, maskVal);
+    // psfImage always has unity normalization (I0 = 1.0)
+    pmModelAdd (source->psfImage, source->maskObj, source->modelPSF, PM_MODEL_OP_FULL | PM_MODEL_OP_NORM, maskVal);
     return true;
 }
@@ -919,4 +905,6 @@
     pmModel *model = pmSourceGetModel (NULL, source);
     if (model == NULL) return false;  // model must be defined
+
+    bool addNoise = mode & PM_MODEL_OP_NOISE;
 
     if (source->modelFlux) {
@@ -941,10 +929,9 @@
 
         psF32 **target = source->pixels->data.F32;
-        if (mode & PM_MODEL_OP_NOISE) {
-            // XXX need to scale by the gain...
+        if (addNoise) {
+	    // when adding noise, we assume the shape and Io have been modified
             target = source->variance->data.F32;
         }
 
-        // XXX need to respect the source and model masks?
         for (int iy = 0; iy < source->modelFlux->numRows; iy++) {
             int oy = iy + dY;
@@ -960,16 +947,27 @@
             }
         }
+	if (!addNoise) {
+	    if (add) {
+		source->tmpFlags &= ~PM_SOURCE_TMPF_SUBTRACTED;
+	    } else {
+		source->tmpFlags |= PM_SOURCE_TMPF_SUBTRACTED;
+	    }
+	}
         return true;
     }
 
     psImage *target = source->pixels;
-    if (mode & PM_MODEL_OP_NOISE) {
+    if (addNoise) {
         target = source->variance;
     }
 
-    if (add) {
-        status = pmModelAddWithOffset (target, source->maskObj, model, PM_MODEL_OP_FULL, maskVal, dx, dy);
-    } else {
-        status = pmModelSubWithOffset (target, source->maskObj, model, PM_MODEL_OP_FULL, maskVal, dx, dy);
+    if (!addNoise) {
+	if (add) {
+	    status = pmModelAddWithOffset (target, source->maskObj, model, PM_MODEL_OP_FULL, maskVal, dx, dy);
+	    source->tmpFlags &= ~PM_SOURCE_TMPF_SUBTRACTED;
+	} else {
+	    status = pmModelSubWithOffset (target, source->maskObj, model, PM_MODEL_OP_FULL, maskVal, dx, dy);
+	    source->tmpFlags |= PM_SOURCE_TMPF_SUBTRACTED;
+	}
     }
 
@@ -1061,4 +1059,19 @@
     psF32 fA = (A->peak == NULL) ? 0 : A->peak->y;
     psF32 fB = (B->peak == NULL) ? 0 : B->peak->y;
+
+    psF32 diff = fA - fB;
+    if (diff > FLT_EPSILON) return (+1);
+    if (diff < FLT_EPSILON) return (-1);
+    return (0);
+}
+
+// sort by X (ascending)
+int pmSourceSortByX (const void **a, const void **b)
+{
+    pmSource *A = *(pmSource **)a;
+    pmSource *B = *(pmSource **)b;
+
+    psF32 fA = (A->peak == NULL) ? 0 : A->peak->x;
+    psF32 fB = (B->peak == NULL) ? 0 : B->peak->x;
 
     psF32 diff = fA - fB;
Index: branches/simmosaic_branches/psModules/src/objects/pmSource.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSource.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSource.h	(revision 27839)
@@ -16,4 +16,5 @@
 #include "pmMoments.h"
 #include "pmSourceExtendedPars.h"
+#include "pmSourceDiffStats.h"
 
 /// @addtogroup Objects Object Detection / Analysis Functions
@@ -38,6 +39,9 @@
 
 typedef enum {
-    PM_SOURCE_TMPF_MODEL_GUESS = 0x0001,
-    PM_SOURCE_TMPF_SUBTRACTED  = 0x0002,
+    PM_SOURCE_TMPF_MODEL_GUESS       = 0x0001,
+    PM_SOURCE_TMPF_SUBTRACTED        = 0x0002,
+    PM_SOURCE_TMPF_SIZE_MEASURED     = 0x0004,
+    PM_SOURCE_TMPF_SIZE_CR_CANDIDATE = 0x0008,
+    PM_SOURCE_TMPF_MOMENTS_MEASURED  = 0x0010,
 } pmSourceTmpF;
 
@@ -63,5 +67,5 @@
     psImage *maskView;                  ///< view into global image mask for this object region
     psImage *modelFlux;                 ///< cached copy of the best model for this source
-    psImage *psfFlux;                   ///< cached copy of the psf model for this source
+    psImage *psfImage;                   ///< cached copy of the psf model for this source
     pmMoments *moments;                 ///< Basic moments measured for the object.
     pmModel *modelPSF;                  ///< PSF Model fit (parameters and type)
@@ -73,4 +77,6 @@
     psArray *blends;                    ///< collection of sources thought to be confused with object
     float psfMag;                       ///< calculated from flux in modelPSF
+    float psfFlux;                      ///< calculated from flux in modelPSF
+    float psfFluxErr;                   ///< calculated from flux in modelPSF
     float extMag;                       ///< calculated from flux in modelEXT
     float errMag;                       ///< error in psfMag OR extMag (depending on type)
@@ -81,6 +87,9 @@
     float extNsigma;                    ///< Nsigma deviation from PSF to EXT
     float sky, skyErr;                  ///< The sky and its error at the center of the object
+    float apRadius;
     psRegion region;                    ///< area on image covered by selected pixels
     pmSourceExtendedPars *extpars;      ///< extended source parameters
+    pmSourceDiffStats *diffStats;       ///< extra parameters for difference detections
+    int imageID;
 };
 
@@ -98,4 +107,6 @@
     float Y;
     float dY;
+    int nStars;
+    float nSigma;
 }
 pmPSFClump;
@@ -172,13 +183,15 @@
  *
  * The return value indicates the success (TRUE) of the operation.
- *
- * XXX: Limit the S/N of the candidate sources (part of Metadata)? (TBD).
- * XXX: Save the clump parameters on the Metadata (TBD)
- *
- */
+ */
+
 pmPSFClump pmSourcePSFClump(
+    psImage **savedImage, 
     psRegion *region,                   ///< restrict measurement to specified region
     psArray *source,                    ///< The input pmSource
-    psMetadata *metadata                ///< Contains classification parameters
+    float PSF_SN_LIM, 
+    float PSF_CLUMP_GRID_SCALE, 
+    psF32 SX_MAX, 
+    psF32 SY_MAX, 
+    psF32 AR_MAX
 );
 
@@ -196,5 +209,6 @@
     psRegion *region,                   ///< restrict measurement to specified region
     psArray *sources,                    ///< The input pmSources
-    psMetadata *metadata,               ///< Contains classification parameters
+    float PSF_SN_LIM,			 ///< min S/N for source to be used for PSF model
+    float PSF_CLUMP_NSIGMA,		 ///< size of region around peak of clump for PSF stars
     pmPSFClump clump,                   ///< Statistics about the PSF clump
     psImageMaskType maskSat             ///< Mask value for saturated pixels
@@ -216,5 +230,6 @@
     float radius,     ///< Use a circle of pixels around the peak
     float sigma,      ///< size of Gaussian window function (<= 0.0 -> skip window)
-    float minSN	      ///< minimum pixel significance
+    float minSN,	      ///< minimum pixel significance
+    psImageMaskType maskVal
 );
 
@@ -232,4 +247,5 @@
 int  pmSourceSortBySN (const void **a, const void **b);
 int  pmSourceSortByY (const void **a, const void **b);
+int  pmSourceSortByX (const void **a, const void **b);
 int  pmSourceSortBySeq (const void **a, const void **b);
 
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceContour.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceContour.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceContour.c	(revision 27839)
@@ -273,5 +273,5 @@
             x0 = findContourPos (image, threshold, xR, yR, RIGHT, x1);
             if (x0 == x1) {
-                // fprintf (stderr, "top: %d (%d - %d)\n", yR, xR, x1);
+	      // fprintf (stderr, "top: %d (%d - %d)\n", yR, xR, x1);
                 goto pt1;
             }
@@ -282,5 +282,5 @@
             x1 = findContourNeg (image, threshold, xR, yR, RIGHT);
         }
-        // fprintf (stderr, "pos: %d (%d - %d)\n", yR, x0, x1);
+	// fprintf (stderr, "pos: %d (%d - %d)\n", yR, x0, x1);
 
         xVec->data.F32[Npt + 0] = image->col0 + x0;
@@ -288,4 +288,5 @@
         yVec->data.F32[Npt + 0] = image->row0 + yR;
         yVec->data.F32[Npt + 1] = image->row0 + yR;
+
         Npt += 2;
 
@@ -307,5 +308,5 @@
             x0 = findContourPos (image, threshold, xR, yR, RIGHT, x1);
             if (x0 == x1) {
-                // fprintf (stderr, "top: %d (%d - %d)\n", yR, xR, x1);
+	      // fprintf (stderr, "top: %d (%d - %d)\n", yR, xR, x1);
                 goto pt2;
             }
@@ -322,4 +323,5 @@
         yVec->data.F32[Npt + 0] = image->row0 + yR;
         yVec->data.F32[Npt + 1] = image->row0 + yR;
+
         Npt += 2;
 
@@ -334,5 +336,4 @@
     yVec->n = Npt;
 
-    // fprintf (stderr, "done\n");
     psArray *tmpArray = psArrayAlloc(2);
 
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceDiffStats.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceDiffStats.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceDiffStats.c	(revision 27839)
@@ -0,0 +1,42 @@
+/** @file  pmSourceDiffStats.c
+ *
+ *  Functions defining the pmSourceDiffStats structure and associated measurements
+
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-10-03 20:59:16 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include "pmSourceDiffStats.h"
+
+/** initialization function for a pmSourceDiffStats object
+ **/
+void pmSourceDiffStatsInit(pmSourceDiffStats *diffStats)
+{
+    diffStats->fRatio = NAN;
+    diffStats->nRatioBad = NAN;
+    diffStats->nRatioMask = NAN;
+    diffStats->nRatioAll = NAN;
+    diffStats->nGood = 0;
+}
+
+/******************************************************************************
+pmSourceDiffStatsAlloc(): Allocate the pmSourceDiffStats structure and initialize the members
+to zero.
+*****************************************************************************/
+pmSourceDiffStats *pmSourceDiffStatsAlloc(void)
+{
+    pmSourceDiffStats *tmp = (pmSourceDiffStats *) psAlloc(sizeof(pmSourceDiffStats));
+    pmSourceDiffStatsInit(tmp);
+    return(tmp);
+}
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceDiffStats.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceDiffStats.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceDiffStats.h	(revision 27839)
@@ -0,0 +1,44 @@
+/* @file  pmSourceDiffStats.h
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.29 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2009-02-16 22:30:50 $
+ * Copyright 2004 Institute for Astronomy, University of Hawaii
+ */
+
+# ifndef PM_SOURCE_DIFF_STATS_H
+# define PM_SOURCE_DIFF_STATS_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+/* The elements of this structure are used to characterize diff image detections.
+ * The values in the structure are derived from the following measurements:
+ * fGood = \sum(flux) for pixels with S/N > +SN_LIMIT
+ * fBad  = \sum(flux) for pixels with S/N < -SN_LIMIT
+ * nGood = \sum(pixels) with S/N > +SN_LIMIT
+ * nBad  = \sum(pixels) with S/N < -SN_LIMIT
+ * nMask = \sum(pixels) masked
+ */
+
+typedef struct {
+    float fRatio;			// = fGood / (fGood + fBad)
+    float nRatioBad;			// = nGood / (nGood + nBad)
+    float nRatioMask;			// = nGood / (nGood + nMask)
+    float nRatioAll;			// = nGood / (nGood + nMask + nBad)
+    int   nGood;			// nGood as defined above
+} pmSourceDiffStats;
+
+
+/** pmSourceDiffStatsAlloc()
+ */
+pmSourceDiffStats *pmSourceDiffStatsAlloc(void);
+
+/** pmSourceDiffStatsInit()
+ * function to initialize the values in the structure
+ */
+void pmSourceDiffStatsInit(pmSourceDiffStats *diffStats);
+
+/// @}
+# endif
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceExtendedPars.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceExtendedPars.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceExtendedPars.c	(revision 27839)
@@ -17,30 +17,178 @@
 #endif
 
-#include <stdio.h>
-#include <math.h>
-#include <string.h>
+// #include <stdio.h>
+// #include <math.h>
+// #include <string.h>
 #include <pslib.h>
-#include "pmHDU.h"
-#include "pmFPA.h"
-#include "pmFPAMaskWeight.h"
-#include "pmSpan.h"
-#include "pmFootprint.h"
-#include "pmPeaks.h"
-#include "pmMoments.h"
-#include "pmResiduals.h"
-#include "pmGrowthCurve.h"
-#include "pmTrend2D.h"
-#include "pmPSF.h"
-#include "pmModel.h"
-#include "pmSource.h"
-
+// #include "pmHDU.h"
+// #include "pmFPA.h"
+// #include "pmFPAMaskWeight.h"
+// #include "pmSpan.h"
+// #include "pmFootprint.h"
+// #include "pmPeaks.h"
+// #include "pmMoments.h"
+// #include "pmResiduals.h"
+// #include "pmGrowthCurve.h"
+// #include "pmTrend2D.h"
+// #include "pmPSF.h"
+// #include "pmModel.h"
+// #include "pmSource.h"
+#include "pmSourceExtendedPars.h"
+
+// pmSourceRadialFlux carries the raw radial flux information, including angular bins
+static void pmSourceRadialFluxFree(pmSourceRadialFlux *flux)
+{
+    if (!flux) return;
+    psFree(flux->radii);
+    psFree(flux->fluxes);
+    psFree(flux->theta);
+    psFree(flux->isophotalRadii);
+}
+
+pmSourceRadialFlux *pmSourceRadialFluxAlloc()
+{
+    pmSourceRadialFlux *flux = (pmSourceRadialFlux *)psAlloc(sizeof(pmSourceRadialFlux));
+    psMemSetDeallocator(flux, (psFreeFunc) pmSourceRadialFluxFree);
+
+    flux->radii = NULL;
+    flux->fluxes = NULL;
+    flux->theta = NULL;
+    flux->isophotalRadii = NULL;
+
+    return flux;
+}
+
+bool psMemCheckSourceRadialFlux(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceRadialFluxFree);
+}
+
+// pmSourceEllipticalFlux carries the elliptical renormalized radial flux info
+static void pmSourceEllipticalFluxFree(pmSourceEllipticalFlux *flux)
+{
+    if (!flux) return;
+    psFree(flux->radiusElliptical);
+    psFree(flux->fluxElliptical);
+}
+
+pmSourceEllipticalFlux *pmSourceEllipticalFluxAlloc()
+{
+    pmSourceEllipticalFlux *flux = (pmSourceEllipticalFlux *)psAlloc(sizeof(pmSourceEllipticalFlux));
+    psMemSetDeallocator(flux, (psFreeFunc) pmSourceEllipticalFluxFree);
+
+    flux->radiusElliptical = NULL;
+    flux->fluxElliptical = NULL;
+
+    return flux;
+}
+
+bool psMemCheckSourceEllipticalFlux(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceEllipticalFluxFree);
+}
+
+// pmSourceRadialProfile defines flux information in radial bins
+static void pmSourceRadialProfileFree(pmSourceRadialProfile *profile)
+{
+    if (!profile) return;
+    psFree(profile->binSB);
+    psFree(profile->binSBstdev);
+    psFree(profile->binSBerror);
+    psFree(profile->binSum);
+    psFree(profile->binFill);
+    psFree(profile->radialBins);
+    psFree(profile->area);
+}
+
+pmSourceRadialProfile *pmSourceRadialProfileAlloc()
+{
+    pmSourceRadialProfile *profile = (pmSourceRadialProfile *)psAlloc(sizeof(pmSourceRadialProfile));
+    psMemSetDeallocator(profile, (psFreeFunc) pmSourceRadialProfileFree);
+
+    profile->binSB = NULL;
+    profile->binSBstdev = NULL;
+    profile->binSBerror = NULL;
+    profile->binSum = NULL;
+    profile->binFill = NULL;
+    profile->radialBins = NULL;
+    profile->area = NULL;
+    return profile;
+}
+
+bool psMemCheckSourceRadialProfile(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceRadialProfileFree);
+}
+
+# if (0)
+// pmSourceRadialProfileFreeVectors frees the intermediate data values
+bool pmSourceRadialProfileFreeVectors(pmSourceRadialProfile *profile) {
+
+    psFree(profile->radii);
+    psFree(profile->fluxes);
+    psFree(profile->theta);
+    psFree(profile->isophotalRadii);
+
+    psFree(profile->radiusElliptical);
+    psFree(profile->fluxElliptical);
+
+    // psFree(profile->binSB);
+    // psFree(profile->binSBstdev);
+    // psFree(profile->binSBerror);
+    
+    // psFree(profile->radialBins);
+    psFree(profile->area);
+
+    profile->radii = NULL;
+    profile->fluxes = NULL;
+    profile->theta = NULL;
+    profile->isophotalRadii = NULL;
+
+    profile->radiusElliptical = NULL;
+    profile->fluxElliptical = NULL;
+
+    // profile->binSB = NULL;
+    // profile->binSBstdev = NULL;
+    // profile->binSBerror = NULL;
+    
+    // profile->radialBins = NULL;
+    profile->area = NULL;
+
+    return true;
+}
+# endif
+
+// *** pmSourceRadialProfileSortPair is a utility function for sorting a pair of vectors
+# define COMPARE_INDEX(A,B) (index->data.F32[A] < index->data.F32[B])
+# define SWAP_INDEX(TYPE,A,B) { \
+  float tmp; \
+  if (A != B) { \
+    tmp = index->data.F32[A]; \
+    index->data.F32[A] = index->data.F32[B]; \
+    index->data.F32[B] = tmp; \
+    tmp = extra->data.F32[A]; \
+    extra->data.F32[A] = extra->data.F32[B]; \
+    extra->data.F32[B] = tmp; \
+  } \
+}
+
+bool pmSourceRadialProfileSortPair (psVector *index, psVector *extra) {
+
+    // sort the vector set by the radius
+    PSSORT (index->n, COMPARE_INDEX, SWAP_INDEX, NONE);
+    return true;
+}
+
+// *** pmSourceExtendedPars describes the possible collection of extended flux measurements for a source
 static void pmSourceExtendedParsFree (pmSourceExtendedPars *pars) {
     if (!pars) return;
 
-    psFree(pars->profile);
-    psFree(pars->annuli);
-    psFree(pars->isophot);
-    psFree(pars->petrosian);
-    psFree(pars->kron);
+    psFree(pars->radFlux);
+    psFree(pars->ellipticalFlux);
+    psFree(pars->radProfile);
+    psFree(pars->petProfile);
     return;
 }
@@ -50,10 +198,17 @@
     psMemSetDeallocator(pars, (psFreeFunc) pmSourceExtendedParsFree);
 
-    pars->profile = NULL;
-    pars->annuli = NULL;
-    pars->isophot = NULL;
-    pars->petrosian = NULL;
-    pars->kron = NULL;
-
+    pars->radFlux = NULL;
+    pars->ellipticalFlux = NULL;
+    pars->radProfile = NULL;
+    pars->petProfile = NULL;
+
+    pars->petrosianFlux = NAN;
+    pars->petrosianFluxErr = NAN;
+    pars->petrosianRadius = NAN;
+    pars->petrosianRadiusErr = NAN;
+    pars->petrosianR90 = NAN;
+    pars->petrosianR90Err = NAN;
+    pars->petrosianR50 = NAN;
+    pars->petrosianR50Err = NAN;
     return pars;
 }
@@ -66,135 +221,27 @@
 
 
-static void pmSourceRadialProfileFree (pmSourceRadialProfile *profile) {
-    if (!profile) return;
-
-    psFree(profile->radius);
-    psFree(profile->flux);
-    psFree(profile->variance);
+// *** pmSourceExtendedFlux describes the flux within an elliptical aperture of some kind 
+static void pmSourceExtendedFluxFree (pmSourceExtendedFlux *flux) {
+    if (!flux) return;
     return;
 }
 
-pmSourceRadialProfile *pmSourceRadialProfileAlloc (void) {
-
-    pmSourceRadialProfile *profile = (pmSourceRadialProfile *) psAlloc(sizeof(pmSourceRadialProfile));
-    psMemSetDeallocator(profile, (psFreeFunc) pmSourceRadialProfileFree);
-
-    profile->radius = NULL;
-    profile->flux = NULL;
-    profile->variance = NULL;
-
-    return profile;
-}
-
-bool psMemCheckSourceRadialProfile(psPtr ptr)
-{
-    PS_ASSERT_PTR(ptr, false);
-    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceRadialProfileFree);
-}
-
-
-static void pmSourceIsophotalValuesFree (pmSourceIsophotalValues *isophot) {
-    if (!isophot) return;
-    return;
-}
-
-pmSourceIsophotalValues *pmSourceIsophotalValuesAlloc (void) {
-
-    pmSourceIsophotalValues *isophot = (pmSourceIsophotalValues *) psAlloc(sizeof(pmSourceIsophotalValues));
-    psMemSetDeallocator(isophot, (psFreeFunc) pmSourceIsophotalValuesFree);
-
-    isophot->mag = 0.0;
-    isophot->magErr = 0.0;
-    isophot->rad = 0.0;
-    isophot->radErr = 0.0;
-
-    return isophot;
-}
-
-
-bool psMemCheckSourceIsophotalValues(psPtr ptr)
-{
-    PS_ASSERT_PTR(ptr, false);
-    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceIsophotalValuesFree);
-}
-
-
-static void pmSourcePetrosianValuesFree (pmSourcePetrosianValues *petrosian) {
-    if (!petrosian) return;
-    return;
-}
-
-pmSourcePetrosianValues *pmSourcePetrosianValuesAlloc (void) {
-
-    pmSourcePetrosianValues *petrosian = (pmSourcePetrosianValues *) psAlloc(sizeof(pmSourcePetrosianValues));
-    psMemSetDeallocator(petrosian, (psFreeFunc) pmSourcePetrosianValuesFree);
-
-    petrosian->mag = 0.0;
-    petrosian->magErr = 0.0;
-    petrosian->rad = 0.0;
-    petrosian->radErr = 0.0;
-
-    return petrosian;
-}
-
-
-bool psMemCheckSourcePetrosianValues(psPtr ptr)
-{
-    PS_ASSERT_PTR(ptr, false);
-    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourcePetrosianValuesFree);
-}
-
-static void pmSourceKronValuesFree (pmSourceKronValues *kron) {
-    if (!kron) return;
-    return;
-}
-
-pmSourceKronValues *pmSourceKronValuesAlloc (void) {
-
-    pmSourceKronValues *kron = (pmSourceKronValues *) psAlloc(sizeof(pmSourceKronValues));
-    psMemSetDeallocator(kron, (psFreeFunc) pmSourceKronValuesFree);
-
-    kron->mag = 0.0;
-    kron->magErr = 0.0;
-    kron->rad = 0.0;
-    kron->radErr = 0.0;
-
-    return kron;
-}
-
-
-bool psMemCheckSourceKronValues(psPtr ptr)
-{
-    PS_ASSERT_PTR(ptr, false);
-    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceKronValuesFree);
-}
-
-
-static void pmSourceAnnuliFree (pmSourceAnnuli *annuli) {
-    if (!annuli) return;
-
-    psFree (annuli->flux);
-    psFree (annuli->fluxErr);
-    psFree (annuli->fluxVar);
-
-    return;
-}
-
-pmSourceAnnuli *pmSourceAnnuliAlloc (void) {
-
-    pmSourceAnnuli *annuli = (pmSourceAnnuli *) psAlloc(sizeof(pmSourceAnnuli));
-    psMemSetDeallocator(annuli, (psFreeFunc) pmSourceAnnuliFree);
-
-    annuli->flux = NULL;
-    annuli->fluxErr = NULL;
-    annuli->fluxVar = NULL;
-
-    return annuli;
-}
-
-
-bool psMemCheckSourceAnnuli(psPtr ptr)
-{
-    PS_ASSERT_PTR(ptr, false);
-    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceAnnuliFree);
-}
+pmSourceExtendedFlux *pmSourceExtendedFluxAlloc (void) {
+
+    pmSourceExtendedFlux *flux = (pmSourceExtendedFlux *) psAlloc(sizeof(pmSourceExtendedFlux));
+    psMemSetDeallocator(flux, (psFreeFunc) pmSourceExtendedFluxFree);
+
+    flux->flux = 0.0;
+    flux->fluxErr = 0.0;
+    flux->radius = 0.0;
+    flux->radiusErr = 0.0;
+
+    return flux;
+}
+
+
+bool psMemCheckSourceExtendedFlux(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceExtendedFluxFree);
+}
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceExtendedPars.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceExtendedPars.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceExtendedPars.h	(revision 27839)
@@ -15,56 +15,71 @@
 
 typedef struct {
-  psVector *radius;
-  psVector *flux;
-  psVector *variance;
+    psArray  *radii;			// radii for raw radial profiles at evenly-spaced angles
+    psArray  *fluxes;			// fluxes measured at above radii
+    psVector *theta;			// angles corresponding to above radial profiles
+    psVector *isophotalRadii;		// isophotal radius for the above angles
+} pmSourceRadialFlux;
+
+typedef struct {
+    psVector *radiusElliptical;		// normalized radial coordinates for all relevant pixels
+    psVector *fluxElliptical;		// flux for the above radial coordinates
+} pmSourceEllipticalFlux;
+
+typedef struct {
+    psVector *binSB;			// mean surface brightness within radial bins
+    psVector *binSBstdev;		// scatter of mean surface brightness within radial bins
+    psVector *binSBerror;		// formal error on mean surface brightness within radial bins
+    psVector *binSum;			// sum of flux within radial bins
+    psVector *binFill;			// fraction of area actually lit
+    psVector *radialBins;		// radii corresponding to above binnedFlux
+    psVector *area;			// differential area of the non-overlapping radial bins
 } pmSourceRadialProfile;
 
 typedef struct {
-  psVector *flux;
-  psVector *fluxErr;
-  psVector *fluxVar;
-} pmSourceAnnuli;
+    float flux;
+    float fluxErr;
+    float radius;
+    float radiusErr;
+} pmSourceExtendedFlux;
 
 typedef struct {
-  float mag;
-  float magErr;
-  float rad;
-  float radErr;
-} pmSourceIsophotalValues;
-
-typedef struct {
-  float mag;
-  float magErr;
-  float rad;
-  float radErr;
-} pmSourcePetrosianValues;
-
-typedef struct {
-  float mag;
-  float magErr;
-  float rad;
-  float radErr;
-} pmSourceKronValues;
-
-typedef struct {
-  pmSourceRadialProfile   *profile;
-  pmSourceAnnuli          *annuli;
-  pmSourceIsophotalValues *isophot;
-  pmSourcePetrosianValues *petrosian;
-  pmSourceKronValues      *kron;
+    pmSourceRadialFlux     *radFlux;	    // raw radial flux information
+    pmSourceEllipticalFlux *ellipticalFlux; // flux for elliptically-renormalized radii
+    pmSourceRadialProfile  *radProfile;	    // surface brightness profile in specified fixed bins
+    pmSourceRadialProfile  *petProfile;	    // surface brightness profile in petrosian bins
+    psEllipseAxes axes;			    // shape of elliptical contour
+    float petrosianFlux;
+    float petrosianFluxErr;
+    float petrosianRadius;
+    float petrosianRadiusErr;
+    float petrosianR90;
+    float petrosianR90Err;
+    float petrosianR50;
+    float petrosianR50Err;
 } pmSourceExtendedPars;
 
-pmSourceExtendedPars *pmSourceExtendedParsAlloc(void);
+pmSourceRadialFlux *pmSourceRadialFluxAlloc();
+bool psMemCheckSourceRadialFlux(psPtr ptr);
+
+pmSourceEllipticalFlux *pmSourceEllipticalFluxAlloc();
+bool psMemCheckSourceEllipticalFlux(psPtr ptr);
+
+// *** pmSourceRadialProfile describes the radial profile of a source in elliptical contours, and 
+// intermediate data used to measure the profile
+pmSourceRadialProfile *pmSourceRadialProfileAlloc();
+bool psMemCheckSourceRadialProfile(psPtr ptr);
+
+// *** pmSourceExtendedPars describes the possible collection of extended flux measurements for a source
+pmSourceExtendedPars *pmSourceExtendedParsAlloc (void);
 bool psMemCheckSourceExtendedPars(psPtr ptr);
-pmSourceRadialProfile *pmSourceRadialProfileAlloc(void);
-bool psMemCheckSourceRadialProfile(psPtr ptr);
-pmSourceIsophotalValues *pmSourceIsophotalValuesAlloc(void);
-bool psMemCheckSourceIsophotalValues(psPtr ptr);
-pmSourcePetrosianValues *pmSourcePetrosianValuesAlloc(void);
-bool psMemCheckSourcePetrosianValues(psPtr ptr);
-pmSourceKronValues *pmSourceKronValuesAlloc(void);
-bool psMemCheckSourceKronValues(psPtr ptr);
-pmSourceAnnuli *pmSourceAnnuliAlloc(void);
-bool psMemCheckSourceAnnuli(psPtr ptr);
+
+// *** pmSourceExtendedFlux describes the flux within an elliptical aperture of some kind 
+pmSourceExtendedFlux *pmSourceExtendedFluxAlloc(void);
+bool psMemCheckSourceExtendedFlux(psPtr ptr);
+
+// *** pmSourceRadialProfileSortPair is a utility function for sorting a pair of vectors
+bool pmSourceRadialProfileSortPair(psVector *index, psVector *extra);
+
+
 
 /// @}
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceFitModel.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceFitModel.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceFitModel.c	(revision 27839)
@@ -93,9 +93,16 @@
             }
 
+            // skip nan values in image
+            if (!isfinite(source->variance->data.F32[i][j])) {
+	      fprintf (stderr, "impossible! %x vs %x\n", source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[i][j], maskVal);
+	      continue;
+            }
+
             psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
 
             // Convert i/j to image space:
-            coord->data.F32[0] = (psF32) (j + source->pixels->col0);
-            coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+	    // 0.5 PIX: the coordinate values must be in pixel coords, not index	    
+            coord->data.F32[0] = (psF32) (j + 0.5 + source->pixels->col0);
+            coord->data.F32[1] = (psF32) (i + 0.5 + source->pixels->row0);
             x->data[nPix] = (psPtr *) coord;
             y->data.F32[nPix] = source->pixels->data.F32[i][j];
@@ -175,7 +182,5 @@
             continue;
         dparams->data.F32[i] = sqrt(covar->data.F32[i][i]);
-        if (psTraceGetLevel("psModules.objects") >= 4) {
-            fprintf (stderr, "%f +/- %f\n", params->data.F32[i], dparams->data.F32[i]);
-        }
+        psTrace ("psModules.objects", 4, "%f +/- %f", params->data.F32[i], dparams->data.F32[i]);
     }
     psTrace ("psModules.objects", 4, "niter: %d, chisq: %f", myMin->iter, myMin->value);
@@ -186,4 +191,5 @@
     model->nPix  = y->n;
     model->nDOF  = y->n - nParams;
+    model->chisqNorm = model->chisq / model->nDOF;
     model->flags |= PM_MODEL_STATUS_FITTED;
     if (!fitStatus) model->flags |= PM_MODEL_STATUS_NONCONVERGE;
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceFitSet.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceFitSet.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceFitSet.c	(revision 27839)
@@ -321,9 +321,7 @@
 
         for (int j = 0; j < model->params->n; j++, n++) {
-            if (psTraceGetLevel("psModules.objects") >= 4) {
-                fprintf (stderr, "%f ", param->data.F32[n]);
-            }
             model->params->data.F32[j] = param->data.F32[n];
             model->dparams->data.F32[j] = dparam->data.F32[n];
+            psTrace ("psModules.objects", 4, "%f +/- %f", param->data.F32[n], dparam->data.F32[n]);
         }
         psTrace ("psModules.objects", 4, " src %d", i);
@@ -483,6 +481,7 @@
 
             // Convert i/j to image space:
-            coord->data.F32[0] = (psF32) (j + source->pixels->col0);
-            coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+	    // 0.5 PIX: the coordinate values must be in pixel coords, not index	    
+            coord->data.F32[0] = (psF32) (j + 0.5 + source->pixels->col0);
+            coord->data.F32[1] = (psF32) (i + 0.5 + source->pixels->row0);
             x->data[nPix] = (psPtr *) coord;
             y->data.F32[nPix] = source->pixels->data.F32[i][j];
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceGroups.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceGroups.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceGroups.c	(revision 27839)
@@ -0,0 +1,201 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmFPA.h"
+#include "pmSource.h"
+#include "pmSourceGroups.h"
+
+// the strategy here is to divide the image into 2x2 blocks of cells and cycle through
+// the four discontiguous sets of cells, threading all within a set and blocking between
+// sets
+
+// we divide the image region into 2*2 blocks of size Nx*Ny, the image will have
+// Cx*Cy blocks so that (2Nx)Cx = numCols, (2Ny)Cy = numRows.  We want to choose Cx and
+// Cy so that (2Nx)Cx * (2Ny)Cy = 4 * NFILL * nThreads -- each of the four sets of cells
+// has enough cells to allow NFILL cells for each thread (to better distribute heavy and
+// light load cells
+
+// the array runs from readout->image->col0 to readout->image->col0 + readout->image->numCols
+
+
+static void sourceGroupsFree(pmSourceGroups *groups)
+{
+    psFree(groups->groups);
+    return;
+}
+
+
+bool pmSourceGroupsCoordToCell(int *group, // Group number, returned
+                               int *cell,  // Cell number, returned
+                               float x, float y, // Coordinates
+                               const pmSourceGroups *groups // Groups
+                               )
+{
+    // XXX need to handle edges
+    int ix = (x - groups->Xo) / (2 * groups->Nx);
+    ix = PS_MAX(0, PS_MIN(ix, groups->Cx - 1));
+
+    int iy = (y - groups->Yo) / (2 * groups->Ny);
+    iy = PS_MAX(0, PS_MIN(iy, groups->Cy - 1));
+
+    int jx = (((int)(x - groups->Xo)) % (2 * groups->Nx)) / groups->Nx;
+    jx = PS_MAX(0, PS_MIN(jx, groups->Nx - 1));
+
+    int jy = (((int)(y - groups->Yo)) % (2 * groups->Ny)) / groups->Ny;
+    jy = PS_MAX(0, PS_MIN(jy, groups->Ny - 1));
+
+    *group = jx + 2 * jy;
+    *cell  = ix + groups->Cx * iy;
+
+    return true;
+}
+
+
+pmSourceGroups *pmSourceGroupsAlloc(const pmReadout *readout, int nThreads)
+{
+    pmSourceGroups *groups = psAlloc(sizeof(pmSourceGroups)); // Groups, to return
+    psMemSetDeallocator(groups, (psFreeFunc)sourceGroupsFree);
+
+    groups->Xo = readout->image->col0;
+    groups->Yo = readout->image->row0;
+
+    if (nThreads == 0 || nThreads == 1) {
+        // Trivial case
+        groups->Cx = groups->Cy = 1;
+        groups->Nx = readout->image->numCols;
+        groups->Ny = readout->image->numRows;
+        groups->groups = psArrayAlloc(1);
+        return groups;
+    }
+
+    int nCells = nThreads * 2*2;        // number of cells in a single set
+    int C = sqrt(nCells) + 0.5;
+    int Cx = 1, Cy = 1;                 // Number of cells in x and y
+
+    // we need to assign Cx and Cy based on the dimensionality of the image
+    // crude way to find most evenly balanced factors of nCells:
+    for (int i = C; i >= 1; i--) {
+        int C1 = nCells / C;
+        int C2 = nCells / C1;
+        if (C1*C2 != nCells) continue;
+
+        if (readout->image->numRows > readout->image->numCols) {
+            Cx = PS_MAX(C1, C2);
+            Cy = PS_MIN(C1, C2);
+        } else {
+            Cx = PS_MAX(C1, C2);
+            Cy = PS_MIN(C1, C2);
+        }
+    }
+
+    groups->Cx = Cx;
+    groups->Cy = Cy;
+    groups->Nx = readout->image->numCols / (Cx*2);
+    groups->Ny = readout->image->numRows / (Cy*2);
+    groups->groups = psArrayAlloc(4);
+
+    return groups;
+}
+
+
+
+pmSourceGroups *pmSourceGroupsFromSources(const pmReadout *readout, const psArray *sources, int nThreads)
+{
+    PM_ASSERT_READOUT_NON_NULL(readout, NULL);
+    PM_ASSERT_READOUT_IMAGE(readout, NULL);
+    PS_ASSERT_ARRAY_NON_NULL(sources, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(nThreads, NULL);
+
+    pmSourceGroups *groups = pmSourceGroupsAlloc(readout, nThreads);
+
+    int numSources = sources->n;        // Number of sources
+    psVector *x = psVectorAlloc(numSources, PS_TYPE_F32), *y = psVectorAlloc(numSources, PS_TYPE_F32);
+    for (int i = 0; i < numSources; i++) {
+        pmSource *source = sources->data[i]; // Source of interest
+        x->data.F32[i] = source->peak->xf;
+        y->data.F32[i] = source->peak->yf;
+    }
+
+    if (!pmSourceGroupsPopulate(groups, x, y)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to populate source groups");
+        psFree(x);
+        psFree(y);
+        psFree(groups);
+        return NULL;
+    }
+
+    psFree(x);
+    psFree(y);
+    return groups;
+}
+
+pmSourceGroups *pmSourceGroupsFromVectors(const pmReadout *readout, const psVector *x,
+                                          const psVector *y, int nThreads)
+{
+    PM_ASSERT_READOUT_NON_NULL(readout, NULL);
+    PM_ASSERT_READOUT_IMAGE(readout, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(x, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(y, NULL);
+    PS_ASSERT_VECTOR_TYPE(x, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(y, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(x, y, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(nThreads, NULL);
+
+    pmSourceGroups *groups = pmSourceGroupsAlloc(readout, nThreads);
+    if (!pmSourceGroupsPopulate(groups, x, y)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to populate source groups");
+        psFree(groups);
+        return NULL;
+    }
+
+    return groups;
+}
+
+
+bool pmSourceGroupsPopulate(pmSourceGroups *groups, const psVector *x, const psVector *y)
+{
+    PS_ASSERT_PTR_NON_NULL(groups, false);
+    PS_ASSERT_VECTOR_NON_NULL(x, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(y, NULL);
+    PS_ASSERT_VECTOR_TYPE(x, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(y, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(x, y, NULL);
+
+    int numSources = x->n;                // Number of sources
+
+    // Populate the groups
+    long total = groups->Cx * groups->Cy; // Total size
+    for (int i = 0; i < groups->groups->n; i++) {
+        psArray *cells = psArrayAlloc(total); // Cells within a group
+        groups->groups->data[i] = cells;
+        for (int j = 0; j < cells->n; j++) {
+            cells->data[j] = psVectorAllocEmpty(numSources / total, PS_TYPE_S32);
+        }
+    }
+
+    // Populate the cells
+    if (total == 1) {
+        // Trivial case: Cx == Cy == 1
+        psArray *cells = groups->groups->data[0]; // Cell
+        psVector *cellSources = cells->data[0];   // Indices of sources for cell
+        for (int i = 0; i < numSources; i++) {
+            cellSources->data.S32[i] = i;
+        }
+	cellSources->n = numSources;
+    } else {
+        for (int i = 0; i < numSources; i++) {
+            int group = 0, cell = 0;        // Group and cell index for source
+            pmSourceGroupsCoordToCell(&group, &cell, x->data.F32[i], y->data.F32[i], groups);
+
+            psArray *cells = groups->groups->data[group]; // Cells for group
+            psVector *cellSources = cells->data[cell];    // Indices of sources for cell within group
+            psVectorAppend(cellSources, i);
+        }
+    }
+
+    return groups;
+}
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceGroups.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceGroups.h	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceGroups.h	(revision 27839)
@@ -0,0 +1,54 @@
+#ifndef PM_SOURCE_GROUPS_H
+#define PM_SOURCE_GROUPS_H
+
+#include <pslib.h>
+
+#include "pmFPA.h"
+
+/// Groups of sources
+///
+/// We divide up the sources for threading.
+typedef struct {
+    int Xo, Yo;                         // Offset
+    int Nx, Ny;                         // Size of cells
+    int Cx, Cy;                         // Number of cells in x and y
+    psArray *groups;                    // Cell groups
+} pmSourceGroups;
+
+/// Allocate the source groups
+pmSourceGroups *pmSourceGroupsAlloc(
+    const pmReadout *readout,           // Readout on which the sources are defined
+    int nThreads                        // Number of threads
+    );
+
+
+/// Return the group and cell indices given x,y coordinates
+bool pmSourceGroupsCoordToCell(
+    int *group,                         // Group number, returned
+    int *cell,                          // Cell number, returned
+    float x, float y,                   // Coordinates
+    const pmSourceGroups *groups        // Groups
+    );
+
+/// Populate the source groups
+bool pmSourceGroupsPopulate(
+    pmSourceGroups *groups,              // Source groups to populate
+    const psVector *x, const psVector *y // Coordinates of sources
+    );
+
+
+/// Generate source groups from an array of sources
+pmSourceGroups *pmSourceGroupsFromSources(
+    const pmReadout *readout,           // Readout on which the sources are defined
+    const psArray *sources,             // Array of sources
+    int nThreads                        // Number of threads
+    );
+
+/// Generate source groups from vectors with source positions
+pmSourceGroups *pmSourceGroupsFromVectors(
+    const pmReadout *readout,             // Readout on which the sources are defined
+    const psVector *x, const psVector *y, // Coordinates of sources
+    int nThreads                          // Number of threads
+    );
+
+#endif
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceIO.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceIO.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceIO.c	(revision 27839)
@@ -40,10 +40,85 @@
 #include "pmPSF.h"
 #include "pmModel.h"
+#include "pmDetections.h"
 #include "pmSource.h"
 #include "pmModelClass.h"
+#include "pmDetEff.h"
 #include "pmSourceIO.h"
 
 #define BLANK_HEADERS "BLANK.HEADERS"   // Name of metadata in camera configuration containing header names
                                         // for putting values into a blank PHU
+
+// lookup the EXTNAME values used for table data and image header segments
+static bool sourceExtensions(psString *headname, // Extension name for header
+                             psString *dataname, // Extension name for data
+                             psString *deteffname, // Extension name for detection efficiency
+                             psString *xsrcname, // Extension name for extended sources
+                             psString *xfitname, // Extension name for extended fits
+                             const pmFPAfile *file, // File of interest
+                             const pmFPAview *view // View to level of interest
+                             )
+{
+    bool status;                        // Status of MD lookup
+
+    // Menu of EXTNAME rules
+    psMetadata *menu = psMetadataLookupMetadata(&status, file->camera, "EXTNAME.RULES");
+    if (!menu) {
+        psError(PS_ERR_UNKNOWN, true, "missing EXTNAME.RULES in camera.config");
+        return false;
+    }
+
+    // EXTNAME for image header
+    if (headname) {
+        const char *rule = psMetadataLookupStr(&status, menu, "CMF.HEAD");
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.HEAD in EXTNAME.RULES in camera.config");
+            return false;
+        }
+        *headname = pmFPAfileNameFromRule(rule, file, view);
+    }
+
+    // EXTNAME for table data
+    if (dataname) {
+        const char *rule = psMetadataLookupStr(&status, menu, "CMF.DATA");
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.DATA in EXTNAME.RULES in camera.config");
+            return false;
+        }
+        *dataname = pmFPAfileNameFromRule(rule, file, view);
+    }
+
+    // EXTNAME for detection efficiency
+    if (deteffname) {
+        const char *rule = psMetadataLookupStr(&status, menu, "CMF.DETEFF");
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.DETEFF in EXTNAME.RULES in camera.config");
+            return false;
+        }
+        *deteffname = pmFPAfileNameFromRule(rule, file, view);
+    }
+
+    // EXTNAME for extended source data table
+    if (xsrcname) {
+        const char *rule = psMetadataLookupStr(&status, menu, "CMF.XSRC");
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.XSRC in EXTNAME.RULES in camera.config");
+            return false;
+        }
+        *xsrcname = pmFPAfileNameFromRule (rule, file, view);
+    }
+
+    if (xfitname) {
+        // EXTNAME for extended source data table
+        const char *rule = psMetadataLookupStr(&status, menu, "CMF.XFIT");
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.XFIT in EXTNAME.RULES in camera.config");
+            return false;
+        }
+        *xfitname = pmFPAfileNameFromRule (rule, file, view);
+    }
+
+    return true;
+}
+
 
 // translations between psphot object types and dophot object types
@@ -268,19 +343,19 @@
     pmHDU *hdu;
     psMetadata *updates;
-    psMetadata *outhead;
-
-    char *exttype  = NULL;
-    char *dataname = NULL;
-    char *xsrcname = NULL;
-    char *xfitname = NULL;
-    char *headname = NULL;
 
     // if sources is NULL, write out an empty table
-    // input / output sources are stored on the readout->analysis as "PSPHOT.SOURCES" -- a better name might be something like PM_SOURCE_DATA
-    psArray *sources = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.SOURCES");
+    // input / output sources are stored on the readout->analysis as "PSPHOT.DETECTIONS"
+
+    psArray *sources = NULL;
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    if (detections) {
+	sources = detections->allSources;
+    }
     if (!sources) {
+	detections = pmDetectionsAlloc();
         sources = psArrayAlloc(0);
-        psMetadataAddArray(readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_META_REPLACE, "Blank array of sources", sources);
-        psFree(sources); // Held onto by the metadata, so we can continue to use
+	detections->allSources = sources;
+        psMetadataAddPtr(readout->analysis, PS_LIST_TAIL, "PSPHOT.DETECTIONS", PS_DATA_UNKNOWN | PS_META_REPLACE, "Blank array of sources", detections);
+        psFree(detections); // Held onto by the metadata, so we can continue to use
     }
 
@@ -298,37 +373,38 @@
         break;
 
-      case PM_FPA_FILE_CMP:
-        // a SPLIT format : only one header and object table per file
-        hdu = pmFPAviewThisHDU (view, fpa);
-        if (!hdu) {
-            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find HDU to write sources.");
-            return false;
-        }
-
-        // copy the header to an output header, add the output header data
-        outhead = psMetadataCopy (NULL, hdu->header);
-
-        // copy over the entries saved by PSPHOT
-        updates = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.HEADER");
-        if (updates) {
-            psMetadataCopy (outhead, updates);
-        }
-
-        // copy over the entries saved by PSASTRO
-        updates = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.HEADER");
-        if (updates) {
-            psMetadataCopy (outhead, updates);
-        }
-
-        bool status = pmSourcesWriteCMP (sources, file->filename, outhead);
-        psFree (outhead);
-
-        if (!status) {
-            psError(PS_ERR_IO, false, "Failed to write CMP file\n");
-            return false;
-        }
-        break;
-
-      case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_CMP: {
+	  // a SPLIT format : only one header and object table per file
+	  hdu = pmFPAviewThisHDU (view, fpa);
+	  if (!hdu) {
+	      psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find HDU to write sources.");
+	      return false;
+	  }
+
+	  // copy the header to an output header, add the output header data
+	  psMetadata *outhead = psMetadataCopy (NULL, hdu->header);
+
+	  // copy over the entries saved by PSPHOT
+	  updates = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.HEADER");
+	  if (updates) {
+	      psMetadataCopy (outhead, updates);
+	  }
+
+	  // copy over the entries saved by PSASTRO
+	  updates = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.HEADER");
+	  if (updates) {
+	      psMetadataCopy (outhead, updates);
+	  }
+
+	  bool status = pmSourcesWriteCMP (sources, file->filename, outhead);
+	  psFree (outhead);
+
+	  if (!status) {
+	      psError(PS_ERR_IO, false, "Failed to write CMP file\n");
+	      return false;
+	  }
+	  break;
+      }
+
+      case PM_FPA_FILE_CMF: 
         // write a header? (only if this is the first readout for cell)
         //   note that the file->header is set to track the last hdu->header written
@@ -345,6 +421,6 @@
         psMetadata *recipe = psMetadataLookupMetadata(&status, config->recipes, "PSPHOT");
         if (!status) {
-          psError(PS_ERR_UNKNOWN, true, "missing recipe PSPHOT in config data");
-          return false;
+	    psError(PS_ERR_UNKNOWN, true, "missing recipe PSPHOT in config data");
+	    return false;
         }
 
@@ -354,49 +430,14 @@
 
         // define the EXTNAME values for the different data segments:
-        {
-            // lookup the EXTNAME values used for table data and image header segments
-            char *rule = NULL;
-
-            // Menu of EXTNAME rules
-            psMetadata *menu = psMetadataLookupMetadata(&status, file->camera, "EXTNAME.RULES");
-            if (!menu) {
-                psError(PS_ERR_UNKNOWN, true, "missing EXTNAME.RULES in camera.config");
-                return false;
-            }
-
-            // EXTNAME for image header
-            rule = psMetadataLookupStr(&status, menu, "CMF.HEAD");
-            if (!rule) {
-                psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.HEAD in EXTNAME.RULES in camera.config");
-                return false;
-            }
-            headname = pmFPAfileNameFromRule (rule, file, view);
-
-            // EXTNAME for table data
-            rule = psMetadataLookupStr(&status, menu, "CMF.DATA");
-            if (!rule) {
-                psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.DATA in EXTNAME.RULES in camera.config");
-                return false;
-            }
-            dataname = pmFPAfileNameFromRule (rule, file, view);
-
-            if (XSRC_OUTPUT) {
-              // EXTNAME for extended source data table
-              rule = psMetadataLookupStr(&status, menu, "CMF.XSRC");
-              if (!rule) {
-                psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.XSRC in EXTNAME.RULES in camera.config");
-                return false;
-              }
-              xsrcname = pmFPAfileNameFromRule (rule, file, view);
-            }
-            if (XFIT_OUTPUT) {
-              // EXTNAME for extended source data table
-              rule = psMetadataLookupStr(&status, menu, "CMF.XFIT");
-              if (!rule) {
-                psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.XFIT in EXTNAME.RULES in camera.config");
-                return false;
-              }
-              xfitname = pmFPAfileNameFromRule (rule, file, view);
-            }
+        psString headname = NULL;
+        psString dataname = NULL;
+        psString deteffname = NULL;
+        psString xsrcname = NULL;
+        psString xfitname = NULL;
+        if (!sourceExtensions(&headname, &dataname, &deteffname, 
+			      XSRC_OUTPUT ? &xsrcname : NULL,
+                              XFIT_OUTPUT ? &xfitname : NULL, 
+			      file, view)) {
+            return false;
         }
 
@@ -456,10 +497,10 @@
         }
 
-        // write out the TABLE data segment
+        // write out the Object TABLE data segment(s)
         {
             // create a header to hold the output data
-            outhead = psMetadataAlloc ();
-
-            exttype = psMemIncrRefCounter (psMetadataLookupStr(&status, recipe, "OUTPUT.FORMAT"));
+            psMetadata *outhead = psMetadataAlloc ();
+	    
+	    char *exttype = psMemIncrRefCounter (psMetadataLookupStr(&status, recipe, "OUTPUT.FORMAT"));
             if (!exttype) {
                 exttype = psStringCopy ("SMPDATA");
@@ -469,81 +510,112 @@
             psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTHEAD", PS_META_REPLACE, "name of image extension w/", headname);
             psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTTYPE", PS_META_REPLACE, "extension type", exttype);
-            psFree (exttype);
 
             // if we request XSRC output, add the XSRC name to this header
             if (xsrcname) {
-              psMetadataAddStr (outhead, PS_LIST_TAIL, "XSRCNAME", PS_META_REPLACE, "name of XSRC table extension", xsrcname);
+		psMetadataAddStr (outhead, PS_LIST_TAIL, "XSRCNAME", PS_META_REPLACE, "name of XSRC table extension", xsrcname);
             }
             if (xfitname) {
-              psMetadataAddStr (outhead, PS_LIST_TAIL, "XFITNAME", PS_META_REPLACE, "name of XFIT table extension", xfitname);
+		psMetadataAddStr (outhead, PS_LIST_TAIL, "XFITNAME", PS_META_REPLACE, "name of XFIT table extension", xfitname);
             }
 
             // XXX these are case-sensitive since the EXTYPE is case-sensitive
-            status = false;
+            status = true;
             if (!strcmp (exttype, "SMPDATA")) {
-                status = pmSourcesWrite_SMPDATA (file->fits, sources, file->header, outhead, dataname);
+                status &= pmSourcesWrite_SMPDATA (file->fits, sources, file->header, outhead, dataname);
             }
             if (!strcmp (exttype, "PS1_DEV_0")) {
-                status = pmSourcesWrite_PS1_DEV_0 (file->fits, sources, file->header, outhead, dataname);
+                status &= pmSourcesWrite_PS1_DEV_0 (file->fits, sources, file->header, outhead, dataname);
             }
             if (!strcmp (exttype, "PS1_DEV_1")) {
-                status = pmSourcesWrite_PS1_DEV_1 (file->fits, sources, file->header, outhead, dataname);
+                status &= pmSourcesWrite_PS1_DEV_1 (file->fits, sources, file->header, outhead, dataname);
             }
             if (!strcmp (exttype, "PS1_CAL_0")) {
-                status = pmSourcesWrite_PS1_CAL_0 (file->fits, readout, sources, file->header, outhead, dataname);
+                status &= pmSourcesWrite_PS1_CAL_0 (file->fits, readout, sources, file->header, outhead, dataname);
             }
             if (!strcmp (exttype, "PS1_V1")) {
-                status = pmSourcesWrite_CMF_PS1_V1 (file->fits, readout, sources, file->header, outhead, dataname);
+                status &= pmSourcesWrite_CMF_PS1_V1 (file->fits, readout, sources, file->header, outhead, dataname);
             }
             if (!strcmp (exttype, "PS1_V2")) {
-                status = pmSourcesWrite_CMF_PS1_V2 (file->fits, readout, sources, file->header, outhead, dataname);
-            }
+                status &= pmSourcesWrite_CMF_PS1_V2 (file->fits, readout, sources, file->header, outhead, dataname);
+            }
+            if (!strcmp (exttype, "PS1_DV1")) {
+                status &= pmSourcesWrite_CMF_PS1_DV1 (file->fits, readout, sources, file->header, outhead, dataname);
+            }
+
             if (xsrcname) {
-              if (!strcmp (exttype, "PS1_DEV_1")) {
-                  status = pmSourcesWrite_PS1_DEV_1_XSRC (file->fits, sources, xsrcname, recipe);
-              }
-              if (!strcmp (exttype, "PS1_CAL_0")) {
-                  status = pmSourcesWrite_PS1_CAL_0_XSRC (file->fits, readout, sources, file->header, xsrcname, recipe);
-              }
-              if (!strcmp (exttype, "PS1_V1")) {
-                  status = pmSourcesWrite_CMF_PS1_V1_XSRC (file->fits, sources, xsrcname, recipe);
-              }
-              if (!strcmp (exttype, "PS1_V2")) {
-                  status = pmSourcesWrite_CMF_PS1_V2_XSRC (file->fits, sources, xsrcname, recipe);
-              }
+		if (!strcmp (exttype, "PS1_DEV_1")) {
+		    status &= pmSourcesWrite_PS1_DEV_1_XSRC (file->fits, sources, xsrcname, recipe);
+		}
+		if (!strcmp (exttype, "PS1_CAL_0")) {
+		    status &= pmSourcesWrite_PS1_CAL_0_XSRC (file->fits, readout, sources, file->header, xsrcname, recipe);
+		}
+		if (!strcmp (exttype, "PS1_V1")) {
+		    status &= pmSourcesWrite_CMF_PS1_V1_XSRC (file->fits, readout, sources, file->header, xsrcname, recipe);
+		}
+		if (!strcmp (exttype, "PS1_V2")) {
+		    status &= pmSourcesWrite_CMF_PS1_V2_XSRC (file->fits, readout, sources, file->header, xsrcname, recipe);
+		}
+		if (!strcmp (exttype, "PS1_DV1")) {
+		    status &= pmSourcesWrite_CMF_PS1_DV1_XSRC (file->fits, readout, sources, file->header, xsrcname, recipe);
+		}
             }
             if (xfitname) {
-              if (!strcmp (exttype, "PS1_DEV_1")) {
-                  status = pmSourcesWrite_PS1_DEV_1_XFIT (file->fits, sources, xfitname);
-              }
-              if (!strcmp (exttype, "PS1_CAL_0")) {
-                  status = pmSourcesWrite_PS1_CAL_0_XFIT (file->fits, readout, sources, file->header, xfitname);
-              }
-              if (!strcmp (exttype, "PS1_V1")) {
-                  status = pmSourcesWrite_CMF_PS1_V1_XFIT (file->fits, sources, xfitname);
-              }
-              if (!strcmp (exttype, "PS1_V2")) {
-                  status = pmSourcesWrite_CMF_PS1_V2_XFIT (file->fits, sources, xfitname);
-              }
-            }
+		if (!strcmp (exttype, "PS1_DEV_1")) {
+		    status &= pmSourcesWrite_PS1_DEV_1_XFIT (file->fits, sources, xfitname);
+		}
+		if (!strcmp (exttype, "PS1_CAL_0")) {
+		    status &= pmSourcesWrite_PS1_CAL_0_XFIT (file->fits, readout, sources, file->header, xfitname);
+		}
+		if (!strcmp (exttype, "PS1_V1")) {
+		    status &= pmSourcesWrite_CMF_PS1_V1_XFIT (file->fits, readout, sources, xfitname);
+		}
+		if (!strcmp (exttype, "PS1_V2")) {
+		    status &= pmSourcesWrite_CMF_PS1_V2_XFIT (file->fits, readout, sources, xfitname);
+		}
+		if (!strcmp (exttype, "PS1_DV1")) {
+		    status &= pmSourcesWrite_CMF_PS1_DV1_XFIT (file->fits, readout, sources, xfitname);
+		}
+            }
+	    psFree (outhead);
+	    psFree (exttype);
+
             if (!status) {
                 psError(PS_ERR_IO, false, "writing CMF data to %s with format %s\n", file->filename, exttype);
-                psFree (headname);
-                psFree (dataname);
-                psFree (xsrcname);
-                psFree (xfitname);
-                psFree (outhead);
-                return false;
-            }
-        }
+		goto escape;
+            }
+        }
+
+
+	// write out the detection efficiency TABLE segments
+	if (deteffname) {
+            // create a header to hold the output data
+            psMetadata *outhead = psMetadataAlloc ();
+            psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTHEAD", PS_META_REPLACE, "name of image extension w/", headname);
+	    psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTTYPE", PS_META_REPLACE, "extension type", "DETEFF");
+
+	    status = pmReadoutWriteDetEff(file->fits, readout, outhead, deteffname);
+	    psFree (outhead);
+
+            if (!status) {
+                psError(PS_ERR_IO, false, "writing DETEFF data to %s\n", file->filename);
+		goto escape;
+            }
+	}
+	psFree (headname);
+	psFree (dataname);
+	psFree (xsrcname);
+	psFree (xfitname);
+	psFree (deteffname);
 
         psTrace ("pmFPAfile", 5, "wrote ext data %s (type: %d)\n", file->filename, file->type);
-
-        psFree (headname);
-        psFree (dataname);
-        psFree (xsrcname);
-        psFree (xfitname);
-        psFree (outhead);
         break;
+
+      escape:
+	psFree (headname);
+	psFree (dataname);
+	psFree (xsrcname);
+	psFree (xfitname);
+	psFree (deteffname);
+	return false;
 
       default:
@@ -552,4 +624,5 @@
     }
     return true;
+
 }
 // a MEF CMF file has: PHU, CELL-HEAD, TABLE, CELL-HEAD, TABLE, TABLE, TABLE...
@@ -572,6 +645,6 @@
     // not needed if only one chip
     if (file->fpa->chips->n == 1) {
-	pmSourceIO_WriteMatchedRefs (file->fits, file->fpa, config);
-	return true;
+        pmSourceIO_WriteMatchedRefs (file->fits, file->fpa, config);
+        return true;
     }
 
@@ -885,34 +958,20 @@
         hdu = pmFPAviewThisHDU (view, file->fpa);
 
-        // lookup the EXTNAME values used for table data and image header segments
-        char *rule = NULL;
-        // Menu of EXTNAME rules
-        psMetadata *menu = psMetadataLookupMetadata(&status, file->camera, "EXTNAME.RULES");
-        if (!menu) {
-            psError(PS_ERR_UNKNOWN, true, "missing EXTNAME.RULES in camera.config");
-            return false;
-        }
-        // EXTNAME for image header
-        rule = psMetadataLookupStr(&status, menu, "CMF.HEAD");
-        if (!rule) {
-            psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.HEAD in EXTNAME.RULES in camera.config");
-            return false;
-        }
-        char *headname = pmFPAfileNameFromRule (rule, file, view);
-        // EXTNAME for table data
-        rule = psMetadataLookupStr(&status, menu, "CMF.DATA");
-        if (!rule) {
-            psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.DATA in EXTNAME.RULES in camera.config");
-            return false;
-        }
-        char *dataname = pmFPAfileNameFromRule (rule, file, view);
+        // define the EXTNAME values for the different data segments:
+        psString headname = NULL;
+        psString dataname = NULL;
+        psString deteffname = NULL;
+        if (!sourceExtensions(&headname, &dataname, &deteffname, NULL, NULL, file, view)) {
+            return false;
+        }
 
         // advance to the IMAGE HEADER extension
         if (hdu->header == NULL) {
             // if the IMAGE header does not exist, we have no data for this view
-            if (!psFitsMoveExtName (file->fits, headname)) {
+            if (!psFitsMoveExtNameClean (file->fits, headname)) {
                 readout->data_exists = false;
                 psFree (headname);
                 psFree (dataname);
+                psFree (deteffname);
                 return true;
             }
@@ -938,4 +997,11 @@
         if (!tableHeader) psAbort("cannot read table header");
 
+        char *xtension = psMetadataLookupStr (NULL, tableHeader, "XTENSION");
+        if (!xtension) psAbort("cannot read table type");
+	if (strcmp (xtension, "BINTABLE")) {
+	    psWarning ("no binary table in extension %s, skipping\n", dataname);
+	    return false;
+	}
+
         char *exttype = psMetadataLookupStr (NULL, tableHeader, "EXTTYPE");
         if (!exttype) psAbort("cannot read table type");
@@ -958,4 +1024,17 @@
                 sources = pmSourcesRead_CMF_PS1_V2 (file->fits, hdu->header);
             }
+            if (!strcmp (exttype, "PS1_DV1")) {
+                sources = pmSourcesRead_CMF_PS1_DV1 (file->fits, hdu->header);
+            }
+
+            if (!pmReadoutReadDetEff(file->fits, readout, deteffname)) {
+#if 0
+                psError(PS_ERR_IO, false, "Unable to read detection efficiency");
+                return false;
+#else
+                // No great loss
+                psErrorClear();
+#endif
+            }
         }
 
@@ -963,4 +1042,5 @@
         psFree (headname);
         psFree (dataname);
+	psFree (deteffname);
         psFree (tableHeader);
         break;
@@ -971,6 +1051,9 @@
     }
     readout->data_exists = true;
-    status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY, "input sources", sources);
-    psFree (sources);
+
+    pmDetections *detections = pmDetectionsAlloc();
+    detections->allSources = sources;
+    status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.DETECTIONS", PS_DATA_ARRAY, "input sources", detections);
+    psFree (detections);
     return true;
 }
@@ -1064,9 +1147,10 @@
     bool status;
 
-    // select the psf of interest
-    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.SOURCES");
-    if (!psf) return false;
-    return true;
-}
-
-    
+    // select the detections of interest
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    if (!detections) return false;
+    if (!detections->allSources) return false;
+    return true;
+}
+
+
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceIO.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceIO.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceIO.h	(revision 27839)
@@ -36,10 +36,14 @@
 
 bool pmSourcesWrite_CMF_PS1_V1 (psFits *fits, pmReadout *readout, psArray *sources, psMetadata *imageHeader, psMetadata *tableHeader, char *extname);
-bool pmSourcesWrite_CMF_PS1_V1_XSRC (psFits *fits, psArray *sources, char *extname, psMetadata *recipe);
-bool pmSourcesWrite_CMF_PS1_V1_XFIT (psFits *fits, psArray *sources, char *extname);
+bool pmSourcesWrite_CMF_PS1_V1_XSRC (psFits *fits, pmReadout *readout, psArray *sources, psMetadata *imageHeader, char *extname, psMetadata *recipe);
+bool pmSourcesWrite_CMF_PS1_V1_XFIT (psFits *fits, pmReadout *readout, psArray *sources, char *extname);
 
 bool pmSourcesWrite_CMF_PS1_V2 (psFits *fits, pmReadout *readout, psArray *sources, psMetadata *imageHeader, psMetadata *tableHeader, char *extname);
-bool pmSourcesWrite_CMF_PS1_V2_XSRC (psFits *fits, psArray *sources, char *extname, psMetadata *recipe);
-bool pmSourcesWrite_CMF_PS1_V2_XFIT (psFits *fits, psArray *sources, char *extname);
+bool pmSourcesWrite_CMF_PS1_V2_XSRC (psFits *fits, pmReadout *readout, psArray *sources, psMetadata *imageHeader, char *extname, psMetadata *recipe);
+bool pmSourcesWrite_CMF_PS1_V2_XFIT (psFits *fits, pmReadout *readout, psArray *sources, char *extname);
+
+bool pmSourcesWrite_CMF_PS1_DV1 (psFits *fits, pmReadout *readout, psArray *sources, psMetadata *imageHeader, psMetadata *tableHeader, char *extname);
+bool pmSourcesWrite_CMF_PS1_DV1_XSRC (psFits *fits, pmReadout *readout, psArray *sources, psMetadata *imageHeader, char *extname, psMetadata *recipe);
+bool pmSourcesWrite_CMF_PS1_DV1_XFIT (psFits *fits, pmReadout *readout, psArray *sources, char *extname);
 
 bool pmSource_CMF_WritePHU (const pmFPAview *view, pmFPAfile *file, pmConfig *config);
@@ -53,4 +57,5 @@
 psArray *pmSourcesRead_CMF_PS1_V1 (psFits *fits, psMetadata *header);
 psArray *pmSourcesRead_CMF_PS1_V2 (psFits *fits, psMetadata *header);
+psArray *pmSourcesRead_CMF_PS1_DV1 (psFits *fits, psMetadata *header);
 
 bool pmSourcesWritePSFs (psArray *sources, char *filename);
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceIO_CMF_PS1_DV1.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceIO_CMF_PS1_DV1.c	(revision 27839)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceIO_CMF_PS1_DV1.c	(revision 27839)
@@ -0,0 +1,660 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-18 02:44:19 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmSpan.h"
+#include "pmFootprint.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourceIO.h"
+
+// panstarrs-style FITS table output (header + table in 1st extension)
+// this format consists of a header derived from the image header
+// followed by a zero-size matrix, followed by the table data
+
+// This version has elements intended for difference images & forced photometry:
+// diffStats entries (good for dipoles); flux + flux error (for insignificant detections)
+bool pmSourcesWrite_CMF_PS1_DV1 (psFits *fits, pmReadout *readout, psArray *sources,
+                                psMetadata *imageHeader, psMetadata *tableHeader, char *extname)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(extname, false);
+
+    int i;
+    psArray *table;
+    psMetadata *row;
+    psF32 *PAR, *dPAR;
+    psEllipseAxes axes;
+    psF32 xPos, yPos;
+    psF32 xErr, yErr;
+    psF32 errMag, chisq, apRadius;
+    psS32 nPix, nDOF;
+
+    pmChip *chip = readout->parent->parent;
+    pmFPA  *fpa  = chip->parent;
+
+    bool status1 = false;
+    bool status2 = false;
+    float magOffset = NAN;
+    float exptime   = psMetadataLookupF32 (&status1, fpa->concepts, "FPA.EXPOSURE");
+    float zeropt    = psMetadataLookupF32(&status2, fpa->concepts, "FPA.ZP");
+    if (!isfinite(zeropt)) {
+        zeropt    = psMetadataLookupF32 (&status2, imageHeader, "ZPT_OBS");
+    }
+    if (status1 && status2 && (exptime > 0.0)) {
+        magOffset = zeropt + 2.5*log10(exptime);
+    }
+    float zeroptErr = psMetadataLookupF32 (&status2, imageHeader, "ZPT_ERR");
+
+    // if the sequence is defined, write these in seq order; otherwise
+    // write them in S/N order:
+    if (sources->n > 0) {
+        pmSource *source = (pmSource *) sources->data[0];
+        if (source->seq == -1) {
+          // let's write these out in S/N order
+          sources = psArraySort (sources, pmSourceSortBySN);
+        } else {
+          sources = psArraySort (sources, pmSourceSortBySeq);
+        }
+    }
+
+    table = psArrayAllocEmpty (sources->n);
+
+    // we write out PSF-fits for all sources, regardless of quality.  the source flags tell us the state
+    // by the time we call this function, all values should be assigned.  let's use asserts to be sure in some cases.
+    for (i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+
+        // If source->seq is -1, source was generated in this analysis.  If source->seq is
+        // not -1, source was read from elsewhere: in the latter case, preserve the source
+        // ID.  source.seq is used instead of source.id since the latter is a const
+        // generated on Alloc, and would thus be wrong for read in sources.
+        if (source->seq == -1) {
+          source->seq = i;
+        }
+
+        // no difference between PSF and non-PSF model
+        pmModel *model = source->modelPSF;
+
+        if (model != NULL) {
+            PAR = model->params->data.F32;
+            dPAR = model->dparams->data.F32;
+            xPos = PAR[PM_PAR_XPOS];
+            yPos = PAR[PM_PAR_YPOS];
+            if (source->mode & PM_SOURCE_MODE_NONLINEAR_FIT) {
+              xErr = dPAR[PM_PAR_XPOS];
+              yErr = dPAR[PM_PAR_YPOS];
+            } else {
+              // in linear-fit mode, there is no error on the centroid
+              xErr = source->peak->dx;
+              yErr = source->peak->dy;
+            }
+            if (isfinite(PAR[PM_PAR_SXX]) && isfinite(PAR[PM_PAR_SXX]) && isfinite(PAR[PM_PAR_SXX])) {
+                axes = pmPSF_ModelToAxes (PAR, 20.0);
+            } else {
+                axes.major = NAN;
+                axes.minor = NAN;
+                axes.theta = NAN;
+            }
+            chisq = model->chisq;
+            nDOF = model->nDOF;
+            nPix = model->nPix;
+            apRadius = source->apRadius;
+            errMag = model->dparams->data.F32[PM_PAR_I0] / model->params->data.F32[PM_PAR_I0];
+        } else {
+            xPos = source->peak->xf;
+            yPos = source->peak->yf;
+            xErr = source->peak->dx;
+            yErr = source->peak->dy;
+            axes.major = NAN;
+            axes.minor = NAN;
+            axes.theta = NAN;
+            chisq = NAN;
+            nDOF = 0;
+            nPix = 0;
+            apRadius = NAN;
+            errMag = NAN;
+        }
+
+        float calMag = isfinite(magOffset) ? source->psfMag + magOffset : NAN;
+        float peakMag = (source->peak->flux > 0) ? -2.5*log10(source->peak->flux) : NAN;
+        psS16 nImageOverlap = 1;
+
+        psSphere ptSky = {0.0, 0.0, 0.0, 0.0};
+        float posAngle = 0.0;
+        float pltScale = 0.0;
+        pmSourceLocalAstrometry (&ptSky, &posAngle, &pltScale, chip, xPos, yPos);
+
+	pmSourceDiffStats diffStats;
+	pmSourceDiffStatsInit(&diffStats);
+	if (source->diffStats) {
+	    diffStats = *source->diffStats;
+	}
+
+        row = psMetadataAlloc ();
+        psMetadataAdd (row, PS_LIST_TAIL, "IPP_IDET",         PS_DATA_U32, "IPP detection identifier index",             source->seq);
+        psMetadataAdd (row, PS_LIST_TAIL, "X_PSF",            PS_DATA_F32, "PSF x coordinate",                           xPos);
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_PSF",            PS_DATA_F32, "PSF y coordinate",                           yPos);
+        psMetadataAdd (row, PS_LIST_TAIL, "X_PSF_SIG",        PS_DATA_F32, "Sigma in PSF x coordinate",                  xErr); // XXX this is only measured for non-linear fits
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_PSF_SIG",        PS_DATA_F32, "Sigma in PSF y coordinate",                  yErr); // XXX this is only measured for non-linear fits
+        psMetadataAdd (row, PS_LIST_TAIL, "POSANGLE",         PS_DATA_F32, "position angle at source (degrees)",         posAngle*PS_DEG_RAD);
+        psMetadataAdd (row, PS_LIST_TAIL, "PLTSCALE",         PS_DATA_F32, "plate scale at source (arcsec/pixel)",       pltScale*PS_DEG_RAD*3600.0);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_INST_MAG",     PS_DATA_F32, "PSF fit instrumental magnitude",             source->psfMag);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_INST_MAG_SIG", PS_DATA_F32, "Sigma of PSF instrumental magnitude",        errMag);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_INST_FLUX",     PS_DATA_F32, "PSF fit instrumental magnitude",            source->psfFlux);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_INST_FLUX_SIG", PS_DATA_F32, "Sigma of PSF instrumental magnitude",       source->psfFluxErr);
+        psMetadataAdd (row, PS_LIST_TAIL, "AP_MAG",           PS_DATA_F32, "magnitude in standard aperture",             source->apMag);
+        psMetadataAdd (row, PS_LIST_TAIL, "AP_MAG_RADIUS",    PS_DATA_F32, "radius used for aperture mags",              apRadius);
+        psMetadataAdd (row, PS_LIST_TAIL, "PEAK_FLUX_AS_MAG", PS_DATA_F32, "Peak flux expressed as magnitude",           peakMag);
+        psMetadataAdd (row, PS_LIST_TAIL, "CAL_PSF_MAG",      PS_DATA_F32, "PSF Magnitude using supplied calibration",   calMag);
+        psMetadataAdd (row, PS_LIST_TAIL, "CAL_PSF_MAG_SIG",  PS_DATA_F32, "measured scatter of zero point calibration", zeroptErr);
+        psMetadataAdd (row, PS_LIST_TAIL, "RA_PSF",           PS_DATA_F64, "PSF RA coordinate (degrees)",                ptSky.r*PS_DEG_RAD);
+        psMetadataAdd (row, PS_LIST_TAIL, "DEC_PSF",          PS_DATA_F64, "PSF DEC coordinate (degrees)",               ptSky.d*PS_DEG_RAD);
+        psMetadataAdd (row, PS_LIST_TAIL, "SKY",              PS_DATA_F32, "Sky level",                                  source->sky);
+        psMetadataAdd (row, PS_LIST_TAIL, "SKY_SIGMA",        PS_DATA_F32, "Sigma of sky level",                         source->skyErr);
+
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_CHISQ",        PS_DATA_F32, "Chisq of PSF-fit",                           chisq);
+        psMetadataAdd (row, PS_LIST_TAIL, "CR_NSIGMA",        PS_DATA_F32, "Nsigma deviations from PSF to CF",           source->crNsigma);
+        psMetadataAdd (row, PS_LIST_TAIL, "EXT_NSIGMA",       PS_DATA_F32, "Nsigma deviations from PSF to EXT",          source->extNsigma);
+
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_MAJOR",        PS_DATA_F32, "PSF width (major axis)",                     axes.major);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_MINOR",        PS_DATA_F32, "PSF width (minor axis)",                     axes.minor);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_THETA",        PS_DATA_F32, "PSF orientation angle",                      axes.theta);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_QF",           PS_DATA_F32, "PSF coverage/quality factor",                source->pixWeight);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_NDOF",         PS_DATA_S32, "degrees of freedom",                         nDOF);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_NPIX",         PS_DATA_S32, "number of pixels in fit",                    nPix);
+
+        // distinguish moments measure from window vs S/N > XX ??
+        float mxx = source->moments ? source->moments->Mxx : NAN;
+        float mxy = source->moments ? source->moments->Mxy : NAN;
+        float myy = source->moments ? source->moments->Myy : NAN;
+        psMetadataAdd (row, PS_LIST_TAIL, "MOMENTS_XX",       PS_DATA_F32, "second moments (X^2)",                      mxx);
+        psMetadataAdd (row, PS_LIST_TAIL, "MOMENTS_XY",       PS_DATA_F32, "second moments (X*Y)",                      mxy);
+        psMetadataAdd (row, PS_LIST_TAIL, "MOMENTS_YY",       PS_DATA_F32, "second moments (Y*Y)",                      myy);
+
+        psMetadataAdd (row, PS_LIST_TAIL, "DIFF_NPOS",        PS_DATA_S32, "nPos (n pix > 3 sigma)",                    diffStats.nGood);
+        psMetadataAdd (row, PS_LIST_TAIL, "DIFF_FRATIO",      PS_DATA_F32, "fPos / (fPos + fNeg)",                      diffStats.fRatio);
+        psMetadataAdd (row, PS_LIST_TAIL, "DIFF_NRATIO_BAD",  PS_DATA_F32, "nPos / (nPos + nNeg)",                      diffStats.nRatioBad);
+        psMetadataAdd (row, PS_LIST_TAIL, "DIFF_NRATIO_MASK", PS_DATA_F32, "nPos / (nPos + nMask)",                     diffStats.nRatioMask);
+        psMetadataAdd (row, PS_LIST_TAIL, "DIFF_NRATIO_ALL",  PS_DATA_F32, "nPos / (nGood + nMask + nBad)",             diffStats.nRatioAll);
+
+        psMetadataAdd (row, PS_LIST_TAIL, "FLAGS",            PS_DATA_U32, "psphot analysis flags",                      source->mode);
+
+        // XXX not sure how to get this : need to load Nimages with weight?
+        psMetadataAdd (row, PS_LIST_TAIL, "N_FRAMES",         PS_DATA_U16, "Number of frames overlapping source center", nImageOverlap);
+        psMetadataAdd (row, PS_LIST_TAIL, "PADDING",          PS_DATA_S16, "padding", 0);
+
+        psArrayAdd (table, 100, row);
+        psFree (row);
+
+        // EXT_NSIGMA will be NAN if: 1) contour ellipse is imaginary; 2) source is not
+        // subtracted
+
+        // CR_NSIGMA will be NAN if: 1) source is not subtracted; 2) source is on the image
+        // edge; 3) any pixels in the 3x3 peak region are masked;
+    }
+
+    psMetadata *header = psMetadataCopy(NULL, tableHeader);
+    pmSourceMasksHeader(header);
+
+    if (table->n == 0) {
+        psFitsWriteBlank(fits, header, extname);
+        psFree(table);
+        psFree(header);
+        return true;
+    }
+
+    psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
+    if (!psFitsWriteTable(fits, header, table, extname)) {
+        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+        psFree(table);
+        psFree(header);
+        return false;
+    }
+    psFree(table);
+    psFree(header);
+
+    return true;
+}
+
+// read in a readout from the fits file
+psArray *pmSourcesRead_CMF_PS1_DV1 (psFits *fits, psMetadata *header)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(header, false);
+
+    bool status;
+    psF32 *PAR, *dPAR;
+    psEllipseAxes axes;
+
+    // define PSF model type
+    // XXX need to carry the extra model parameters
+    int modelType = pmModelClassGetType ("PS_MODEL_GAUSS");
+
+    char *PSF_NAME = psMetadataLookupStr (&status, header, "PSF_NAME");
+    if (PSF_NAME != NULL) {
+        modelType = pmModelClassGetType (PSF_NAME);
+    }
+    assert (modelType > -1);
+
+    // We get the size of the table, and allocate the array of sources first because the table
+    // is large and ephemeral --- when the table gets blown away, whatever is allocated after
+    // the table is read blocks the free.  In fact, it's better to read the table row by row.
+    long numSources = psFitsTableSize(fits); // Number of sources in table
+    psArray *sources = psArrayAlloc(numSources); // Array of sources, to return
+
+    // convert the table to the pmSource entriesa
+    for (int i = 0; i < numSources; i++) {
+        psMetadata *row = psFitsReadTableRow(fits, i); // Table row
+
+        pmSource *source = pmSourceAlloc ();
+        pmModel *model = pmModelAlloc (modelType);
+        source->modelPSF  = model;
+        source->type = PM_SOURCE_TYPE_STAR; // XXX this should be added to the flags
+
+        // NOTE: A SEGV here because "model" is NULL is probably caused by not initialising the models.
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        source->seq       = psMetadataLookupU32 (&status, row, "IPP_IDET");
+        PAR[PM_PAR_XPOS]  = psMetadataLookupF32 (&status, row, "X_PSF");
+        PAR[PM_PAR_YPOS]  = psMetadataLookupF32 (&status, row, "Y_PSF");
+        dPAR[PM_PAR_XPOS] = psMetadataLookupF32 (&status, row, "X_PSF_SIG");
+        dPAR[PM_PAR_YPOS] = psMetadataLookupF32 (&status, row, "Y_PSF_SIG");
+        axes.major        = psMetadataLookupF32 (&status, row, "PSF_MAJOR");
+        axes.minor        = psMetadataLookupF32 (&status, row, "PSF_MINOR");
+        axes.theta        = psMetadataLookupF32 (&status, row, "PSF_THETA");
+
+        PAR[PM_PAR_SKY]   = psMetadataLookupF32 (&status, row, "SKY");
+        dPAR[PM_PAR_SKY]  = psMetadataLookupF32 (&status, row, "SKY_SIGMA");
+        source->sky       = PAR[PM_PAR_SKY];
+        source->skyErr    = dPAR[PM_PAR_SKY];
+
+        // XXX use these to determine PAR[PM_PAR_I0]?
+        source->psfFlux    = psMetadataLookupF32 (&status, row, "PSF_INST_FLUX");
+        source->psfFluxErr = psMetadataLookupF32 (&status, row, "PSF_INST_FLUX_SIG");
+
+        source->psfMag    = psMetadataLookupF32 (&status, row, "PSF_INST_MAG");
+        source->errMag    = psMetadataLookupF32 (&status, row, "PSF_INST_MAG_SIG");
+        source->apMag     = psMetadataLookupF32 (&status, row, "AP_MAG");
+
+        // XXX this scaling is incorrect: does not include the 2 \pi AREA factor
+        PAR[PM_PAR_I0]    = (isfinite(source->psfMag)) ? pow(10.0, -0.4*source->psfMag) : NAN;
+        dPAR[PM_PAR_I0]   = (isfinite(source->psfMag)) ? PAR[PM_PAR_I0] * source->errMag : NAN;
+
+        pmPSF_AxesToModel (PAR, axes);
+
+        float peakMag     = psMetadataLookupF32 (&status, row, "PEAK_FLUX_AS_MAG");
+        float peakFlux    = (isfinite(peakMag)) ? pow(10.0, -0.4*peakMag) : NAN;
+
+        // recreate the peak to match (xPos, yPos) +/- (xErr, yErr)
+        source->peak = pmPeakAlloc(PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], peakFlux, PM_PEAK_LONE);
+        source->peak->flux = peakFlux;
+        source->peak->dx   = dPAR[PM_PAR_XPOS];
+        source->peak->dy   = dPAR[PM_PAR_YPOS];
+        source->peak->SN   = sqrt(source->peak->flux); // XXX a proxy: various functions sort by peak S/N
+
+        source->pixWeight = psMetadataLookupF32 (&status, row, "PSF_QF");
+        source->crNsigma  = psMetadataLookupF32 (&status, row, "CR_NSIGMA");
+        source->extNsigma = psMetadataLookupF32 (&status, row, "EXT_NSIGMA");
+        source->apRadius  = psMetadataLookupS32 (&status, row, "AP_MAG_RADIUS");
+
+        // note that some older versions used PSF_PROBABILITY: this was not well defined.
+        model->chisq      = psMetadataLookupF32 (&status, row, "PSF_CHISQ");
+        model->nDOF       = psMetadataLookupS32 (&status, row, "PSF_NDOF");
+        model->nPix       = psMetadataLookupS32 (&status, row, "PSF_NPIX");
+
+        source->moments = pmMomentsAlloc ();
+        source->moments->Mxx = psMetadataLookupF32 (&status, row, "MOMENTS_XX");
+        source->moments->Mxy = psMetadataLookupF32 (&status, row, "MOMENTS_XY");
+        source->moments->Myy = psMetadataLookupF32 (&status, row, "MOMENTS_YY");
+
+        source->mode = psMetadataLookupU32 (&status, row, "FLAGS");
+        assert (status);
+
+	int nPos = psMetadataLookupS32 (&status, row, "DIFF_NPOS");
+	if (nPos) {
+	    source->diffStats = pmSourceDiffStatsAlloc();
+	    source->diffStats->nGood      = nPos;
+	    source->diffStats->fRatio     = psMetadataLookupF32 (&status, row, "DIFF_FRATIO");
+	    source->diffStats->nRatioBad  = psMetadataLookupF32 (&status, row, "DIFF_NRATIO_BAD");
+	    source->diffStats->nRatioMask = psMetadataLookupF32 (&status, row, "DIFF_NRATIO_MASK");
+	    source->diffStats->nRatioAll  = psMetadataLookupF32 (&status, row, "DIFF_NRATIO_ALL");
+	}
+
+        sources->data[i] = source;
+        psFree(row);
+    }
+
+    return sources;
+}
+
+// XXX this layout is still the same as PS1_DEV_1
+bool pmSourcesWrite_CMF_PS1_DV1_XSRC (psFits *fits, pmReadout *readout, psArray *sources, psMetadata *imageHeader, char *extname, psMetadata *recipe)
+{
+
+    bool status;
+    psArray *table;
+    psMetadata *row;
+    psF32 *PAR, *dPAR;
+    psF32 xPos, yPos;
+    psF32 xErr, yErr;
+
+    // create a header to hold the output data
+    psMetadata *outhead = psMetadataAlloc ();
+
+    // write the links to the image header
+    psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTNAME", PS_META_REPLACE, "xsrc table extension", extname);
+
+    // let's write these out in S/N order
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    table = psArrayAllocEmpty (sources->n);
+
+    // which extended source analyses should we perform?
+    // bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+    // bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
+    // bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
+    // bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
+
+    psVector *radialBinsLower = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.LOWER");
+    psVector *radialBinsUpper = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.UPPER");
+    assert (radialBinsLower->n == radialBinsUpper->n);
+
+    // we write out all sources, regardless of quality.  the source flags tell us the state
+    for (int i = 0; i < sources->n; i++) {
+        // skip source if it is not a ext sourc
+        // XXX we have two places that extended source parameters are measured:
+        // psphotExtendedSources, which measures the aperture-like parameters and (potentially) the psf-convolved extended source models,
+        // psphotFitEXT, which does the simple extended source model fit (not psf-convolved)
+        // should we require both?
+
+        pmSource *source = sources->data[i];
+
+        // skip sources without measurements
+        if (source->extpars == NULL) continue;
+
+        // we require a PSF model fit (ignore the real crud)
+        pmModel *model = source->modelPSF;
+        if (model == NULL) continue;
+
+        // XXX I need to split the extended models from the extended aperture measurements
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+        xPos = PAR[PM_PAR_XPOS];
+        yPos = PAR[PM_PAR_YPOS];
+        xErr = dPAR[PM_PAR_XPOS];
+        yErr = dPAR[PM_PAR_YPOS];
+
+        row = psMetadataAlloc ();
+
+        // XXX we are not writing out the mode (flags) or the type (psf, ext, etc)
+        psMetadataAdd (row, PS_LIST_TAIL, "IPP_IDET",         PS_DATA_U32, "IPP detection identifier index",             source->seq);
+        psMetadataAdd (row, PS_LIST_TAIL, "X_EXT",            PS_DATA_F32, "EXT model x coordinate",                     xPos);
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_EXT",            PS_DATA_F32, "EXT model y coordinate",                     yPos);
+        psMetadataAdd (row, PS_LIST_TAIL, "X_EXT_SIG",        PS_DATA_F32, "Sigma in EXT x coordinate",                  xErr);
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_EXT_SIG",        PS_DATA_F32, "Sigma in EXT y coordinate",                  yErr);
+
+# if (0)
+        // Petrosian measurements
+        // XXX insert header data: petrosian ref radius, flux ratio
+        if (doPetrosian) {
+            pmSourcePetrosianValues *petrosian = source->extpars->petrosian;
+            if (petrosian) {
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG",        PS_DATA_F32, "Petrosian Magnitude",       petrosian->mag);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG_ERR",    PS_DATA_F32, "Petrosian Magnitude Error", petrosian->magErr);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS",     PS_DATA_F32, "Petrosian Radius",          petrosian->rad);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_ERR", PS_DATA_F32, "Petrosian Radius Error",    petrosian->radErr);
+            } else {
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG",        PS_DATA_F32, "Petrosian Magnitude",       NAN);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG_ERR",    PS_DATA_F32, "Petrosian Magnitude Error", NAN);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS",     PS_DATA_F32, "Petrosian Radius",          NAN);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_ERR", PS_DATA_F32, "Petrosian Radius Error",    NAN);
+            }
+        }
+
+        // Kron measurements
+        if (doKron) {
+            pmSourceKronValues *kron = source->extpars->kron;
+            if (kron) {
+                psMetadataAdd (row, PS_LIST_TAIL, "KRON_MAG",        PS_DATA_F32, "Kron Magnitude",       kron->mag);
+                psMetadataAdd (row, PS_LIST_TAIL, "KRON_MAG_ERR",    PS_DATA_F32, "Kron Magnitude Error", kron->magErr);
+                psMetadataAdd (row, PS_LIST_TAIL, "KRON_RADIUS",     PS_DATA_F32, "Kron Radius",          kron->rad);
+                psMetadataAdd (row, PS_LIST_TAIL, "KRON_RADIUS_ERR", PS_DATA_F32, "Kron Radius Error",    kron->radErr);
+            } else {
+                psMetadataAdd (row, PS_LIST_TAIL, "KRON_MAG",        PS_DATA_F32, "Kron Magnitude",       NAN);
+                psMetadataAdd (row, PS_LIST_TAIL, "KRON_MAG_ERR",    PS_DATA_F32, "Kron Magnitude Error", NAN);
+                psMetadataAdd (row, PS_LIST_TAIL, "KRON_RADIUS",     PS_DATA_F32, "Kron Radius",          NAN);
+                psMetadataAdd (row, PS_LIST_TAIL, "KRON_RADIUS_ERR", PS_DATA_F32, "Kron Radius Error",    NAN);
+            }
+        }
+
+        // Isophot measurements
+        // XXX insert header data: isophotal level
+        if (doIsophotal) {
+            pmSourceIsophotalValues *isophot = source->extpars->isophot;
+            if (isophot) {
+                psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_MAG",        PS_DATA_F32, "Isophot Magnitude",       isophot->mag);
+                psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_MAG_ERR",    PS_DATA_F32, "Isophot Magnitude Error", isophot->magErr);
+                psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_RADIUS",     PS_DATA_F32, "Isophot Radius",          isophot->rad);
+                psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_RADIUS_ERR", PS_DATA_F32, "Isophot Radius Error",    isophot->radErr);
+            } else {
+                psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_MAG",        PS_DATA_F32, "Isophot Magnitude",       NAN);
+                psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_MAG_ERR",    PS_DATA_F32, "Isophot Magnitude Error", NAN);
+                psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_RADIUS",     PS_DATA_F32, "Isophot Radius",          NAN);
+                psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_RADIUS_ERR", PS_DATA_F32, "Isophot Radius Error",    NAN);
+            }
+        }
+
+        // Flux Annuli
+        if (doAnnuli) {
+            pmSourceAnnuli *annuli = source->extpars->annuli;
+            if (annuli) {
+                psVector *fluxVal = annuli->flux;
+                psVector *fluxErr = annuli->fluxErr;
+                psVector *fluxVar = annuli->fluxVar;
+
+                for (int j = 0; j < fluxVal->n; j++) {
+                    char name[32];
+                    sprintf (name, "FLUX_VAL_R_%02d", j);
+                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux value in annulus", fluxVal->data.F32[j]);
+                    sprintf (name, "FLUX_ERR_R_%02d", j);
+                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux error in annulus", fluxErr->data.F32[j]);
+                    sprintf (name, "FLUX_VAR_R_%02d", j);
+                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux stdev in annulus", fluxVar->data.F32[j]);
+                }
+            } else {
+                for (int j = 0; j < radialBinsLower->n; j++) {
+                    char name[32];
+                    sprintf (name, "FLUX_VAL_R_%02d", j);
+                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux value in annulus", NAN);
+                    sprintf (name, "FLUX_ERR_R_%02d", j);
+                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux error in annulus", NAN);
+                    sprintf (name, "FLUX_VAR_R_%02d", j);
+                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux stdev in annulus", NAN);
+                }
+            }
+        }
+
+# endif
+        psArrayAdd (table, 100, row);
+        psFree (row);
+    }
+
+    if (table->n == 0) {
+        psFitsWriteBlank (fits, outhead, extname);
+        psFree (outhead);
+        psFree (table);
+        return true;
+    }
+
+    psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
+    if (!psFitsWriteTable (fits, outhead, table, extname)) {
+        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+        psFree (outhead);
+        psFree(table);
+        return false;
+    }
+    psFree (outhead);
+    psFree (table);
+
+    return true;
+}
+
+// XXX this layout is still the same as PS1_DEV_1
+bool pmSourcesWrite_CMF_PS1_DV1_XFIT (psFits *fits, pmReadout *readout, psArray *sources, char *extname)
+{
+
+    psArray *table;
+    psMetadata *row;
+    psF32 *PAR, *dPAR;
+    psEllipseAxes axes;
+    psF32 xPos, yPos;
+    psF32 xErr, yErr;
+    char name[64];
+
+    // create a header to hold the output data
+    psMetadata *outhead = psMetadataAlloc ();
+
+    // write the links to the image header
+    psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTNAME", PS_META_REPLACE, "xsrc table extension", extname);
+
+    // let's write these out in S/N order
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // we are writing one row per model; we need to write out same number of columns for each row: find the max Nparams
+    int nParamMax = 0;
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (source->modelFits == NULL) continue;
+        for (int j = 0; j < source->modelFits->n; j++) {
+            pmModel *model = source->modelFits->data[j];
+            assert (model);
+            nParamMax = PS_MAX (nParamMax, model->params->n);
+        }
+    }
+
+    table = psArrayAllocEmpty (sources->n);
+
+    // we write out all sources, regardless of quality.  the source flags tell us the state
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        // XXX if no model fits are saved, write out modelEXT?
+        if (source->modelFits == NULL) continue;
+
+        // We have multiple sources : need to flag the one used to subtract the light (the 'best' model)
+        for (int j = 0; j < source->modelFits->n; j++) {
+
+            // choose the convolved EXT model, if available, otherwise the simple one
+            pmModel *model = source->modelFits->data[j];
+            assert (model);
+
+            PAR = model->params->data.F32;
+            dPAR = model->dparams->data.F32;
+            xPos = PAR[PM_PAR_XPOS];
+            yPos = PAR[PM_PAR_YPOS];
+            xErr = dPAR[PM_PAR_XPOS];
+            yErr = dPAR[PM_PAR_YPOS];
+
+            axes = pmPSF_ModelToAxes (PAR, 20.0);
+
+            row = psMetadataAlloc ();
+
+            // XXX we are not writing out the mode (flags) or the type (psf, ext, etc)
+            psMetadataAddU32 (row, PS_LIST_TAIL, "IPP_IDET",         0, "IPP detection identifier index",             source->seq);
+            psMetadataAddF32 (row, PS_LIST_TAIL, "X_EXT",            0, "EXT model x coordinate",                     xPos);
+            psMetadataAddF32 (row, PS_LIST_TAIL, "Y_EXT",            0, "EXT model y coordinate",                     yPos);
+            psMetadataAddF32 (row, PS_LIST_TAIL, "X_EXT_SIG",        0, "Sigma in EXT x coordinate",                  xErr);
+            psMetadataAddF32 (row, PS_LIST_TAIL, "Y_EXT_SIG",        0, "Sigma in EXT y coordinate",                  yErr);
+            psMetadataAddF32 (row, PS_LIST_TAIL, "EXT_INST_MAG",     0, "EXT fit instrumental magnitude",             model->mag);
+            psMetadataAddF32 (row, PS_LIST_TAIL, "EXT_INST_MAG_SIG", 0, "Sigma of PSF instrumental magnitude",        model->magErr);
+
+            psMetadataAddF32 (row, PS_LIST_TAIL, "NPARAMS",          0, "number of model parameters",                 model->params->n);
+            psMetadataAddStr (row, PS_LIST_TAIL, "MODEL_TYPE",       0, "name of model",                              pmModelClassGetName (model->type));
+
+            // XXX these should be major and minor, not 'x' and 'y'
+            psMetadataAddF32 (row, PS_LIST_TAIL, "EXT_WIDTH_MAJ",    0, "EXT width in x coordinate",                  axes.major);
+            psMetadataAddF32 (row, PS_LIST_TAIL, "EXT_WIDTH_MIN",    0, "EXT width in y coordinate",                  axes.minor);
+            psMetadataAddF32 (row, PS_LIST_TAIL, "EXT_THETA",        0, "EXT orientation angle",                      axes.theta);
+
+            // write out the other generic parameters
+            for (int k = 0; k < nParamMax; k++) {
+                if (k == PM_PAR_I0) continue;
+                if (k == PM_PAR_SKY) continue;
+                if (k == PM_PAR_XPOS) continue;
+                if (k == PM_PAR_YPOS) continue;
+                if (k == PM_PAR_SXX) continue;
+                if (k == PM_PAR_SXY) continue;
+                if (k == PM_PAR_SYY) continue;
+
+                snprintf (name, 64, "EXT_PAR_%02d", k);
+
+                if (k < model->params->n) {
+                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "", model->params->data.F32[k]);
+                } else {
+                    psMetadataAddF32 (row, PS_LIST_TAIL, name, PS_DATA_F32, "", NAN);
+                }
+            }
+
+            // XXX other parameters which may be set.
+            // XXX flag / value to define the model
+            // XXX write out the model type, fit status flags
+
+            psArrayAdd (table, 100, row);
+            psFree (row);
+        }
+    }
+
+    if (table->n == 0) {
+        psFitsWriteBlank (fits, outhead, extname);
+        psFree (outhead);
+        psFree (table);
+        return true;
+    }
+
+    psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
+    if (!psFitsWriteTable (fits, outhead, table, extname)) {
+        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+        psFree (outhead);
+        psFree(table);
+        return false;
+    }
+    psFree (outhead);
+    psFree (table);
+    return true;
+}
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceIO_CMF_PS1_V1.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceIO_CMF_PS1_V1.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceIO_CMF_PS1_V1.c	(revision 27839)
@@ -69,9 +69,12 @@
     float magOffset = NAN;
     float exptime   = psMetadataLookupF32 (&status1, fpa->concepts, "FPA.EXPOSURE");
-    float zeropt    = psMetadataLookupF32 (&status2, imageHeader, "ZPT_OBS");
-    float zeroptErr = psMetadataLookupF32 (&status2, imageHeader, "ZPT_ERR");
+    float zeropt    = psMetadataLookupF32(&status2, fpa->concepts, "FPA.ZP");
+    if (!isfinite(zeropt)) {
+        zeropt    = psMetadataLookupF32 (&status2, imageHeader, "ZPT_OBS");
+    }
     if (status1 && status2 && (exptime > 0.0)) {
         magOffset = zeropt + 2.5*log10(exptime);
     }
+    float zeroptErr = psMetadataLookupF32 (&status2, imageHeader, "ZPT_ERR");
 
     // if the sequence is defined, write these in seq order; otherwise
@@ -128,5 +131,5 @@
             nDOF = model->nDOF;
             nPix = model->nPix;
-            apRadius = model->radiusFit; // XXX should we really use the fitRadius for aperture Radius?
+            apRadius = source->apRadius;
             errMag = model->dparams->data.F32[PM_PAR_I0] / model->params->data.F32[PM_PAR_I0];
         } else {
@@ -213,5 +216,10 @@
 
     if (table->n == 0) {
-        psFitsWriteBlank(fits, header, extname);
+        if (!psFitsWriteBlank(fits, header, extname)) {
+            psError(psErrorCodeLast(), false, "Unable to write empty sources file.");
+            psFree(table);
+            psFree(header);
+            return false;
+        }
         psFree(table);
         psFree(header);
@@ -221,5 +229,5 @@
     psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
     if (!psFitsWriteTable(fits, header, table, extname)) {
-        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+        psError(psErrorCodeLast(), false, "writing ext data %s\n", extname);
         psFree(table);
         psFree(header);
@@ -308,4 +316,5 @@
         source->crNsigma  = psMetadataLookupF32 (&status, row, "CR_NSIGMA");
         source->extNsigma = psMetadataLookupF32 (&status, row, "EXT_NSIGMA");
+        source->apRadius  = psMetadataLookupS32 (&status, row, "AP_MAG_RADIUS");
 
         // note that some older versions used PSF_PROBABILITY: this was not well defined.
@@ -313,5 +322,4 @@
         model->nDOF       = psMetadataLookupS32 (&status, row, "PSF_NDOF");
         model->nPix       = psMetadataLookupS32 (&status, row, "PSF_NPIX");
-        model->radiusFit  = psMetadataLookupS32 (&status, row, "AP_MAG_RADIUS");
 
         source->moments = pmMomentsAlloc ();
@@ -331,5 +339,5 @@
 
 // XXX this layout is still the same as PS1_DEV_1
-bool pmSourcesWrite_CMF_PS1_V1_XSRC (psFits *fits, psArray *sources, char *extname, psMetadata *recipe)
+bool pmSourcesWrite_CMF_PS1_V1_XSRC (psFits *fits, pmReadout *readout, psArray *sources, psMetadata *imageHeader, char *extname, psMetadata *recipe)
 {
 
@@ -353,8 +361,8 @@
 
     // which extended source analyses should we perform?
-    bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
-    bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
-    bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
-    bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
+    // bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+    // bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
+    // bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
+    // bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
 
     psVector *radialBinsLower = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.LOWER");
@@ -396,4 +404,5 @@
         psMetadataAdd (row, PS_LIST_TAIL, "Y_EXT_SIG",        PS_DATA_F32, "Sigma in EXT y coordinate",                  yErr);
 
+# if (0)
         // Petrosian measurements
         // XXX insert header data: petrosian ref radius, flux ratio
@@ -476,4 +485,5 @@
         }
 
+# endif
         psArrayAdd (table, 100, row);
         psFree (row);
@@ -481,5 +491,10 @@
 
     if (table->n == 0) {
-        psFitsWriteBlank (fits, outhead, extname);
+        if (!psFitsWriteBlank (fits, outhead, extname)) {
+            psError(psErrorCodeLast(), false, "Unable to write empty sources.");
+            psFree(outhead);
+            psFree(table);
+            return false;
+        }
         psFree (outhead);
         psFree (table);
@@ -489,5 +504,5 @@
     psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
     if (!psFitsWriteTable (fits, outhead, table, extname)) {
-        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+        psError(psErrorCodeLast(), false, "writing ext data %s\n", extname);
         psFree (outhead);
         psFree(table);
@@ -501,5 +516,5 @@
 
 // XXX this layout is still the same as PS1_DEV_1
-bool pmSourcesWrite_CMF_PS1_V1_XFIT (psFits *fits, psArray *sources, char *extname)
+bool pmSourcesWrite_CMF_PS1_V1_XFIT (psFits *fits, pmReadout *readout, psArray *sources, char *extname)
 {
 
@@ -607,5 +622,10 @@
 
     if (table->n == 0) {
-        psFitsWriteBlank (fits, outhead, extname);
+        if (!psFitsWriteBlank (fits, outhead, extname)) {
+            psError(psErrorCodeLast(), false, "Unable to write empty sources.");
+            psFree(outhead);
+            psFree(table);
+            return false;
+        }
         psFree (outhead);
         psFree (table);
@@ -615,5 +635,5 @@
     psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
     if (!psFitsWriteTable (fits, outhead, table, extname)) {
-        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+        psError(psErrorCodeLast(), false, "writing ext data %s\n", extname);
         psFree (outhead);
         psFree(table);
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceIO_CMF_PS1_V2.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceIO_CMF_PS1_V2.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceIO_CMF_PS1_V2.c	(revision 27839)
@@ -45,6 +45,5 @@
 // followed by a zero-size matrix, followed by the table data
 
-bool pmSourcesWrite_CMF_PS1_V2 (psFits *fits, pmReadout *readout, psArray *sources,
-                                psMetadata *imageHeader, psMetadata *tableHeader, char *extname)
+bool pmSourcesWrite_CMF_PS1_V2 (psFits *fits, pmReadout *readout, psArray *sources, psMetadata *imageHeader, psMetadata *tableHeader, char *extname)
 {
     PS_ASSERT_PTR_NON_NULL(fits, false);
@@ -54,5 +53,4 @@
     psArray *table;
     psMetadata *row;
-    int i;
     psF32 *PAR, *dPAR;
     psEllipseAxes axes;
@@ -69,9 +67,12 @@
     float magOffset = NAN;
     float exptime   = psMetadataLookupF32 (&status1, fpa->concepts, "FPA.EXPOSURE");
-    float zeropt    = psMetadataLookupF32 (&status2, imageHeader, "ZPT_OBS");
-    float zeroptErr = psMetadataLookupF32 (&status2, imageHeader, "ZPT_ERR");
+    float zeropt    = psMetadataLookupF32(&status2, fpa->concepts, "FPA.ZP");
+    if (!isfinite(zeropt)) {
+        zeropt    = psMetadataLookupF32 (&status2, imageHeader, "ZPT_OBS");
+    }
     if (status1 && status2 && (exptime > 0.0)) {
         magOffset = zeropt + 2.5*log10(exptime);
     }
+    float zeroptErr = psMetadataLookupF32 (&status2, imageHeader, "ZPT_ERR");
 
     // if the sequence is defined, write these in seq order; otherwise
@@ -80,8 +81,8 @@
         pmSource *source = (pmSource *) sources->data[0];
         if (source->seq == -1) {
-          // let's write these out in S/N order
-          sources = psArraySort (sources, pmSourceSortBySN);
+	    // let's write these out in S/N order
+	    sources = psArraySort (sources, pmSourceSortBySN);
         } else {
-          sources = psArraySort (sources, pmSourceSortBySeq);
+	    sources = psArraySort (sources, pmSourceSortBySeq);
         }
     }
@@ -91,5 +92,5 @@
     // we write out PSF-fits for all sources, regardless of quality.  the source flags tell us the state
     // by the time we call this function, all values should be assigned.  let's use asserts to be sure in some cases.
-    for (i = 0; i < sources->n; i++) {
+    for (int i = 0; i < sources->n; i++) {
         pmSource *source = (pmSource *) sources->data[i];
 
@@ -99,5 +100,5 @@
         // generated on Alloc, and would thus be wrong for read in sources.
         if (source->seq == -1) {
-          source->seq = i;
+	    source->seq = i;
         }
 
@@ -111,10 +112,10 @@
             yPos = PAR[PM_PAR_YPOS];
             if (source->mode & PM_SOURCE_MODE_NONLINEAR_FIT) {
-              xErr = dPAR[PM_PAR_XPOS];
-              yErr = dPAR[PM_PAR_YPOS];
+		xErr = dPAR[PM_PAR_XPOS];
+		yErr = dPAR[PM_PAR_YPOS];
             } else {
-              // in linear-fit mode, there is no error on the centroid
-              xErr = source->peak->dx;
-              yErr = source->peak->dy;
+		// in linear-fit mode, there is no error on the centroid
+		xErr = source->peak->dx;
+		yErr = source->peak->dy;
             }
             if (isfinite(PAR[PM_PAR_SXX]) && isfinite(PAR[PM_PAR_SXX]) && isfinite(PAR[PM_PAR_SXX])) {
@@ -128,5 +129,5 @@
             nDOF = model->nDOF;
             nPix = model->nPix;
-            apRadius = model->radiusFit; // XXX should we really use the fitRadius for aperture Radius?
+            apRadius = source->apRadius;
             errMag = model->dparams->data.F32[PM_PAR_I0] / model->params->data.F32[PM_PAR_I0];
         } else {
@@ -209,9 +210,15 @@
     }
 
+    // XXX why do we make a copy here to be supplemented with the masks?  why not do this in the calling function?
     psMetadata *header = psMetadataCopy(NULL, tableHeader);
     pmSourceMasksHeader(header);
 
     if (table->n == 0) {
-        psFitsWriteBlank(fits, header, extname);
+        if (!psFitsWriteBlank(fits, header, extname)) {
+            psError(psErrorCodeLast(), false, "Unable to write blank sources file.");
+            psFree(table);
+            psFree(header);
+            return false;
+        }
         psFree(table);
         psFree(header);
@@ -221,5 +228,5 @@
     psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
     if (!psFitsWriteTable(fits, header, table, extname)) {
-        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+        psError(psErrorCodeLast(), false, "writing ext data %s\n", extname);
         psFree(table);
         psFree(header);
@@ -261,4 +268,9 @@
     for (int i = 0; i < numSources; i++) {
         psMetadata *row = psFitsReadTableRow(fits, i); // Table row
+        if (!row) {
+            psError(psErrorCodeLast(), false, "Unable to read row %d of sources", i);
+            psFree(sources);
+            return NULL;
+        }
 
         pmSource *source = pmSourceAlloc ();
@@ -304,8 +316,10 @@
         source->peak->dx   = dPAR[PM_PAR_XPOS];
         source->peak->dy   = dPAR[PM_PAR_YPOS];
+        source->peak->SN   = sqrt(source->peak->flux); // XXX a proxy: various functions sort by peak S/N
 
         source->pixWeight = psMetadataLookupF32 (&status, row, "PSF_QF");
         source->crNsigma  = psMetadataLookupF32 (&status, row, "CR_NSIGMA");
         source->extNsigma = psMetadataLookupF32 (&status, row, "EXT_NSIGMA");
+        source->apRadius  = psMetadataLookupS32 (&status, row, "AP_MAG_RADIUS");
 
         // note that some older versions used PSF_PROBABILITY: this was not well defined.
@@ -313,5 +327,4 @@
         model->nDOF       = psMetadataLookupS32 (&status, row, "PSF_NDOF");
         model->nPix       = psMetadataLookupS32 (&status, row, "PSF_NPIX");
-        model->radiusFit  = psMetadataLookupS32 (&status, row, "AP_MAG_RADIUS");
 
         source->moments = pmMomentsAlloc ();
@@ -330,6 +343,5 @@
 }
 
-// XXX this layout is still the same as PS1_DEV_1
-bool pmSourcesWrite_CMF_PS1_V2_XSRC (psFits *fits, psArray *sources, char *extname, psMetadata *recipe)
+bool pmSourcesWrite_CMF_PS1_V2_XSRC (psFits *fits, pmReadout *readout, psArray *sources, psMetadata *imageHeader, char *extname, psMetadata *recipe)
 {
 
@@ -340,4 +352,5 @@
     psF32 xPos, yPos;
     psF32 xErr, yErr;
+    int nRow = -1;
 
     // create a header to hold the output data
@@ -347,4 +360,20 @@
     psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTNAME", PS_META_REPLACE, "xsrc table extension", extname);
 
+    pmChip *chip = readout->parent->parent;
+    pmFPA  *fpa  = chip->parent;
+
+    // zero point corrections
+    bool status1 = false;
+    bool status2 = false;
+    float magOffset = 0.0;
+    float exptime   = psMetadataLookupF32(&status1, fpa->concepts, "FPA.EXPOSURE");
+    float zeropt    = psMetadataLookupF32(&status2, fpa->concepts, "FPA.ZP");
+    if (!isfinite(zeropt)) {
+        zeropt    = psMetadataLookupF32 (&status2, imageHeader, "ZPT_OBS");
+    }
+    if (status1 && status2 && (exptime > 0.0)) {
+        magOffset = zeropt + 2.5*log10(exptime);
+    }
+
     // let's write these out in S/N order
     sources = psArraySort (sources, pmSourceSortBySN);
@@ -353,12 +382,24 @@
 
     // which extended source analyses should we perform?
+    bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
     bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
-    bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
-    bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
-    bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
-
-    psVector *radialBinsLower = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.LOWER");
-    psVector *radialBinsUpper = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.UPPER");
-    assert (radialBinsLower->n == radialBinsUpper->n);
+    // bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
+    // bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
+
+    psVector *radMin = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.LOWER");
+    psVector *radMax = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.UPPER");
+    psAssert (radMin->n == radMax->n, "inconsistent annular bins");
+
+    // int nRadialBins = 0;
+    // if (doAnnuli) {
+    // 	// get the max count of radial bins
+    // 	for (int i = 0; i < sources->n; i++) {
+    // 	    pmSource *source = sources->data[i];
+    // 	    if (!source->extpars) continue;
+    // 	    if (!source->extpars->radProfile ) continue;
+    //         if (!source->extpars->radProfile->binSB) continue;
+    // 	    nRadialBins = PS_MAX(nRadialBins, source->extpars->radProfile->binSB->n);
+    // 	}
+    // }
 
     // we write out all sources, regardless of quality.  the source flags tell us the state
@@ -396,13 +437,31 @@
         psMetadataAdd (row, PS_LIST_TAIL, "Y_EXT_SIG",        PS_DATA_F32, "Sigma in EXT y coordinate",                  yErr);
 
+	float AxialRatio = NAN;
+	float AxialTheta = NAN;
+	pmSourceExtendedPars *extpars = source->extpars;
+	if (extpars) {
+	    AxialRatio = extpars->axes.minor / extpars->axes.major;
+	    AxialTheta = extpars->axes.theta;
+	}
+        psMetadataAdd (row, PS_LIST_TAIL, "F25_ARATIO",       PS_DATA_F32, "Axial Ratio of radial profile",              AxialRatio);
+        psMetadataAdd (row, PS_LIST_TAIL, "F25_THETA",        PS_DATA_F32, "Angle of radial profile ellipse",                  AxialTheta);
+
         // Petrosian measurements
         // XXX insert header data: petrosian ref radius, flux ratio
+	// XXX check flags to see if Pet was measured
         if (doPetrosian) {
-            pmSourcePetrosianValues *petrosian = source->extpars->petrosian;
-            if (petrosian) {
-                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG",        PS_DATA_F32, "Petrosian Magnitude",       petrosian->mag);
-                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG_ERR",    PS_DATA_F32, "Petrosian Magnitude Error", petrosian->magErr);
-                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS",     PS_DATA_F32, "Petrosian Radius",          petrosian->rad);
-                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_ERR", PS_DATA_F32, "Petrosian Radius Error",    petrosian->radErr);
+	    pmSourceExtendedPars *extpars = source->extpars;
+            if (extpars) {
+		// XXX note that this mag is either calibrated or instrumental depending on existence of zero point 
+		float mag = (extpars->petrosianFlux > 0.0) ? -2.5*log10(extpars->petrosianFlux) + magOffset : NAN; // XXX zero point
+		float magErr = (extpars->petrosianFlux > 0.0) ? extpars->petrosianFlux / extpars->petrosianFluxErr : NAN; // XXX zero point
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG",        PS_DATA_F32, "Petrosian Magnitude", mag);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG_ERR",    PS_DATA_F32, "Petrosian Magnitude Error", magErr);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS",     PS_DATA_F32, "Petrosian Radius (pix)", extpars->petrosianRadius);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_ERR", PS_DATA_F32, "Petrosian Radius Error (pix)", extpars->petrosianRadiusErr);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_50",     PS_DATA_F32, "Petrosian R50 (pix)", extpars->petrosianR50);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_50_ERR", PS_DATA_F32, "Petrosian R50 Error (pix)", extpars->petrosianR50Err);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_90",     PS_DATA_F32, "Petrosian R90 (pix)", extpars->petrosianR90);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_90_ERR", PS_DATA_F32, "Petrosian R90 Error (pix)", extpars->petrosianR90Err);
             } else {
                 psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG",        PS_DATA_F32, "Petrosian Magnitude",       NAN);
@@ -410,7 +469,12 @@
                 psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS",     PS_DATA_F32, "Petrosian Radius",          NAN);
                 psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_ERR", PS_DATA_F32, "Petrosian Radius Error",    NAN);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_50",     PS_DATA_F32, "Petrosian R50 (pix)", NAN);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_50_ERR", PS_DATA_F32, "Petrosian R50 Error (pix)",NAN); 
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_90",     PS_DATA_F32, "Petrosian R90 (pix)", NAN);
+                psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_90_ERR", PS_DATA_F32, "Petrosian R90 Error (pix)",NAN); 
             }
         }
 
+# if (0)
         // Kron measurements
         if (doKron) {
@@ -445,61 +509,74 @@
             }
         }
-
-        // Flux Annuli
+# endif
+
+        // Flux Annuli (if we have extended source measurements, we have these.  only optionally save them)
         if (doAnnuli) {
-            pmSourceAnnuli *annuli = source->extpars->annuli;
-            if (annuli) {
-                psVector *fluxVal = annuli->flux;
-                psVector *fluxErr = annuli->fluxErr;
-                psVector *fluxVar = annuli->fluxVar;
-
-                for (int j = 0; j < fluxVal->n; j++) {
-                    char name[32];
-                    sprintf (name, "FLUX_VAL_R_%02d", j);
-                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux value in annulus", fluxVal->data.F32[j]);
-                    sprintf (name, "FLUX_ERR_R_%02d", j);
-                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux error in annulus", fluxErr->data.F32[j]);
-                    sprintf (name, "FLUX_VAR_R_%02d", j);
-                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux stdev in annulus", fluxVar->data.F32[j]);
-                }
-            } else {
-                for (int j = 0; j < radialBinsLower->n; j++) {
-                    char name[32];
-                    sprintf (name, "FLUX_VAL_R_%02d", j);
-                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux value in annulus", NAN);
-                    sprintf (name, "FLUX_ERR_R_%02d", j);
-                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux error in annulus", NAN);
-                    sprintf (name, "FLUX_VAR_R_%02d", j);
-                    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux stdev in annulus", NAN);
-                }
-            }
-        }
-
-        psArrayAdd (table, 100, row);
-        psFree (row);
-    }
-
+	    psVector *radSB   = psVectorAlloc(radMin->n, PS_TYPE_F32);
+	    psVector *radFlux = psVectorAlloc(radMin->n, PS_TYPE_F32);
+	    psVector *radFill = psVectorAlloc(radMin->n, PS_TYPE_F32);
+	    psVectorInit (radSB, NAN);
+	    psVectorInit (radFlux, NAN);
+	    psVectorInit (radFill, NAN);
+	    if (!source->extpars) goto empty_annuli;
+	    if (!source->extpars->radProfile) goto empty_annuli;
+	    if (!source->extpars->radProfile->binSB) goto empty_annuli;
+	    psAssert (source->extpars->radProfile->binSum, "programming error");
+	    psAssert (source->extpars->radProfile->binFill, "programming error");
+	    psAssert (source->extpars->radProfile->binSB->n <= radFlux->n, "inconsistent vector lengths");
+	    psAssert (source->extpars->radProfile->binSum->n <= radFlux->n, "inconsistent vector lengths");
+	    psAssert (source->extpars->radProfile->binFill->n <= radFlux->n, "inconsistent vector lengths");
+
+	    // copy the data from fluxVal (which is not guaranteed to be the full length) to radFlux
+	    for (int j = 0; j < source->extpars->radProfile->binSB->n; j++) {
+		radSB->data.F32[j]   = source->extpars->radProfile->binSB->data.F32[j];
+		radFlux->data.F32[j] = source->extpars->radProfile->binSum->data.F32[j];
+		radFill->data.F32[j] = source->extpars->radProfile->binFill->data.F32[j];
+	    }
+
+	empty_annuli:
+	    psMetadataAdd (row, PS_LIST_TAIL, "PROF_SB", PS_DATA_VECTOR, "mean surface brightness annuli", radSB);
+	    psMetadataAdd (row, PS_LIST_TAIL, "PROF_FLUX", PS_DATA_VECTOR, "flux within annuli", radFlux);
+	    psMetadataAdd (row, PS_LIST_TAIL, "PROF_FILL", PS_DATA_VECTOR, "fill factor of annuli", radFill);
+	    psFree (radSB);
+	    psFree (radFlux);
+	    psFree (radFill);
+	}
+	if (nRow < 0) {
+	    nRow = row->list->n;
+	} else {
+	    psAssert (nRow == row->list->n, "inconsistent row lengths");
+	}
+	psArrayAdd (table, 100, row);
+	psFree (row);
+    }
+    
     if (table->n == 0) {
-        psFitsWriteBlank (fits, outhead, extname);
-        psFree (outhead);
-        psFree (table);
-        return true;
-    }
-
+	if (!psFitsWriteBlank (fits, outhead, extname)) {
+	    psError(psErrorCodeLast(), false, "Unable to write empty sources file.");
+	    psFree(outhead);
+	    psFree(table);
+	    return false;
+	}
+	psFree (outhead);
+	psFree (table);
+	return true;
+    }
+    
     psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
     if (!psFitsWriteTable (fits, outhead, table, extname)) {
-        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
-        psFree (outhead);
-        psFree(table);
-        return false;
+	psError(psErrorCodeLast(), false, "writing ext data %s\n", extname);
+	psFree (outhead);
+    psFree(table);
+    return false;
     }
     psFree (outhead);
     psFree (table);
-
+    
     return true;
 }
 
 // XXX this layout is still the same as PS1_DEV_1
-bool pmSourcesWrite_CMF_PS1_V2_XFIT (psFits *fits, psArray *sources, char *extname)
+bool pmSourcesWrite_CMF_PS1_V2_XFIT (psFits *fits, pmReadout *readout, psArray *sources, char *extname)
 {
 
@@ -607,5 +684,10 @@
 
     if (table->n == 0) {
-        psFitsWriteBlank (fits, outhead, extname);
+        if (!psFitsWriteBlank (fits, outhead, extname)) {
+            psError(psErrorCodeLast(), false, "Unable to write empty sources file.");
+            psFree(outhead);
+            psFree(table);
+            return false;
+        }
         psFree (outhead);
         psFree (table);
@@ -615,5 +697,5 @@
     psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
     if (!psFitsWriteTable (fits, outhead, table, extname)) {
-        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+        psError(psErrorCodeLast(), false, "writing ext data %s\n", extname);
         psFree (outhead);
         psFree(table);
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceIO_MatchedRefs.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceIO_MatchedRefs.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceIO_MatchedRefs.c	(revision 27839)
@@ -67,81 +67,83 @@
 
     if (!table) {
-	table = psArrayAllocEmpty (0x1000);
-	pmFPAview *view = pmFPAviewAlloc (0);
+        table = psArrayAllocEmpty (0x1000);
+        pmFPAview *view = pmFPAviewAlloc (0);
 
-	// this loop selects the matched stars for all chips
-	while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
-	    psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
-	    if (!chip->process || !chip->file_exists) continue;
+        // this loop selects the matched stars for all chips
+        while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+            if (!chip->process || !chip->file_exists) continue;
 
-	    char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
+            char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
 
-	    while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
-		psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
-		if (!cell->process || !cell->file_exists) continue;
+            while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+                psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+                if (!cell->process || !cell->file_exists) continue;
 
-		// process each of the readouts
-		// XXX there can only be one readout per chip, right?
-		while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
-		    if (! readout->data_exists) continue;
+                // process each of the readouts
+                // XXX there can only be one readout per chip, right?
+                while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+                    if (! readout->data_exists) continue;
 
-		    // select the raw objects for this readout
-		    psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
-		    if (rawstars == NULL) continue;
+                    // select the raw objects for this readout
+                    psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+                    if (rawstars == NULL) continue;
 
-		    // select the raw objects for this readout
-		    psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
-		    if (refstars == NULL) continue;
-		    psTrace ("psastro", 4, "Trying %ld refstars\n", refstars->n);
+                    // select the raw objects for this readout
+                    psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+                    if (refstars == NULL) continue;
+                    psTrace ("psastro", 4, "Trying %ld refstars\n", refstars->n);
 
 # if (0)
-		    // XXX test
-		    FILE *outfile = fopen ("refstars.dat", "w");
-		    assert (outfile);
-		    for (int nn = 0; nn < refstars->n; nn++) {
-			pmAstromObj *ref = refstars->data[nn];
-			fprintf (outfile, "%lf %lf\n", ref->sky->r*PS_DEG_RAD, ref->sky->d*PS_DEG_RAD);
-		    }
-		    fclose (outfile);
+                    // XXX test
+                    FILE *outfile = fopen ("refstars.dat", "w");
+                    assert (outfile);
+                    for (int nn = 0; nn < refstars->n; nn++) {
+                        pmAstromObj *ref = refstars->data[nn];
+                        fprintf (outfile, "%lf %lf\n", ref->sky->r*PS_DEG_RAD, ref->sky->d*PS_DEG_RAD);
+                    }
+                    fclose (outfile);
 # endif
 
-		    psArray *matches = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.MATCH");
-		    if (matches == NULL) continue;
+                    psArray *matches = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.MATCH");
+                    if (matches == NULL) continue;
 
-		    for (int i = 0; i < matches->n; i++) {
-			pmAstromMatch *match = matches->data[i];
+                    for (int i = 0; i < matches->n; i++) {
+                        pmAstromMatch *match = matches->data[i];
 
-			pmAstromObj *raw = rawstars->data[match->raw];
-			pmAstromObj *ref = refstars->data[match->ref];
+                        pmAstromObj *raw = rawstars->data[match->raw];
+                        pmAstromObj *ref = refstars->data[match->ref];
 
-			psMetadata *row = psMetadataAlloc ();
-			psMetadataAdd (row, PS_LIST_TAIL, "RA",       PS_DATA_F64, "right ascension (deg, J2000)", PM_DEG_RAD*ref->sky->r);
-			psMetadataAdd (row, PS_LIST_TAIL, "DEC",      PS_DATA_F64, "declination (deg, J2000)",     PM_DEG_RAD*ref->sky->d);
-			psMetadataAdd (row, PS_LIST_TAIL, "X_CHIP",   PS_DATA_F32, "x coord on chip",              raw->chip->x);
-			psMetadataAdd (row, PS_LIST_TAIL, "Y_CHIP",   PS_DATA_F32, "y coord on chip",              raw->chip->y);
-			psMetadataAdd (row, PS_LIST_TAIL, "X_FPA",    PS_DATA_F32, "x coord on focal plane",       raw->FP->x);
-			psMetadataAdd (row, PS_LIST_TAIL, "Y_FPA",    PS_DATA_F32, "y coord on focal plane",       raw->FP->y);
-			psMetadataAdd (row, PS_LIST_TAIL, "MAG_INST", PS_DATA_F32, "instrumental magnitude",       raw->Mag);
-			psMetadataAdd (row, PS_LIST_TAIL, "MAG_REF",  PS_DATA_F32, "reference star magnitude",     ref->Mag);
-			psMetadataAdd (row, PS_LIST_TAIL, "COLOR_REF",PS_DATA_F32, "reference star color",         ref->Color);
-			psMetadataAdd (row, PS_LIST_TAIL, "CHIP_ID",  PS_DATA_STRING, "chip identifier",           chipName);
-			// XXX need to add the reference color, but this needs getstar / dvo.photcodes for the reference to be refined.
+                        psMetadata *row = psMetadataAlloc ();
+                        psMetadataAdd (row, PS_LIST_TAIL, "RA",       PS_DATA_F64, "right ascension (deg, J2000)", PM_DEG_RAD*ref->sky->r);
+                        psMetadataAdd (row, PS_LIST_TAIL, "DEC",      PS_DATA_F64, "declination (deg, J2000)",     PM_DEG_RAD*ref->sky->d);
+                        psMetadataAdd (row, PS_LIST_TAIL, "X_CHIP",   PS_DATA_F32, "x coord on chip",              raw->chip->x);
+                        psMetadataAdd (row, PS_LIST_TAIL, "Y_CHIP",   PS_DATA_F32, "y coord on chip",              raw->chip->y);
+                        psMetadataAdd (row, PS_LIST_TAIL, "X_CHIP_FIT",PS_DATA_F32, "x fitted coord on chip",      ref->chip->x);
+                        psMetadataAdd (row, PS_LIST_TAIL, "Y_CHIP_FIT",PS_DATA_F32, "y fitted coord on chip",      ref->chip->y);
+                        psMetadataAdd (row, PS_LIST_TAIL, "X_FPA",    PS_DATA_F32, "x coord on focal plane",       raw->FP->x);
+                        psMetadataAdd (row, PS_LIST_TAIL, "Y_FPA",    PS_DATA_F32, "y coord on focal plane",       raw->FP->y);
+                        psMetadataAdd (row, PS_LIST_TAIL, "MAG_INST", PS_DATA_F32, "instrumental magnitude",       raw->Mag);
+                        psMetadataAdd (row, PS_LIST_TAIL, "MAG_REF",  PS_DATA_F32, "reference star magnitude",     ref->Mag);
+                        psMetadataAdd (row, PS_LIST_TAIL, "COLOR_REF",PS_DATA_F32, "reference star color",         ref->Color);
+                        psMetadataAdd (row, PS_LIST_TAIL, "CHIP_ID",  PS_DATA_STRING, "chip identifier",           chipName);
+                        // XXX need to add the reference color, but this needs getstar / dvo.photcodes for the reference to be refined.
 
-			psArrayAdd (table, 100, row);
-			psFree (row);
-		    }
-		}
-	    }
-	}
-	psFree (view);
+                        psArrayAdd (table, 100, row);
+                        psFree (row);
+                    }
+                }
+            }
+        }
+        psFree (view);
 
-	if (table->n == 0) {
-	    psFree(table);
-	    return true;
-	}
+        if (table->n == 0) {
+            psFree(table);
+            return true;
+        }
     }
 
     if (!psFitsWriteTable(fits, NULL, table, "MATCHED_REFS")) {
-        psError(PS_ERR_IO, false, "writing MATCHED_REFS\n");
+        psError(psErrorCodeLast(), false, "writing MATCHED_REFS\n");
         psFree(table);
         return false;
@@ -161,9 +163,10 @@
 
     // try find the MATCHED_REFS extension.  if non-existent, note that we tried, and move on.
-    if (!psFitsMoveExtName (fits, "MATCHED_REFS")) {
-	psMetadataAddBool (fpa->analysis, PS_LIST_TAIL, "READ.REFMATCH", PS_META_REPLACE, "attempted to read MATCHED_REFS", true);
-	return true;
+    // It is not an error to lack this entry -- psFitsMoveExtNameClean does not raise an error
+    if (!psFitsMoveExtNameClean (fits, "MATCHED_REFS")) {
+        psMetadataAddBool (fpa->analysis, PS_LIST_TAIL, "READ.REFMATCH", PS_META_REPLACE, "attempted to read MATCHED_REFS", true);
+        return true;
     }
-    
+
     // We get the size of the table, and allocate the array of sources first because the table
     // is large and ephemeral --- when the table gets blown away, whatever is allocated after
@@ -175,5 +178,10 @@
     for (int i = 0; i < numRows; i++) {
         psMetadata *row = psFitsReadTableRow(fits, i); // Table row
-	rows->data[i] = row;
+        if (!row) {
+            psError(psErrorCodeLast(), false, "Unable to read row %d of matched references.", i);
+            psFree(rows);
+            return false;
+        }
+        rows->data[i] = row;
     }
 
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceIO_PS1_CAL_0.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceIO_PS1_CAL_0.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceIO_PS1_CAL_0.c	(revision 27839)
@@ -282,8 +282,11 @@
         pmPSF_AxesToModel (PAR, axes);
 
-        float lflux = psMetadataLookupF32 (&status, row, "PEAK_FLUX_AS_MAG");
-        float flux = (isfinite(lflux)) ? pow(10.0, -0.4*lflux) : NAN;
-        source->peak = pmPeakAlloc(PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], flux, PM_PEAK_LONE);
-        source->peak->flux = flux;
+        float peakMag     = psMetadataLookupF32 (&status, row, "PEAK_FLUX_AS_MAG");
+        float peakFlux    = (isfinite(peakMag)) ? pow(10.0, -0.4*peakMag) : NAN;
+
+        source->peak       = pmPeakAlloc(PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], peakFlux, PM_PEAK_LONE);
+        source->peak->flux = peakFlux;
+        source->peak->dx   = dPAR[PM_PAR_XPOS];
+        source->peak->dy   = dPAR[PM_PAR_YPOS];
 
         source->pixWeight = psMetadataLookupF32 (&status, row, "PSF_QF");
@@ -349,8 +352,8 @@
 
     // which extended source analyses should we perform?
-    bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
-    bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
-    bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
-    bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
+    // bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+    // bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
+    // bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
+    // bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
 
     psVector *radialBinsLower = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.LOWER");
@@ -410,4 +413,5 @@
         psMetadataAdd (row, PS_LIST_TAIL, "Y_EXT_SIG",        PS_DATA_F32, "Sigma in EXT y coordinate",                  yErr);
 
+# if (0)
 	// Petrosian measurements
 	// XXX insert header data: petrosian ref radius, flux ratio
@@ -501,4 +505,5 @@
 	    }
 	}
+# endif
 
 	psArrayAdd (table, 100, row);
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceIO_PS1_DEV_0.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceIO_PS1_DEV_0.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceIO_PS1_DEV_0.c	(revision 27839)
@@ -208,8 +208,11 @@
         pmPSF_AxesToModel (PAR, axes);
 
-        float lflux = psMetadataLookupF32 (&status, row, "PEAK_FLUX_AS_MAG");
-        float flux = (isfinite(lflux)) ? pow(10.0, -0.4*lflux) : NAN;
-        source->peak = pmPeakAlloc(PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], flux, PM_PEAK_LONE);
-        source->peak->flux = flux;
+        float peakMag = psMetadataLookupF32 (&status, row, "PEAK_FLUX_AS_MAG");
+        float peakFlux = (isfinite(peakMag)) ? pow(10.0, -0.4*peakMag) : NAN;
+
+        source->peak = pmPeakAlloc(PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], peakFlux, PM_PEAK_LONE);
+        source->peak->flux = peakFlux;
+        source->peak->dx   = dPAR[PM_PAR_XPOS];
+        source->peak->dy   = dPAR[PM_PAR_YPOS];
 
         source->pixWeight = psMetadataLookupF32 (&status, row, "PSF_QF");
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceIO_PS1_DEV_1.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceIO_PS1_DEV_1.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceIO_PS1_DEV_1.c	(revision 27839)
@@ -252,8 +252,11 @@
         pmPSF_AxesToModel (PAR, axes);
 
-        float lflux = psMetadataLookupF32 (&status, row, "PEAK_FLUX_AS_MAG");
-        float flux = (isfinite(lflux)) ? pow(10.0, -0.4*lflux) : NAN;
-        source->peak = pmPeakAlloc(PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], flux, PM_PEAK_LONE);
-        source->peak->flux = flux;
+        float peakMag     = psMetadataLookupF32 (&status, row, "PEAK_FLUX_AS_MAG");
+        float peakFlux    = (isfinite(peakMag)) ? pow(10.0, -0.4*peakMag) : NAN;
+
+        source->peak = pmPeakAlloc(PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], peakFlux, PM_PEAK_LONE);
+        source->peak->flux = peakFlux;
+        source->peak->dx   = dPAR[PM_PAR_XPOS];
+        source->peak->dy   = dPAR[PM_PAR_YPOS];
 
         source->pixWeight = psMetadataLookupF32 (&status, row, "PSF_QF");
@@ -300,8 +303,8 @@
 
     // which extended source analyses should we perform?
-    bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
-    bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
-    bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
-    bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
+    // bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+    // bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
+    // bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
+    // bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
 
     psVector *radialBinsLower = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.LOWER");
@@ -343,4 +346,6 @@
         psMetadataAdd (row, PS_LIST_TAIL, "Y_EXT_SIG",        PS_DATA_F32, "Sigma in EXT y coordinate",                  yErr);
 
+// XXX disable these outputs until we clean up the names
+# if (0)
 	// Petrosian measurements
 	// XXX insert header data: petrosian ref radius, flux ratio
@@ -422,4 +427,5 @@
 	    }
 	}
+# endif
 
 	psArrayAdd (table, 100, row);
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceIO_RAW.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceIO_RAW.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceIO_RAW.c	(revision 27839)
@@ -119,5 +119,5 @@
                  logChi, logChiNorm,
                  source[0].peak->SN,
-                 model[0].radiusFit,
+                 source[0].apRadius,
                  source[0].pixWeight,
                  model[0].nDOF,
@@ -181,5 +181,5 @@
                  log10(model[0].chisqNorm/model[0].nDOF),
                  source[0].peak->SN,
-                 model[0].radiusFit,
+                 source[0].apRadius,
                  source[0].pixWeight,
                  model[0].nDOF,
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceIO_SMPDATA.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceIO_SMPDATA.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceIO_SMPDATA.c	(revision 27839)
@@ -184,5 +184,4 @@
 	pmPSF_AxesToModel (PAR, axes);
 
-
         source->psfMag = psMetadataLookupF32 (&status, row, "MAG_RAW") - ZERO_POINT;
         source->extMag = psMetadataLookupF32 (&status, row, "MAG_GAL") - ZERO_POINT;
@@ -198,4 +197,6 @@
 
 	source->peak = pmPeakAlloc(PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], peakFlux, PM_PEAK_LONE);
+        source->peak->flux = peakFlux;
+
         sources->data[i] = source;
     }
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceMatch.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceMatch.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceMatch.c	(revision 27839)
@@ -8,4 +8,5 @@
 
 #include "pmSource.h"
+#include "pmErrorCodes.h"
 
 #include "pmSourceMatch.h"
@@ -112,6 +113,9 @@
     psFree(match->mag);
     psFree(match->magErr);
+    psFree(match->x);
+    psFree(match->y);
     psFree(match->image);
     psFree(match->index);
+    psFree(match->mask);
 }
 
@@ -124,4 +128,6 @@
     match->mag = psVectorAllocEmpty(ARRAY_BUFFER, PS_TYPE_F32);
     match->magErr = psVectorAllocEmpty(ARRAY_BUFFER, PS_TYPE_F32);
+    match->x = psVectorAllocEmpty(ARRAY_BUFFER, PS_TYPE_F32);
+    match->y = psVectorAllocEmpty(ARRAY_BUFFER, PS_TYPE_F32);
     match->image = psVectorAllocEmpty(ARRAY_BUFFER, PS_TYPE_U32);
     match->index = psVectorAllocEmpty(ARRAY_BUFFER, PS_TYPE_U32);
@@ -133,4 +139,5 @@
 void pmSourceMatchAdd(pmSourceMatch *match, // Match data
                       float mag, float magErr, // Magnitude and error
+                      float x, float y,        // Position
                       int image, // Image index
                       int index // Source index
@@ -141,4 +148,6 @@
     match->mag = psVectorExtend(match->mag, match->mag->nalloc, 1);
     match->magErr = psVectorExtend(match->magErr, match->magErr->nalloc, 1);
+    match->x = psVectorExtend(match->x, match->x->nalloc, 1);
+    match->y = psVectorExtend(match->y, match->y->nalloc, 1);
     match->image = psVectorExtend(match->image, match->image->nalloc, 1);
     match->index = psVectorExtend(match->index, match->index->nalloc, 1);
@@ -147,4 +156,6 @@
     match->mag->data.F32[num] = mag;
     match->magErr->data.F32[num] = magErr;
+    match->x->data.F32[num] = x;
+    match->y->data.F32[num] = y;
     match->image->data.S32[num] = image;
     match->index->data.S32[num] = index;
@@ -172,5 +183,5 @@
     for (int i = 0; i < numImages; i++) {
         psArray *sources = sourceArrays->data[i]; // Sources in image
-        if (!sources) {
+        if (!sources || sources->n == 0) {
             continue;
         }
@@ -192,5 +203,5 @@
                 pmSourceMatch *match = pmSourceMatchAlloc(); // Match data
                 pmSourceMatchAdd(match, magImage->data.F32[j], magErrImage->data.F32[j],
-                                 i, indices->data.S32[j]);
+                                 xImage->data.F32[j], yImage->data.F32[j], i, indices->data.S32[j]);
                 matches->data[j] = match;
             }
@@ -212,5 +223,5 @@
                 pmSourceMatch *match = pmSourceMatchAlloc(); // Match data
                 pmSourceMatchAdd(match, magImage->data.F32[j], magErrImage->data.F32[j],
-                                 i, indices->data.S32[j]);
+                                 xImage->data.F32[j], yImage->data.F32[j], i, indices->data.S32[j]);
                 matches->data[k] = match;
             }
@@ -221,5 +232,5 @@
         } else {
             // Match with the master list
-            psTree *tree = psTreePlant(2, SOURCES_MAX_LEAF, xMaster, yMaster); // kd Tree with sources
+            psTree *tree = psTreePlant(2, SOURCES_MAX_LEAF, PS_TREE_EUCLIDEAN, xMaster, yMaster); // kd Tree
             long numMatch = 0;          // Number of matches
 
@@ -238,5 +249,5 @@
                     pmSourceMatch *match = matches->data[index]; // Match data
                     pmSourceMatchAdd(match, magImage->data.F32[j], magErrImage->data.F32[j],
-                                     i, indices->data.S32[j]);
+                                     xImage->data.F32[j], yImage->data.F32[j], i, indices->data.S32[j]);
                     numMatch++;
                 } else {
@@ -244,5 +255,5 @@
                     pmSourceMatch *match = pmSourceMatchAlloc(); // Match data
                     pmSourceMatchAdd(match, magImage->data.F32[j], magErrImage->data.F32[j],
-                                     i, indices->data.S32[j]);
+                                     xImage->data.F32[j], yImage->data.F32[j], i, indices->data.S32[j]);
                     xMaster->data.F32[numMaster] = xImage->data.F32[j];
                     yMaster->data.F32[numMaster] = yImage->data.F32[j];
@@ -262,5 +273,10 @@
         psFree(magImage);
         psFree(magErrImage);
-    }
+        psFree(indices);
+    }
+
+    psFree(xMaster);
+    psFree(yMaster);
+    psFree(boundsMaster);
 
     if (cullSingles) {
@@ -304,5 +320,6 @@
         pmSourceMatch *match = matches->data[i]; // Match of interest
         for (int j = 0; j < match->num && !source; j++) {
-            if (!isfinite(match->mag->data.F32[j]) || !isfinite(match->magErr->data.F32[j])) {
+            if (!isfinite(match->mag->data.F32[j]) || !isfinite(match->magErr->data.F32[j]) ||
+                !isfinite(match->x->data.F32[j]) || !isfinite(match->y->data.F32[j])) {
                 continue;
             }
@@ -365,5 +382,5 @@
         double star = 0.0, starErr = 0.0; // Accumulators for star
         for (int j = 0; j < match->num; j++) {
-            if (match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j]) {
+            if (match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j] & PM_SOURCE_MATCH_MASK_PHOT) {
                 continue;
             }
@@ -396,5 +413,5 @@
         pmSourceMatch *match = matches->data[i]; // Matched stars
         for (int j = 0; j < match->num; j++) {
-            if (match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j]) {
+            if (match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j] & PM_SOURCE_MATCH_MASK_PHOT) {
                 continue;
             }
@@ -424,5 +441,5 @@
         pmSourceMatch *match = matches->data[i]; // Matched stars
         for (int j = 0; j < match->num; j++) {
-            if (match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j]) {
+            if (match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j] & PM_SOURCE_MATCH_MASK_PHOT) {
                 continue;
             }
@@ -543,5 +560,5 @@
         pmSourceMatch *match = matches->data[i]; // Matched stars
         for (int j = 0; j < match->num; j++) {
-            if (match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j]) {
+            if (match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j] & PM_SOURCE_MATCH_MASK_PHOT) {
                 continue;
             }
@@ -564,5 +581,5 @@
                 if (PS_SQR(dev) > starClip * (PS_SQR(magErr) + sysErr2)) {
                     numRejected++;
-                    match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j] = 0xFF;
+                    match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j] |= PM_SOURCE_MATCH_MASK_PHOT;
                 }
             }
@@ -599,10 +616,39 @@
     int numImages = zp->n;              // Number of images
     int numStars = matches->n;          // Number of stars
+    psVector *badImage = psVectorAlloc(numImages, PS_TYPE_U8); // Bad image?
+    psVectorInit(badImage, 0);
+
+    // Check for data integrity
+    {
+        psVector *num = psVectorAlloc(numImages, PS_TYPE_S32); // Number of stars per image
+        psVectorInit(num, 0);
+        for (int i = 0; i < numStars; i++) {
+            pmSourceMatch *match = matches->data[i]; // Matched stars
+            for (int j = 0; j < match->num; j++) {
+                int index = match->image->data.U32[j]; // Image index
+                psAssert(index >= 0 && index < numImages, "Bad index: %d", index);
+                num->data.S32[index]++;
+            }
+        }
+        int numGood = 0;                // Number of good images
+        for (int i = 0; i < numImages; i++) {
+            if (num->data.S32[i] == 0 || !isfinite(zp->data.F32[i])) {
+                badImage->data.U8[i] = 0xFF;
+                continue;
+            }
+            numGood++;
+        }
+        psFree(num);
+        if (numGood == 0) {
+            psError(PM_ERR_DATA, true, "No images with good stars.");
+            psFree(badImage);
+            return false;
+        }
+    }
+
     psVector *trans = psVectorAlloc(numImages, PS_TYPE_F32); // Transparencies for each image, magnitudes
     psVectorInit(trans, 0.0);
     psVector *photo = psVectorAlloc(numImages, PS_TYPE_U8); // Photometric determination for each image
     psVectorInit(photo, 0);
-    psVector *badImage = psVectorAlloc(numImages, PS_TYPE_U8); // Bad image?
-    psVectorInit(badImage, 0);
     psVector *stars = psVectorAlloc(numStars, PS_TYPE_F32); // Magnitudes for each star
 
@@ -627,8 +673,8 @@
             return NULL;
         }
-        psTrace("psModules.objects", 3, "Pass 1: Determined %d/%d are photometric", numPhoto, numImages);
+        psTrace("psModules.objects", 3, "Pass 1: Determined %d/%d are photometric\n", numPhoto, numImages);
 
         fracRej = sourceMatchRelphotReject(trans, stars, matches, zp, photo, badImage, rej1, sys1);
-        psTrace("psModules.objects", 3, "Pass 1: %f%% of measurements rejected", fracRej * 100);
+        psTrace("psModules.objects", 3, "Pass 1: %f%% of measurements rejected\n", fracRej * 100);
 
         chi2 = sourceMatchRelphotIterate(trans, stars, badImage, matches, zp, photo, sys1);
@@ -649,8 +695,8 @@
             return NULL;
         }
-        psTrace("psModules.objects", 3, "Pass 2: Determined %d/%d are photometric", numPhoto, numImages);
+        psTrace("psModules.objects", 3, "Pass 2: Determined %d/%d are photometric\n", numPhoto, numImages);
 
         fracRej = sourceMatchRelphotReject(trans, stars, matches, zp, photo, badImage, rej2, sys2);
-        psTrace("psModules.objects", 3, "Pass 2: %f%% of measurements rejected", fracRej * 100);
+        psTrace("psModules.objects", 3, "Pass 2: %f%% of measurements rejected\n", fracRej * 100);
 
         chi2 = sourceMatchRelphotIterate(trans, stars, badImage, matches, zp, photo, sys2);
@@ -668,2 +714,220 @@
     return trans;
 }
+
+
+// Iterate on the star positions and image shifts
+// Returns the solution chi^2
+static float sourceMatchRelastroIterate(psVector *xShift, psVector *yShift, // Shift for image
+                                        psVector *xStar, psVector *yStar,   // Position for star
+                                        const psArray *matches // Array of matches
+    )
+{
+    psAssert(matches, "Need list of matches");
+
+    int numImages = xShift->n;          // Number of images
+    int numStars = matches->n;          // Number of stars
+
+    psAssert(xShift && xShift->type.type == PS_TYPE_F32 && yShift && yShift->type.type == PS_TYPE_F32,
+             "Need shifts");
+    psAssert(yShift->n == numImages, "Not enough shifts: %ld\n", yShift->n);
+    psAssert(xStar && xStar->type.type == PS_TYPE_F32 && yStar && yStar->type.type == PS_TYPE_F32,
+             "Need star positions");
+    psAssert(xStar->n == numStars && yStar->n == numStars, "Not enough stars\n");
+
+    // Solve the star positions
+    psVectorInit(xStar, NAN);
+    psVectorInit(yStar, NAN);
+    psVector *starMask = psVectorAlloc(numStars, PS_TYPE_U8); // Mask for stars
+    psVectorInit(starMask, 0xFF);
+    int numGoodStars = 0;               // Number of stars with good measurements
+    for (int i = 0; i < numStars; i++) {
+        pmSourceMatch *match = matches->data[i]; // Matched stars
+        int numMeasurements = 0;        // Number of unmasked measurements for star
+        double xSum = 0.0, ySum = 0.0;  // Accumulators for star
+        for (int j = 0; j < match->num; j++) {
+            if (match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j] & PM_SOURCE_MATCH_MASK_ASTRO) {
+                continue;
+            }
+            numMeasurements++;
+            int index = match->image->data.U32[j]; // Image index
+
+            xSum += match->x->data.F32[j] - xShift->data.F32[index];
+            ySum += match->y->data.F32[j] - yShift->data.F32[index];
+        }
+        if (numMeasurements > 1) {
+            // It's only a good star (contributing to the chi^2) if there's more than 1 measurement
+            numGoodStars++;
+            xStar->data.F32[i] = xSum / numMeasurements;
+            yStar->data.F32[i] = ySum / numMeasurements;
+            starMask->data.U8[i] = 0;
+        }
+    }
+
+    // Solve for the shifts
+    psVectorInit(xShift, 0.0);
+    psVectorInit(yShift, 0.0);
+    psVector *num = psVectorAlloc(numImages, PS_TYPE_S32);    // Number of stars
+    psVectorInit(num, 0);
+    for (int i = 0; i < numStars; i++) {
+        if (starMask->data.U8[i]) {
+            continue;
+        }
+        pmSourceMatch *match = matches->data[i]; // Matched stars
+        for (int j = 0; j < match->num; j++) {
+            if (match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j] & PM_SOURCE_MATCH_MASK_ASTRO) {
+                continue;
+            }
+            int index = match->image->data.U32[j]; // Image index
+
+            xShift->data.F32[index] += match->x->data.F32[j] - xStar->data.F32[i];
+            yShift->data.F32[index] += match->y->data.F32[j] - yStar->data.F32[i];
+            num->data.S32[index]++;
+        }
+    }
+    for (int i = 0; i < numImages; i++) {
+        xShift->data.F32[i] /= num->data.S32[i];
+        yShift->data.F32[i] /= num->data.S32[i];
+        psTrace("psModules.objects", 3, "Shift for image %d: %f,%f\n",
+                i, xShift->data.F32[i], yShift->data.F32[i]);
+    }
+    psFree(num);
+
+    // Once more through to evaluate chi^2
+    float chi2 = 0.0;                   // chi^2 for iteration
+    int dof = 0;                        // Degrees of freedom
+    for (int i = 0; i < numStars; i++) {
+        pmSourceMatch *match = matches->data[i]; // Matched stars
+        if (starMask->data.U8[i]) {
+            continue;
+        }
+        for (int j = 0; j < match->num; j++) {
+            if (match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j]) {
+                continue;
+            }
+
+            int index = match->image->data.U32[j]; // Image index
+            float dx = match->x->data.F32[j] - xShift->data.F32[index] - xStar->data.F32[i];
+            float dy = match->y->data.F32[j] - yShift->data.F32[index] - yStar->data.F32[i];
+
+            chi2 += PS_SQR(dx) + PS_SQR(dy);
+            dof++;
+        }
+    }
+    dof -= numGoodStars + numImages;
+    chi2 /= dof;
+
+    return chi2;
+}
+
+// Reject star measurements
+// Returns the fraction of measurements that were rejected
+static float sourceMatchRelastroReject(const psVector *xShift, const psVector *yShift, // Shifts for each image
+                                       const psVector *xStar, const psVector *yStar, // Positions for each star
+                                       const psArray *matches, // Array of matches
+                                       float chi2,             // chi^2 from fit
+                                       float rej               // Rejection threshold
+                                )
+{
+    psAssert(matches, "Need list of matches");
+
+    int numImages = xShift->n;          // Number of images
+    int numStars = matches->n;          // Number of stars
+
+    psAssert(xShift && xShift->type.type == PS_TYPE_F32 && yShift && yShift->type.type == PS_TYPE_F32,
+             "Need shifts");
+    psAssert(yShift->n == numImages, "Not enough shifts: %ld\n", yShift->n);
+    psAssert(xStar && xStar->type.type == PS_TYPE_F32 && yStar && yStar->type.type == PS_TYPE_F32,
+             "Need star positions");
+    psAssert(xStar->n == numStars && yStar->n == numStars, "Not enough stars\n");
+
+    int numRejected = 0;                // Number rejected
+    int numMeasurements = 0;            // Number of measurements
+
+    float thresh = PS_SQR(rej) * chi2;    // Threshold for rejection
+
+    for (int i = 0; i < numStars; i++) {
+        pmSourceMatch *match = matches->data[i]; // Matched stars
+        for (int j = 0; j < match->num; j++) {
+            if (match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j] & PM_SOURCE_MATCH_MASK_ASTRO) {
+                continue;
+            }
+            numMeasurements++;
+            int index = match->image->data.U32[j]; // Image index
+
+            float dx = match->x->data.F32[j] - xShift->data.F32[index] - xStar->data.F32[i];
+            float dy = match->y->data.F32[j] - yShift->data.F32[index] - yStar->data.F32[i];
+
+            if (PS_SQR(dx) + PS_SQR(dy) > thresh) {
+                numRejected++;
+                match->mask->data.PS_TYPE_VECTOR_MASK_DATA[j] |= PM_SOURCE_MATCH_MASK_ASTRO;
+            }
+        }
+    }
+
+    return (float)numRejected / (float)numMeasurements;
+}
+
+psArray *pmSourceMatchRelastro(const psArray *matches, // Array of matches
+                               int numImages,          // Number of images
+                               float tol, // Relative tolerance for convergence
+                               int iter1, // Number of iterations for pass 1
+                               float rej1, // Limit on rejection between iterations for pass 1
+                               int iter2, // Number of iterations for pass 2
+                               float rej2, // Limit on rejection between iterations for pass 2
+                               float rejLimit // Limit on rejection between iterations
+    )
+{
+    PS_ASSERT_ARRAY_NON_NULL(matches, NULL);
+
+    int numStars = matches->n;          // Number of stars
+    psVector *xShift = psVectorAlloc(numImages, PS_TYPE_F32); // x shift for each image
+    psVector *yShift = psVectorAlloc(numImages, PS_TYPE_F32); // y shift for each image
+    psVectorInit(xShift, 0.0);
+    psVectorInit(yShift, 0.0);
+    psVector *xStar = psVectorAlloc(numStars, PS_TYPE_F32); // x position for each star
+    psVector *yStar = psVectorAlloc(numStars, PS_TYPE_F32); // y position for each star
+
+    float chi2 = sourceMatchRelastroIterate(xShift, yShift, xStar, yStar, matches); // chi^2 for solution
+    psTrace("psModules.objects", 1, "Initial: chi^2 = %f\n", chi2);
+    float lastChi2 = INFINITY;          // chi^2 on last iteration
+    float fracRej = INFINITY;           // Fraction of measurements rejected
+
+    // In the first passes, the shifts are not well deteremined: use high systematic error and
+    // rejection thresholds
+    for (int i = 0; i < iter1; i++) {
+        fracRej = sourceMatchRelastroReject(xShift, yShift, xStar, yStar, matches, chi2, rej1);
+        psTrace("psModules.objects", 3, "Pass 1: %f%% of measurements rejected\n", fracRej * 100);
+
+        chi2 = sourceMatchRelastroIterate(xShift, yShift, xStar, yStar, matches);
+        psTrace("psModules.objects", 1, "Pass 1: iter = %d: chi^2 = %f rejected = %f\n", i, chi2, fracRej);
+    }
+
+    for (int i = 0; i < iter2 && (fabsf(lastChi2 - chi2) > tol * chi2 || fracRej > rejLimit); i++) {
+        lastChi2 = chi2;
+
+        fracRej = sourceMatchRelastroReject(xShift, yShift, xStar, yStar, matches, chi2, rej2);
+        psTrace("psModules.objects", 3, "Pass 2: %f%% of measurements rejected\n", fracRej * 100);
+
+        chi2 = sourceMatchRelastroIterate(xShift, yShift, xStar, yStar, matches);
+        psTrace("psModules.objects", 1, "Pass 2: iter = %d: chi^2 = %f rejected = %f\n", i, chi2, fracRej);
+    }
+
+    psFree(xStar);
+    psFree(yStar);
+
+    if (fabsf(lastChi2 - chi2) > tol * chi2 || fracRej > rejLimit) {
+        psWarning("Unable to converge to relphot solution (%f,%f)", (lastChi2 - chi2) / chi2, fracRej);
+    }
+
+    psArray *results = psArrayAlloc(numImages); // Array of results
+    for (int i = 0; i < numImages; i++) {
+        psVector *offset = results->data[i] = psVectorAlloc(2, PS_TYPE_F32); // Offset for image
+        offset->data.F32[0] = xShift->data.F32[i];
+        offset->data.F32[1] = yShift->data.F32[i];
+    }
+    psFree(xShift);
+    psFree(yShift);
+
+    return results;
+}
+
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceMatch.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceMatch.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceMatch.h	(revision 27839)
@@ -1,4 +1,12 @@
 #ifndef PM_SOURCE_MATCH_H
 #define PM_SOURCE_MATCH_H
+
+#include <pslib.h>
+
+/// Mask values for matched sources
+typedef enum {
+    PM_SOURCE_MATCH_MASK_PHOT = 0x01,   // Source was rejected from photometry fit
+    PM_SOURCE_MATCH_MASK_ASTRO = 0x02,     // Source was rejected from astrometry fit
+} pmSourceMatchMask;
 
 /// Matched sources
@@ -12,4 +20,5 @@
     psVector *mag;                      // Magnitudes
     psVector *magErr;                   // Magnitude errors
+    psVector *x, *y;                    // Positions
     psVector *mask;                     // Mask for measurements
 } pmSourceMatch;
@@ -26,8 +35,12 @@
     PS_ASSERT_VECTOR_NON_NULL((MATCH)->mag, RVAL); \
     PS_ASSERT_VECTOR_NON_NULL((MATCH)->magErr, RVAL); \
+    PS_ASSERT_VECTOR_NON_NULL((MATCH)->x, RVAL); \
+    PS_ASSERT_VECTOR_NON_NULL((MATCH)->y, RVAL); \
     PS_ASSERT_VECTOR_SIZE((MATCH)->image, (MATCH)->num, RVAL); \
     PS_ASSERT_VECTOR_SIZE((MATCH)->index, (MATCH)->num, RVAL); \
     PS_ASSERT_VECTOR_SIZE((MATCH)->mag, (MATCH)->num, RVAL); \
     PS_ASSERT_VECTOR_SIZE((MATCH)->magErr, (MATCH)->num, RVAL); \
+    PS_ASSERT_VECTOR_SIZE((MATCH)->x, (MATCH)->num, RVAL); \
+    PS_ASSERT_VECTOR_SIZE((MATCH)->y, (MATCH)->num, RVAL); \
 }
 
@@ -38,4 +51,5 @@
 void pmSourceMatchAdd(pmSourceMatch *match, // Match data
                       float mag, float magErr, // Magnitude and error
+                      float x, float y,        // Position
                       int image, // Image index
                       int index // Source index
@@ -75,3 +89,14 @@
     );
 
+/// Perform relative astrometry to calibrate images
+psArray *pmSourceMatchRelastro(const psArray *matches, // Array of matches
+                               int numImages,          // Number of images
+                               float tol, // Relative tolerance for convergence
+                               int iter1, // Number of iterations for pass 1
+                               float rej1, // Limit on rejection between iterations for pass 1
+                               int iter2, // Number of iterations for pass 2
+                               float rej2, // Limit on rejection between iterations for pass 2
+                               float rejLimit // Limit on rejection between iterations
+    );
+
 #endif
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceMoments.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceMoments.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceMoments.c	(revision 27839)
@@ -36,6 +36,4 @@
 #include "pmSource.h"
 
-bool pmSourceMoments_Old(pmSource *source, psF32 radius);
-
 /******************************************************************************
 pmSourceMoments(source, radius): this function takes a subImage defined in the
@@ -50,12 +48,11 @@
     pmSource->mask (optional)
 
-XXX: The peak calculations are done in image coords, not subImage coords.
-
-this version clips input pixels on S/N
-XXX EAM : this version returns false for several reasons
+The peak calculations are done in image coords, not subImage coords.
+
+this version optionally clips input pixels on S/N 
 *****************************************************************************/
 # define VALID_RADIUS(X,Y,RAD2) (((RAD2) >= (PS_SQR(X) + PS_SQR(Y))) ? 1 : 0)
 
-bool pmSourceMoments(pmSource *source, psF32 radius, psF32 sigma, psF32 minSN)
+bool pmSourceMoments(pmSource *source, psF32 radius, psF32 sigma, psF32 minSN, psImageMaskType maskVal)
 {
     PS_ASSERT_PTR_NON_NULL(source, false);
@@ -64,5 +61,5 @@
     PS_ASSERT_FLOAT_LARGER_THAN(radius, 0.0, false);
 
-    // XXX supply sky in a different way?
+    // use sky from moments if defined, 0.0 otherwise
     psF32 sky = 0.0;
     if (source->moments == NULL) {
@@ -91,10 +88,18 @@
     // 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
-
-    psF32 xOff  = source->peak->x;
-    psF32 yOff  = source->peak->y;
-    psF32 xPeak = source->peak->x - source->pixels->col0; // coord of peak in subimage
-    psF32 yPeak = source->peak->y - source->pixels->row0; // coord of peak in subimage
+    // 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.
+
+    int xOff  = source->peak->x;
+    int yOff  = source->peak->y;
+    int xPeak = source->peak->x - source->pixels->col0; // coord of peak in subimage
+    int yPeak = source->peak->y - source->pixels->row0; // coord of peak in subimage
+
+    // we are guaranteed to have a valid pixel and variance at this location (right? right?)
+    // psF32 weightNorm = source->pixels->data.F32[yPeak][xPeak] / sqrt (source->variance->data.F32[yPeak][xPeak]);
+    // psAssert (isfinite(source->pixels->data.F32[yPeak][xPeak]), "peak must be on valid pixel");
+    // psAssert (isfinite(source->variance->data.F32[yPeak][xPeak]), "peak must be on valid pixel");
+    // psAssert (source->variance->data.F32[yPeak][xPeak] > 0, "peak must be on valid pixel");
 
     for (psS32 row = 0; row < source->pixels->numRows ; row++) {
@@ -109,5 +114,5 @@
         for (psS32 col = 0; col < source->pixels->numCols ; col++, vPix++, vWgt++) {
             if (vMsk) {
-                if (*vMsk) {
+                if (*vMsk & maskVal) {
                     vMsk++;
                     continue;
@@ -126,21 +131,20 @@
             psF32 wDiff = *vWgt;
 
-	    // skip pixels below specified significance level
+	    // skip pixels below specified significance level.  this is allowed, but should be
+	    // avoided -- the over-weights the wings of bright stars compared to those of faint
+	    // stars.
             if (PS_SQR(pDiff) < minSN2*wDiff) continue;
-            if (pDiff < 0) continue;
-
+            // if (pDiff < 0) continue; // XXX : MWV says I should include < 0.0 valued points...
+
+	    // Apply a Gaussian window function.  Be careful with the window function.  S/N
+	    // weighting over weights the sky for faint sources
 	    if (sigma > 0.0) {
-	      // apply a pseudo-gaussian weight
-
-	      // XXX a lot of extra flops; can we do pre-calculate?
-	      psF32 z  = (PS_SQR(xDiff) + PS_SQR(yDiff))*rsigma2;
-	      assert (z >= 0.0);
-	      psF32 t = 1.0 + z*(1.0 + z/2.0*(1.0 + z/3.0));
-	      psF32 weight  = 1.0 / t;
-
-	      // fprintf (stderr, "%6.1f %6.1f  %8.1f %8.1f  %5.3f  ", xDiff, yDiff, pDiff, wDiff, weight);
-
-	      wDiff *= weight;
-	      pDiff *= weight;
+		// XXX a lot of extra flops; can we pre-calculate?
+		psF32 z  = (PS_SQR(xDiff) + PS_SQR(yDiff))*rsigma2;
+		assert (z >= 0.0);
+		psF32 weight  = exp(-z);
+
+		wDiff *= weight;
+		pDiff *= weight;
 	    } 
 
@@ -154,16 +158,16 @@
 	    Y1  += yWght;
 
-	    // fprintf (stderr, " : %6.1f %6.1f  %8.1f %8.1f\n", X1, Y1, Sum, Var);
-
-            peakPixel = PS_MAX (*vPix, peakPixel);
-            numPixels++;
-        }
-    }
-
-    // if we have less than (1/2) of the possible pixels, force a retry
+	    peakPixel = PS_MAX (*vPix, peakPixel);
+	    numPixels++;
+	}
+    }
+
+    // if we have less than (1/4) of the possible pixels (in circle or box), force a retry
+    int minPixels = PS_MIN(0.75*R2, source->pixels->numCols*source->pixels->numRows/4.0);
+
     // XXX EAM - the limit is a bit arbitrary.  make it user defined?
-    if ((numPixels < 0.75*R2) || (Sum <= 0)) {
-        psTrace ("psModules.objects", 3, "insufficient valid pixels (%d vs %d; %f) for source\n", numPixels, (int)(0.75*R2), Sum);
-        return (false);
+    if ((numPixels < minPixels) || (Sum <= 0)) {
+	psTrace ("psModules.objects", 3, "insufficient valid pixels (%d vs %d; %f) for source\n", numPixels, minPixels, Sum);
+	return (false);
     }
 
@@ -172,6 +176,6 @@
     float My = Y1/Sum;
     if ((fabs(Mx) > radius) || (fabs(My) > radius)) {
-        psTrace ("psModules.objects", 3, "large centroid swing; invalid peak %d, %d\n", source->peak->x, source->peak->y);
-        return (false);
+	psTrace ("psModules.objects", 3, "large centroid swing; invalid peak %d, %d\n", source->peak->x, source->peak->y);
+	return (false);
     }
 
@@ -179,6 +183,9 @@
 
     // add back offset of peak in primary image
-    source->moments->Mx = Mx + xOff;
-    source->moments->My = My + yOff;
+    // also offset from pixel index to pixel coordinate
+    // (the calculation above uses pixel index instead of coordinate)
+    // 0.5 PIX: moments are calculated using the pixel index and converted here to pixel coords
+    source->moments->Mx = Mx + xOff + 0.5;
+    source->moments->My = My + yOff + 0.5;
 
     source->moments->Sum = Sum;
@@ -205,5 +212,6 @@
     Sum = 0.0;  // the second pass may include slightly different pixels, re-determine Sum
 
-    // center of mass in subimage
+    // center of mass in subimage.  Note: the calculation below uses pixel index, so we do not
+    // correct xCM, yCM to pixel coordinate here.
     psF32 xCM = Mx + xPeak; // coord of peak in subimage
     psF32 yCM = My + yPeak; // coord of peak in subimage
@@ -211,87 +219,85 @@
     for (psS32 row = 0; row < source->pixels->numRows ; row++) {
 
-        psF32 yDiff = row - yCM;
+	psF32 yDiff = row - yCM;
 	if (fabs(yDiff) > radius) continue;
 
-        psF32 *vPix = source->pixels->data.F32[row];
-        psF32 *vWgt = source->variance->data.F32[row];
-        psImageMaskType  *vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[row];
-
-        for (psS32 col = 0; col < source->pixels->numCols ; col++, vPix++, vWgt++) {
-            if (vMsk) {
-                if (*vMsk) {
-                    vMsk++;
-                    continue;
-                }
-                vMsk++;
-            }
-            if (isnan(*vPix)) continue;
-
-            psF32 xDiff = col - xCM;
+	psF32 *vPix = source->pixels->data.F32[row];
+	psF32 *vWgt = source->variance->data.F32[row];
+	psImageMaskType  *vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[row];
+
+	for (psS32 col = 0; col < source->pixels->numCols ; col++, vPix++, vWgt++) {
+	    if (vMsk) {
+		if (*vMsk & maskVal) {
+		    vMsk++;
+		    continue;
+		}
+		vMsk++;
+	    }
+	    if (isnan(*vPix)) continue;
+
+	    psF32 xDiff = col - xCM;
 	    if (fabs(xDiff) > radius) continue;
 
-            // radius is just a function of (xDiff, yDiff)
-            psF32 r2  = PS_SQR(xDiff) + PS_SQR(yDiff);
-            psF32 r  = sqrt(r2);
-            if (r > radius) continue;
-
-            psF32 pDiff = *vPix - sky;
-            psF32 wDiff = *vWgt;
-
-            // XXX EAM : should this limit be user-defined?
-
-            if (PS_SQR(pDiff) < minSN2*wDiff) continue;
-            if (pDiff < 0) continue;
-
+	    // radius is just a function of (xDiff, yDiff)
+	    psF32 r2  = PS_SQR(xDiff) + PS_SQR(yDiff);
+	    psF32 r  = sqrt(r2);
+	    if (r > radius) continue;
+
+	    psF32 pDiff = *vPix - sky;
+	    psF32 wDiff = *vWgt;
+
+	    // skip pixels below specified significance level.  this is allowed, but should be
+	    // avoided -- the over-weights the wings of bright stars compared to those of faint
+	    // stars.
+	    if (PS_SQR(pDiff) < minSN2*wDiff) continue;
+	    // if (pDiff < 0) continue;
+
+	    // Apply a Gaussian window function.  Be careful with the window function.  S/N
+	    // weighting over weights the sky for faint sources
 	    if (sigma > 0.0) {
-	      // apply a pseudo-gaussian weight
-
-	      // XXX a lot of extra flops; can we do pre-calculate?
-	      psF32 z  = (PS_SQR(xDiff) + PS_SQR(yDiff))*rsigma2;
-	      assert (z >= 0.0);
-	      psF32 t = 1.0 + z*(1.0 + z/2.0*(1.0 + z/3.0));
-	      psF32 weight  = 1.0 / t;
-
-	      // fprintf (stderr, "%6.1f %6.1f  %8.1f %8.1f  %5.3f  ", xDiff, yDiff, pDiff, wDiff, weight);
-
-	      wDiff *= weight;
-	      pDiff *= weight;
+		// XXX a lot of extra flops; can we do pre-calculate?
+		psF32 z  = (PS_SQR(xDiff) + PS_SQR(yDiff))*rsigma2;
+		assert (z >= 0.0);
+		psF32 weight  = exp(-z);
+
+		wDiff *= weight;
+		pDiff *= weight;
 	    } 
 
-            Sum += pDiff;
-
-            psF32 x = xDiff * pDiff;
-            psF32 y = yDiff * pDiff;
-
-            psF32 xx = xDiff * x;
-            psF32 xy = xDiff * y;
-            psF32 yy = yDiff * y;
-
-            psF32 xxx = xDiff * xx / r;
-            psF32 xxy = xDiff * xy / r;
-            psF32 xyy = xDiff * yy / r;
-            psF32 yyy = yDiff * yy / r;
-
-            psF32 xxxx = xDiff * xxx / r2;
-            psF32 xxxy = xDiff * xxy / r2;
-            psF32 xxyy = xDiff * xyy / r2;
-            psF32 xyyy = xDiff * yyy / r2;
-            psF32 yyyy = yDiff * yyy / r2;
-
-            XX  += xx;
-            XY  += xy;
-            YY  += yy;
-
-            XXX  += xxx;
-            XXY  += xxy;
-            XYY  += xyy;
-            YYY  += yyy;
-
-            XXXX  += xxxx;
-            XXXY  += xxxy;
-            XXYY  += xxyy;
-            XYYY  += xyyy;
-            YYYY  += yyyy;
-        }
+	    Sum += pDiff;
+
+	    psF32 x = xDiff * pDiff;
+	    psF32 y = yDiff * pDiff;
+
+	    psF32 xx = xDiff * x;
+	    psF32 xy = xDiff * y;
+	    psF32 yy = yDiff * y;
+
+	    psF32 xxx = xDiff * xx / r;
+	    psF32 xxy = xDiff * xy / r;
+	    psF32 xyy = xDiff * yy / r;
+	    psF32 yyy = yDiff * yy / r;
+
+	    psF32 xxxx = xDiff * xxx / r2;
+	    psF32 xxxy = xDiff * xxy / r2;
+	    psF32 xxyy = xDiff * xyy / r2;
+	    psF32 xyyy = xDiff * yyy / r2;
+	    psF32 yyyy = yDiff * yyy / r2;
+
+	    XX  += xx;
+	    XY  += xy;
+	    YY  += yy;
+
+	    XXX  += xxx;
+	    XXY  += xxy;
+	    XYY  += xyy;
+	    YYY  += yyy;
+
+	    XXXX  += xxxx;
+	    XXXY  += xxxy;
+	    XXYY  += xxyy;
+	    XYYY  += xyyy;
+	    YYYY  += yyyy;
+	}
     }
 
@@ -311,152 +317,19 @@
     source->moments->Myyyy = YYYY/Sum;
 
-    if (source->moments->Mxx < 0) {
-      fprintf (stderr, "error: neg second moment??\n");
-    }
-    if (source->moments->Myy < 0) {
-      fprintf (stderr, "error: neg second moment??\n");
-    }
+    // if (source->moments->Mxx < 0) {
+    // 	fprintf (stderr, "error: neg second moment??\n");
+    // }
+    // if (source->moments->Myy < 0) {
+    // 	fprintf (stderr, "error: neg second moment??\n");
+    // }
 
     psTrace ("psModules.objects", 4, "Mxx: %f  Mxy: %f  Myy: %f  Mxxx: %f  Mxxy: %f  Mxyy: %f  Myyy: %f  Mxxxx: %f  Mxxxy: %f  Mxxyy: %f  Mxyyy: %f  Mxyyy: %f\n",
-             source->moments->Mxx,   source->moments->Mxy,   source->moments->Myy,
-             source->moments->Mxxx,  source->moments->Mxxy,  source->moments->Mxyy,  source->moments->Myyy,
-             source->moments->Mxxxx, source->moments->Mxxxy, source->moments->Mxxyy, source->moments->Mxyyy, source->moments->Myyyy);
+	     source->moments->Mxx,   source->moments->Mxy,   source->moments->Myy,
+	     source->moments->Mxxx,  source->moments->Mxxy,  source->moments->Mxyy,  source->moments->Myyy,
+	     source->moments->Mxxxx, source->moments->Mxxxy, source->moments->Mxxyy, source->moments->Mxyyy, source->moments->Myyyy);
 
     psTrace ("psModules.objects", 3, "peak %f %f (%f = %f) Mx: %f  My: %f  Sum: %f  Mxx: %f  Mxy: %f  Myy: %f  sky: %f  Npix: %d\n",
-             source->peak->xf, source->peak->yf, source->peak->flux, source->peak->SN, source->moments->Mx,   source->moments->My, Sum, source->moments->Mxx,   source->moments->Mxy,   source->moments->Myy, sky, numPixels);
-
-    if (source->moments->Mxx < 0) {
-        fprintf (stderr, "error: neg second moment??\n");
-    }
-    if (source->moments->Myy < 0) {
-        fprintf (stderr, "error: neg second moment??\n");
-    }
-
-    // XXX TEST:
-    // pmSourceMoments_Old (source, radius);
+	     source->peak->xf, source->peak->yf, source->peak->flux, source->peak->SN, source->moments->Mx,   source->moments->My, Sum, source->moments->Mxx,   source->moments->Mxy,   source->moments->Myy, sky, numPixels);
+
     return(true);
 }
-
-
-bool pmSourceMoments_Old(pmSource *source, psF32 radius)
-{
-    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 sky = 0.0;
-    if (source->moments == NULL) {
-        source->moments = pmMomentsAlloc();
-    } else {
-        sky = source->moments->Sky;
-    }
-
-    // Sum = SUM (z - sky)
-    // X1  = SUM (x - xc)*(z - sky)
-    // X2  = SUM (x - xc)^2 * (z - sky)
-    // XY  = SUM (x - xc)*(y - yc)*(z - sky)
-    psF32 peakPixel = -PS_MAX_F32;
-    psS32 numPixels = 0;
-    psF32 Sum = 0.0;
-    psF32 Var = 0.0;
-    psF32 X1 = 0.0;
-    psF32 Y1 = 0.0;
-    psF32 X2 = 0.0;
-    psF32 Y2 = 0.0;
-    psF32 XY = 0.0;
-    psF32 x  = 0;
-    psF32 y  = 0;
-    psF32 R2 = PS_SQR(radius);
-
-    // 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
-
-    psF32 xPeak = source->peak->x - source->pixels->col0; // coord of peak in subimage
-    psF32 yPeak = source->peak->y - source->pixels->row0; // coord of peak in subimage
-
-    for (psS32 row = 0; row < source->pixels->numRows ; row++) {
-
-        psF32 *vPix = source->pixels->data.F32[row];
-        psF32 *vWgt = source->variance->data.F32[row];
-        psImageMaskType  *vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[row];
-
-        for (psS32 col = 0; col < source->pixels->numCols ; col++, vPix++, vWgt++) {
-            if (vMsk) {
-                if (*vMsk) {
-                    vMsk++;
-                    continue;
-                }
-                vMsk++;
-            }
-            if (isnan(*vPix)) continue;
-
-            psF32 xDiff = col - xPeak;
-            psF32 yDiff = row - yPeak;
-
-            // radius is just a function of (xDiff, yDiff)
-            if (!VALID_RADIUS(xDiff, yDiff, R2)) continue;
-
-            psF32 pDiff = *vPix - sky;
-            psF32 wDiff = *vWgt;
-
-            // XXX EAM : should this limit be user-defined?
-            if (PS_SQR(pDiff) < wDiff) continue;
-
-            Var += wDiff;
-            Sum += pDiff;
-
-            psF32 xWght = xDiff * pDiff;
-            psF32 yWght = yDiff * pDiff;
-
-            X1  += xWght;
-            Y1  += yWght;
-
-            X2  += xDiff * xWght;
-            XY  += xDiff * yWght;
-            Y2  += yDiff * yWght;
-
-            peakPixel = PS_MAX (*vPix, peakPixel);
-            numPixels++;
-        }
-    }
-
-    // if we have less than (1/4) of the possible pixels, force a retry
-    // XXX EAM - the limit is a bit arbitrary.  make it user defined?
-    if ((numPixels < 0.75*R2) || (Sum <= 0)) {
-        psTrace ("psModules.objects", 3, "insufficient valid pixels (%d vs %d; %f) for source\n", numPixels, (int)(0.75*R2), Sum);
-        return (false);
-    }
-
-    psTrace ("psModules.objects", 4, "sky: %f  Sum: %f  X1: %f  Y1: %f  X2: %f  Y2: %f  XY: %f  Npix: %d\n",
-             sky, Sum, X1, Y1, X2, Y2, XY, numPixels);
-
-    //
-    // first moment X  = X1/Sum + xc
-    // second moment X = sqrt (X2/Sum - (X1/Sum)^2)
-    // Sxy             = XY / Sum
-    //
-    x = X1/Sum;
-    y = Y1/Sum;
-    if ((fabs(x) > radius) || (fabs(y) > radius)) {
-        psTrace ("psModules.objects", 3, "large centroid swing; invalid peak %d, %d\n",
-                 source->peak->x, source->peak->y);
-        return (false);
-    }
-
-# if (PS_TRACE_ON)
-    float Sxx = PS_MAX(X2/Sum - PS_SQR(x), 0);
-    float Sxy = XY / Sum;
-    float Syy = PS_MAX(Y2/Sum - PS_SQR(y), 0);
-
-    psTrace ("psModules.objects", 3,
-             "sky: %f  Sum: %f  x: %f  y: %f  Sx: %f  Sy: %f  Sxy: %f\n",
-             sky, Sum, x, y, Sxx, Sxy, Syy);
-# endif
-
-    return(true);
-}
-
Index: branches/simmosaic_branches/psModules/src/objects/pmSourcePhotometry.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourcePhotometry.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourcePhotometry.c	(revision 27839)
@@ -84,4 +84,5 @@
 
     // we must have a valid model
+    // XXX allow aperture magnitudes for sources without a model
     model = pmSourceGetModel (&isPSF, source);
     if (model == NULL) {
@@ -90,4 +91,5 @@
     }
 
+    // XXX handle negative flux, low-significance
     if (model->dparams->data.F32[PM_PAR_I0] > 0) {
         SN = model->params->data.F32[PM_PAR_I0] / model->dparams->data.F32[PM_PAR_I0];
@@ -101,37 +103,35 @@
 
     // measure PSF model photometry
-    if (psf->FluxScale) {
+    // XXX TEST: do not use flux scale
+    if (0 && psf->FluxScale) {
         // the source peak pixel is guaranteed to be on the image, and only minimally different from the source center
         double fluxScale = pmTrend2DEval (psf->FluxScale, (float)source->peak->x, (float)source->peak->y);
-        if (!isfinite(fluxScale)) {
-            // XXX objects on the edge can be slightly outside -- if we get an
-            // error, use the full fit.
-            psErrorClear();
-            status = pmSourcePhotometryModel (&source->psfMag, source->modelPSF);
-        } else {
-            if (isfinite(fluxScale) && (fluxScale > 0.0)) {
-                source->psfMag = -2.5*log10(fluxScale * source->modelPSF->params->data.F32[PM_PAR_I0]);
-            } else {
-                source->psfMag = NAN;
-            }
-        }
+	psAssert (isfinite(fluxScale), "how can the flux scale be invalid? source at %d, %d\n", source->peak->x, source->peak->y);
+	psAssert (fluxScale > 0.0, "how can the flux scale be negative? source at %d, %d\n", source->peak->x, source->peak->y);
+	source->psfFlux = fluxScale * source->modelPSF->params->data.F32[PM_PAR_I0];
+	source->psfFluxErr = fluxScale * source->modelPSF->dparams->data.F32[PM_PAR_I0];
+	source->psfMag = -2.5*log10(source->psfFlux);
     } else {
-        status = pmSourcePhotometryModel (&source->psfMag, source->modelPSF);
-    }
-
-    // if we have a collection of model fits, one of them is a pointer to modelEXT?
-    // XXX not guaranteed
+        status = pmSourcePhotometryModel (&source->psfMag, &source->psfFlux, source->modelPSF);
+	source->psfFluxErr = source->psfFlux * (source->modelPSF->dparams->data.F32[PM_PAR_I0] / source->modelPSF->params->data.F32[PM_PAR_I0]);
+    }
+
+    // if we have a collection of model fits, check if one of them is a pointer to modelEXT
     if (source->modelFits) {
-      for (int i = 0; i < source->modelFits->n; i++) {
-        pmModel *model = source->modelFits->data[i];
-        status = pmSourcePhotometryModel (&model->mag, model);
-      }
-      if (source->modelEXT) {
-        source->extMag = source->modelEXT->mag;
-      }
+        bool foundEXT = false;
+	for (int i = 0; i < source->modelFits->n; i++) {
+	    pmModel *model = source->modelFits->data[i];
+	    status = pmSourcePhotometryModel (&model->mag, NULL, model);
+	    if (model == source->modelEXT) foundEXT = true;
+	}
+	if (foundEXT) {
+	    source->extMag = source->modelEXT->mag;
+	} else {
+	    status = pmSourcePhotometryModel (&source->extMag, NULL, source->modelEXT);
+	}
     } else {
-      if (source->modelEXT) {
-        status = pmSourcePhotometryModel (&source->extMag, source->modelEXT);
-      }
+	if (source->modelEXT) {
+	    status = pmSourcePhotometryModel (&source->extMag, NULL, source->modelEXT);
+	}
     }
 
@@ -148,4 +148,9 @@
     }
 
+    // measure the contribution of included pixels
+    if (mode & PM_SOURCE_PHOT_DIFFSTATS) {
+        pmSourceMeasureDiffStats (source, maskVal);
+    }
+
     // measure the aperture magnitude, if (SN > AP_MIN_SN)
     if (!isfinite(SN)) {
@@ -160,11 +165,6 @@
     }
 
-    // replace source flux
-    // XXX need to be certain which model and size of mask for prior subtraction
-    // XXX full model or just analytical?
-    // XXX use pmSourceAdd instead?
-    if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
-        pmModelAdd (source->pixels, source->maskObj, model, PM_MODEL_OP_FULL, maskVal);
-    }
+    // if we measure aperture magnitudes, the source must not currently be subtracted!
+    psAssert (!(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED), "cannot measure ap mags if source is subtracted!");
 
     // if we are measuring aperture photometry and applying the growth correction,
@@ -201,9 +201,10 @@
     if (isfinite (source->apMag) && isPSF && psf) {
         if (psf->growth && (mode & PM_SOURCE_PHOT_GROWTH)) {
-            source->apMag += pmGrowthCurveCorrect (psf->growth, model->radiusFit);
+            source->apMag += pmGrowthCurveCorrect (psf->growth, source->apRadius);
         }
         if (mode & PM_SOURCE_PHOT_APCORR) {
+	    // XXX this should be removed -- we no longer fit for the 'sky bias'
             rflux   = pow (10.0, 0.4*source->psfMag);
-            source->apMag -= PS_SQR(model->radiusFit)*rflux * psf->skyBias + psf->skySat / rflux;
+            source->apMag -= PS_SQR(source->apRadius)*rflux * psf->skyBias + psf->skySat / rflux;
         }
     }
@@ -211,10 +212,4 @@
         psFree(flux);
         psFree(mask);
-    }
-
-    // if source was originally subtracted, re-subtract object, leave local sky
-    // XXX replace with pmSourceSub...
-    if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
-        pmModelSub (source->pixels, source->maskObj, model, PM_MODEL_OP_FULL, maskVal);
     }
 
@@ -230,21 +225,26 @@
 
 // return source model magnitude
-bool pmSourcePhotometryModel (float *fitMag, pmModel *model)
-{
-    PS_ASSERT_PTR_NON_NULL(fitMag, false);
-    if (model == NULL) {
-        return false;
-    }
-
-    float fitSum = 0;
-    *fitMag = NAN;
+bool pmSourcePhotometryModel (float *fitMag, float *fitFlux, pmModel *model)
+{
+    psAssert (fitMag || fitFlux, "at least one of magnitude or flux must be requested (not NULL)");
+    if (model == NULL) return false;
+
+    float mag  = NAN;
+    float flux = NAN;
 
     // measure fitMag
-    fitSum = model->modelFlux (model->params);
-    if (fitSum <= 0)
-        return false;
-    if (!isfinite(fitSum))
-        return false;
-    *fitMag = -2.5*log10(fitSum);
+    flux = model->modelFlux (model->params);
+    if (flux > 0) {
+	mag = -2.5*log10(flux);
+    }
+    if (fitMag) {
+	*fitMag = mag;
+    }
+    if (fitFlux) {
+	*fitFlux = flux;
+    }
+
+    if (flux <= 0) return false;
+    if (!isfinite(flux)) return false;
 
     return (true);
@@ -369,4 +369,55 @@
 
     *pixWeight = validSum / modelSum;
+    return (true);
+}
+
+# define FLUX_LIMIT 3.0
+
+// return source aperture magnitude
+bool pmSourceMeasureDiffStats (pmSource *source, psImageMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    if (source->diffStats == NULL) {
+	source->diffStats = pmSourceDiffStatsAlloc();
+    }
+
+    float fGood = 0.0;
+    float fBad  = 0.0;
+    int   nGood = 0;
+    int   nMask = 0;
+    int   nBad  = 0;
+    
+    psImage *flux     = source->pixels;
+    psImage *variance = source->variance;
+    psImage *mask     = source->maskObj;
+
+    for (int iy = 0; iy < flux->numRows; iy++) {
+	for (int ix = 0; ix < flux->numCols; ix++) {
+            if (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] & maskVal) {
+		nMask ++;
+                continue;
+	    }
+
+	    float SN = flux->data.F32[iy][ix] / sqrt(variance->data.F32[iy][ix]);
+
+	    if (SN > +FLUX_LIMIT) { 
+		nGood ++;
+		fGood += fabs(flux->data.F32[iy][ix]);
+	    }
+
+	    if (SN < -FLUX_LIMIT) { 
+		nBad ++;
+		fBad += fabs(flux->data.F32[iy][ix]);
+	    }
+	}
+    }
+
+    source->diffStats->nGood      = nGood;
+    source->diffStats->fRatio     = (fGood + fBad         == 0.0) ? NAN : fGood / (fGood + fBad);	   
+    source->diffStats->nRatioBad  = (nGood + nBad         == 0)   ? NAN : nGood / (float) (nGood + nBad);	   
+    source->diffStats->nRatioMask = (nGood + nMask        == 0)   ? NAN : nGood / (float) (nGood + nMask);	   
+    source->diffStats->nRatioAll  = (nGood + nMask + nBad == 0)   ? NAN : nGood / (float) (nGood + nMask + nBad);
+
     return (true);
 }
@@ -558,6 +609,5 @@
 
 // determine chisq, etc for linear normalization-only fit
-bool pmSourceChisq (pmModel *model, psImage *image, psImage *mask, psImage *variance,
-                    psImageMaskType maskVal)
+bool pmSourceChisq (pmModel *model, psImage *image, psImage *mask, psImage *variance, psImageMaskType maskVal, const float covarFactor)
 {
     PS_ASSERT_PTR_NON_NULL(model, false);
@@ -574,5 +624,5 @@
             if (variance->data.F32[j][i] <= 0)
                 continue;
-            dC += PS_SQR (image->data.F32[j][i]) / variance->data.F32[j][i];
+            dC += PS_SQR (image->data.F32[j][i]) / (covarFactor * variance->data.F32[j][i]);
             Npix ++;
         }
@@ -586,7 +636,5 @@
 
 
-double pmSourceModelWeight(const pmSource *Mi,
-                      int term,
-                      const bool unweighted_sum) // should the cross product be weighted?
+double pmSourceModelWeight(const pmSource *Mi, int term, const bool unweighted_sum, const float covarFactor)
 {
     PS_ASSERT_PTR_NON_NULL(Mi, NAN);
@@ -607,5 +655,5 @@
                 continue;
             if (!unweighted_sum) {
-                wt = Wi->data.F32[yi][xi];
+                wt = covarFactor * Wi->data.F32[yi][xi];
                 if (wt == 0)
                     continue;
@@ -636,7 +684,5 @@
 }
 
-double pmSourceModelDotModel (const pmSource *Mi,
-                              const pmSource *Mj,
-                              const bool unweighted_sum) // should the cross product be weighted?
+double pmSourceModelDotModel (const pmSource *Mi, const pmSource *Mj, const bool unweighted_sum, const float covarFactor)
 {
     PS_ASSERT_PTR_NON_NULL(Mi, NAN);
@@ -690,5 +736,5 @@
                 flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]);
             } else {
-                wt = Wi->data.F32[yi][xi];
+                wt = covarFactor * Wi->data.F32[yi][xi];
                 if (wt > 0) {
                     flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]) / wt;
@@ -700,7 +746,5 @@
 }
 
-double pmSourceDataDotModel (const pmSource *Mi,
-                             const pmSource *Mj,
-                             const bool unweighted_sum) // should the cross product be weighted?
+double pmSourceDataDotModel (const pmSource *Mi, const pmSource *Mj, const bool unweighted_sum, const float covarFactor)
 {
     PS_ASSERT_PTR_NON_NULL(Mi, NAN);
@@ -754,5 +798,5 @@
                 flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]);
             } else {
-                wt = Wi->data.F32[yi][xi];
+                wt = covarFactor * Wi->data.F32[yi][xi];
                 if (wt > 0) {
                     flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]) / wt;
@@ -763,55 +807,2 @@
     return flux;
 }
-
-// XXX this is test code to verify the shift is doing the right thing (seems to be)
-# if (0)
-// measure centroid of unshifted gaussian (should be 16.0,16.0)
-        {
-	  psImage *image = source->pixels;
-	  float xo = 0.0;
-	  float yo = 0.0;
-	  float xo2 = 0.0;
-	  float yo2 = 0.0;
-	  float no = 0.0;
-	  for (int j = 0; j < image->numRows; j++)
-	  {
-	    for (int i = 0; i < image->numCols; i++) {
-	      xo += i*image->data.F32[j][i];
-	      yo += j*image->data.F32[j][i];
-	      xo2 += i*i*image->data.F32[j][i];
-	      yo2 += j*j*image->data.F32[j][i];
-	      no += image->data.F32[j][i];
-	    }
-	  }
-	  xo /= no;
-	  yo /= no;
-	  xo2 = sqrt (xo2/no - xo*xo);
-	  yo2 = sqrt (yo2/no - yo*yo);
-	  fprintf (stderr, "pre-shift centroid: %f,%f, sigma: %f,%f: flux: %f\n", xo, yo, xo2, yo2, no);
-        }
-
-// measure centroid of unshifted gaussian (should be 16.0,16.0)
-        {
-	  psImage *image = flux;
-	  float xo = 0.0;
-	  float yo = 0.0;
-	  float xo2 = 0.0;
-	  float yo2 = 0.0;
-	  float no = 0.0;
-	  for (int j = 0; j < image->numRows; j++)
-	  {
-	    for (int i = 0; i < image->numCols; i++) {
-	      xo += i*image->data.F32[j][i];
-	      yo += j*image->data.F32[j][i];
-	      xo2 += i*i*image->data.F32[j][i];
-	      yo2 += j*j*image->data.F32[j][i];
-	      no += image->data.F32[j][i];
-	    }
-	  }
-	  xo /= no;
-	  yo /= no;
-	  xo2 = sqrt (xo2/no - xo*xo);
-	  yo2 = sqrt (yo2/no - yo*yo);
-	  fprintf (stderr, "pre-shift centroid: %f,%f, sigma: %f,%f: flux: %f\n", xo, yo, xo2, yo2, no);
-        }
-# endif
Index: branches/simmosaic_branches/psModules/src/objects/pmSourcePhotometry.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourcePhotometry.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourcePhotometry.h	(revision 27839)
@@ -29,13 +29,15 @@
 
 typedef enum {
-    PM_SOURCE_PHOT_NONE   = 0x0000,
-    PM_SOURCE_PHOT_GROWTH = 0x0001,
-    PM_SOURCE_PHOT_APCORR = 0x0002,
-    PM_SOURCE_PHOT_WEIGHT = 0x0004,
-    PM_SOURCE_PHOT_INTERP = 0x0008,
+    PM_SOURCE_PHOT_NONE      = 0x0000,
+    PM_SOURCE_PHOT_GROWTH    = 0x0001,
+    PM_SOURCE_PHOT_APCORR    = 0x0002,
+    PM_SOURCE_PHOT_WEIGHT    = 0x0004,
+    PM_SOURCE_PHOT_INTERP    = 0x0008,
+    PM_SOURCE_PHOT_DIFFSTATS = 0x0010,
 } pmSourcePhotometryMode;
 
 bool pmSourcePhotometryModel(
     float *fitMag,                      ///< integrated fit magnitude
+    float *fitFlux,                     ///< integrated fit magnitude
     pmModel *model                      ///< model used for photometry
 );
@@ -52,10 +54,11 @@
 bool pmSourceMagnitudes (pmSource *source, pmPSF *psf, pmSourcePhotometryMode mode, psImageMaskType maskVal);
 bool pmSourcePixelWeight (float *pixWeight, pmModel *model, psImage *mask, psImageMaskType maskVal);
-bool pmSourceChisq (pmModel *model, psImage *image, psImage *mask, psImage *weight, psImageMaskType maskVal);
+bool pmSourceChisq (pmModel *model, psImage *image, psImage *mask, psImage *weight, psImageMaskType maskVal, const float covarFactor);
 
+bool pmSourceMeasureDiffStats (pmSource *source, psImageMaskType maskVal);
 
-double pmSourceDataDotModel (const pmSource *Mi, const pmSource *Mj, const bool unweighted_sum);
-double pmSourceModelDotModel (const pmSource *Mi, const pmSource *Mj, const bool unweighted_sum);
-double pmSourceModelWeight(const pmSource *Mi, int term, const bool unweighted_sum);
+double pmSourceDataDotModel (const pmSource *Mi, const pmSource *Mj, const bool unweighted_sum, const float covarFactor);
+double pmSourceModelDotModel (const pmSource *Mi, const pmSource *Mj, const bool unweighted_sum, const float covarFactor);
+double pmSourceModelWeight(const pmSource *Mi, int term, const bool unweighted_sum, const float covarFactor);
 
 // retire these:
Index: branches/simmosaic_branches/psModules/src/objects/pmSourcePlotApResid.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourcePlotApResid.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourcePlotApResid.c	(revision 27839)
@@ -34,4 +34,5 @@
 #include "pmPSF.h"
 #include "pmModel.h"
+#include "pmDetections.h"
 #include "pmSource.h"
 #include "pmSourcePlots.h"
@@ -53,4 +54,5 @@
     PS_ASSERT_PTR_NON_NULL(layout, false);
 
+    bool status;
     Graphdata graphdata;
     KapaSection section;
@@ -61,7 +63,9 @@
     pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
 
-    psArray *sources = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.SOURCES");
-    if (sources == NULL)
-        return false;
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    if (detections == NULL) return false;
+
+    psArray *sources = detections->allSources;
+    if (sources == NULL) return false;
 
     int kapa = pmKapaOpen (false);
Index: branches/simmosaic_branches/psModules/src/objects/pmSourcePlotMoments.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourcePlotMoments.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourcePlotMoments.c	(revision 27839)
@@ -37,4 +37,5 @@
 #include "pmPSF.h"
 #include "pmModel.h"
+#include "pmDetections.h"
 #include "pmSource.h"
 #include "pmSourcePlots.h"
@@ -54,4 +55,5 @@
     PS_ASSERT_PTR_NON_NULL(layout, false);
 
+    bool status;
     Graphdata graphdata;
     KapaSection section;
@@ -62,7 +64,9 @@
     pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
 
-    psArray *sources = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.SOURCES");
-    if (sources == NULL)
-        return false;
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    if (detections == NULL) return false;
+
+    psArray *sources = detections->allSources;
+    if (sources == NULL) return false;
 
     int kapa = pmKapaOpen (false);
Index: branches/simmosaic_branches/psModules/src/objects/pmSourcePlotPSFModel.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourcePlotPSFModel.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourcePlotPSFModel.c	(revision 27839)
@@ -37,4 +37,5 @@
 #include "pmPSF.h"
 #include "pmModel.h"
+#include "pmDetections.h"
 #include "pmSource.h"
 #include "pmSourcePlots.h"
@@ -56,4 +57,5 @@
     PS_ASSERT_PTR_NON_NULL(layout, false);
 
+    bool status;
     Graphdata graphdata;
     KapaSection section;
@@ -64,7 +66,9 @@
     pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
 
-    psArray *sources = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.SOURCES");
-    if (sources == NULL)
-        return false;
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    if (detections == NULL) return false;
+
+    psArray *sources = detections->allSources;
+    if (sources == NULL) return false;
 
     int kapa = pmKapaOpen (false);
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceVisual.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceVisual.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceVisual.c	(revision 27839)
@@ -5,4 +5,7 @@
 #include <pslib.h>
 #include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmPSFtry.h"
+#include "pmSource.h"
 #include "pmSourceVisual.h"
 
@@ -15,6 +18,6 @@
 
 static int kapa1 = -1;
+static int kapa2 = -1;
 static bool plotPSF = true;
-// static int kapa2 = -1;
 // static int kapa3 = -1;
 
@@ -27,12 +30,9 @@
 bool pmSourcePlotPoints3D (int myKapa, Graphdata *graphdata, psVector *xn, psVector *yn, psVector *zn, float theta, float phi);
 
-
-bool pmSourceVisualPSFModelResid (pmTrend2D *trend, psVector *x, psVector *y, psVector *param, psVector *mask) {
-
-    KapaSection section;  // put the positive profile in one and the residuals in another?
+bool pmSourceVisualPlotPSFMetric (pmPSFtry *psfTry) {
 
     Graphdata graphdata;
 
-    if (!pmVisualIsVisual() || !plotPSF) return true;
+    if (!pmVisualIsVisual()) return true;
 
     if (kapa1 == -1) {
@@ -45,41 +45,100 @@
     }
 
-    KapaClearPlots (kapa1);
+    KapaClearSections (kapa1);
     KapaInitGraph (&graphdata);
 
-    float min = +1e32;
-    float max = -1e32;
-    float Min = +1e32;
-    float Max = -1e32;
-
-    psVector *resid = psVectorAlloc (x->n, PS_TYPE_F32);
-    psVector *model = psVectorAlloc (x->n, PS_TYPE_F32);
-
-    for (int i = 0; i < x->n; i++) {
-        model->data.F32[i] = pmTrend2DEval (trend, x->data.F32[i], y->data.F32[i]);
-        resid->data.F32[i] = param->data.F32[i] - model->data.F32[i];
-        if (mask->data.PS_TYPE_VECTOR_MASK_DATA[i]) continue;
-        min = PS_MIN (min, resid->data.F32[i]);
-        max = PS_MAX (max, resid->data.F32[i]);
-        Min = PS_MIN (min, param->data.F32[i]);
-        Max = PS_MAX (max, param->data.F32[i]);
-    }
-
-    psVector *xn = psVectorAlloc (x->n, PS_TYPE_F32);
-    psVector *yn = psVectorAlloc (x->n, PS_TYPE_F32);
-    psVector *zn = psVectorAlloc (x->n, PS_TYPE_F32);
-    psVector *Zn = psVectorAlloc (x->n, PS_TYPE_F32);
-    psVector *Fn = psVectorAlloc (x->n, PS_TYPE_F32);
-    for (int i = 0; i < x->n; i++) {
-        xn->data.F32[i] = x->data.F32[i] / 5000.0;
-        yn->data.F32[i] = y->data.F32[i] / 5000.0;
-        zn->data.F32[i] = (resid->data.F32[i] - min) / (max - min);
-        Zn->data.F32[i] = (param->data.F32[i] - Min) / (Max - Min);
-        Fn->data.F32[i] = (model->data.F32[i] - Min) / (Max - Min);
-    }
-
-    // view 1 on resid
-    section.dx = 0.5;
-    section.dy = 0.33;
+    psVector *x = psVectorAllocEmpty (psfTry->sources->n, PS_TYPE_F32);
+    psVector *y = psVectorAllocEmpty (psfTry->sources->n, PS_TYPE_F32);
+    psVector *dy = psVectorAllocEmpty(psfTry->sources->n, PS_TYPE_F32);
+
+    graphdata.xmin = +32.0;
+    graphdata.xmax = -32.0;
+    graphdata.ymin = +32.0;
+    graphdata.ymax = -32.0;
+
+    // construct the plot vectors
+    int n = 0;
+    for (int i = 0; i < psfTry->sources->n; i++) {
+	if (psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PSFTRY_MASK_ALL) continue;
+        x->data.F32[n] = psfTry->fitMag->data.F32[i];
+	y->data.F32[n] = psfTry->metric->data.F32[i];
+        dy->data.F32[n] = psfTry->metricErr->data.F32[i];
+        graphdata.xmin = PS_MIN(graphdata.xmin, x->data.F32[n]);
+        graphdata.xmax = PS_MAX(graphdata.xmax, x->data.F32[n]);
+        graphdata.ymin = PS_MIN(graphdata.ymin, y->data.F32[n]);
+        graphdata.ymax = PS_MAX(graphdata.ymax, y->data.F32[n]);
+	n++;
+    }
+    x->n = y->n = dy->n = n;
+
+    float range;
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    // better choice for range?
+    // graphdata.xmin = -17.0;
+    // graphdata.xmax =  -9.0;
+    graphdata.ymin = -0.51;
+    graphdata.ymax = +0.51;
+
+    KapaSetLimits (kapa1, &graphdata);
+
+    KapaSetFont (kapa1, "helvetica", 14);
+    KapaBox (kapa1, &graphdata);
+    KapaSendLabel (kapa1, "PSF Mag", KAPA_LABEL_XM);
+    KapaSendLabel (kapa1, "Ap Mag - PSF Mag", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    graphdata.etype |= 0x01;
+
+    KapaPrepPlot (kapa1, n, &graphdata);
+    KapaPlotVector (kapa1, n, x->data.F32, "x");
+    KapaPlotVector (kapa1, n, y->data.F32, "y");
+    KapaPlotVector (kapa1, n, dy->data.F32, "dym");
+    KapaPlotVector (kapa1, n, dy->data.F32, "dyp");
+
+    psFree (x);
+    psFree (y);
+    psFree (dy);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool pmSourceVisualPlotPSFMetricSubpix (pmPSFtry *psfTry) {
+
+    KapaSection section;  // put the positive profile in one and the residuals in another?
+    Graphdata graphdata;
+
+    if (!pmVisualIsVisual()) return true;
+
+    if (kapa1 == -1) {
+        kapa1 = KapaOpenNamedSocket ("kapa", "pmSource:plots");
+        if (kapa1 == -1) {
+            fprintf (stderr, "failure to open kapa; visual mode disabled\n");
+            pmVisualSetVisual(false);
+            return false;
+        }
+    }
+
+    KapaClearSections (kapa1);
+    KapaInitGraph (&graphdata);
+
+    int n;
+    float range;
+    psVector *x = psVectorAllocEmpty (psfTry->sources->n, PS_TYPE_F32);
+    psVector *y = psVectorAllocEmpty (psfTry->sources->n, PS_TYPE_F32);
+    psVector *dy = psVectorAllocEmpty(psfTry->sources->n, PS_TYPE_F32);
+
+    // section a: fractional-x pixel
+    section.dx = 1.0;
+    section.dy = 0.5;
     section.x = 0.0;
     section.y = 0.0;
@@ -88,67 +147,414 @@
     KapaSetSection (kapa1, &section);
     psFree (section.name);
-    pmSourcePlotPoints3D (kapa1, &graphdata, xn, yn, zn, 30.0*PS_RAD_DEG, -15.0*PS_RAD_DEG);
-
-    // view 2 on resid
-    section.dx = 0.5;
-    section.dy = 0.33;
-    section.x = 0.5;
-    section.y = 0.0;
+
+    graphdata.xmin = +32.0;
+    graphdata.xmax = -32.0;
+    graphdata.ymin = +32.0;
+    graphdata.ymax = -32.0;
+
+    // construct the plot vectors
+    n = 0;
+    for (int i = 0; i < psfTry->sources->n; i++) {
+	if (psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PSFTRY_MASK_ALL) continue;
+
+        pmSource *source = psfTry->sources->data[i];
+        x->data.F32[n] = source->modelEXT->params->data.F32[PM_PAR_XPOS] - (int)source->modelEXT->params->data.F32[PM_PAR_XPOS];
+
+	y->data.F32[n] = psfTry->metric->data.F32[i];
+        dy->data.F32[n] = psfTry->metricErr->data.F32[i];
+        graphdata.xmin = PS_MIN(graphdata.xmin, x->data.F32[n]);
+        graphdata.xmax = PS_MAX(graphdata.xmax, x->data.F32[n]);
+        graphdata.ymin = PS_MIN(graphdata.ymin, y->data.F32[n]);
+        graphdata.ymax = PS_MAX(graphdata.ymax, y->data.F32[n]);
+	n++;
+    }
+    x->n = y->n = dy->n = n;
+
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    // better choice for range?
+    // graphdata.xmin = -17.0;
+    // graphdata.xmax =  -9.0;
+    graphdata.ymin = -0.51;
+    graphdata.ymax = +0.51;
+
+    KapaSetLimits (kapa1, &graphdata);
+
+    KapaSetFont (kapa1, "helvetica", 14);
+    KapaBox (kapa1, &graphdata);
+    KapaSendLabel (kapa1, "PSF Mag", KAPA_LABEL_XM);
+    KapaSendLabel (kapa1, "Ap Mag - PSF Mag", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    graphdata.etype |= 0x01;
+
+    KapaPrepPlot (kapa1, n, &graphdata);
+    KapaPlotVector (kapa1, n, x->data.F32, "x");
+    KapaPlotVector (kapa1, n, y->data.F32, "y");
+    KapaPlotVector (kapa1, n, dy->data.F32, "dym");
+    KapaPlotVector (kapa1, n, dy->data.F32, "dyp");
+
+    // *** section b: fractional-x pixel
+    section.dx = 1.0;
+    section.dy = 0.5;
+    section.x = 0.0;
+    section.y = 0.5;
     section.name = NULL;
     psStringAppend (&section.name, "a2");
     KapaSetSection (kapa1, &section);
     psFree (section.name);
-    pmSourcePlotPoints3D (kapa1, &graphdata, xn, yn, zn, -60.0*PS_RAD_DEG, -15.0*PS_RAD_DEG);
-
-    // view 3 on resid
-    section.dx = 0.5;
-    section.dy = 0.33;
+
+    graphdata.xmin = +32.0;
+    graphdata.xmax = -32.0;
+    graphdata.ymin = +32.0;
+    graphdata.ymax = -32.0;
+
+    // construct the plot vectors
+    n = 0;
+    for (int i = 0; i < psfTry->sources->n; i++) {
+	if (psfTry->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PSFTRY_MASK_ALL) continue;
+
+        pmSource *source = psfTry->sources->data[i];
+        x->data.F32[n] = source->modelEXT->params->data.F32[PM_PAR_YPOS] - (int)source->modelEXT->params->data.F32[PM_PAR_YPOS];
+
+	y->data.F32[n] = psfTry->metric->data.F32[i];
+        dy->data.F32[n] = psfTry->metricErr->data.F32[i];
+        graphdata.xmin = PS_MIN(graphdata.xmin, x->data.F32[n]);
+        graphdata.xmax = PS_MAX(graphdata.xmax, x->data.F32[n]);
+        graphdata.ymin = PS_MIN(graphdata.ymin, y->data.F32[n]);
+        graphdata.ymax = PS_MAX(graphdata.ymax, y->data.F32[n]);
+	n++;
+    }
+    x->n = y->n = dy->n = n;
+
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    // better choice for range?
+    // graphdata.xmin = -17.0;
+    // graphdata.xmax =  -9.0;
+    graphdata.ymin = -0.51;
+    graphdata.ymax = +0.51;
+
+    KapaSetLimits (kapa1, &graphdata);
+
+    KapaSetFont (kapa1, "helvetica", 14);
+    KapaBox (kapa1, &graphdata);
+    KapaSendLabel (kapa1, "PSF Mag", KAPA_LABEL_XM);
+    KapaSendLabel (kapa1, "Ap Mag - PSF Mag", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    graphdata.etype |= 0x01;
+
+    KapaPrepPlot (kapa1, n, &graphdata);
+    KapaPlotVector (kapa1, n, x->data.F32, "x");
+    KapaPlotVector (kapa1, n, y->data.F32, "y");
+    KapaPlotVector (kapa1, n, dy->data.F32, "dym");
+    KapaPlotVector (kapa1, n, dy->data.F32, "dyp");
+
+    psFree (x);
+    psFree (y);
+    psFree (dy);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+// to see the structure of the psf model, place the sources in a fake image 1/10th the size
+// at their appropriate relative location. later sources stomp on earlier sources
+bool pmSourceVisualShowModelFits (pmPSF *psf, psArray *sources, psImageMaskType maskVal) {
+
+    if (!pmVisualIsVisual()) return true;
+
+    if (kapa2 == -1) {
+        kapa2 = KapaOpenNamedSocket ("kapa", "pmSource:images");
+        if (kapa2 == -1) {
+            fprintf (stderr, "failure to open kapa; visual mode disabled\n");
+            pmVisualSetVisual(false);
+            return false;
+        }
+    }
+
+    // create images 1/10 scale:
+    psImage *image = psImageAlloc (0.1*psf->fieldNx, 0.1*psf->fieldNy, PS_TYPE_F32);
+    psImage *model = psImageAlloc (0.1*psf->fieldNx, 0.1*psf->fieldNy, PS_TYPE_F32);
+    psImage *resid = psImageAlloc (0.1*psf->fieldNx, 0.1*psf->fieldNy, PS_TYPE_F32);
+    psImageInit (image, 0.0);
+    psImageInit (model, 0.0);
+    psImageInit (resid, 0.0);
+
+    for (int i = sources->n - 1; i >= 0; i--) {
+	pmSource *source = sources->data[i];
+	if (!source) continue;
+	if (!source->pixels) continue;
+
+	pmSourceCacheModel (source, maskVal);
+	if (!source->modelFlux) continue;
+
+	pmModel *srcModel = pmSourceGetModel (NULL, source);
+	if (!model) continue;
+
+	float norm = srcModel->params->data.F32[PM_PAR_I0];
+
+	int Xo = 0.1*srcModel->params->data.F32[PM_PAR_XPOS];
+	int Yo = 0.1*srcModel->params->data.F32[PM_PAR_YPOS];
+
+	// insert source pixels in the image at 1/10th offset
+	for (int iy = 0; iy < source->pixels->numRows; iy++) {
+	    int jy = iy + Yo;
+	    if (jy >= image->numRows) continue;
+	    for (int ix = 0; ix < source->pixels->numCols; ix++) {
+		int jx = ix + Xo;
+		if (jx >= image->numCols) continue;
+		if (source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix]) continue;
+		if (source->modelFlux->data.F32[iy][ix] < 0.001) continue;
+		image->data.F32[jy][jx] = source->pixels->data.F32[iy][ix];
+		model->data.F32[jy][jx] = source->modelFlux->data.F32[iy][ix];
+		resid->data.F32[jy][jx] = source->pixels->data.F32[iy][ix] - norm*source->modelFlux->data.F32[iy][ix];
+	    }
+	}
+    }
+
+    // KapaClearSections (kapa2);
+    pmVisualScaleImage (kapa2, image, "image", 0, true);
+    pmVisualScaleImage (kapa2, model, "model", 1, true);
+    pmVisualScaleImage (kapa2, resid, "resid", 2, true);
+
+# ifdef DEBUG
+    { 
+	psFits *fits = psFitsOpen ("image.fits", "w");
+	psFitsWriteImage (fits, NULL, image, 0, NULL);
+	psFitsClose (fits);
+	fits = psFitsOpen ("model.fits", "w");
+	psFitsWriteImage (fits, NULL, model, 0, NULL);
+	psFitsClose (fits);
+	fits = psFitsOpen ("resid.fits", "w");
+	psFitsWriteImage (fits, NULL, resid, 0, NULL);
+	psFitsClose (fits);
+    }
+# endif
+
+    psFree (image);
+    psFree (model);
+    psFree (resid);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool pmSourceVisualShowModelFit (pmSource *source) {
+
+    if (!pmVisualIsVisual()) return true;
+    if (!source->pixels) return false;
+    if (!source->modelFlux) return false;
+
+    if (kapa2 == -1) {
+        kapa2 = KapaOpenNamedSocket ("kapa", "pmSource:images");
+        if (kapa2 == -1) {
+            fprintf (stderr, "failure to open kapa; visual mode disabled\n");
+            pmVisualSetVisual(false);
+            return false;
+        }
+    }
+
+    // KapaClearSections (kapa2);
+    pmVisualScaleImage (kapa2, source->pixels, "source", 0, false);
+    pmVisualScaleImage (kapa2, source->modelFlux, "model", 1, false);
+
+    pmModel *model = pmSourceGetModel (NULL, source);
+    float norm = model->params->data.F32[PM_PAR_I0];
+
+    psImage *resid = psImageAlloc (source->pixels->numCols, source->pixels->numRows, PS_TYPE_F32);
+    for (int iy = 0; iy < source->pixels->numRows; iy++) {
+	for (int ix = 0; ix < source->pixels->numCols; ix++) {
+	    if (source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix]) {
+		resid->data.F32[iy][ix] = NAN;
+		continue;
+	    }
+	    resid->data.F32[iy][ix] = source->pixels->data.F32[iy][ix] - norm*source->modelFlux->data.F32[iy][ix];
+	}
+    }
+    pmVisualScaleImage (kapa2, resid, "resid", 2, false);
+
+    psFree (resid);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool pmSourceVisualPSFModelResid (pmTrend2D *trend, psVector *x, psVector *y, psVector *param, psVector *mask) {
+
+    KapaSection section;  // put the positive profile in one and the residuals in another?
+
+    Graphdata graphdata;
+
+    if (!pmVisualIsVisual() || !plotPSF) return true;
+
+    if (kapa1 == -1) {
+        kapa1 = KapaOpenNamedSocket ("kapa", "pmSource:plots");
+        if (kapa1 == -1) {
+            fprintf (stderr, "failure to open kapa; visual mode disabled\n");
+            pmVisualSetVisual(false);
+            return false;
+        }
+    }
+
+    KapaClearPlots (kapa1);
+    KapaInitGraph (&graphdata);
+
+    float Xmin = +1e32;
+    float Xmax = -1e32;
+    float Ymin = +1e32;
+    float Ymax = -1e32;
+    float Fmin = +1e32;
+    float Fmax = -1e32;
+
+    psVector *resid = psVectorAlloc (x->n, PS_TYPE_F32);
+    psVector *model = psVectorAlloc (x->n, PS_TYPE_F32);
+
+    psVector *xm = psVectorAlloc (x->n, PS_TYPE_F32);
+    psVector *ym = psVectorAlloc (x->n, PS_TYPE_F32);
+    psVector *Fm = psVectorAlloc (x->n, PS_TYPE_F32);
+
+    int n = 0;
+    for (int i = 0; i < x->n; i++) {
+        model->data.F32[i] = pmTrend2DEval (trend, x->data.F32[i], y->data.F32[i]);
+        resid->data.F32[i] = param->data.F32[i] - model->data.F32[i];
+        if (mask->data.PS_TYPE_VECTOR_MASK_DATA[i]) continue;
+        Xmin = PS_MIN (Xmin, x->data.F32[i]);
+        Xmax = PS_MAX (Xmax, x->data.F32[i]);
+        Ymin = PS_MIN (Ymin, y->data.F32[i]);
+        Ymax = PS_MAX (Ymax, y->data.F32[i]);
+        Fmin = PS_MIN (Fmin, param->data.F32[i]);
+        Fmax = PS_MAX (Fmax, param->data.F32[i]);
+	xm->data.F32[n] = x->data.F32[i];
+	ym->data.F32[n] = y->data.F32[i];
+	Fm->data.F32[n] = param->data.F32[i];
+	n++;
+    }
+    xm->n = ym->n = Fm->n = n;
+
+    // view 1 on resid
+    section.dx = 1.0;
+    section.dy = 0.5;
     section.x = 0.0;
-    section.y = 0.33;
+    section.y = 0.0;
     section.name = NULL;
-    psStringAppend (&section.name, "a3");
+    psStringAppend (&section.name, "a1");
     KapaSetSection (kapa1, &section);
     psFree (section.name);
-    pmSourcePlotPoints3D (kapa1, &graphdata, xn, yn, Zn, 30.0*PS_RAD_DEG, -15.0*PS_RAD_DEG);
-
-    // view 4 on resid
-    section.dx = 0.5;
-    section.dy = 0.33;
-    section.x = 0.5;
-    section.y = 0.33;
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.xmin = Xmin;
+    graphdata.xmax = Xmax;
+    graphdata.ymin = Fmin;
+    graphdata.ymax = Fmax;
+
+    { 
+	float range;
+	range = graphdata.xmax - graphdata.xmin;
+	graphdata.xmax += 0.05*range;
+	graphdata.xmin -= 0.05*range;
+	range = graphdata.ymax - graphdata.ymin;
+	graphdata.ymax += 0.05*range;
+	graphdata.ymin -= 0.05*range;
+    }
+
+    KapaSetLimits (kapa1, &graphdata);
+    KapaSetFont (kapa1, "helvetica", 14);
+    KapaBox (kapa1, &graphdata);
+    KapaSendLabel (kapa1, "X (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa1, "Model Param", KAPA_LABEL_YM);
+
+    graphdata.ptype = 2;
+    graphdata.size = 1.0;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa1,   x->n, &graphdata);
+    KapaPlotVector (kapa1, x->n, x->data.F32, "x");
+    KapaPlotVector (kapa1, x->n, param->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 1;
+    KapaPrepPlot (kapa1,   xm->n, &graphdata);
+    KapaPlotVector (kapa1, xm->n, xm->data.F32, "x");
+    KapaPlotVector (kapa1, xm->n, Fm->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 1;
+    KapaPrepPlot (kapa1,   x->n, &graphdata);
+    KapaPlotVector (kapa1, x->n, x->data.F32, "x");
+    KapaPlotVector (kapa1, x->n, model->data.F32, "y");
+
+    // view 2 on resid
+    section.dx = 1.0;
+    section.dy = 0.5;
+    section.x = 0.0;
+    section.y = 0.5;
     section.name = NULL;
-    psStringAppend (&section.name, "a4");
+    psStringAppend (&section.name, "a2");
     KapaSetSection (kapa1, &section);
     psFree (section.name);
-    pmSourcePlotPoints3D (kapa1, &graphdata, xn, yn, Zn, -60.0*PS_RAD_DEG, -15.0*PS_RAD_DEG);
-
-    // view 5 on resid
-    section.dx = 0.5;
-    section.dy = 0.33;
-    section.x = 0.0;
-    section.y = 0.66;
-    section.name = NULL;
-    psStringAppend (&section.name, "a5");
-    KapaSetSection (kapa1, &section);
-    psFree (section.name);
-    pmSourcePlotPoints3D (kapa1, &graphdata, xn, yn, Fn, 30.0*PS_RAD_DEG, -15.0*PS_RAD_DEG);
-
-    // view 6 on resid
-    section.dx = 0.5;
-    section.dy = 0.33;
-    section.x = 0.5;
-    section.y = 0.66;
-    section.name = NULL;
-    psStringAppend (&section.name, "a6");
-    KapaSetSection (kapa1, &section);
-    psFree (section.name);
-    pmSourcePlotPoints3D (kapa1, &graphdata, xn, yn, Fn, -60.0*PS_RAD_DEG, -15.0*PS_RAD_DEG);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.xmin = Ymin;
+    graphdata.xmax = Ymax;
+    graphdata.ymin = Fmin;
+    graphdata.ymax = Fmax;
+    { 
+	float range;
+	range = graphdata.xmax - graphdata.xmin;
+	graphdata.xmax += 0.05*range;
+	graphdata.xmin -= 0.05*range;
+	range = graphdata.ymax - graphdata.ymin;
+	graphdata.ymax += 0.05*range;
+	graphdata.ymin -= 0.05*range;
+    }
+
+    KapaSetLimits (kapa1, &graphdata);
+    KapaSetFont (kapa1, "helvetica", 14);
+    KapaBox (kapa1, &graphdata);
+    KapaSendLabel (kapa1, "Y (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa1, "Model Param", KAPA_LABEL_YM);
+
+    graphdata.ptype = 2;
+    graphdata.size = 1.0;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa1,   y->n, &graphdata);
+    KapaPlotVector (kapa1, y->n, y->data.F32, "x");
+    KapaPlotVector (kapa1, y->n, param->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 1;
+    KapaPrepPlot (kapa1,   xm->n, &graphdata);
+    KapaPlotVector (kapa1, xm->n, ym->data.F32, "x");
+    KapaPlotVector (kapa1, xm->n, Fm->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 1;
+    KapaPrepPlot (kapa1,   y->n, &graphdata);
+    KapaPlotVector (kapa1, y->n, y->data.F32, "x");
+    KapaPlotVector (kapa1, y->n, model->data.F32, "y");
+
+    psFree (xm);
+    psFree (ym);
+    psFree (Fm);
 
     psFree (resid);
-
-    psFree (xn);
-    psFree (yn);
-    psFree (zn);
-    psFree (Zn);
+    psFree (model);
 
     // pause and wait for user input:
@@ -159,6 +565,8 @@
 }
 
-// send in normalized points
+// Somewhat broken 3D plotting function (was used by pmSourceVisualPSFModelResid, but not anymore)
 bool pmSourcePlotPoints3D (int myKapa, Graphdata *graphdata, psVector *xn, psVector *yn, psVector *zn, float theta, float phi) {
+
+    return true;
 
     psVector *xv = psVectorAlloc (PS_MAX(6, 2*xn->n), PS_TYPE_F32);
@@ -192,9 +600,4 @@
     KapaSetLimits (myKapa, graphdata);
 
-    // KapaSetFont (myKapa, "helvetica", 14);
-    // KapaBox (myKapa, graphdata);
-    // KapaSendLabel (myKapa, "&ss&h_x| (pixels)", KAPA_LABEL_XM);
-    // KapaSendLabel (myKapa, "&ss&h_y| (pixels)", KAPA_LABEL_YM);
-
     graphdata->color = KapaColorByName ("black");
     graphdata->ptype = 100;
Index: branches/simmosaic_branches/psModules/src/objects/pmSourceVisual.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmSourceVisual.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmSourceVisual.h	(revision 27839)
@@ -18,4 +18,9 @@
 
 bool pmSourceVisualPSFModelResid (pmTrend2D *trend, psVector *x, psVector *y, psVector *param, psVector *mask);
+bool pmSourceVisualPlotPSFMetric (pmPSFtry *try);
+bool pmSourceVisualPlotPSFMetricSubpix (pmPSFtry *try);
+
+bool pmSourceVisualShowModelFit (pmSource *source);
+bool pmSourceVisualShowModelFits (pmPSF *psf, psArray *sources, psImageMaskType maskVal);
 
 /// @}
Index: branches/simmosaic_branches/psModules/src/objects/pmTrend2D.c
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmTrend2D.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmTrend2D.c	(revision 27839)
@@ -298,2 +298,21 @@
     return PM_TREND_NONE;
 }
+
+bool pmTrend2DPrintMap (pmTrend2D *trend) {
+
+    if (!trend->map) return false;
+    if (!trend->map->map) return false;
+
+    for (int j = 0; j < trend->map->map->numRows; j++) {
+        for (int i = 0; i < trend->map->map->numCols; i++) {
+            fprintf (stderr, "%5.2f  ", trend->map->map->data.F32[j][i]);
+        }
+        fprintf (stderr, "\t\t\t");
+        for (int i = 0; i < trend->map->map->numCols; i++) {
+            fprintf (stderr, "%5.2f  ", trend->map->error->data.F32[j][i]);
+        }
+        fprintf (stderr, "\n");
+    }
+    return true;
+}
+
Index: branches/simmosaic_branches/psModules/src/objects/pmTrend2D.h
===================================================================
--- branches/simmosaic_branches/psModules/src/objects/pmTrend2D.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/objects/pmTrend2D.h	(revision 27839)
@@ -97,4 +97,6 @@
 pmTrend2DMode pmTrend2DModeFromString(psString name);
 
+bool pmTrend2DPrintMap (pmTrend2D *trend);
+
 /// @}
 # endif
Index: branches/simmosaic_branches/psModules/src/psmodules.h
===================================================================
--- branches/simmosaic_branches/psModules/src/psmodules.h	(revision 25094)
+++ branches/simmosaic_branches/psModules/src/psmodules.h	(revision 27839)
@@ -10,4 +10,6 @@
 #include <pmKapaPlots.h>
 #include <pmVisual.h>
+#include <ippStages.h>
+#include <ippDiffMode.h>
 
 // XXX the following headers define constructs needed by the elements below
@@ -29,4 +31,5 @@
 #include <pmConfigDump.h>
 #include <pmConfigRun.h>
+#include <pmConfigRecipeValue.h>
 #include <pmVersion.h>
 
@@ -77,4 +80,6 @@
 #include <pmDark.h>
 #include <pmRemnance.h>
+#include <pmPattern.h>
+#include <pmPatternIO.h>
 
 // the following headers are from psModule:astrom
@@ -95,4 +100,5 @@
 #include <pmSubtractionStamps.h>
 #include <pmSubtractionKernels.h>
+#include <pmSubtractionDeconvolve.h>
 #include <pmSubtractionAnalysis.h>
 #include <pmSubtractionMatch.h>
@@ -107,8 +113,11 @@
 // the following headers are from psModule:objects
 #include <pmSpan.h>
+#include <pmFootprintSpans.h>
 #include <pmFootprint.h>
 #include <pmPeaks.h>
 #include <pmDetections.h>
 #include <pmMoments.h>
+#include <pmSourceExtendedPars.h>
+#include <pmSourceDiffStats.h>
 #include <pmResiduals.h>
 #include <pmGrowthCurve.h>
@@ -118,4 +127,5 @@
 #include <pmSourceMasks.h>
 #include <pmSource.h>
+#include <pmPhotObj.h>
 #include <pmSourceUtils.h>
 #include <pmSourceIO.h>
@@ -132,4 +142,5 @@
 #include <pmSourceVisual.h>
 #include <pmSourceMatch.h>
+#include <pmDetEff.h>
 
 // The following headers are from random locations, here because they cross bounds
Index: branches/simmosaic_branches/psModules/test/objects/tap_pmGrowthCurve.c
===================================================================
--- branches/simmosaic_branches/psModules/test/objects/tap_pmGrowthCurve.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/test/objects/tap_pmGrowthCurve.c	(revision 27839)
@@ -131,30 +131,30 @@
         source->mode = PM_SOURCE_MODE_PSFSTAR;
 
-        source->modelPSF->radiusFit = 15.0;
+        source->apRadius = 15.0;
 
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         double refMag = source->apMag;
 
-        source->modelPSF->radiusFit = 10.0;
-        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
-        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
-
-        source->modelPSF->radiusFit = 8.0;
-        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
-        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
-
-        source->modelPSF->radiusFit = 6.0;
+        source->apRadius = 10.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->apRadius = 8.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->apRadius = 6.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         ok_float_tol(refMag - source->apMag, +0.0003, 0.0001, "growth offset is is %f", refMag - source->apMag);
 
-        source->modelPSF->radiusFit = 4.0;
+        source->apRadius = 4.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         ok_float_tol(refMag - source->apMag, +0.0020, 0.0001, "growth offset is is %f", refMag - source->apMag);
 
-        source->modelPSF->radiusFit = 3.0;
+        source->apRadius = 3.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         ok_float_tol(refMag - source->apMag, -0.0001, 0.0001, "growth offset is is %f", refMag - source->apMag);
 
-        source->modelPSF->radiusFit = 2.0;
+        source->apRadius = 2.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         ok_float_tol(refMag - source->apMag, -0.0075, 0.0001, "growth offset is is %f", refMag - source->apMag);
@@ -234,29 +234,29 @@
         source->mode = PM_SOURCE_MODE_PSFSTAR;
 
-        source->modelPSF->radiusFit = 15.0;
+        source->apRadius = 15.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         double refMag = source->apMag;
 
-        source->modelPSF->radiusFit = 10.0;
-        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
-        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
-
-        source->modelPSF->radiusFit = 8.0;
-        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
-        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
-
-        source->modelPSF->radiusFit = 6.0;
+        source->apRadius = 10.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->apRadius = 8.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->apRadius = 6.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         ok_float_tol(refMag - source->apMag, +0.0004, 0.0001, "growth offset is is %f", refMag - source->apMag);
 
-        source->modelPSF->radiusFit = 4.0;
+        source->apRadius = 4.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         ok_float_tol(refMag - source->apMag, +0.0026, 0.0001, "growth offset is is %f", refMag - source->apMag);
 
-        source->modelPSF->radiusFit = 3.0;
+        source->apRadius = 3.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         ok_float_tol(refMag - source->apMag, -0.0001, 0.0001, "growth offset is is %f", refMag - source->apMag);
 
-        source->modelPSF->radiusFit = 2.0;
+        source->apRadius = 2.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         ok_float_tol(refMag - source->apMag, -0.0103, 0.0001, "growth offset is is %f", refMag - source->apMag);
@@ -336,29 +336,29 @@
         source->mode = PM_SOURCE_MODE_PSFSTAR;
 
-        source->modelPSF->radiusFit = 15.0;
+        source->apRadius = 15.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         double refMag = source->apMag;
 
-        source->modelPSF->radiusFit = 10.0;
-        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
-        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
-
-        source->modelPSF->radiusFit = 8.0;
-        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
-        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
-
-        source->modelPSF->radiusFit = 6.0;
+        source->apRadius = 10.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->apRadius = 8.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->apRadius = 6.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         ok_float_tol(refMag - source->apMag, +0.0006, 0.0001, "growth offset is is %f", refMag - source->apMag);
 
-        source->modelPSF->radiusFit = 4.0;
+        source->apRadius = 4.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         ok_float_tol(refMag - source->apMag, +0.0038, 0.0001, "growth offset is is %f", refMag - source->apMag);
 
-        source->modelPSF->radiusFit = 3.0;
-        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
-        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
-
-        source->modelPSF->radiusFit = 2.0;
+        source->apRadius = 3.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->apRadius = 2.0;
         pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0);
         ok_float_tol(refMag - source->apMag, -0.0164, 0.0001, "growth offset is is %f", refMag - source->apMag);
Index: branches/simmosaic_branches/psModules/test/objects/tap_pmModel.c
===================================================================
--- branches/simmosaic_branches/psModules/test/objects/tap_pmModel.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/test/objects/tap_pmModel.c	(revision 27839)
@@ -77,5 +77,5 @@
         ok(model->nDOF == 0, "pmModelAlloc() set pmModel->nDOF correctly");
         ok(model->nIter == 0, "pmModelAlloc() set pmModel->nIter correctly");
-        ok(model->radiusFit == 0, "pmModelAlloc() set pmModel->radiusFit correctly");
+        ok(model->fitRadius == 0, "pmModelAlloc() set pmModel->fitRadius correctly");
         ok(model->flags == PM_MODEL_STATUS_NONE, "pmModelAlloc() set pmModel->flags correctly");
         ok(model->residuals == NULL, "pmModelAlloc() set pmModel->residuals correctly");
@@ -132,5 +132,5 @@
         modelSrc->nIter = 3;
         modelSrc->flags = PM_MODEL_STATUS_NONE;
-        modelSrc->radiusFit = 4;
+        modelSrc->fitRadius = 4;
         pmModel *modelDst = pmModelCopy(modelSrc);
         ok(modelDst != NULL && psMemCheckModel(modelDst), "pmModelCopy() returned a non-NULL pmModel");
@@ -139,5 +139,5 @@
         ok(modelDst->nIter == 3, "pmModelCopy() set the pmModel->nIter member correctly");
         ok(modelDst->flags == PM_MODEL_STATUS_NONE, "pmModelCopy() set the pmModel->flags member correctly");
-        ok(modelDst->radiusFit == 4, "pmModelCopy() set the pmModel->radiusFit member correctly");
+        ok(modelDst->fitRadius == 4, "pmModelCopy() set the pmModel->fitRadius member correctly");
 
         psFree(modelSrc);
Index: branches/simmosaic_branches/psModules/test/objects/tap_pmModelUtils.c
===================================================================
--- branches/simmosaic_branches/psModules/test/objects/tap_pmModelUtils.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/test/objects/tap_pmModelUtils.c	(revision 27839)
@@ -81,5 +81,5 @@
         ok(tmpModel->nIter == testModelPSF->nIter, "pmModelFromPSF() set the model->nIter correctly");
         ok(tmpModel->flags == testModelPSF->flags, "pmModelFromPSF() set the model->flags correctly");
-        ok(tmpModel->radiusFit == testModelPSF->radiusFit, "pmModelFromPSF() set the model->radiusFit correctly");
+        ok(tmpModel->fitRadius == testModelPSF->fitRadius, "pmModelFromPSF() set the model->fitRadius correctly");
         ok(tmpModel->modelFunc == testModelPSF->modelFunc, "pmModelFromPSF() set the model->modelFunc correctly");
         ok(tmpModel->modelFlux == testModelPSF->modelFlux, "pmModelFromPSF() set the model->modelFlux correctly");
@@ -140,5 +140,5 @@
         ok(tmpModel->nIter == testModelPSF->nIter, "pmModelFromPSF() set the model->nIter correctly");
         ok(tmpModel->flags == testModelPSF->flags, "pmModelFromPSF() set the model->flags correctly");
-        ok(tmpModel->radiusFit == testModelPSF->radiusFit, "pmModelFromPSF() set the model->radiusFit correctly");
+        ok(tmpModel->fitRadius == testModelPSF->fitRadius, "pmModelFromPSF() set the model->fitRadius correctly");
         ok(tmpModel->modelFunc == testModelPSF->modelFunc, "pmModelFromPSF() set the model->modelFunc correctly");
         ok(tmpModel->modelFlux == testModelPSF->modelFlux, "pmModelFromPSF() set the model->modelFlux correctly");
Index: branches/simmosaic_branches/psModules/test/objects/tap_pmSourcePhotometry.c
===================================================================
--- branches/simmosaic_branches/psModules/test/objects/tap_pmSourcePhotometry.c	(revision 25094)
+++ branches/simmosaic_branches/psModules/test/objects/tap_pmSourcePhotometry.c	(revision 27839)
@@ -96,5 +96,5 @@
     source->modelPSF = pmModelFromPSF (modelRef, psf);
     source->modelPSF->dparams->data.F32[PM_PAR_I0] = 1;
-    source->modelPSF->radiusFit = radius;
+    source->apRadius = radius;
 
     // measure photometry for centered source (fractional pix : 0.5,0.5)
