Index: trunk/psModules/src/astrom/pmAstrometryObjects.c
===================================================================
--- trunk/psModules/src/astrom/pmAstrometryObjects.c	(revision 26682)
+++ trunk/psModules/src/astrom/pmAstrometryObjects.c	(revision 26893)
@@ -108,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;
@@ -128,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
@@ -138,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++;
@@ -196,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); \
@@ -314,5 +314,5 @@
     if (!xRes) abort();
     psFree (xFit);
-    
+
     psVector *yFit = psPolynomial2DEvalVector (map->y, X, Y);
     if (!yFit) abort();
@@ -322,5 +322,5 @@
 
     // extract a high-quality subset (unmasked, S/N > XXX) and position errors
-    // XXX for now, generate a position error based on the magnitude error 
+    // 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);
@@ -329,5 +329,5 @@
 
     for (int i = 0; i < match->n; i++) {
-	if (mask->data.PS_TYPE_VECTOR_MASK_DATA[i]) continue;
+        if (mask->data.PS_TYPE_VECTOR_MASK_DATA[i]) continue;
         pmAstromMatch *pair = match->data[i];
         pmAstromObj *rawStar = raw->data[pair->raw];
@@ -335,21 +335,21 @@
         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]);
+        // 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]);
     }
 
@@ -434,12 +434,12 @@
     // Get the minimum and maximum values
     if (!psVectorStats(stats, myVector, NULL, NULL, 0)) {
-	psFree(stats);
-	return NAN;
+        psFree(stats);
+        return NAN;
     }
     min = stats->min;
     max = stats->max;
     if (isnan(min) || isnan(max)) {
-	psFree(stats);
-	return NAN;
+        psFree(stats);
+        return NAN;
     }
 
@@ -448,9 +448,9 @@
     // 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.  
+        psFree (stats);
+        return 0.0;
+    }
+
+    // Define the histogram bin size.
     float binSize = 0.001;
     long numBins = PS_MIN(100000, (max - min) / binSize); // Number of bins
@@ -463,10 +463,10 @@
 
     if (!psVectorHistogram(histogram, myVector, NULL, NULL, 0)) {
-	// if psVectorHistogram returns false, we have a programming error
-	psAbort ("Unable to generate histogram");
+        // 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);
+        PS_VECTOR_PRINT_F32(histogram->bounds);
+        PS_VECTOR_PRINT_F32(histogram->nums);
     }
 
@@ -475,10 +475,10 @@
     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];
+        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);
+        PS_VECTOR_PRINT_F32(cumulative->bounds);
+        PS_VECTOR_PRINT_F32(cumulative->nums);
     }
 
@@ -837,14 +837,14 @@
         }
 
-	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, "Something went wrong; continuing.");
+        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
@@ -1191,10 +1191,10 @@
 psArray *pmAstromRadiusMatchUniq (psArray *rawstars, psArray *refstars, psArray *matches) {
 
-    // I have the matches between the refstars and the rawstars.  
+    // 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.  
+    // all others.
 
     // create an array of refstars->n arrays, each containing all of the matches for the
-    // given refstar.  
+    // given refstar.
 
     psArray *refstarMatches = psArrayAlloc (refstars->n);
@@ -1202,23 +1202,23 @@
     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);
+        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);
     }
 
@@ -1229,25 +1229,25 @@
     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,
+        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,
     }
 
Index: trunk/psModules/src/camera/pmFPA.c
===================================================================
--- trunk/psModules/src/camera/pmFPA.c	(revision 26682)
+++ trunk/psModules/src/camera/pmFPA.c	(revision 26893)
@@ -105,5 +105,5 @@
     psFree(fpa->concepts);
     psFree(fpa->analysis);
-    psFree((void *)fpa->camera);
+    psFree(fpa->camera);
     psFree(fpa->hdu);
 
Index: trunk/psModules/src/camera/pmFPAMaskWeight.c
===================================================================
--- trunk/psModules/src/camera/pmFPAMaskWeight.c	(revision 26682)
+++ trunk/psModules/src/camera/pmFPAMaskWeight.c	(revision 26893)
@@ -370,8 +370,34 @@
 
     psImage *image = readout->image, *mask = readout->mask, *variance = readout->variance; // Readout parts
-
     int numCols = image->numCols, numRows = image->numRows; // Size of image
-    int numPix = numCols * numRows;                         // Number of pixels
-    int num = PS_MAX(sample, numPix);                       // Number we care about
+
+    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;
+                }
+                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
 
@@ -379,6 +405,6 @@
         // We have an image smaller than Nsubset, so just loop over the image pixels
         int index = 0;                  // Index for vector
-        for (int y = 0; y < numRows; y++) {
-            for (int x = 0; x < numCols; x++) {
+        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])) {
@@ -397,6 +423,6 @@
             // Pixel coordinates
             int pixel = numPix * psRandomUniform(rng);
-            int x = pixel % numCols;
-            int y = pixel / numCols;
+            int x = xMin + pixel % xNum;
+            int y = yMin + pixel / xNum;
 
             if ((mask && mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskVal) ||
@@ -428,10 +454,12 @@
     // Check valid range of correction factor
     if ((isfinite(minValid) && correction < minValid) || (isfinite(maxValid) && correction > maxValid)) {
-        psWarning("Variance renormalisation is outside valid range: %f vs %f:%f --- no correction made",
-                  correction, minValid, maxValid);
-        return true;
-    }
-
-    psBinaryOp(variance, variance, "*", psScalarAlloc(PS_SQR(correction), PS_TYPE_F32));
+	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;
+    }
+
+    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
Index: trunk/psModules/src/camera/pmFPAMosaic.c
===================================================================
--- trunk/psModules/src/camera/pmFPAMosaic.c	(revision 26682)
+++ trunk/psModules/src/camera/pmFPAMosaic.c	(revision 26893)
@@ -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;
         }
Index: trunk/psModules/src/camera/pmFPAfile.c
===================================================================
--- trunk/psModules/src/camera/pmFPAfile.c	(revision 26682)
+++ trunk/psModules/src/camera/pmFPAfile.c	(revision 26893)
@@ -519,4 +519,7 @@
     if (!strcasecmp(type, "SUBKERNEL"))     {
         return PM_FPA_FILE_SUBKERNEL;
+    }
+    if (!strcasecmp(type, "PATTERN")) {
+        return PM_FPA_FILE_PATTERN;
     }
 
@@ -563,4 +566,6 @@
       case PM_FPA_FILE_SUBKERNEL:
         return ("SUBKERNEL");
+      case PM_FPA_FILE_PATTERN:
+        return "PATTERN";
       default:
         return ("NONE");
Index: trunk/psModules/src/camera/pmFPAfile.h
===================================================================
--- trunk/psModules/src/camera/pmFPAfile.h	(revision 26682)
+++ trunk/psModules/src/camera/pmFPAfile.h	(revision 26893)
@@ -49,4 +49,5 @@
     PM_FPA_FILE_SUBKERNEL,
     PM_FPA_FILE_SRCTEXT,
+    PM_FPA_FILE_PATTERN,
 } pmFPAfileType;
 
Index: trunk/psModules/src/camera/pmFPAfileIO.c
===================================================================
--- trunk/psModules/src/camera/pmFPAfileIO.c	(revision 26682)
+++ trunk/psModules/src/camera/pmFPAfileIO.c	(revision 26893)
@@ -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,6 +77,6 @@
             }
             break;
-	  case PM_FPA_AFTER:
-	    if (!pmFPAfileWrite (file, view, config)) {
+          case PM_FPA_AFTER:
+            if (!pmFPAfileWrite (file, view, config)) {
                 psError(PS_ERR_IO, false, "failed WRITE in FPA_AFTER block for %s", file->name);
                 goto failure;
@@ -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:
@@ -273,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");
@@ -462,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:
@@ -543,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:
@@ -610,4 +620,5 @@
         break;
       case PM_FPA_FILE_SUBKERNEL:
+      case PM_FPA_FILE_PATTERN:
       case PM_FPA_FILE_SX:
       case PM_FPA_FILE_RAW:
@@ -771,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:
@@ -962,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: trunk/psModules/src/config/pmConfigRecipeValue.c
===================================================================
--- trunk/psModules/src/config/pmConfigRecipeValue.c	(revision 26682)
+++ trunk/psModules/src/config/pmConfigRecipeValue.c	(revision 26893)
@@ -68,4 +68,6 @@
     }
 
+    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
Index: trunk/psModules/src/detrend/Makefile.am
===================================================================
--- trunk/psModules/src/detrend/Makefile.am	(revision 26682)
+++ trunk/psModules/src/detrend/Makefile.am	(revision 26893)
@@ -17,5 +17,6 @@
 	pmDark.c \
 	pmRemnance.c \
-	pmPattern.c
+	pmPattern.c \
+	pmPatternIO.c
 
 #	pmSkySubtract.c
@@ -35,5 +36,6 @@
 	pmDark.h \
 	pmRemnance.h \
-	pmPattern.h
+	pmPattern.h \
+	pmPatternIO.h
 
 #	pmSkySubtract.h
Index: trunk/psModules/src/detrend/pmPattern.c
===================================================================
--- trunk/psModules/src/detrend/pmPattern.c	(revision 26682)
+++ trunk/psModules/src/detrend/pmPattern.c	(revision 26893)
@@ -1,3 +1,4 @@
 #include <stdio.h>
+#include <string.h>
 #include <pslib.h>
 
@@ -9,5 +10,5 @@
 // Mask a row as bad
 static void patternMaskRow(pmReadout *ro, // Readout to mask
-                           int y,         // Row to mask
+                           int y,       // Row to mask
                            psImageMaskType bad // Mask value to give
                            )
@@ -17,5 +18,5 @@
     psAssert(y < image->numRows, "Row not in image");
 
-    int numCols = image->numCols;       // Size of image
+    int numCols = image->numCols;       // Number of columns
     for (int x = 0; x < numCols; x++) {
         image->data.F32[y][x] = NAN;
@@ -29,4 +30,8 @@
     return;
 }
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Measurement and application
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
 bool pmPatternRow(pmReadout *ro, int order, int iter, float rej, float thresh,
@@ -63,4 +68,5 @@
     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);
@@ -79,4 +85,7 @@
     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++) {
@@ -104,4 +113,7 @@
             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) {
@@ -117,4 +129,8 @@
         psFree(solution);
     }
+
+    psMetadataAddImage(ro->analysis, PS_LIST_TAIL, PM_PATTERN_ROW_CORRECTION, PS_META_REPLACE,
+                       "Pattern row correction", corr);
+    psFree(corr);
 
     psFree(indices);
@@ -126,2 +142,239 @@
     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)) {
+        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)) {
+        psError(PS_ERR_UNKNOWN, false, "Non-finite mean cell background.");
+        psFree(mean);
+        psFree(meanMask);
+        return false;
+    }
+
+    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: trunk/psModules/src/detrend/pmPattern.h
===================================================================
--- trunk/psModules/src/detrend/pmPattern.h	(revision 26682)
+++ trunk/psModules/src/detrend/pmPattern.h	(revision 26893)
@@ -18,4 +18,7 @@
 /// @{
 
+#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(
@@ -31,4 +34,25 @@
     );
 
+/// 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: trunk/psModules/src/detrend/pmPatternIO.c
===================================================================
--- trunk/psModules/src/detrend/pmPatternIO.c	(revision 26893)
+++ trunk/psModules/src/detrend/pmPatternIO.c	(revision 26893)
@@ -0,0 +1,460 @@
+#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;
+    }
+
+    pmFPAview *thisView = pmFPAviewAlloc(view->nRows); // Copy of input view
+    *thisView = *view;
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        thisView->readout = i;
+        if (!pmReadoutWritePattern(readout, file->fits)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth readout", i);
+            psFree(thisView);
+            return false;
+        }
+    }
+    psFree(thisView);
+    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: trunk/psModules/src/detrend/pmPatternIO.h
===================================================================
--- trunk/psModules/src/detrend/pmPatternIO.h	(revision 26893)
+++ trunk/psModules/src/detrend/pmPatternIO.h	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/Makefile.am
===================================================================
--- trunk/psModules/src/imcombine/Makefile.am	(revision 26682)
+++ trunk/psModules/src/imcombine/Makefile.am	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/pmImageCombine.c
===================================================================
--- trunk/psModules/src/imcombine/pmImageCombine.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmImageCombine.c	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/pmPSFEnvelope.c
===================================================================
--- trunk/psModules/src/imcombine/pmPSFEnvelope.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmPSFEnvelope.c	(revision 26893)
@@ -65,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
                      )
 {
@@ -154,12 +155,16 @@
             pmModelClassSetLimits(PM_MODEL_LIMITS_IGNORE);
             pmModel *model = pmModelFromPSFforXY(psf, numCols / 2.0, numRows / 2.0, PEAK_FLUX); // Test model
-            model->modelSetLimits(PM_MODEL_LIMITS_STRICT);
-            for (int j = 0; j < model->params->n && goodPSF; 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)) {
-                    goodPSF = false;
+            if (!model) {
+                goodPSF = false;
+            } else {
+                model->modelSetLimits(PM_MODEL_LIMITS_STRICT);
+                for (int j = 0; j < model->params->n && goodPSF; 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)) {
+                        goodPSF = false;
+                    }
                 }
-            }
-            psFree(model);
+                psFree(model);
+            }
             if (!goodPSF) {
                 psWarning("PSF %d is bad --- not including in envelope calculation.", i);
@@ -360,5 +365,6 @@
 
         // measure the source moments: tophat windowing, no pixel S/N cutoff
-        if (!pmSourceMoments(source, maxRadius, 0.0, 1.0)) {
+        // 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();
Index: trunk/psModules/src/imcombine/pmPSFEnvelope.h
===================================================================
--- trunk/psModules/src/imcombine/pmPSFEnvelope.h	(revision 26682)
+++ trunk/psModules/src/imcombine/pmPSFEnvelope.h	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/pmStackReject.c
===================================================================
--- trunk/psModules/src/imcombine/pmStackReject.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmStackReject.c	(revision 26893)
@@ -35,6 +35,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);
@@ -150,8 +152,9 @@
     pmReadout *inRO = pmReadoutAlloc(NULL); // Readout with input image
     inRO->image = image;
+    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(NULL, convRO, NULL, inRO, 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);
@@ -165,9 +168,5 @@
 
         // 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.");
Index: trunk/psModules/src/imcombine/pmSubtraction.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtraction.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtraction.c	(revision 26893)
@@ -21,4 +21,5 @@
 #include "pmSubtractionStamps.h"
 #include "pmSubtractionEquation.h"
+#include "pmSubtractionVisual.h"
 #include "pmSubtractionThreads.h"
 
@@ -63,21 +64,19 @@
 }
 
-// Contribute to an image of the solved kernel component for ISIS
-static void solvedKernelISIS(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
+// 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
-    psArray *preCalc = kernels->preCalc->data[index]; // Precalculated values
+    pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[index]; // Precalculated values
 #if 0
-    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++) {
-        float yValue = value * yKernel->data.F32[y];
+        float yValue = value * preCalc->yKernel->data.F32[y];
         for (int x = 0, u = -size; u <= size; x++, u++) {
-            kernel->kernel[v][u] +=  yValue * xKernel->data.F32[x];
+            kernel->kernel[v][u] +=  yValue * preCalc->xKernel->data.F32[x];
         }
     }
@@ -87,8 +86,7 @@
     }
 #else
-    psKernel *k = preCalc->data[2]; // Kernel image
     for (int v = -size; v <= size; v++) {
         for (int u = -size; u <= size; u++) {
-            kernel->kernel[v][u] +=  value * k->kernel[v][u];
+            kernel->kernel[v][u] +=  value * preCalc->kernel->kernel[v][u];
         }
     }
@@ -119,4 +117,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) {
@@ -149,5 +152,5 @@
           case PM_SUBTRACTION_KERNEL_GUNK: {
               if (i < kernels->inner) {
-                  solvedKernelISIS(kernel, kernels, value, i);
+                  solvedKernelPreCalc(kernel, kernels, value, i);
               } else {
                   // Using delta function
@@ -159,18 +162,19 @@
               break;
           }
-          case PM_SUBTRACTION_KERNEL_ISIS: {
-              solvedKernelISIS(kernel, kernels, value, i);
+          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!
@@ -419,5 +423,8 @@
                     *target |= maskBad;
                 } else if (*source & subConvPoor) {
+                    *target &= ~maskBad;
                     *target |= maskPoor;
+                } else {
+                    *target &= ~maskBad & ~maskPoor;
                 }
             }
@@ -454,16 +461,14 @@
 #endif
 
-// Convolve a stamp using an ISIS kernel basis function
-static psKernel *convolveStampISIS(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
+// 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
     )
 {
-    psArray *preCalc = kernels->preCalc->data[index]; // Precalculated data
+    pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[index]; // Precalculated data
 #if 0
     // Convolving using separable convolution
-    psVector *xKernel = preCalc->data[0]; // Kernel in x
-    psVector *yKernel = preCalc->data[1]; // Kernel in y
     int size = kernels->size;     // Size of kernel
 
@@ -477,5 +482,5 @@
             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 *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++) {
@@ -492,5 +497,5 @@
             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 *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++) {
@@ -509,5 +514,5 @@
 #else
     // Convolving using precalculated kernel
-    return p_pmSubtractionConvolveStampPrecalc(image, preCalc->data[2]);
+    return p_pmSubtractionConvolveStampPrecalc(image, preCalc->kernel);
 #endif
 }
@@ -525,4 +530,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;
@@ -569,19 +577,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);
 }
@@ -664,5 +674,5 @@
           if (index < kernels->inner) {
               // Photometric scaling is already built in to the precalculated kernel
-              return convolveStampISIS(image, kernels, index, footprint);
+              return convolveStampPreCalc(image, kernels, index, footprint);
           }
           // Using delta function
@@ -673,17 +683,18 @@
           return convolved;
       }
-      case PM_SUBTRACTION_KERNEL_ISIS: {
-          return convolveStampISIS(image, kernels, index, footprint);
-      }
+      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
@@ -765,6 +776,4 @@
 
 
-
-
 int pmSubtractionRejectStamps(pmSubtractionKernels *kernels, pmSubtractionStampList *stamps,
                               const psVector *deviations, psImage *subMask, float sigmaRej)
@@ -860,4 +869,6 @@
     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
@@ -872,5 +883,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++) {
@@ -895,11 +909,8 @@
                 psFree(stamp->weight);
                 stamp->image1 = stamp->image2 = stamp->weight = 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->matrix);
+                stamp->matrix = NULL;
+                psFree(stamp->vector);
+                stamp->vector = NULL;
             } else {
                 numGood++;
@@ -910,4 +921,10 @@
     }
     newMean /= numGood;
+
+    if (numRejected == 0) {
+        psStringAppend(&log, "<none>\n");
+    }
+    psLogMsg("psModules.imcombine", PS_LOG_DETAIL, "%s", log);
+    psFree(log);
 
     if (ds9) {
@@ -995,5 +1012,5 @@
     psVector *backup = psVectorCopy(NULL, solution, PS_TYPE_F64);  // Backup version
 
-    int num = wantDual ? solution->n - 1 : solution->n; // Number of kernel basis functions
+    int num = kernels->num;             // Number of kernel basis functions
 
     psImage *polyValues = p_pmSubtractionPolynomial(NULL, kernels->spatialOrder, x, y); // Solved polynomial
@@ -1060,7 +1077,6 @@
     // 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
 
@@ -1137,5 +1153,5 @@
 bool pmSubtractionConvolve(pmReadout *out1, pmReadout *out2, const pmReadout *ro1, const pmReadout *ro2,
                            psImage *subMask, int stride, psImageMaskType maskBad, psImageMaskType maskPoor,
-                           float poorFrac, float kernelError, const psRegion *region,
+                           float poorFrac, float kernelError, float covarFrac, const psRegion *region,
                            const pmSubtractionKernels *kernels, bool doBG, bool useFFT)
 {
@@ -1146,4 +1162,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;
@@ -1155,4 +1172,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;
@@ -1177,4 +1195,6 @@
     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);
@@ -1188,41 +1208,10 @@
     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);
-        }
-        if (ro1->variance) {
-            if (!out1->variance) {
-                out1->variance = psImageAlloc(numCols, numRows, PS_TYPE_F32);
-            }
-            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);
-        }
-        if (ro2->variance) {
-            if (!out2->variance) {
-                out2->variance = psImageAlloc(numCols, numRows, PS_TYPE_F32);
-            }
-            psImageInit(out2->variance, 0.0);
-        }
-    }
     psImage *convMask = NULL;           // Convolved mask image (common to inputs 1 and 2)
     if (subMask) {
         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);
-            }
-            psImageInit(out1->mask, 0);
             convMask = out1->mask;
         }
         if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-            if (!out2->mask) {
-                out2->mask = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK);
-            }
-            psImageInit(out2->mask, 0);
             if (!convMask) {
                 convMask = out2->mask;
@@ -1244,6 +1233,6 @@
 
     // 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);
@@ -1336,8 +1325,10 @@
 
     // 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);
@@ -1346,7 +1337,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
@@ -1388,5 +1381,4 @@
     }
 
-
     psLogMsg("psModules.imcombine", PS_LOG_INFO, "Convolve image: %f sec",
              psTimerClear("pmSubtractionConvolve"));
Index: trunk/psModules/src/imcombine/pmSubtraction.h
===================================================================
--- trunk/psModules/src/imcombine/pmSubtraction.h	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtraction.h	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/pmSubtractionAnalysis.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionAnalysis.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionAnalysis.c	(revision 26893)
@@ -117,4 +117,8 @@
     }
 
+    // sample difference images
+    {
+        psMetadataAddArray(analysis, PS_LIST_TAIL, "SUBTRACTION.SAMPLE.STAMP.SET", PS_META_DUPLICATE_OK, "Sample Difference Stamps", kernels->sampleStamps);
+    }
 
 #ifdef TESTING
@@ -269,6 +273,5 @@
     // 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
 
@@ -295,4 +298,18 @@
         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: trunk/psModules/src/imcombine/pmSubtractionAnalysis.h
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionAnalysis.h	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionAnalysis.h	(revision 26893)
@@ -24,4 +24,11 @@
 #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(
Index: trunk/psModules/src/imcombine/pmSubtractionDeconvolve.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionDeconvolve.c	(revision 26893)
+++ trunk/psModules/src/imcombine/pmSubtractionDeconvolve.c	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/pmSubtractionDeconvolve.h
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionDeconvolve.h	(revision 26893)
+++ trunk/psModules/src/imcombine/pmSubtractionDeconvolve.h	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/pmSubtractionEquation.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionEquation.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionEquation.c	(revision 26893)
@@ -17,5 +17,6 @@
 //#define TESTING                         // TESTING output for debugging; may not work with threads!
 
-#define USE_WEIGHT                      // Include weight (1/variance) in equation?
+//#define USE_WEIGHT                      // Include weight (1/variance) in equation?
+//#define USE_WINDOW                      // Include weight (1/variance) in equation?
 
 
@@ -27,11 +28,15 @@
 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 footprint, // (Half-)Size of stamp
+                                  int normWindow, // Window (half-)size for normalisation measurement
+                                  const pmSubtractionEquationCalculationMode mode
                                   )
 {
@@ -51,4 +56,5 @@
 
     // 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++) {
@@ -64,4 +70,18 @@
     }
 
+    // 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++) {
@@ -74,17 +94,22 @@
                 for (int x = - footprint; x <= footprint; x++) {
                     double cc = iConv->kernel[y][x] * jConv->kernel[y][x];
-#ifdef USE_WEIGHT
-                    cc *= weight->kernel[y][x];
-#endif
+                    if (weight) {
+                        cc *= weight->kernel[y][x];
+                    }
+                    if (window) {
+                        cc *= window->kernel[y][x];
+                    }
                     sumCC += cc;
                 }
             }
 
-            // Spatial variation
-            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;
+            // 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;
+                    }
                 }
             }
@@ -102,10 +127,16 @@
                 double rc = ref * conv;
                 double c = conv;
-#ifdef USE_WEIGHT
-                float wtVal = weight->kernel[y][x];
-                ic *= wtVal;
-                rc *= wtVal;
-                c *= wtVal;
-#endif
+                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;
@@ -117,9 +148,22 @@
             double normTerm = sumRC * poly[iTerm];
             double bgTerm = sumC * poly[iTerm];
-            matrix->data.F64[iIndex][normIndex] = normTerm;
-            matrix->data.F64[normIndex][iIndex] = normTerm;
-            matrix->data.F64[iIndex][bgIndex] = bgTerm;
-            matrix->data.F64[bgIndex][iIndex] = bgTerm;
-            vector->data.F64[iIndex] = sumIC * 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;
+                }
+            }
         }
     }
@@ -130,4 +174,5 @@
     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++) {
@@ -137,12 +182,26 @@
             double rr = PS_SQR(ref);
             double one = 1.0;
-#ifdef USE_WEIGHT
-            float wtVal = weight->kernel[y][x];
-            rr *= wtVal;
-            ir *= wtVal;
-            in *= wtVal;
-            ref *= wtVal;
-            one *= wtVal;
-#endif
+
+            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;
@@ -152,41 +211,58 @@
         }
     }
-    matrix->data.F64[normIndex][normIndex] = sumRR;
-    matrix->data.F64[bgIndex][bgIndex] = sum1;
-    matrix->data.F64[normIndex][bgIndex] = matrix->data.F64[bgIndex][normIndex] = sumR;
-    vector->data.F64[normIndex] = sumIR;
-    vector->data.F64[bgIndex] = sumI;
+
+    *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 *matrix1, // Least-squares matrix, updated
-                                      psVector *vector1, // Least-squares vector, updated
-                                      psImage *matrix2,  // Least-squares matrix, updated
-                                      psVector *vector2, // Least-squares vector, updated
-                                      psImage *matrixX,  // Cross-matrix
+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 footprint, // (Half-)Size of stamp
+                                      int normWindow, // Window (half-)size for normalisation measurement
+                                      const pmSubtractionEquationCalculationMode mode
                                       )
 {
-    // A_ij = A_i A_j
-    // B_ij = B_i B_j
-    // C_ij = A_i B_j
-    // d_i = A_i I_2
-    // e_i = B_i I_2
-
-    // A_i = I_1 * k_i
-    // B_i = I_2 * k_i
-
-    // Background: A_i = 1.0
-    // Normalisation: A_i = I_1
-
     int numKernels = kernels->num;                      // Number of kernels
     int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
@@ -196,4 +272,19 @@
     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
@@ -212,4 +303,12 @@
 
 
+    // 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 *iConv1 = convolutions1->data[i]; // Convolution 1 for index i
@@ -219,7 +318,7 @@
             psKernel *jConv2 = convolutions2->data[j]; // Convolution 2 for index j
 
-            double sumAA = 0.0;         // Sum of convolution products for matrix A
-            double sumBB = 0.0;         // Sum of convolution products for matrix B
-            double sumAB = 0.0;         // Sum of convolution products for matrix C
+            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++) {
@@ -227,10 +326,16 @@
                     double bb = iConv2->kernel[y][x] * jConv2->kernel[y][x];
                     double ab = iConv1->kernel[y][x] * jConv2->kernel[y][x];
-#ifdef USE_WEIGHT
-                    float wtVal = weight->kernel[y][x];
-                    aa *= wtVal;
-                    bb *= wtVal;
-                    ab *= wtVal;
-#endif
+                    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;
@@ -239,15 +344,21 @@
             }
 
-            // Spatial variation
-            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];
-                    matrix1->data.F64[iIndex][jIndex] = aa;
-                    matrix1->data.F64[jIndex][iIndex] = aa;
-                    matrix2->data.F64[iIndex][jIndex] = bb;
-                    matrix2->data.F64[jIndex][iIndex] = bb;
-                    matrixX->data.F64[iIndex][jIndex] = 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;
+                    }
                 }
             }
@@ -259,32 +370,37 @@
                 for (int x = - footprint; x <= footprint; x++) {
                     double ab = iConv1->kernel[y][x] * jConv2->kernel[y][x];
-#ifdef USE_WEIGHT
-                    ab *= weight->kernel[y][x];
-#endif
+                    if (weight) {
+                        ab *= weight->kernel[y][x];
+                    }
+                    if (window) {
+                        ab *= window->kernel[y][x];
+                    }
                     sumAB += ab;
                 }
             }
 
-            // Spatial variation
-            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];
-                    matrixX->data.F64[iIndex][jIndex] = ab;
-                }
-            }
-        }
-
-        double sumAI2 = 0.0;            // Sum of A.I_2 products (for vector 1)
-        double sumBI2 = 0.0;            // Sum of B.I_2 products (for vector 2)
-        double sumAI1 = 0.0;            // Sum of A.I_1 products (for matrix 1, normalisation)
-        double sumA = 0.0;              // Sum of A (for matrix 1, background)
-        double sumBI1 = 0.0;            // Sum of B.I_1 products (for matrix X, normalisation)
-        double sumB = 0.0;              // Sum of B products (for matrix X, background)
-        double sumI2 = 0.0;             // Sum of I_2 (for vector 1, background)
-        double sumI1I2 = 0.0;           // Sum of I_1.I_2 (for vector 1, normalisation)
+            // 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++) {
-                float a = iConv1->kernel[y][x];
-                float b = iConv2->kernel[y][x];
+                double a = iConv1->kernel[y][x];
+                double b = iConv2->kernel[y][x];
                 float i1 = image1->kernel[y][x];
                 float i2 = image2->kernel[y][x];
@@ -294,18 +410,25 @@
                 double ai1 = a * i1;
                 double bi1 = b * i1;
-                double i1i2 = i1 * i2;
-
-#ifdef USE_WEIGHT
-                float wtVal = weight->kernel[y][x];
-                ai2 *= wtVal;
-                bi2 *= wtVal;
-                ai1 *= wtVal;
-                bi1 *= wtVal;
-                i1i2 *= wtVal;
-                a *= wtVal;
-                b *= wtVal;
-                i2 *= wtVal;
-#endif
-
+
+                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;
@@ -315,5 +438,4 @@
                 sumB += b;
                 sumI2 += i2;
-                sumI1I2 += i1i2;
             }
         }
@@ -323,28 +445,45 @@
             double bi2 = sumBI2 * poly[iTerm];
             double ai1 = sumAI1 * poly[iTerm];
-            double a = sumA * poly[iTerm];
+            double a   = sumA * poly[iTerm];
             double bi1 = sumBI1 * poly[iTerm];
-            double b = sumB * poly[iTerm];
-
-            matrix1->data.F64[iIndex][normIndex] = ai1;
-            matrix1->data.F64[normIndex][iIndex] = ai1;
-            matrix1->data.F64[iIndex][bgIndex] = a;
-            matrix1->data.F64[bgIndex][iIndex] = a;
-            vector1->data.F64[iIndex] = ai2;
-            vector2->data.F64[iIndex] = bi2;
-            matrixX->data.F64[iIndex][normIndex] = bi1;
-            matrixX->data.F64[iIndex][bgIndex] = b;
-        }
-    }
-
-    double sumI1 = 0.0;                 // Sum of I_1 (for matrix 1, background-normalisation)
-    double sumI1I1 = 0.0;               // Sum of I_1^2 (for matrix 1, normalisation-normalisation)
-    double sum1 = 0.0;                  // Sum of 1 (for matrix 1, background-background)
-    double sumI2 = 0.0;                 // Sum of I_2 (for vector 1, background)
-    double sumI1I2 = 0.0;               // Sum of I_1.I_2 (for vector 1, normalisation)
+            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++) {
-            float i1 = image1->kernel[y][x];
-            float i2 = image2->kernel[y][x];
+            double i1 = image1->kernel[y][x];
+            double i2 = image2->kernel[y][x];
 
             double i1i1 = i1 * i1;
@@ -352,13 +491,25 @@
             double i1i2 = i1 * i2;
 
-#ifdef USE_WEIGHT
-            float wtVal = weight->kernel[y][x];
-            i1 *= wtVal;
-            i1i1 *= wtVal;
-            one *= wtVal;
-            i2 *= wtVal;
-            i1i2 *= wtVal;
-#endif
-
+            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;
@@ -368,80 +519,43 @@
         }
     }
-    matrix1->data.F64[bgIndex][normIndex] = sumI1;
-    matrix1->data.F64[normIndex][bgIndex] = sumI1;
-    matrix1->data.F64[normIndex][normIndex] = sumI1I1;
-    matrix1->data.F64[bgIndex][bgIndex] = sum1;
-    vector1->data.F64[bgIndex] = sumI2;
-    vector1->data.F64[normIndex] = sumI1I2;
+
+    *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;
+        }
+    }
+
 
     return true;
 }
 
-// Merge dual matrices and vectors into single matrix equation
-// Have: Aa = Ct.b + d
-// Have: Ca = Bb + e
-// Set: F = ( A -Ct ;  C -B )
-// Set: g = ( a ; b )
-// Set: h = ( d ; e )
-// So that we combine the above two equations: Fg = h
-static bool calculateEquationDual(psImage **outMatrix,
-                                  psVector **outVector,
-                                  const psImage *sumMatrix1,
-                                  const psImage *sumMatrix2,
-                                  const psImage *sumMatrixX,
-                                  const psVector *sumVector1,
-                                  const psVector *sumVector2
-                                  )
-{
-    psAssert(sumMatrix1 && sumMatrix2 && sumMatrixX, "Require input matrices");
-    psAssert(sumVector1 && sumVector2, "Require input vectors");
-    int num1 = sumVector1->n;   // Number of parameters in first set
-    int num2 = sumVector2->n;   // Number of parameters in second set
-    int num = num1 + num2;      // Number of parameters in new set
-
-    psAssert(sumMatrix1->type.type == PS_TYPE_F64 &&
-             sumMatrix2->type.type == PS_TYPE_F64 &&
-             sumMatrixX->type.type == PS_TYPE_F64 &&
-             sumVector1->type.type == PS_TYPE_F64 &&
-             sumVector2->type.type == PS_TYPE_F64,
-             "Require input type is F64");
-
-    psAssert(outMatrix, "Require output matrix");
-    psAssert(outVector, "Require output vector");
-    if (!*outMatrix) {
-        *outMatrix = psImageAlloc(num, num, PS_TYPE_F64);
-    }
-    if (!*outVector) {
-        *outVector = psVectorAlloc(num, PS_TYPE_F64);
-    }
-    psImage *matrix = *outMatrix;
-    psVector *vector = *outVector;
-
-    psAssert(sumMatrix1->numCols == num1 && sumMatrix1->numRows == num1, "Require size NxN");
-    psAssert(sumMatrix2->numCols == num2 && sumMatrix2->numRows == num2, "Require size MxM");
-    psAssert(sumMatrixX->numCols == num1 && sumMatrixX->numRows == num2, "Require size MxN");
-
-    memcpy(vector->data.F64, sumVector1->data.F64, num1 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
-    memcpy(&vector->data.F64[num1], sumVector2->data.F64, num2 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
-
-    for (int i = 0; i < num1; i++) {
-        memcpy(matrix->data.F64[i], sumMatrix1->data.F64[i], num1 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
-        for (int j = 0, k = num1; j < num2; j++, k++) {
-            matrix->data.F64[i][k] = - sumMatrixX->data.F64[j][i];
-        }
-    }
-    for (int i1 = 0, i2 = num1; i1 < num2; i1++, i2++) {
-        memcpy(matrix->data.F64[i2], sumMatrixX->data.F64[i1], num1 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
-        for (int j = 0, k = num1; j < num2; j++, k++) {
-            matrix->data.F64[i2][k] = - sumMatrix2->data.F64[i1][j];
-        }
-    }
-
-    return true;
-}
-
-
+#if 1
 // Add in penalty term to least-squares vector
-static bool calculatePenalty(psImage *matrix,                     // Matrix to which to add in penalty term
+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
@@ -456,9 +570,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) {
                 // Contribution to chi^2: a_i^2 P_i
-                matrix->data.F64[index][index] -= norm * penalties->data.F32[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
+                    //
+                }
             }
         }
@@ -467,4 +601,5 @@
     return true;
 }
+# endif
 
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -476,9 +611,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
@@ -495,5 +630,5 @@
 
 double p_pmSubtractionSolutionCoeff(const pmSubtractionKernels *kernels, const psImage *polyValues,
-                                           int index, bool wantDual)
+                                    int index, bool wantDual)
 {
 #if 0
@@ -548,10 +683,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);
@@ -566,12 +702,19 @@
     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);
@@ -603,44 +746,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 = calculateMatrixVector(stamp->matrix1, stamp->vector1, stamp->image2, stamp->image1,
-                                       stamp->weight, stamp->convolutions1, kernels, polyValues,
-                                       footprint);
+        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 = calculateMatrixVector(stamp->matrix1, stamp->vector1, stamp->image1, stamp->image2,
-                                       stamp->weight, stamp->convolutions2, kernels, polyValues,
-                                       footprint);
+        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 = calculateDualMatrixVector(stamp->matrix1, stamp->vector1, stamp->matrix2, stamp->vector2,
-                                           stamp->matrixX, stamp->image1, stamp->image2, stamp->weight,
-                                           stamp->convolutions1, stamp->convolutions2, kernels, polyValues,
-                                           footprint);
+        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:
@@ -651,5 +797,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;
@@ -659,15 +805,15 @@
     {
         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);
@@ -675,33 +821,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
@@ -712,5 +829,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);
@@ -727,4 +845,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");
@@ -732,4 +857,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);
@@ -738,5 +864,5 @@
             psFree(job);
         } else {
-            pmSubtractionCalculateEquationStamp(stamps, kernels, i);
+            pmSubtractionCalculateEquationStamp(stamps, kernels, i, mode);
         }
     }
@@ -748,4 +874,6 @@
 
     pmSubtractionVisualPlotLeastSquares(stamps);
+    pmSubtractionVisualShowKernels((pmSubtractionKernels  *)kernels);
+    pmSubtractionVisualShowBasis(stamps);
 
     psLogMsg("psModules.imcombine", PS_LOG_INFO, "Calculate equation: %f sec",
@@ -756,5 +884,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);
@@ -762,6 +913,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
@@ -771,32 +931,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);
     }
 
@@ -814,28 +952,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++;
@@ -845,12 +969,4 @@
         }
 
-#ifdef TESTING
-        for (int ix = 0; ix < sumVector->n; ix++) {
-            if (!isfinite(sumVector->data.F64[ix])) {
-                fprintf (stderr, "WARNING: NAN in vector1\n");
-            }
-        }
-#endif
-
 #if 0
         int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background
@@ -858,41 +974,80 @@
 #endif
 
-#ifdef TESTING
-        for (int ix = 0; ix < sumVector->n; ix++) {
-            if (!isfinite(sumVector->data.F64[ix])) {
-                fprintf (stderr, "WARNING: NAN in vector1\n");
-            }
-        }
+        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
         {
-            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);
+            // 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(PS_ERR_UNKNOWN, 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);
+
+            fprintf(stderr, "Norm: %lf\n", normValue);
+
+            // 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(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
@@ -900,30 +1055,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
@@ -931,9 +1075,9 @@
             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++;
@@ -941,174 +1085,136 @@
         }
 
-        int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background
-        calculatePenalty(sumMatrix1, sumVector1, kernels, sumMatrix1->data.F64[bgIndex][bgIndex]);
-        calculatePenalty(sumMatrix2, sumVector2, kernels, -sumMatrix1->data.F64[bgIndex][bgIndex]);
-
-        psImage *sumMatrix = NULL;      // Combined matrix
-        psVector *sumVector = NULL;     // Combined vector
-        calculateEquationDual(&sumMatrix, &sumVector, sumMatrix1, sumMatrix2,
-                              sumMatrixX, sumVector1, sumVector2);
-
 #ifdef TESTING
+        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
         {
-            psFits *fits = psFitsOpen("sumMatrix.fits", "w");
-            psFitsWriteImage(fits, NULL, sumMatrix, 0, NULL);
-            psFitsClose(fits);
-        }
-        {
-            psImage *image = psImageAlloc(1, numParams + numParams2, PS_TYPE_F64);
-            psFits *fits = psFitsOpen("sumVector.fits", "w");
-            for (int i = 0; i < numParams + numParams2; i++) {
-                image->data.F64[0][i] = sumVector->data.F64[i];
-            }
-            psFitsWriteImage(fits, NULL, image, 0, NULL);
-            psFree(image);
-            psFitsClose(fits);
-        }
-#endif
-
-        psVector *solution = NULL;                       // Solution to equation!
-        {
-            solution = psMatrixSolveSVD(solution, sumMatrix, sumVector);
-            if (!solution) {
-                psError(PS_ERR_UNKNOWN, false, "SVD solution of least-squares equation failed.\n");
+            // 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(PS_ERR_UNKNOWN, false, "Unable to determine median normalisation");
+                psFree(stats);
                 psFree(sumMatrix);
                 psFree(sumVector);
-                return NULL;
-            }
-
-            int numSpatial = PM_SUBTRACTION_POLYTERMS(kernels->spatialOrder); // Number of spatial variations
-            int numKernels = kernels->num; // Number of kernel basis functions
-
-            // Remove a kernel basis for image 1 from the equation
-#define MASK_BASIS_1(INDEX)                                             \
-            {                                                           \
-                for (int j = 0, index = INDEX; j < numSpatial; j++, index += numKernels) { \
-                    for (int k = 0; k < numParams2; k++) {              \
-                        sumMatrix1->data.F64[k][index] = 0.0;           \
-                        sumMatrix1->data.F64[index][k] = 0.0;           \
-                        sumMatrixX->data.F64[k][index] = 0.0;           \
-                    }                                                   \
-                    sumMatrix1->data.F64[bgIndex][index] = 0.0;         \
-                    sumMatrix1->data.F64[index][bgIndex] = 0.0;         \
-                    sumMatrix1->data.F64[normIndex][index] = 0.0;       \
-                    sumMatrix1->data.F64[index][normIndex] = 0.0;       \
-                    sumMatrix1->data.F64[index][index] = 1.0;           \
-                    sumVector1->data.F64[index] = 0.0;                  \
-                }                                                       \
-            }
-
-            // Remove a kernel basis for image 2 from the equation
-#define MASK_BASIS_2(INDEX)                                             \
-            {                                                           \
-                for (int j = 0, index = INDEX; j < numSpatial; j++, index += numKernels) { \
-                    for (int k = 0; k < numParams2; k++) {              \
-                        sumMatrix2->data.F64[k][index] = 0.0;           \
-                        sumMatrix2->data.F64[index][k] = 0.0;           \
-                        sumMatrixX->data.F64[index][k] = 0.0;           \
-                    }                                                   \
-                    sumMatrix2->data.F64[index][index] = 1.0;           \
-                    sumMatrixX->data.F64[index][normIndex] = 0.0;       \
-                    sumMatrixX->data.F64[index][bgIndex] = 0.0;         \
-                    sumVector2->data.F64[index] = 0.0;                  \
-                }                                                       \
-            }
-
-            #define TOL 1.0e-5
-            int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
-            double norm = fabs(solution->data.F64[normIndex]);  // Normalisation
-            double thresh = norm * TOL;                         // Threshold for low parameters
-            for (int i = 0; i < numKernels; i++) {
-                // Getting 0th order parameter value.  In the presence of spatial variation, the actual value
-                // of the parameter will vary over the image.  We are in effect getting the value in the
-                // centre of the image.  If we use different polynomial functions (e.g., Chebyshev), we may
-                // have to change this to properly determine the value of the parameter at the centre.
-                double param1 = solution->data.F64[i],
-                    param2 = solution->data.F64[numParams + i]; // Parameters of interest
-                bool mask1 = false, mask2 = false;              // Masked the parameter?
-                if (fabs(param1) < thresh) {
-                    psTrace("psModules.imcombine", 7, "Parameter %d: 1 below threshold\n", i);
-                    MASK_BASIS_1(i);
-                    mask1 = true;
-                }
-                if (fabs(param2) < thresh) {
-                    psTrace("psModules.imcombine", 7, "Parameter %d: 2 below threshold\n", i);
-                    MASK_BASIS_2(i);
-                    mask2 = true;
-                }
-
-                if (!mask1 && !mask2) {
-                    if (fabs(param1) < fabs(param2)) {
-                        psTrace("psModules.imcombine", 7, "Parameter %d: 1 < 2\n", i);
-                        MASK_BASIS_1(i);
-                    } else {
-                        psTrace("psModules.imcombine", 7, "Parameter %d: 2 < 1\n", i);
-                        MASK_BASIS_2(i);
-                    }
-                }
-            }
-        }
-
-        calculateEquationDual(&sumMatrix, &sumVector, sumMatrix1, sumMatrix2,
-                              sumMatrixX, sumVector1, sumVector2);
+                psFree(norms);
+                return false;
+            }
+
+            double normValue = stats->robustMedian;
+
+            psFree(stats);
+
+            fprintf(stderr, "Norm: %lf\n", normValue);
+
+            // 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
-        {
-            psFits *fits = psFitsOpen("sumMatrixFix.fits", "w");
-            psFitsWriteImage(fits, NULL, sumMatrix, 0, NULL);
-            psFitsClose(fits);
-        }
-        {
-            psImage *image = psImageAlloc(1, numParams + numParams2, PS_TYPE_F64);
-            psFits *fits = psFitsOpen("sumVectorFix.fits", "w");
-            for (int i = 0; i < numParams + numParams2; i++) {
-                image->data.F64[0][i] = sumVector->data.F64[i];
-            }
-            psFitsWriteImage(fits, NULL, image, 0, NULL);
-            psFree(image);
-            psFitsClose(fits);
-        }
-#endif
-
-        solution = psMatrixSolveSVD(solution, sumMatrix, sumVector);
-        if (!solution) {
-            psError(PS_ERR_UNKNOWN, false, "SVD solution of least-squares equation failed.\n");
-            psFree(sumMatrix);
-            psFree(sumVector);
-            return NULL;
-        }
-
-        psFree(sumMatrix1);
-        psFree(sumMatrix2);
-        psFree(sumMatrixX);
-        psFree(sumVector1);
-        psFree(sumVector2);
+        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);
 
-#ifdef TESTING
-        {
-            psImage *image = psImageAlloc(1, numParams + numParams2, PS_TYPE_F64);
-            psFits *fits = psFitsOpen("solnVector.fits", "w");
-            for (int i = 0; i < numParams + numParams2; i++) {
-                image->data.F64[0][i] = solution->data.F64[i];
-            }
-            psFitsWriteImage(fits, NULL, image, 0, NULL);
-            psFree(image);
-            psFitsClose(fits);
-        }
-#endif
+        psFree(norms);
 
         if (!kernels->solution1) {
-            kernels->solution1 = psVectorAlloc(numParams, PS_TYPE_F64);
+            kernels->solution1 = psVectorAlloc(numSolution1, PS_TYPE_F64);
+            psVectorInit (kernels->solution1, 0.0);
         }
         if (!kernels->solution2) {
-            kernels->solution2 = psVectorAlloc(numParams2, PS_TYPE_F64);
-        }
-
-        memcpy(kernels->solution1->data.F64, solution->data.F64, numParams * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
-        memcpy(kernels->solution2->data.F64, &solution->data.F64[numParams],
-               numParams2 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
+            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);
@@ -1131,10 +1237,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);
@@ -1151,4 +1302,43 @@
     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;
+            fprintf (stderr, "keeping %d (%d of %d)\n", index->data.S32[n], n, 9);
+        }
+        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
@@ -1210,13 +1400,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
@@ -1234,13 +1437,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);
         }
 
@@ -1257,10 +1471,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;
         }
@@ -1302,7 +1518,681 @@
 
     }
+
+    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(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, 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: trunk/psModules/src/imcombine/pmSubtractionEquation.h
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionEquation.h	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionEquation.h	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/pmSubtractionHermitian.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionHermitian.c	(revision 26893)
+++ trunk/psModules/src/imcombine/pmSubtractionHermitian.c	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/pmSubtractionHermitian.h
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionHermitian.h	(revision 26893)
+++ trunk/psModules/src/imcombine/pmSubtractionHermitian.h	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/pmSubtractionIO.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionIO.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionIO.c	(revision 26893)
@@ -80,10 +80,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);
         }
@@ -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);
@@ -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;
Index: trunk/psModules/src/imcombine/pmSubtractionKernels.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionKernels.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionKernels.c	(revision 26893)
@@ -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);
+        }
     }
 
@@ -96,5 +157,5 @@
             kernels->preCalc->data[index] = NULL;
             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);
         }
@@ -110,15 +171,127 @@
 }
 
+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
@@ -133,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);
 
@@ -141,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
@@ -147,60 +319,225 @@
         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(3); // 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
-                psKernel *kernel = preCalc->data[2] = psKernelAlloc(-size, size, -size, size);      // 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
-                        kernel->kernel[v][u] = value;
-                        moment += value * PS_SQR((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);
+            }
+        }
+    }
+
+    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));
-                    psBinaryOp(kernel->image, kernel->image, "*", psScalarAlloc(PS_SQR(sum), PS_TYPE_F32));
-                    kernel->kernel[0][0] -= 1.0;
-                    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];
                 }
-
-
-#if 0
-                double sum = 0.0;   // Sum of kernel component
-                for (int v = -size; v <= size; v++) {
-                    for (int u = -size; u <= size; u++) {
-                        sum += kernel->kernel[v][u];
-                    }
-                }
-                fprintf(stderr, "%d sum: %lf\n", index, sum);
-#endif
-
-                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;
@@ -212,5 +549,5 @@
 
 pmSubtractionKernels *pmSubtractionKernelsAlloc(int numBasisFunctions, pmSubtractionKernelsType type,
-                                                int size, int spatialOrder, float penalty,
+                                                int size, int spatialOrder, float penalty, psRegion bounds,
                                                 pmSubtractionMode mode)
 {
@@ -224,9 +561,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;
@@ -234,13 +575,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)
 {
@@ -251,5 +656,5 @@
 
     pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(0, PM_SUBTRACTION_KERNEL_POIS, size,
-                                                              spatialOrder, penalty, mode); // The kernels
+                                                              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",
@@ -266,8 +671,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;
@@ -278,5 +683,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);
@@ -299,5 +704,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,
@@ -370,5 +775,5 @@
 
 pmSubtractionKernels *pmSubtractionKernelsFRIES(int size, int spatialOrder, int inner, float penalty,
-                                                pmSubtractionMode mode)
+                                                psRegion bounds, pmSubtractionMode mode)
 {
     PS_ASSERT_INT_POSITIVE(size, NULL);
@@ -397,5 +802,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);
@@ -463,8 +868,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);
@@ -479,5 +884,5 @@
 
     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=");
@@ -495,5 +900,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);
@@ -526,5 +931,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,
@@ -566,15 +971,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;
@@ -594,15 +999,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(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++;
                                 }
@@ -612,25 +1017,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,
@@ -645,20 +1053,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);
@@ -696,5 +1110,5 @@
 
 pmSubtractionKernels *pmSubtractionKernelsFromDescription(const char *description, int bgOrder,
-                                                          pmSubtractionMode mode)
+                                                          psRegion bounds, pmSubtractionMode mode)
 {
     PS_ASSERT_STRING_NON_EMPTY(description, NULL);
@@ -715,36 +1129,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);
@@ -752,32 +1170,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;
     }
@@ -810,8 +1252,11 @@
     out->bgOrder = in->bgOrder;
     out->mode = in->mode;
-    out->numCols = in->numCols;
-    out->numRows = in->numRows;
+    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: trunk/psModules/src/imcombine/pmSubtractionKernels.h
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionKernels.h	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionKernels.h	(revision 26893)
@@ -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
@@ -64,4 +85,19 @@
         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); \
+        PS_ASSERT_VECTOR_SIZE((KERNELS)->widths, (KERNELS)->num, RETURNVALUE); \
+    } \
     if ((KERNELS)->uStop || (KERNELS)->vStop) { \
         PS_ASSERT_VECTOR_NON_NULL((KERNELS)->uStop, 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
     );
Index: trunk/psModules/src/imcombine/pmSubtractionMask.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionMask.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionMask.c	(revision 26893)
@@ -38,14 +38,15 @@
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
-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)
+{
+    PM_ASSERT_READOUT_NON_NULL(ro1, NULL);
+    PM_ASSERT_READOUT_IMAGE(ro1, NULL);
+    PM_ASSERT_READOUT_MASK(ro1, NULL);
+    PM_ASSERT_READOUT_NON_NULL(ro2, NULL);
+    PM_ASSERT_READOUT_IMAGE(ro2, NULL);
+    PM_ASSERT_READOUT_MASK(ro2, NULL);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(ro1->image, ro2->image, NULL);
     PS_ASSERT_INT_NONNEGATIVE(size, NULL);
     PS_ASSERT_INT_NONNEGATIVE(footprint, NULL);
@@ -55,28 +56,38 @@
     }
 
-    int numCols = mask1->numCols, numRows = mask1->numRows; // Size of the images
+    int numCols = ro1->image->numCols, numRows = ro1->image->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->image->data.F32, **imageData2 = ro2->image->data.F32;
+    psImageMaskType **maskData1 = ro1->mask->data.PS_TYPE_IMAGE_MASK_DATA,
+        **maskData2 = ro2->mask->data.PS_TYPE_IMAGE_MASK_DATA;
 
     // 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 ((maskData1[y][x] & maskVal) || !isfinite(imageData1[y][x])) {
                     numBad++;
                     continue;
                 }
-                if (data2 && data2[y][x] & maskVal) {
+                if ((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 +128,8 @@
     for (int y = 0; y < numRows; y++) {
         for (int x = 0; x < numCols; x++) {
-            if (data1[y][x] & maskVal) {
+            if (maskData1[y][x] & maskVal) {
                 maskData[y][x] |= PM_SUBTRACTION_MASK_BAD_1;
             }
-            if (data2 && data2[y][x] & maskVal) {
+            if (maskData2[y][x] & maskVal) {
                 maskData[y][x] |= PM_SUBTRACTION_MASK_BAD_2;
             }
Index: trunk/psModules/src/imcombine/pmSubtractionMask.h
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionMask.h	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionMask.h	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/pmSubtractionMatch.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionMatch.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionMatch.c	(revision 26893)
@@ -28,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
@@ -64,9 +70,11 @@
                                  const psImage *subMask, // Mask for subtraction, or NULL
                                  psImage *variance,  // Variance map
-                                 const psRegion *region, // Region of interest, or NULL
+                                 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
@@ -74,4 +82,11 @@
     )
 {
+    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");
 
@@ -79,5 +94,5 @@
 
     *stamps = pmSubtractionStampsFind(*stamps, image1, image2, subMask, region, thresh1, thresh2,
-                                      size, footprint, stampSpacing, sysError, mode);
+                                      size, footprint, stampSpacing, normFrac, sysError, skyError, mode);
     if (!*stamps) {
         psError(psErrorCodeLast(), false, "Unable to find stamps.");
@@ -88,5 +103,5 @@
 
     psTrace("psModules.imcombine", 3, "Extracting stamps...\n");
-    if (!pmSubtractionStampsExtract(*stamps, ro1->image, ro2 ? ro2->image : NULL, variance, size)) {
+    if (!pmSubtractionStampsExtract(*stamps, ro1->image, ro2->image, variance, size, *region)) {
         psError(PS_ERR_UNKNOWN, false, "Unable to extract stamps.");
         return false;
@@ -102,6 +117,9 @@
                                   const pmReadout *ro1, const pmReadout *ro2, // Input images
                                   int stride, // Size for convolution patches
+                                  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
@@ -149,12 +167,21 @@
     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
@@ -198,7 +225,29 @@
 }
 
+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 kernelError,
+                               psMetadata *analysis, int stride, float kernelError, float covarFrac,
                                psImageMaskType maskVal, psImageMaskType maskBad, psImageMaskType maskPoor,
                                float poorFrac, float badFrac)
@@ -270,5 +319,5 @@
     }
 
-    if (!subtractionMatchCheck(conv1, conv2, ro1, ro2, stride, NAN, kernelError,
+    if (!subtractionMatchCheck(conv1, conv2, ro1, ro2, stride, NAN, NAN, NAN, kernelError, covarFrac,
                                maskVal, maskBad, maskPoor, poorFrac, badFrac, mode)) {
         psFree(kernels);
@@ -277,5 +326,10 @@
     }
 
-    psImage *subMask = pmSubtractionMask(ro1->mask, ro2 ? ro2->mask : NULL, maskVal, size, 0,
+    pmSubtractionMaskInvalid(ro1, maskVal);
+    pmSubtractionMaskInvalid(ro2, maskVal);
+
+    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) {
@@ -306,5 +360,5 @@
 
         if (!pmSubtractionConvolve(conv1, conv2, ro1, ro2, subMask, stride, maskBad, maskPoor, poorFrac,
-                                   kernelError, region, kernel, true, useFFT)) {
+                                   kernelError, covarFrac, region, kernel, true, useFFT)) {
             psError(PS_ERR_UNKNOWN, false, "Unable to convolve image.");
             psFree(outAnalysis);
@@ -336,10 +390,10 @@
                         int inner, int ringsOrder, int binning, float penalty,
                         bool optimum, const psVector *optFWHMs, int optOrder, float optThreshold,
-                        int iter, float rej, float sysError, float kernelError, psImageMaskType maskVal,
-                        psImageMaskType maskBad, psImageMaskType maskPoor, float poorFrac,
-                        float badFrac, pmSubtractionMode subMode)
+                        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, kernelError,
-                               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;
     }
@@ -399,5 +453,5 @@
     // 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
@@ -412,6 +466,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.");
@@ -423,17 +481,21 @@
     // 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.");
@@ -441,7 +503,9 @@
                 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.");
@@ -449,8 +513,50 @@
                 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);
     }
+
+    // Just in case the iso-kernel region doesn't cover the entire 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);
+        }
+    }
+
 
     // Iterate over iso-kernel regions
@@ -459,33 +565,42 @@
             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, sysError, subMode);
+                                                        footprint, stampSpacing, normFrac,
+                                                        sysError, skyError, subMode);
             } else if (sources) {
                 stamps = pmSubtractionStampsSetFromSources(sources, ro1->image, subMask, region, size,
-                                                           footprint, stampSpacing, sysError, 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 (!subtractionGetStamps(&stamps, ro1, ro2, subMask, variance, NULL, stampThresh1, stampThresh2,
-                                      stampSpacing, sysError, 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(PS_ERR_UNKNOWN, 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, subMode);
+                kernels = pmSubtractionKernelsOptimumISIS(type, size, inner, spatialOrder,
+                                                          optFWHMs, optOrder, stamps, footprint,
+                                                          optThreshold, penalty, bounds, subMode);
                 if (!kernels) {
                     psErrorClear();
@@ -496,5 +611,6 @@
                 // Not an ISIS/GUNK kernel, or the optimum kernel search failed
                 kernels = pmSubtractionKernelsGenerate(type, size, spatialOrder, isisWidths, isisOrders,
-                                                       inner, binning, ringsOrder, penalty, subMode);
+                                                       inner, binning, ringsOrder, penalty, bounds, subMode);
+                // pmSubtractionVisualShowKernels(kernels);
             }
 
@@ -545,26 +661,48 @@
 
                 if (!subtractionGetStamps(&stamps, ro1, ro2, subMask, variance, region,
-                                          stampThresh1, stampThresh2, stampSpacing, sysError,
-                                          size, footprint, subMode)) {
-                    goto MATCH_ERROR;
-                }
-
-                psTrace("psModules.imcombine", 3, "Calculating equation...\n");
-                if (!pmSubtractionCalculateEquation(stamps, kernels)) {
+                                          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(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
                 memCheck("  calculate equation");
 
-                psTrace("psModules.imcombine", 3, "Solving equation...\n");
-
-                if (!pmSubtractionSolveEquation(kernels, stamps)) {
+                psTrace("psModules.imcombine", 3, "Solving equation for kernels...\n");
+                if (!pmSubtractionSolveEquation(kernels, stamps, PM_SUBTRACTION_EQUATION_KERNELS)) {
                     psError(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
                     goto MATCH_ERROR;
                 }
-
                 memCheck("  solve equation");
-
+# endif
                 psVector *deviations = pmSubtractionCalculateDeviations(stamps, kernels); // Stamp deviations
                 if (!deviations) {
@@ -587,10 +725,39 @@
             }
 
+            // 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)) {
+                // XXX step 1: calculate normalization
+                psTrace("psModules.imcombine", 3, "Calculating equation for normalization...\n");
+                if (!pmSubtractionCalculateEquation(stamps, kernels, SUBMODE)) {
                     psError(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
+                memCheck("  solve equation");
+# endif
                 psVector *deviations = pmSubtractionCalculateDeviations(stamps, kernels); // Stamp deviations
                 if (!deviations) {
@@ -615,5 +782,5 @@
             psTrace("psModules.imcombine", 2, "Convolving...\n");
             if (!pmSubtractionConvolve(conv1, conv2, ro1, ro2, subMask, stride, maskBad, maskPoor, poorFrac,
-                                       kernelError, region, kernels, true, useFFT)) {
+                                       kernelError, covarFrac, region, kernels, true, useFFT)) {
                 psError(PS_ERR_UNKNOWN, false, "Unable to convolve image.");
                 goto MATCH_ERROR;
@@ -897,15 +1064,32 @@
     assert(kernels);
 
-    psTrace("psModules.imcombine", 3, "Calculating %s equation...\n", description);
-    if (!pmSubtractionCalculateEquation(stamps, kernels)) {
+    psTrace("psModules.imcombine", 3, "Calculating %s normalization equation...\n", description);
+    if (!pmSubtractionCalculateEquation(stamps, kernels, SUBMODE)) {
         psError(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
         return false;
     }
 
-    psTrace("psModules.imcombine", 3, "Solving %s equation...\n", description);
-    if (!pmSubtractionSolveEquation(kernels, stamps)) {
+    psTrace("psModules.imcombine", 3, "Solving %s normalization equation...\n", description);
+    if (!pmSubtractionSolveEquation(kernels, stamps, SUBMODE)) {
         psError(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
+        return false;
+    }
+# endif
 
     psTrace("psModules.imcombine", 3, "Calculate %s deviations...\n", description);
@@ -927,10 +1111,35 @@
     if (numRejected > 0) {
         // Allow re-fit with reduced stamps set
-        psTrace("psModules.imcombine", 3, "Resolving %s equation...\n", description);
-        if (!pmSubtractionSolveEquation(kernels, stamps)) {
+        psTrace("psModules.imcombine", 3, "Calculating %s normalization equation...\n", description);
+        if (!pmSubtractionCalculateEquation(stamps, kernels, PM_SUBTRACTION_EQUATION_ALL)) {
             psError(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, 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(PS_ERR_UNKNOWN, 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) {
@@ -1019,2 +1228,38 @@
 }
 
+
+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: trunk/psModules/src/imcombine/pmSubtractionMatch.h
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionMatch.h	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionMatch.h	(revision 26893)
@@ -39,6 +39,9 @@
                         int iter,       ///< Rejection iterations
                         float rej,      ///< Rejection threshold
+                        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
@@ -57,4 +60,5 @@
                                int stride, ///< Size for convolution patches
                                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
@@ -94,3 +98,16 @@
     );
 
+
+/// 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: trunk/psModules/src/imcombine/pmSubtractionParams.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionParams.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionParams.c	(revision 26893)
@@ -204,5 +204,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 +233,5 @@
     psVectorInit(orders, maxOrder);
     pmSubtractionKernels *kernels = p_pmSubtractionKernelsRawISIS(size, spatialOrder, fwhms, orders,
-                                                                  penalty, mode); // Kernels
+                                                                  penalty, bounds, mode); // Kernels
     psFree(orders);
     psFree(kernels->description);
@@ -482,9 +483,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 +505,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: trunk/psModules/src/imcombine/pmSubtractionParams.h
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionParams.h	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionParams.h	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/pmSubtractionStamps.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionStamps.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionStamps.c	(revision 26893)
@@ -46,4 +46,5 @@
     psFree(list->y);
     psFree(list->flux);
+    psFree(list->window);
 }
 
@@ -57,11 +58,6 @@
     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, float sysErr)
+                                                    int footprint, float spacing, float normFrac,
+                                                    float sysErr, float skyErr)
 {
     pmSubtractionStampList *list = psAlloc(sizeof(pmSubtractionStampList)); // Stamp list to return
@@ -220,6 +225,10 @@
     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;
@@ -239,5 +248,7 @@
     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++) {
@@ -280,9 +291,6 @@
         }
 
-        outStamp->matrix1 = inStamp->matrix1 ? psImageCopy(NULL, inStamp->matrix1, PS_TYPE_F64) : NULL;
-        outStamp->matrix2 = inStamp->matrix2 ? psImageCopy(NULL, inStamp->matrix2, PS_TYPE_F64) : NULL;
-        outStamp->matrixX = inStamp->matrixX ? psImageCopy(NULL, inStamp->matrixX, PS_TYPE_F64) : NULL;
-        outStamp->vector1 = inStamp->vector1 ? psVectorCopy(NULL, inStamp->vector1, PS_TYPE_F64) : NULL;
-        outStamp->vector2 = inStamp->vector2 ? psVectorCopy(NULL, inStamp->vector2, PS_TYPE_F64) : 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;
@@ -310,9 +318,7 @@
     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;
@@ -323,6 +329,6 @@
                                                 const psImage *image2, const psImage *subMask,
                                                 const psRegion *region, float thresh1, float thresh2,
-                                                int size, int footprint, float spacing, float sysErr,
-                                                pmSubtractionMode mode)
+                                                int size, int footprint, float spacing, float normFrac,
+                                                float sysErr, float skyErr, pmSubtractionMode mode)
 {
     if (!image1 && !image2) {
@@ -380,5 +386,6 @@
 
     if (!stamps) {
-        stamps = pmSubtractionStampListAlloc(numCols, numRows, region, footprint, spacing, sysErr);
+        stamps = pmSubtractionStampListAlloc(numCols, numRows, region, footprint, spacing,
+                                             normFrac, sysErr, skyErr);
     }
 
@@ -402,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?
@@ -414,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;
@@ -421,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);
@@ -434,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 {
@@ -488,5 +508,6 @@
                                                const psImage *image, const psImage *subMask,
                                                const psRegion *region, int size, int footprint,
-                                               float spacing, float sysErr, pmSubtractionMode mode)
+                                               float spacing, float normFrac, float sysErr, float skyErr,
+                                               pmSubtractionMode mode)
 
 {
@@ -509,5 +530,5 @@
     pmSubtractionStampList *stamps = pmSubtractionStampListAlloc(subMask->numCols, subMask->numRows,
                                                                  region, footprint, spacing,
-                                                                 sysErr); // Stamp list
+                                                                 normFrac, sysErr, skyErr); // Stamp list
     int numStamps = stamps->num;        // Number of stamps
 
@@ -531,5 +552,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
@@ -539,4 +560,6 @@
             continue;
         }
+
+        // fprintf (stderr, "stamp: %5.1f %5.1f == %d %d\n", xStamp, yStamp, xPix, yPix);
 
         bool found = false;
@@ -607,6 +630,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(PS_ERR_UNKNOWN, 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);
@@ -625,5 +770,4 @@
     }
 
-    int numCols = image1->numCols, numRows = image1->numRows; // Size of images
     int size = kernelSize + stamps->footprint; // Size of postage stamps
 
@@ -634,14 +778,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(PS_ERR_UNKNOWN, false, "Stamp %d (%d,%d) is within the region border.\n", i, x, y);
             return false;
         }
@@ -668,11 +810,14 @@
             psImage *varSub = psImageSubset(variance, region); // Subimage with stamp
             psKernel *var = psKernelAllocFromImage(varSub, size, size); // Variance postage stamp
-            if (isfinite(stamps->sysErr) && stamps->sysErr > 0) {
+
+            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 / (var->kernel[y][x] + sysErr * PS_SQR(additional));
+                        weight->kernel[y][x] = 1.0 / (skyErr + var->kernel[y][x] + sysErr * PS_SQR(additional));
                     }
                 }
@@ -698,5 +843,6 @@
 pmSubtractionStampList *pmSubtractionStampsSetFromSources(const psArray *sources, const psImage *image,
                                                           const psImage *subMask, const psRegion *region,
-                                                          int size, int footprint, float spacing, float sysErr,
+                                                          int size, int footprint, float spacing,
+                                                          float normFrac, float sysErr, float skyErr,
                                                           pmSubtractionMode mode)
 {
@@ -722,4 +868,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++;
     }
@@ -728,5 +875,6 @@
 
     pmSubtractionStampList *stamps = pmSubtractionStampsSet(x, y, image, subMask, region, size,
-                                                            footprint, spacing, sysErr, mode); // Stamps
+                                                            footprint, spacing, normFrac,
+                                                            sysErr, skyErr, mode); // Stamps
     psFree(x);
     psFree(y);
@@ -742,6 +890,6 @@
 pmSubtractionStampList *pmSubtractionStampsSetFromFile(const char *filename, const psImage *image,
                                                        const psImage *subMask, const psRegion *region,
-                                                       int size, int footprint, float spacing, float sysErr,
-                                                       pmSubtractionMode mode)
+                                                       int size, int footprint, float spacing, float normFrac,
+                                                       float sysErr, float skyErr, pmSubtractionMode mode)
 {
     PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
@@ -760,5 +908,5 @@
 
     pmSubtractionStampList *stamps = pmSubtractionStampsSet(x, y, image, subMask, region, size, footprint,
-                                                            spacing, sysErr, mode);
+                                                            spacing, normFrac, sysErr, skyErr, mode);
     psFree(data);
 
@@ -766,2 +914,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: trunk/psModules/src/imcombine/pmSubtractionStamps.h
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionStamps.h	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionStamps.h	(revision 26893)
@@ -24,14 +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
-                                                    float sysErr  // Relative systematic error or NAN
+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
     );
 
@@ -73,7 +80,7 @@
     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;
@@ -83,29 +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
-                                                float sysErr,  ///< Relative systematic error in images
-                                                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
-                                               float sysErr,  ///< Systematic error in images
-                                               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
     );
 
@@ -119,5 +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
     );
@@ -132,6 +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
     );
 
@@ -141,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
     );
 
@@ -168,3 +192,6 @@
     );
 
+
+bool pmSubtractionStampsResetStatus (pmSubtractionStampList *stamps);
+
 #endif
Index: trunk/psModules/src/imcombine/pmSubtractionThreads.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionThreads.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionThreads.c	(revision 26893)
@@ -69,5 +69,5 @@
 
     {
-        psThreadTask *task = psThreadTaskAlloc("PSMODULES_SUBTRACTION_CALCULATE_EQUATION", 3);
+        psThreadTask *task = psThreadTaskAlloc("PSMODULES_SUBTRACTION_CALCULATE_EQUATION", 4);
         task->function = &pmSubtractionCalculateEquationThread;
         psThreadTaskAdd(task);
Index: trunk/psModules/src/imcombine/pmSubtractionVisual.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionVisual.c	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionVisual.c	(revision 26893)
@@ -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: trunk/psModules/src/imcombine/pmSubtractionVisual.h
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionVisual.h	(revision 26682)
+++ trunk/psModules/src/imcombine/pmSubtractionVisual.h	(revision 26893)
@@ -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: trunk/psModules/src/objects/pmDetections.c
===================================================================
--- trunk/psModules/src/objects/pmDetections.c	(revision 26682)
+++ trunk/psModules/src/objects/pmDetections.c	(revision 26893)
@@ -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: trunk/psModules/src/objects/pmDetections.h
===================================================================
--- trunk/psModules/src/objects/pmDetections.h	(revision 26682)
+++ trunk/psModules/src/objects/pmDetections.h	(revision 26893)
@@ -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: trunk/psModules/src/objects/pmFootprintCullPeaks.c
===================================================================
--- trunk/psModules/src/objects/pmFootprintCullPeaks.c	(revision 26682)
+++ trunk/psModules/src/objects/pmFootprintCullPeaks.c	(revision 26893)
@@ -29,11 +29,11 @@
   */
 
-# define IN_PEAK 1 
+# 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
+                                 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);
@@ -42,8 +42,8 @@
 
     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)
+        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;
@@ -55,5 +55,5 @@
     psImage *idImg = psImageAlloc(subImg->numCols, subImg->numRows, PS_TYPE_S32);
 
-    // We need a psArray of peaks brighter than the current peak.  
+    // We need a psArray of peaks brighter than the current peak.
     // We reject peaks which either:
     // 1) are below the local threshold
@@ -67,68 +67,73 @@
     // 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
+        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;
+        }
+
+        // XXX this is a bit expensive: psImageAlloc for every peak contained in this footprint
+        // perhaps this should alloc a single ID image above and pass it in to be set.
+
+        // XXX optionally use the faster pmFootprintsFind if the subimage size is large (eg, M31)
+
+        // XXX this is not quite there yet:
+        // pmFootprint *peakFootprint = NULL;
+        // int area = subImg->numCols * subImg->numRows;
+        // if (area > 30000) {
+
+        pmFootprint *peakFootprint = pmFootprintsFindAtPoint(subImg, threshold, brightPeaks, peak->y, peak->x);
+
+        // at this point brightPeaks only has the peaks brighter than the current
+
+        // XXX need to supply the image here
+        // we set the IDs to either 1 (in peak) or 0 (not in peak)
+        pmSetFootprintID (idImg, peakFootprint, IN_PEAK);
+        psFree(peakFootprint);
+
+        // 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;
 	}
 
-	// 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;
-	}
-
-	// XXX this is a bit expensive: psImageAlloc for every peak contained in this footprint
-	// perhaps this should alloc a single ID image above and pass it in to be set.
-
-	// XXX optionally use the faster pmFootprintsFind if the subimage size is large (eg, M31)
-
-	// XXX this is not quite there yet:
-	// pmFootprint *peakFootprint = NULL;
-	// int area = subImg->numCols * subImg->numRows;
-	// if (area > 30000) {
-
-	pmFootprint *peakFootprint = pmFootprintsFindAtPoint(subImg, threshold, brightPeaks, peak->y, peak->x);
-
-	// at this point brightPeaks only has the peaks brighter than the current
-
-	// XXX need to supply the image here
-	// we set the IDs to either 1 (in peak) or 0 (not in peak)
-	pmSetFootprintID (idImg, peakFootprint, IN_PEAK);
-	psFree(peakFootprint);
-
-	// 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 && (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) continue;
-
-	psArrayAdd (brightPeaks, 128, fp->peaks->data[i]);
+        psArrayAdd (brightPeaks, 128, fp->peaks->data[i]);
     }
 
@@ -151,9 +156,9 @@
   */
 psErrorCode pmFootprintCullPeaks_OLD(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
+                                 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);
@@ -162,8 +167,8 @@
 
     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)
+        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;
@@ -185,79 +190,79 @@
     //
     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);
-
-	// stdev ~ sqrt(flux) : for bright regions (f ~ 1e6, sqrt(f) = 1e3 -- unrealistically small deviations)
-	// add additional padding in quadrature:
-
-	const float stdev = sqrt(subWt->data.F32[y][x]);
-	const float flux = subImg->data.F32[y][x];
-	const float fStdev = stdev/flux;
-	const float stdev_pad = flux * hypot(fStdev, fPadding);
-	float threshold = flux - nsigma_delta*stdev_pad;
-
-	if (isnan(threshold) || threshold < min_threshold) {
-#if 1	  // min_threshold is assumed to be below the detection threshold,
-	  // so all the peaks are pmFootprint, and this isn't the brightest
-	    // XXX mark peak to be dropped
-	    (void)psArrayRemoveIndex(fp->peaks, i);
-	    i--;			// we moved everything down one
-	    continue;
+        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);
+
+        // stdev ~ sqrt(flux) : for bright regions (f ~ 1e6, sqrt(f) = 1e3 -- unrealistically small deviations)
+        // add additional padding in quadrature:
+
+        const float stdev = sqrt(subWt->data.F32[y][x]);
+        const float flux = subImg->data.F32[y][x];
+        const float fStdev = stdev/flux;
+        const float stdev_pad = flux * hypot(fStdev, fPadding);
+        float threshold = flux - nsigma_delta*stdev_pad;
+
+        if (isnan(threshold) || threshold < min_threshold) {
+#if 1     // min_threshold is assumed to be below the detection threshold,
+          // so all the peaks are pmFootprint, and this isn't the brightest
+            // XXX mark peak to be dropped
+            (void)psArrayRemoveIndex(fp->peaks, i);
+            i--;                        // we moved everything down one
+            continue;
 #else
 #error n.b. We will be running LOTS of checks at this threshold, so only find the footprint once
-	    threshold = min_threshold;
+            threshold = min_threshold;
 #endif
-	}
-
-	// 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;
-	}
-
-	// XXX this is a bit expensive: psImageAlloc for every peak contained in this footprint
-	// perhaps this should alloc a single ID image above and pass it in to be set.
-
-	const int peak_id = 1;		// the ID for the peak of interest
-	brightPeaks->n = i;		// only stop at a peak brighter than we are
-
-	// XXX optionally use the faster pmFootprintsFind if the subimage size is large (eg, M31)
-
-	pmFootprint *peakFootprint = pmFootprintsFindAtPoint(subImg, threshold, brightPeaks, peak->y, peak->x);
-	brightPeaks->n = 0;		// don't double free
-	psImage *idImg = pmSetFootprintID(NULL, peakFootprint, peak_id);
-	psFree(peakFootprint);
-
-	// Check if any of the previous (brighter) peaks are within the footprint of this peak
-	// If so, the current peak is bogus; drop it.
-	int j;
-	for (j = 0; j < i; j++) {
-	    const pmPeak *peak2 = fp->peaks->data[j];
-	    int x2 = peak2->x - subImg->col0;
-	    int y2 = peak2->y - subImg->row0;
-	    const int peak2_id = idImg->data.S32[y2][x2]; // the ID for some other peak
-
-	    if (peak2_id == peak_id) {	// There's a brighter peak within the footprint above
-		;			// threshold; so cull our initial peak
-		(void)psArrayRemoveIndex(fp->peaks, i);
-		i--;			// we moved everything down one
-		break;
-	    }
-	}
-	if (j == i) {
-	    j++;
-	}
-
-	psFree(idImg);
+        }
+
+        // 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;
+        }
+
+        // XXX this is a bit expensive: psImageAlloc for every peak contained in this footprint
+        // perhaps this should alloc a single ID image above and pass it in to be set.
+
+        const int peak_id = 1;          // the ID for the peak of interest
+        brightPeaks->n = i;             // only stop at a peak brighter than we are
+
+        // XXX optionally use the faster pmFootprintsFind if the subimage size is large (eg, M31)
+
+        pmFootprint *peakFootprint = pmFootprintsFindAtPoint(subImg, threshold, brightPeaks, peak->y, peak->x);
+        brightPeaks->n = 0;             // don't double free
+        psImage *idImg = pmSetFootprintID(NULL, peakFootprint, peak_id);
+        psFree(peakFootprint);
+
+        // Check if any of the previous (brighter) peaks are within the footprint of this peak
+        // If so, the current peak is bogus; drop it.
+        int j;
+        for (j = 0; j < i; j++) {
+            const pmPeak *peak2 = fp->peaks->data[j];
+            int x2 = peak2->x - subImg->col0;
+            int y2 = peak2->y - subImg->row0;
+            const int peak2_id = idImg->data.S32[y2][x2]; // the ID for some other peak
+
+            if (peak2_id == peak_id) {  // There's a brighter peak within the footprint above
+                ;                       // threshold; so cull our initial peak
+                (void)psArrayRemoveIndex(fp->peaks, i);
+                i--;                    // we moved everything down one
+                break;
+            }
+        }
+        if (j == i) {
+            j++;
+        }
+
+        psFree(idImg);
     }
 
     brightPeaks->n = 0; psFree(brightPeaks);
-    psFree((psImage *)subImg);
-    psFree((psImage *)subWt);
+    psFree(subImg);
+    psFree(subWt);
 
     return PS_ERR_NONE;
Index: trunk/psModules/src/objects/pmFootprintFindAtPoint.c
===================================================================
--- trunk/psModules/src/objects/pmFootprintFindAtPoint.c	(revision 26682)
+++ trunk/psModules/src/objects/pmFootprintFindAtPoint.c	(revision 26893)
@@ -29,5 +29,5 @@
  * so we set appropriate mask bits
  *
- * EAM : these function were confusingly using "startspan" and "spartspan"  
+ * EAM : these function were confusingly using "startspan" and "spartspan"
  * I've rationalized them all to 'startspan'
  */
@@ -36,18 +36,18 @@
 // 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
+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 
+// 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
+    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
 };
 //
@@ -55,17 +55,17 @@
 //
 typedef struct {
-    const pmSpan *span;			// save the pixel range
-    PM_SSPAN_DIR direction;		// How to continue searching
-    bool stop;				// should we stop searching?
+    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);
+    psFree(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?
+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));
@@ -75,16 +75,16 @@
     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;
-	    }
-	}
-    }
-    
+
+    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;
 }
@@ -94,22 +94,22 @@
 //
 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
+                          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;
-	}
+        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
-	}
+        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
+        }
     }
 
@@ -127,44 +127,44 @@
  */
 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?
+                         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?
     if (img->type.type == PS_TYPE_F32) {
-	F32 = true;
+        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
-    
+        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;
-    
+
     /********************************************************************************************************/
-    
+
     Startspan *sspan = NULL;
     for (int i = 0; i < startspans->n; i++) {
-	sspan = startspans->data[i];
-	if (sspan->direction != PM_SSPAN_DONE) {
-	    break;
-	}
-	if (sspan->stop) {
-	    break;
-	}
+        sspan = startspans->data[i];
+        if (sspan->direction != PM_SSPAN_DONE) {
+            break;
+        }
+        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;
+        return false;
+    }
+    if (sspan->stop) {                  // they don't want any more spans processed
+        return false;
     }
     /*
@@ -179,111 +179,111 @@
      * Go through image identifying objects
      */
-    int nx0, nx1 = -1;			// new values of x0, x1
+    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?
+    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;
-		}
-		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;
-		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;
+        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;
+                }
+                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;
+                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;
     }
     /*
@@ -309,22 +309,22 @@
  */
 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)
+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?
+   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
-   
+   psF32 *imgRowF32 = NULL;             // row pointer if F32
+   psS32 *imgRowS32 = NULL;             //  "   "   "  "  !F32
+
    const int row0 = img->row0;
    const int col0 = img->col0;
@@ -339,5 +339,5 @@
         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);
+                row + row0, col + col0, row0, row0 + numRows - 1, col0, col0 + numCols - 1);
        return NULL;
    }
@@ -347,5 +347,5 @@
        return pmFootprintAlloc(0, img);
    }
-   
+
    pmFootprint *fp = pmFootprintAlloc(1 + img->numRows/10, img);
 /*
@@ -364,6 +364,6 @@
    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;
+           pmPeak *peak = peaks->data[i];
+           mask->data.PS_TYPE_IMAGE_MASK_DATA[peak->y - mask->row0][peak->x - mask->col0] |= PM_SSPAN_STOP;
        }
    }
@@ -372,22 +372,22 @@
  */
    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!
+
+   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;
-	   }
+           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;
-	   }
+           pixVal = F32 ? imgRowF32[i] : imgRowS32[i];
+           if ((maskRow[i] & PM_SSPAN_DETECTED) || pixVal < threshold) {
+               break;
+           }
        }
        int i1 = i;
@@ -404,6 +404,6 @@
     */
    psFree(mask);
-   psFree(startspans);			// restores the image pixel
-
-   return fp;				// pmFootprint really
+   psFree(startspans);                  // restores the image pixel
+
+   return fp;                           // pmFootprint really
 }
Index: trunk/psModules/src/objects/pmObjects.c
===================================================================
--- trunk/psModules/src/objects/pmObjects.c	(revision 26682)
+++ 	(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: trunk/psModules/src/objects/pmObjects.h
===================================================================
--- trunk/psModules/src/objects/pmObjects.h	(revision 26682)
+++ 	(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: trunk/psModules/src/objects/pmPSF.c
===================================================================
--- trunk/psModules/src/objects/pmPSF.c	(revision 26682)
+++ trunk/psModules/src/objects/pmPSF.c	(revision 26893)
@@ -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: trunk/psModules/src/objects/pmPSF.h
===================================================================
--- trunk/psModules/src/objects/pmPSF.h	(revision 26682)
+++ trunk/psModules/src/objects/pmPSF.h	(revision 26893)
@@ -111,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: trunk/psModules/src/objects/pmPeaks.c
===================================================================
--- trunk/psModules/src/objects/pmPeaks.c	(revision 26682)
+++ trunk/psModules/src/objects/pmPeaks.c	(revision 26893)
@@ -416,5 +416,5 @@
 
         } else {
-            psError(PS_ERR_UNKNOWN, true, "peak specified valid column range.");
+            psLogMsg ("psModules.objects", 5, "peak specified outside valid column range.");
         }
     }
@@ -501,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.");
             }
 
@@ -545,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: trunk/psModules/src/objects/pmPhotObj.c
===================================================================
--- trunk/psModules/src/objects/pmPhotObj.c	(revision 26893)
+++ trunk/psModules/src/objects/pmPhotObj.c	(revision 26893)
@@ -0,0 +1,39 @@
+/** @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"
+
+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;
+}
+
Index: trunk/psModules/src/objects/pmPhotObj.h
===================================================================
--- trunk/psModules/src/objects/pmPhotObj.h	(revision 26893)
+++ trunk/psModules/src/objects/pmPhotObj.h	(revision 26893)
@@ -0,0 +1,41 @@
+/* @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 seq;                            ///< ID for output (generated on write OR set on read)
+  psArray *sources;
+} pmPhotObj;
+
+/// @}
+# endif /* PM_PHOT_OBJ_H */
Index: trunk/psModules/src/objects/pmSource.c
===================================================================
--- trunk/psModules/src/objects/pmSource.c	(revision 26682)
+++ trunk/psModules/src/objects/pmSource.c	(revision 26893)
@@ -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
  */
 
@@ -277,5 +275,5 @@
 // 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");
@@ -287,33 +285,7 @@
 
     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;
 
@@ -401,9 +373,6 @@
 	psfClump.nSigma = stats->sampleStdev;
 
-        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);
+	if (savedImage) {
+	    *savedImage = psMemIncrRefCounter(splane);
         }
         psFree (splane);
@@ -411,5 +380,12 @@
 
         // if we failed to find a valid peak, return the empty clump (failure signal)
-        if (!peaks || !peaks->n) {
+	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");
             if (nValid == 0) {
@@ -418,4 +394,5 @@
                 psLogMsg ("psphot", 3, "no significant peak\n");
             }
+	    psFree (peaks);
             return (emptyClump);
         }
@@ -524,10 +501,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;
@@ -542,13 +518,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;
Index: trunk/psModules/src/objects/pmSource.h
===================================================================
--- trunk/psModules/src/objects/pmSource.h	(revision 26682)
+++ trunk/psModules/src/objects/pmSource.h	(revision 26893)
@@ -176,13 +176,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
 );
 
@@ -200,5 +202,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
@@ -220,5 +223,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
 );
 
Index: trunk/psModules/src/objects/pmSourceIO.c
===================================================================
--- trunk/psModules/src/objects/pmSourceIO.c	(revision 26682)
+++ trunk/psModules/src/objects/pmSourceIO.c	(revision 26893)
@@ -40,4 +40,5 @@
 #include "pmPSF.h"
 #include "pmModel.h"
+#include "pmDetections.h"
 #include "pmSource.h"
 #include "pmModelClass.h"
@@ -344,10 +345,17 @@
 
     // 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
     }
 
@@ -1031,6 +1039,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;
 }
@@ -1124,9 +1135,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: trunk/psModules/src/objects/pmSourceIO_CMF_PS1_V2.c
===================================================================
--- trunk/psModules/src/objects/pmSourceIO_CMF_PS1_V2.c	(revision 26682)
+++ trunk/psModules/src/objects/pmSourceIO_CMF_PS1_V2.c	(revision 26893)
@@ -304,4 +304,5 @@
         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");
Index: trunk/psModules/src/objects/pmSourceIO_MatchedRefs.c
===================================================================
--- trunk/psModules/src/objects/pmSourceIO_MatchedRefs.c	(revision 26682)
+++ trunk/psModules/src/objects/pmSourceIO_MatchedRefs.c	(revision 26893)
@@ -163,4 +163,5 @@
 
     // try find the MATCHED_REFS extension.  if non-existent, note that we tried, and move on.
+    // 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);
Index: trunk/psModules/src/objects/pmSourceMoments.c
===================================================================
--- trunk/psModules/src/objects/pmSourceMoments.c	(revision 26682)
+++ trunk/psModules/src/objects/pmSourceMoments.c	(revision 26893)
@@ -54,5 +54,5 @@
 # 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);
@@ -114,5 +114,5 @@
         for (psS32 col = 0; col < source->pixels->numCols ; col++, vPix++, vWgt++) {
             if (vMsk) {
-                if (*vMsk) {
+                if (*vMsk & maskVal) {
                     vMsk++;
                     continue;
@@ -135,5 +135,5 @@
 	    // stars.
             if (PS_SQR(pDiff) < minSN2*wDiff) continue;
-            if (pDiff < 0) continue; // XXX : MWV says I should include < 0.0 valued points...
+            // 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
@@ -226,5 +226,5 @@
 	for (psS32 col = 0; col < source->pixels->numCols ; col++, vPix++, vWgt++) {
 	    if (vMsk) {
-		if (*vMsk) {
+		if (*vMsk & maskVal) {
 		    vMsk++;
 		    continue;
@@ -249,5 +249,5 @@
 	    // stars.
 	    if (PS_SQR(pDiff) < minSN2*wDiff) continue;
-	    if (pDiff < 0) continue;
+	    // if (pDiff < 0) continue;
 
 	    // Apply a Gaussian window function.  Be careful with the window function.  S/N
@@ -315,10 +315,10 @@
     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",
Index: trunk/psModules/src/objects/pmSourcePlotApResid.c
===================================================================
--- trunk/psModules/src/objects/pmSourcePlotApResid.c	(revision 26682)
+++ trunk/psModules/src/objects/pmSourcePlotApResid.c	(revision 26893)
@@ -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: trunk/psModules/src/objects/pmSourcePlotMoments.c
===================================================================
--- trunk/psModules/src/objects/pmSourcePlotMoments.c	(revision 26682)
+++ trunk/psModules/src/objects/pmSourcePlotMoments.c	(revision 26893)
@@ -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: trunk/psModules/src/objects/pmSourcePlotPSFModel.c
===================================================================
--- trunk/psModules/src/objects/pmSourcePlotPSFModel.c	(revision 26682)
+++ trunk/psModules/src/objects/pmSourcePlotPSFModel.c	(revision 26893)
@@ -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: trunk/psModules/src/psmodules.h
===================================================================
--- trunk/psModules/src/psmodules.h	(revision 26682)
+++ trunk/psModules/src/psmodules.h	(revision 26893)
@@ -79,4 +79,5 @@
 #include <pmRemnance.h>
 #include <pmPattern.h>
+#include <pmPatternIO.h>
 
 // the following headers are from psModule:astrom
@@ -97,4 +98,5 @@
 #include <pmSubtractionStamps.h>
 #include <pmSubtractionKernels.h>
+#include <pmSubtractionDeconvolve.h>
 #include <pmSubtractionAnalysis.h>
 #include <pmSubtractionMatch.h>
