Index: /trunk/ppImage/src/ppImage.h
===================================================================
--- /trunk/ppImage/src/ppImage.h	(revision 26894)
+++ /trunk/ppImage/src/ppImage.h	(revision 26895)
@@ -38,5 +38,6 @@
     bool doShutter;                     // Shutter correction
     bool doFlat;                        // Flat-field normalisation
-    bool doPattern;                     // Pattern noise subtraction
+    bool doPatternRow;                  // Row pattern correction
+    bool doPatternCell;                 // Cell pattern correction
     bool doFringe;                      // Fringe subtraction
     bool doPhotom;                      // Source identification and photometry
@@ -95,11 +96,13 @@
     float fringeKeep;                   // Fringe keep fraction
 
-    // Pattern noise subtraction
-    int patternOrder;                   // Polynomial order
-    int patternIter;                    // Clipping iterations
-    float patternRej;                   // Clipping threshold
-    float patternThresh;                // Ignore threshold
-    psStatsOptions patternMean;         // Statistic for mean
-    psStatsOptions patternStdev;        // Statistic for stdev
+    // Pattern correction
+    int patternRowOrder;                   // Polynomial order
+    int patternRowIter;                    // Clipping iterations
+    float patternRowRej;                   // Clipping threshold
+    float patternRowThresh;                // Ignore threshold
+    psStatsOptions patternRowMean;         // Statistic for mean
+    psStatsOptions patternRowStdev;        // Statistic for stdev
+    psStatsOptions patternCellBG;          // statistic for background
+    psStatsOptions patternCellMean;        // Statistic for mean
 
     int remnanceSize;                   // Size for remnance detection
Index: /trunk/ppImage/src/ppImageBurntoolMask.c
===================================================================
--- /trunk/ppImage/src/ppImageBurntoolMask.c	(revision 26894)
+++ /trunk/ppImage/src/ppImageBurntoolMask.c	(revision 26895)
@@ -3,5 +3,5 @@
 #endif
 
-/* #define PPIMAGE_BURNTOOL_DEBUG 1 */
+#define PPIMAGE_BURNTOOL_DEBUG 0
 
 #include "ppImage.h"
@@ -23,5 +23,5 @@
     return(false);
   }
-  long Nrows = psFitsTableSize(fits);  
+  long Nrows = psFitsTableSize(fits);
 
   long row = 0;
@@ -33,8 +33,8 @@
   psImage *image = mask->mask;
 
-  
+
   /* Set the maskValue from the recipes. */
   psImageMaskType maskValue = options->burntoolMask;
-#ifdef PPIMAGE_BURNTOOL_DEBUG
+#if PPIMAGE_BURNTOOL_DEBUG
   psLogMsg("ppImageBurntoolMask", 4, "Status: %ld %d\n",Nrows,maskValue);
 #endif
@@ -47,44 +47,44 @@
 
     if (psMetadataLookupS32(&status,rowMD,"cell") == burntool_cell) {
-      if (((options->burntoolTrails & 0x01)&&(psMetadataLookupS32(&status,rowMD,"nfit") == 0))||
-	  ((options->burntoolTrails & 0x02)&&(psMetadataLookupS32(&status,rowMD,"up") == 1))||
-	  ((options->burntoolTrails & 0x04)&&(psMetadataLookupS32(&status,rowMD,"up") == 0))) {
-	/*       If the fit fails, burntool reports zero here.  This
-		 signifies that it expected to see a trail (else why
-		 fit) but did not find it when it attempted to
-		 correct. */
-#ifdef PPIMAGE_BURNTOOL_DEBUG
-	psLogMsg ("ppImageBurntoolMask", 4, "Masking! %d (%d %d %d) %d %d",
-		  psMetadataLookupS32(&status,rowMD,"cell"),
-		  ((options->burntoolTrails & 0x0001)&&(psMetadataLookupS32(&status,rowMD,"nfit") == 0)),
-		  ((options->burntoolTrails & 0x02)&&(psMetadataLookupS32(&status,rowMD,"up") == 1)),
-		  ((options->burntoolTrails & 0x04)&&(psMetadataLookupS32(&status,rowMD,"up") == 0)),
-		  options->burntoolTrails,
-		  maskValue
-		  );
+      if (((options->burntoolTrails & 0x01)&&(psMetadataLookupS32(&status,rowMD,"func") == 4))||
+          (((options->burntoolTrails & 0x02)&&(psMetadataLookupS32(&status,rowMD,"up") == 1))||
+           ((options->burntoolTrails & 0x04)&&(psMetadataLookupS32(&status,rowMD,"up") == 0)))) {
+        /*       If the fit fails, burntool reports zero here.  This
+                 signifies that it expected to see a trail (else why
+                 fit) but did not find it when it attempted to
+                 correct. */
+#if PPIMAGE_BURNTOOL_DEBUG
+        psLogMsg ("ppImageBurntoolMask", 4, "Masking! %d (%d %d %d) %d %d",
+                  psMetadataLookupS32(&status,rowMD,"cell"),
+                  ((options->burntoolTrails & 0x0001)&&(psMetadataLookupS32(&status,rowMD,"nfit") == 0)),
+                  ((options->burntoolTrails & 0x02)&&(psMetadataLookupS32(&status,rowMD,"up") == 1)),
+                  ((options->burntoolTrails & 0x04)&&(psMetadataLookupS32(&status,rowMD,"up") == 0)),
+                  options->burntoolTrails,
+                  maskValue
+                  );
 #endif
-	for (int i = psMetadataLookupS32(&status,rowMD,"sxfit");
-	     i <= psMetadataLookupS32(&status,rowMD,"exfit");
-	     i++) {
+        for (int i = psMetadataLookupS32(&status,rowMD,"sxfit");
+             i <= psMetadataLookupS32(&status,rowMD,"exfit");
+             i++) {
 
-	  if (psMetadataLookupS32(&status,rowMD,"up") == 0) {
-	    for (int j = 0; j <= psMetadataLookupS32(&status,rowMD,"y1p"); j++) {
-/* #ifdef PPIMAGE_BURNTOOL_DEBUG */
-/* 	      psLogMsg("ppImageBurntoolMask", 4, "Noisy!: %d %d %d %d\n", */
-/* 		       i,j,image->data.PS_TYPE_IMAGE_MASK_DATA[j][i],maskValue); */
-/* #endif */
-	      image->data.PS_TYPE_IMAGE_MASK_DATA[j][i] |= maskValue;
-	    }
-	  }
-	  else {
-	    for (int j = psMetadataLookupS32(&status,rowMD,"y1m"); j < image->numRows ; j++) {
-/* #ifdef PPIMAGE_BURNTOOL_DEBUG */
-/* 	      psLogMsg("ppImageBurntoolMask", 4, "Noisy!: %d %d %d %d\n", */
-/* 		       i,j,image->data.PS_TYPE_IMAGE_MASK_DATA[j][i],maskValue); */
-/* #endif */
-	      image->data.PS_TYPE_IMAGE_MASK_DATA[j][i] |= maskValue;
-	    }
-	  }
-	}
+          if (psMetadataLookupS32(&status,rowMD,"up") == 0) {
+            for (int j = 0; j <= psMetadataLookupS32(&status,rowMD,"y1p"); j++) {
+#if PPIMAGE_BURNTOOL_DEBUG
+              psLogMsg("ppImageBurntoolMask", 4, "Noisy!: %d %d %d %d\n",
+                       i,j,image->data.PS_TYPE_IMAGE_MASK_DATA[j][i],maskValue);
+#endif
+              image->data.PS_TYPE_IMAGE_MASK_DATA[j][i] |= maskValue;
+            }
+          }
+          else {
+            for (int j = psMetadataLookupS32(&status,rowMD,"y1m"); j < image->numRows ; j++) {
+#if PPIMAGE_BURNTOOL_DEBUG
+              psLogMsg("ppImageBurntoolMask", 4, "Noisy!: %d %d %d %d\n",
+                       i,j,image->data.PS_TYPE_IMAGE_MASK_DATA[j][i],maskValue);
+#endif
+              image->data.PS_TYPE_IMAGE_MASK_DATA[j][i] |= maskValue;
+            }
+          }
+        }
 
       }
@@ -95,6 +95,2 @@
   return(status);
 }
-	    
-
-
-    
Index: /trunk/ppImage/src/ppImageDetrendFringe.c
===================================================================
--- /trunk/ppImage/src/ppImageDetrendFringe.c	(revision 26894)
+++ /trunk/ppImage/src/ppImageDetrendFringe.c	(revision 26895)
@@ -14,6 +14,6 @@
     psArray *references = psMemIncrRefCounter(psMetadataLookupPtr(NULL, fringe->analysis, "FRINGE.MEASUREMENTS"));
     if (!references) {
-	psError(PS_ERR_IO, false, "Unable to find fringe references.\n");
-	return false;
+        psError(PS_ERR_IO, false, "Unable to find fringe references.\n");
+        return false;
     }
 
@@ -40,7 +40,7 @@
     char *scienceFringes = NULL;
     if (isResidual) {
-	scienceFringes = psStringCopy ("FRINGE.RESIDUALS");
+        scienceFringes = psStringCopy ("FRINGE.RESIDUALS");
     } else {
-	scienceFringes = psStringCopy ("FRINGE.MEASUREMENTS");
+        scienceFringes = psStringCopy ("FRINGE.MEASUREMENTS");
     }
 
@@ -85,5 +85,5 @@
     psArray *fringes = psArrayAlloc(cells->n); // Fringes, to return
     int video_cell_zero = 0;
-    
+
     for (int i = 0; i < cells->n; i++) {
         fringes->data[i] = NULL;
@@ -91,29 +91,29 @@
         pmCell *cell = cells->data[i];  // Cell of interest
 
-	psTrace("psModules.detrend",7,"Readouts: Cell %d chip: %ld\n",i,cell->readouts->n);
-	// XXX for now, skip the video cells (cell->readouts->n > 1)
-	// CZW: This mess creates a fake set of fringe stats by stealing the previous one.
-	// We let the fitting code know that this is all lies by scaling the weights by a crazy amount.
-
-	if ( (cell->readouts->n > 1) ) {
-	  psTrace("psModules.detrend",7,"Should be skipping scichip: %d\n",i);
-	  psWarning ("Skipping Video Cell (%d) for ppImageDetrendFringe.c:getFringes", i);
-
-	  if (i == 0) {
-	    video_cell_zero = 1;
-	  }
-	  else {
-	    pmFringeStats *prevFringe = fringes->data[i-1];
-	    pmFringeStats *fringe     = pmFringeStatsAlloc(prevFringe->regions);
-	    for (int j = 0; j < fringe->regions->nRequested; j++) {
-	      fringe->f->data.F32[j] = prevFringe->f->data.F32[j];
-	      fringe->df->data.F32[j] = prevFringe->df->data.F32[j] / 1e6;
-	    }
-	    fringes->data[i] = fringe;
-	  }
-	    
-	  continue;
-	}
-	
+        psTrace("psModules.detrend",7,"Readouts: Cell %d chip: %ld\n",i,cell->readouts->n);
+        // XXX for now, skip the video cells (cell->readouts->n > 1)
+        // CZW: This mess creates a fake set of fringe stats by stealing the previous one.
+        // We let the fitting code know that this is all lies by scaling the weights by a crazy amount.
+
+        if ( (cell->readouts->n > 1) ) {
+          psTrace("psModules.detrend",7,"Should be skipping scichip: %d\n",i);
+          psWarning ("Skipping Video Cell (%d) for ppImageDetrendFringe.c:getFringes", i);
+
+          if (i == 0) {
+            video_cell_zero = 1;
+          }
+          else {
+            pmFringeStats *prevFringe = fringes->data[i-1];
+            pmFringeStats *fringe     = pmFringeStatsAlloc(prevFringe->regions);
+            for (int j = 0; j < fringe->regions->nRequested; j++) {
+              fringe->f->data.F32[j] = prevFringe->f->data.F32[j];
+              fringe->df->data.F32[j] = prevFringe->df->data.F32[j] / 1e6;
+            }
+            fringes->data[i] = fringe;
+          }
+
+          continue;
+        }
+
         fringes->data[i] = psMemIncrRefCounter(psMetadataLookupPtr(NULL, cell->analysis, source));
     }
@@ -123,10 +123,10 @@
       pmFringeStats *fringe     = pmFringeStatsAlloc(prevFringe->regions);
       for (int j = 0; j < fringe->regions->nRequested; j++) {
-	fringe->f->data.F32[j] = NAN;
-	fringe->df->data.F32[j] = 1.0;
+        fringe->f->data.F32[j] = NAN;
+        fringe->df->data.F32[j] = 1.0;
       }
       fringes->data[0] = fringe;
     }
-    
+
     return fringes;
 }
@@ -143,7 +143,7 @@
     psArray *science = NULL;
     if (isResidual) {
-	science = getFringes(scienceChip,  "FRINGE.RESIDUALS"); // Fringe residuals on science chip
+        science = getFringes(scienceChip,  "FRINGE.RESIDUALS"); // Fringe residuals on science chip
     } else {
-	science = getFringes(scienceChip, "FRINGE.MEASUREMENTS"); // Fringe measurements on science chip
+        science = getFringes(scienceChip, "FRINGE.MEASUREMENTS"); // Fringe measurements on science chip
     }
 
@@ -160,8 +160,8 @@
         for (int j = 0; j < references->n; j++) {     // Iterate over cells
             psArray *ref = references->data[j];       // Array of references for this cell
-	    
+
             refs->data[j] = psMemIncrRefCounter(ref->data[i]);
         }
-	referencesCat->data[i] = pmFringeStatsConcatenate(refs, NULL, NULL);
+        referencesCat->data[i] = pmFringeStatsConcatenate(refs, NULL, NULL);
         psFree(refs);
     }
@@ -174,7 +174,7 @@
 
     if (isResidual) {
-	psMetadataAdd(scienceChip->analysis, PS_LIST_TAIL, "FRINGE.RESIDUAL.SOLUTION", PS_DATA_UNKNOWN, "Fringe solution", solution);
+        psMetadataAdd(scienceChip->analysis, PS_LIST_TAIL, "FRINGE.RESIDUAL.SOLUTION", PS_DATA_UNKNOWN, "Fringe solution", solution);
     } else {
-	psMetadataAdd(scienceChip->analysis, PS_LIST_TAIL, "FRINGE.SOLUTION", PS_DATA_UNKNOWN, "Fringe solution", solution);
+        psMetadataAdd(scienceChip->analysis, PS_LIST_TAIL, "FRINGE.SOLUTION", PS_DATA_UNKNOWN, "Fringe solution", solution);
     }
 
@@ -184,22 +184,22 @@
     pmHDU *hdu = pmHDUFromCell(science);// HDU  of interest
     for (int i = 0; i < solution->nFringeFrames; i++) {
-	// write metadata header value
-	psString keyword = NULL;
-	if (isResidual) {
-	    psStringAppend (&keyword, "FRES_%02dV", i);
-	} else {
-	    psStringAppend (&keyword, "FRNG_%02dV", i);
-	}
-	psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude", solution->coeff->data.F32[i + 1]);
-	psFree (keyword);
-
-	keyword = NULL;
-	if (isResidual) {
-	    psStringAppend (&keyword, "FRES_%02dE", i);
-	} else {
-	    psStringAppend (&keyword, "FRNG_%02dE", i);
-	}
-	psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude error", solution->coeffErr->data.F32[i + 1]);
-	psFree (keyword);
+        // write metadata header value
+        psString keyword = NULL;
+        if (isResidual) {
+            psStringAppend (&keyword, "FRES_%02dV", i);
+        } else {
+            psStringAppend (&keyword, "FRNG_%02dV", i);
+        }
+        psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude", solution->coeff->data.F32[i + 1]);
+        psFree (keyword);
+
+        keyword = NULL;
+        if (isResidual) {
+            psStringAppend (&keyword, "FRES_%02dE", i);
+        } else {
+            psStringAppend (&keyword, "FRNG_%02dE", i);
+        }
+        psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude error", solution->coeffErr->data.F32[i + 1]);
+        psFree (keyword);
     }
 # endif
@@ -265,15 +265,15 @@
 
 # if (0)
-	// write metadata header value
-	// XXX this is measured per cell, but we only have headers per chip
-	psString keyword = NULL;
-	psStringAppend (&keyword, "FRNG_%02dV", i);
-	psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude", solution->coeff->data.F32[i + 1]);
-	psFree (keyword);
-
-	keyword = NULL;
-	psStringAppend (&keyword, "FRNG_%02dE", i);
-	psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude error", solution->coeffErr->data.F32[i + 1]);
-	psFree (keyword);
+        // write metadata header value
+        // XXX this is measured per cell, but we only have headers per chip
+        psString keyword = NULL;
+        psStringAppend (&keyword, "FRNG_%02dV", i);
+        psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude", solution->coeff->data.F32[i + 1]);
+        psFree (keyword);
+
+        keyword = NULL;
+        psStringAppend (&keyword, "FRNG_%02dE", i);
+        psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude error", solution->coeffErr->data.F32[i + 1]);
+        psFree (keyword);
 # endif
 
@@ -291,13 +291,13 @@
         }
 
-	// subtract fringe and update mask if fringe value is NAN
-	for (int iy = 0; iy < readout->image->numRows; iy++) {
-	    for (int ix = 0; ix < readout->image->numCols; ix++) {
-  	        readout->image->data.F32[iy][ix] -= sumFringe->data.F32[iy][ix];
-		if (!isfinite(sumFringe->data.F32[iy][ix]) && readout->mask) {
-		    readout->mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= maskVal;
-		} 
-	    }
-	}
+        // subtract fringe and update mask if fringe value is NAN
+        for (int iy = 0; iy < readout->image->numRows; iy++) {
+            for (int ix = 0; ix < readout->image->numCols; ix++) {
+                readout->image->data.F32[iy][ix] -= sumFringe->data.F32[iy][ix];
+                if (!isfinite(sumFringe->data.F32[iy][ix]) && readout->mask) {
+                    readout->mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= maskVal;
+                }
+            }
+        }
 
         // XXX: Make generic, so subregions may be subtracted as well
@@ -307,6 +307,6 @@
         // }
 
-	// measure residual fringe amplitude. results go to FRINGE.RESIDUALS
-	ppImageDetrendFringeMeasure (readout, fringes, true, options);
+        // measure residual fringe amplitude. results go to FRINGE.RESIDUALS
+        ppImageDetrendFringeMeasure (readout, fringes, true, options);
     }
     psFree(sumFringe);
@@ -320,5 +320,5 @@
     psFree(timeString);
 
-    return sumFringe;
+    return true;
 }
 
@@ -338,14 +338,14 @@
     pmChip *fringe = pmFPAfileThisChip(config->files, view, "PPIMAGE.FRINGE");
     if (!fringe) {
-	psError(PS_ERR_UNKNOWN, false, "missing fringe reference data.\n");
-	psFree (view);
-	return false;
+        psError(PS_ERR_UNKNOWN, false, "missing fringe reference data.\n");
+        psFree (view);
+        return false;
     }
 
     // Solve the fringe system
     if (!ppImageDetrendFringeSolve(chip, fringe, false, options)) {
-	psError(PS_ERR_UNKNOWN, false, "failed to solve the fringe system.\n");
-	psFree (view);
-	return false;
+        psError(PS_ERR_UNKNOWN, false, "failed to solve the fringe system.\n");
+        psFree (view);
+        return false;
     }
 
@@ -353,35 +353,35 @@
     view->cell = view->readout = -1;
     while ((cell = pmFPAviewNextCell(view, chip->parent, 1)) != NULL) {
-	if (!cell->process || !cell->file_exists) {
-	    continue;
-	}
-
-	// Apply the fringe correction
-	psTrace("ppImage", 3, "Applying fringe correction...\n");
-	pmCell *fringeCell = pmFPAfileThisCell(config->files, view, "PPIMAGE.FRINGE");
-	if (!fringeCell) {
-	    psError(PS_ERR_UNKNOWN, false, "missing fringe reference data.\n");
-	    psFree (view);
-	    return false;
-	}
-
-	// XXX for now, skip the video cells (cell->readouts->n > 1)
-	if (cell->readouts->n > 1) {
-	  psWarning ("Skipping Video Cell for ppImageDetrendFringeApply");
-	  continue;
-	}
-
-	if (!ppImageDetrendFringeGenerate(cell, fringeCell, options)) {
-	    psError(PS_ERR_UNKNOWN, false, "failed to apply fringe image.\n");
-	    psFree (view);
-	    return false;
-	}
+        if (!cell->process || !cell->file_exists) {
+            continue;
+        }
+
+        // Apply the fringe correction
+        psTrace("ppImage", 3, "Applying fringe correction...\n");
+        pmCell *fringeCell = pmFPAfileThisCell(config->files, view, "PPIMAGE.FRINGE");
+        if (!fringeCell) {
+            psError(PS_ERR_UNKNOWN, false, "missing fringe reference data.\n");
+            psFree (view);
+            return false;
+        }
+
+        // XXX for now, skip the video cells (cell->readouts->n > 1)
+        if (cell->readouts->n > 1) {
+          psWarning ("Skipping Video Cell for ppImageDetrendFringeApply");
+          continue;
+        }
+
+        if (!ppImageDetrendFringeGenerate(cell, fringeCell, options)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to apply fringe image.\n");
+            psFree (view);
+            return false;
+        }
     }
 
     // Solve the residual fringe system
     if (!ppImageDetrendFringeSolve(chip, fringe, true, options)) {
-	psError(PS_ERR_UNKNOWN, false, "failed to solve the residual fringe system.\n");
-	psFree (view);
-	return false;
+        psError(PS_ERR_UNKNOWN, false, "failed to solve the residual fringe system.\n");
+        psFree (view);
+        return false;
     }
 
Index: /trunk/ppImage/src/ppImageDetrendPattern.c
===================================================================
--- /trunk/ppImage/src/ppImageDetrendPattern.c	(revision 26894)
+++ /trunk/ppImage/src/ppImageDetrendPattern.c	(revision 26895)
@@ -5,9 +5,11 @@
 #include "ppImage.h"
 
-#define ESCAPE(MESSAGE) {                               \
-        psError(PS_ERR_UNKNOWN, false, MESSAGE);        \
+#define ESCAPE(STATUS,...) {                    \
+        psError(PS_ERR_UNKNOWN, STATUS, __VA_ARGS__);   \
         psFree(view);                                   \
         return false;                                   \
     }
+
+static bool doPatternForView (bool *doit, const pmConfig *config, const pmChip *chip, const pmFPAview *view, const char *recipename, const char *recipevalue);
 
 bool ppImageDetrendPatternApply(pmConfig *config, pmChip *chip, const pmFPAview *inputView,
@@ -16,65 +18,137 @@
     pmCell *cell = NULL;
 
-    assert (options->doPattern); // do not call if not needed
-    assert (inputView->chip != -1);
-    assert (inputView->cell == -1);
-    assert (inputView->readout == -1);
-    bool status;
-    pmFPAfile *input = psMetadataLookupPtr(&status, config->files, "PPIMAGE.INPUT");
+    assert(options->doPatternRow || options->doPatternCell); // do not call if not needed
+    assert(inputView->chip != -1);
+    assert(inputView->cell == -1);
+    assert(inputView->readout == -1);
 
-    pmFPAview *view = pmFPAviewAlloc(0); // View for local processing
-    *view = *inputView;
+    if (options->doPatternRow) {
+        bool status;
+        pmFPAfile *input = psMetadataLookupPtr(&status, config->files, "PPIMAGE.INPUT");
+        pmFPAview *view = pmFPAviewAlloc(0); // View for local processing
+        *view = *inputView;
+        while ((cell = pmFPAviewNextCell(view, input->fpa, 1)) != NULL) {
+            if (!cell->process || !cell->file_exists) {
+                continue;
+            }
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+              ESCAPE(false, "load failure for Cell");
+            }
 
-    while ((cell = pmFPAviewNextCell(view, input->fpa, 1)) != NULL) {
-        if (!cell->process || !cell->file_exists) {
-            continue;
-        }
-        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
-            ESCAPE("load failure for Cell");
-        }
-
-        if (!cell->data_exists) {
-            continue;
-        }
-
-        if (cell->readouts->n > 1) {
-            psWarning ("Skipping Video Cell for ppImageDetrendPatternApply");
-            continue;
-        }
-
-        psMetadataItem *doPattern = pmConfigRecipeValueByView(config, RECIPE_NAME, "PATTERN.SUBSET",
-                                                              chip->parent, view); // Do we do pattern sub?
-        if (!doPattern || doPattern->type != PS_DATA_BOOL) {
-            ESCAPE("Unable to determine whether pattern matching should be applied.");
-        }
-        if (!doPattern->data.B) {
-            continue;
-        }
-
-        psLogMsg("ppImage", PS_LOG_INFO, "Performing pattern subtraction for %d,%d\n", view->chip, view->cell);
-
-        // process each of the readouts
-        pmReadout *readout;         // Readout from cell
-        while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
-            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
-                ESCAPE("load failure for Readout");
-            }
-            if (!readout->data_exists) {
+            if (!cell->data_exists) {
                 continue;
             }
 
-            // perfore pattern correction
-            if (!pmPatternRow(readout, options->patternOrder, options->patternIter, options->patternRej,
-                              options->patternThresh, options->patternMean, options->patternStdev,
-                              options->maskValue, options->darkMask)) {
-                psFree(view);
-                return(false);
+            if (cell->readouts->n > 1) {
+                psWarning ("Skipping Video Cell for ppImageDetrendPatternApply");
+                continue;
+            }
+
+            bool doPattern = false;
+            if (!doPatternForView(&doPattern, config, chip, view, RECIPE_NAME, "PATTERN.ROW.SUBSET")) {
+                ESCAPE(false, "Unable to determine whether row pattern matching should be applied.");
+            }
+            if (!doPattern) continue;
+
+            const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
+            const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME");
+            psLogMsg("ppImage", PS_LOG_INFO, "Performing row pattern correction for %s, %s\n",
+                     chipName, cellName);
+
+            // process each of the readouts
+            pmReadout *readout;         // Readout from cell
+            while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    ESCAPE(false, "load failure for Readout");
+                }
+                if (!readout->data_exists) {
+                    continue;
+                }
+
+                // Perform pattern correction
+                if (!pmPatternRow(readout, options->patternRowOrder, options->patternRowIter,
+                                  options->patternRowRej, options->patternRowThresh, options->patternRowMean,
+                                  options->patternRowStdev, options->maskValue, options->darkMask)) {
+                    psFree(view);
+                    return(false);
+                }
             }
         }
+        psFree(view);
     }
 
-    psFree(view);
+    if (options->doPatternCell) {
+        int numCells = chip->cells->n;       // Number of cells
+        psVector *tweak = psVectorAlloc(numCells, PS_TYPE_U8); // Tweak cell?
+        pmFPAview *view = pmFPAviewAlloc(0); // View for local processing
+        *view = *inputView;
+        for (int i = 0; i < chip->cells->n; i++) {
+            view->cell = i;
+
+            bool doPattern = false;
+            if (!doPatternForView(&doPattern, config, chip, view, RECIPE_NAME, "PATTERN.CELL.SUBSET")) {
+                ESCAPE(false, "Unable to determine whether row pattern matching should be applied.");
+            }
+            if (doPattern) {
+                tweak->data.U8[i] = 0xFF;
+            }
+        }
+
+        // Tweak the cells
+        if (!pmPatternCell(chip, tweak, options->patternCellBG, options->patternCellMean,
+                           options->maskValue, options->darkMask)) {
+            psFree(tweak);
+            psFree(view);
+            return false;
+        }
+        psFree(tweak);
+        psFree(view);
+    }
+
     return(true);
 }
 
+static bool doPatternForView (bool *doit, const pmConfig *config, const pmChip *chip, const pmFPAview *view, const char *recipeName, const char *recipeValue) {
 
+    *doit = false;
+
+    psMetadataItem *doPattern = pmConfigRecipeValueByView(config, recipeName, recipeValue, chip->parent, view);
+    if (!doPattern) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine whether row pattern matching should be applied.");
+        return false;
+    }
+    if (doPattern->type == PS_DATA_BOOL) {
+        *doit = doPattern->data.B;
+        return true;
+    }
+    if (doPattern->type == PS_DATA_STRING) {
+        // expect a string of the form "000110001001" with at least view->cell entries
+        char *string = doPattern->data.str;
+        if (strlen(string) < view->cell) {
+            psError(PS_ERR_UNKNOWN, true, "error in PATTERN.ROW.SUBSET chip string (too few elements %d)", (int) strlen(string));
+            return false;
+        }
+        switch (string[view->cell]) {
+          case '0':
+          case 'f':
+          case 'F':
+          case 'n':
+          case 'N':
+            *doit = false;
+            return true;
+          case '1':
+          case 't':
+          case 'T':
+          case 'y':
+          case 'Y':
+            *doit = true;
+            return true;
+          default:
+            psError(PS_ERR_UNKNOWN, true, "error in PATTERN.ROW.SUBSET chip string %s (unknown value %c))", string, string[view->cell]);
+            return false;
+        }
+        psAbort("imposible to reach here");
+    }
+    psError(PS_ERR_UNKNOWN, true, "error in PATTERN.ROW.SUBSET : invalid data type");
+    return false;
+}
Index: /trunk/ppImage/src/ppImageLoop.c
===================================================================
--- /trunk/ppImage/src/ppImageLoop.c	(revision 26894)
+++ /trunk/ppImage/src/ppImageLoop.c	(revision 26895)
@@ -163,6 +163,6 @@
         }
 
-        // Apply the pattern noise correction
-        if (options->doPattern) {
+        // Apply the pattern correction
+        if (options->doPatternRow || options->doPatternCell) {
           if (!ppImageDetrendPatternApply(config,chip,view,options)) {
             ESCAPE("Unable to apply pattern corrections");
Index: /trunk/ppImage/src/ppImageOptions.c
===================================================================
--- /trunk/ppImage/src/ppImageOptions.c	(revision 26894)
+++ /trunk/ppImage/src/ppImageOptions.c	(revision 26895)
@@ -32,5 +32,6 @@
     options->doShutter       = false;   // Shutter correction
     options->doFlat          = false;   // Flat-field normalisation
-    options->doPattern       = false;   // Pattern noise subtraction
+    options->doPatternRow    = false;   // Row pattern correction
+    options->doPatternCell   = false;   // Cell pattern correction
     options->doFringe        = false;   // Fringe subtraction
     options->doPhotom        = false;   // Source identification and photometry
@@ -90,12 +91,14 @@
     options->fringeKeep      = 1.0;     // Fringe keep fraction
 
-    // Pattern noise values
-
-    options->patternOrder    = 0;       // Polynomial order
-    options->patternIter     = 0;       // Clipping iterations
-    options->patternRej      = NAN;     // Clipping rejection threshold
-    options->patternThresh   = NAN;     // Threshold for ignoring pixels
-    options->patternMean     = PS_STAT_NONE; // Statistic for mean
-    options->patternStdev    = PS_STAT_NONE; // Statistic for standard deviation
+    // Pattern correction values
+
+    options->patternRowOrder    = 0;       // Polynomial order
+    options->patternRowIter     = 0;       // Clipping iterations
+    options->patternRowRej      = NAN;     // Clipping rejection threshold
+    options->patternRowThresh   = NAN;     // Threshold for ignoring pixels
+    options->patternRowMean     = PS_STAT_NONE; // Statistic for mean
+    options->patternRowStdev    = PS_STAT_NONE; // Statistic for standard deviation
+    options->patternCellBG      = PS_STAT_NONE; // Statistic for background
+    options->patternCellMean    = PS_STAT_NONE; // Statistic for mean
 
     // Remnance values
@@ -238,5 +241,6 @@
     options->doFringe = psMetadataLookupBool(NULL, recipe, "FRINGE");
     options->doShutter = psMetadataLookupBool(NULL, recipe, "SHUTTER");
-    options->doPattern = psMetadataLookupBool(NULL, recipe, "PATTERN");
+    options->doPatternRow = psMetadataLookupBool(NULL, recipe, "PATTERN.ROW");
+    options->doPatternCell = psMetadataLookupBool(NULL, recipe, "PATTERN.CELL");
 
     options->doStats = false;
@@ -250,14 +254,14 @@
     options->burntoolTrails = psMetadataLookupS32(&status, recipe, "BURNTOOL.TRAILS");
     psTrace("psModules.detrend", 7, "burntoolTrails: %d BURNTOOL.TRAILS: %d Status: %d\n",
-	    options->burntoolTrails,psMetadataLookupS32(&status,recipe,"BURNTOOL.TRAILS"),status);
+            options->burntoolTrails,psMetadataLookupS32(&status,recipe,"BURNTOOL.TRAILS"),status);
     if (!status) {
       psWarning("BURNTOOL.TRAILS not found in recipe: setting to default value.\n");
     }
-    
+
     // binned image options
     options->xBin1 = psMetadataLookupS32(&status, recipe, "BIN1.XBIN");
     if (!status) {
         psWarning("BIN1.XBIN not found in recipe: setting to default value.\n");
-	options->xBin1 = 4;
+        options->xBin1 = 4;
     }
     options->yBin1 = psMetadataLookupS32(&status, recipe, "BIN1.YBIN");
@@ -320,11 +324,13 @@
     options->fringeKeep = psMetadataLookupF32(NULL, recipe, "FRINGE.KEEP");
 
-    // Pattern noise
-    options->patternOrder = psMetadataLookupS32(NULL, recipe, "PATTERN.ORDER");
-    options->patternIter = psMetadataLookupS32(NULL, recipe, "PATTERN.ITER");
-    options->patternRej = psMetadataLookupF32(NULL, recipe, "PATTERN.REJ");
-    options->patternThresh = psMetadataLookupF32(NULL, recipe, "PATTERN.THRESH");
-    options->patternMean = psStatsOptionFromString(psMetadataLookupStr(NULL, recipe, "PATTERN.MEAN"));
-    options->patternStdev = psStatsOptionFromString(psMetadataLookupStr(NULL, recipe, "PATTERN.STDEV"));
+    // Pattern correction
+    options->patternRowOrder = psMetadataLookupS32(NULL, recipe, "PATTERN.ROW.ORDER");
+    options->patternRowIter = psMetadataLookupS32(NULL, recipe, "PATTERN.ROW.ITER");
+    options->patternRowRej = psMetadataLookupF32(NULL, recipe, "PATTERN.ROW.REJ");
+    options->patternRowThresh = psMetadataLookupF32(NULL, recipe, "PATTERN.ROW.THRESH");
+    options->patternRowMean = psStatsOptionFromString(psMetadataLookupStr(NULL, recipe, "PATTERN.ROW.MEAN"));
+    options->patternRowStdev = psStatsOptionFromString(psMetadataLookupStr(NULL, recipe, "PATTERN.ROW.STDEV"));
+    options->patternCellBG = psStatsOptionFromString(psMetadataLookupStr(NULL, recipe, "PATTERN.CELL.BG"));
+    options->patternCellMean = psStatsOptionFromString(psMetadataLookupStr(NULL, recipe, "PATTERN.CELL.MEAN"));
 
     // Remnance options
Index: /trunk/ppImage/src/ppImageParseCamera.c
===================================================================
--- /trunk/ppImage/src/ppImageParseCamera.c	(revision 26894)
+++ /trunk/ppImage/src/ppImageParseCamera.c	(revision 26895)
@@ -79,4 +79,14 @@
         int nScanRows = psMetadataLookupS32(&status, recipe, "SCAN.ROWS");
         pmDetrendSetThreadTasks(nScanRows);
+    }
+
+    if (options->doPatternRow || options->doPatternCell) {
+        pmFPAfile *outPattern = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.PATTERN");
+        if (!outPattern) {
+            psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.PATTERN"));
+            psFree(options);
+            return NULL;
+        }
+        outPattern->save = true;
     }
 
@@ -255,4 +265,7 @@
         pmFPAfile *psphotInput = pmFPAfileDefineFromFile (config, chipImage, 1, 1, "PSPHOT.INPUT");
         PS_ASSERT (psphotInput, false);
+
+        // specify the number of psphot input images
+        psMetadataAddS32 (config->arguments, PS_LIST_TAIL, "PSPHOT.INPUT.NUM", PS_META_REPLACE, "number of inputs", 1);
 
         // define associated psphot input/output files
Index: /trunk/ppImage/src/ppImageReplaceBackground.c
===================================================================
--- /trunk/ppImage/src/ppImageReplaceBackground.c	(revision 26894)
+++ /trunk/ppImage/src/ppImageReplaceBackground.c	(revision 26895)
@@ -80,5 +80,5 @@
     // the background model has not been defined, or at least not generated
     if (!modelFile || !modelRO) {
-        if (!psphotModelBackground(config, &roView, "PPIMAGE.CHIP")) {
+        if (!psphotModelBackgroundReadoutFileIndex(config, &roView, "PPIMAGE.CHIP", 0)) {
             psError(PS_ERR_UNKNOWN, false, "Unable to model background");
             return false;
