Index: trunk/psModules/src/camera/pmFPABin.c
===================================================================
--- trunk/psModules/src/camera/pmFPABin.c	(revision 42356)
+++ trunk/psModules/src/camera/pmFPABin.c	(revision 42379)
@@ -31,14 +31,14 @@
     psImage *outImage;                  // Output image
     if (out->image && out->image->numCols >= numColsOut && out->image->numRows >= numRowsOut) {
-        outImage = out->image;
+	outImage = out->image;
     } else {
-        outImage = out->image = psImageRecycle(out->image,  numColsOut, numRowsOut, PS_TYPE_F32);
+	outImage = out->image = psImageRecycle(out->image,  numColsOut, numRowsOut, PS_TYPE_F32);
     }
 
     psImage *outMask;                   // Output mask
     if (out->mask && out->mask->numCols >= numColsOut && out->mask->numRows >= numRowsOut) {
-        outMask = out->mask;
+	outMask = out->mask;
     } else {
-        outMask = out->mask = psImageRecycle(out->mask,  numColsOut, numRowsOut, PS_TYPE_IMAGE_MASK);
+	outMask = out->mask = psImageRecycle(out->mask,  numColsOut, numRowsOut, PS_TYPE_IMAGE_MASK);
     }
 
@@ -50,68 +50,74 @@
     int yStart = psImageBinningGetFineY(binning, 0); // Starting input y for binning
     for (int yOut = 0; yOut < numRowsOut; yOut++) {
-        int yStop = psImageBinningGetFineY(binning, yOut + 1); // Stopping input y for binning
-        yStop = PS_MIN(yStop, yLast);
-        int xStart = psImageBinningGetFineX(binning, 0); // Starting input x for binning
-        for (int xOut = 0; xOut < numColsOut; xOut++) {
-            int xStop = psImageBinningGetFineX(binning, xOut + 1); // Stopping input x for binning
-            xStop = PS_MIN(xStop, xLast);
+	int yStop = psImageBinningGetFineY(binning, yOut + 1); // Stopping input y for binning
+	yStop = PS_MIN(yStop, yLast);
+	int xStart = psImageBinningGetFineX(binning, 0); // Starting input x for binning
+	for (int xOut = 0; xOut < numColsOut; xOut++) {
+	    int xStop = psImageBinningGetFineX(binning, xOut + 1); // Stopping input x for binning
+	    xStop = PS_MIN(xStop, xLast);
 
-            float sum = 0.0;            // Sum of pixels
-            int numPix = 0;             // Number of pixels
+	    float sum = 0.0;            // Sum of pixels
+	    int numPix = 0;             // Number of pixels
 
 	    for (int j = 0; j < Nbits; j++) { // Reset bit counter
-	      bitcounter[j] = 0;
+		bitcounter[j] = 0;
 	    }
 	    pxlcount = 0;
 	    
-            for (int y = yStart; y < yStop; y++) {
-                for (int x = xStart; x < xStop; x++) {
-		  if (inMask && (inMask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] != 0)) {
-		      for (int j = 0; j < Nbits; j++) {
-			psImageMaskType M = (psImageMaskType) pow(2,j);
-			if (inMask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & M) {
-			  bitcounter[j]++;
+	    for (int y = yStart; y < yStop; y++) {
+		for (int x = xStart; x < xStop; x++) {
+		    if (false && inMask && (inMask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] != 0)) {
+			for (int j = 0; j < Nbits; j++) {
+			    psImageMaskType M = (psImageMaskType) pow(2,j);
+			    if (inMask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & M) {
+				bitcounter[j]++;
+			    }
 			}
-		      }
 		    }
 		  
-                    if (inMask && (inMask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskVal)) {
-                        continue;
-                    }
-                    if (!isfinite(inImage->data.F32[y][x])) {
-                        continue;
-                    }
-                    sum += inImage->data.F32[y][x];
-                    numPix++;
+		    if (inMask && (inMask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskVal)) {
+			continue;
+		    }
+		    if (!isfinite(inImage->data.F32[y][x])) {
+			continue;
+		    }
+		    sum += inImage->data.F32[y][x];
+		    numPix++;
 
 
-                }
-            }
-	    
+		}
+	    }
 	    
 	    // Values to set
-            float imageValue;
+	    float imageValue;
 	    psImageMaskType maskValue;
-            if (numPix > 0) {
-                imageValue = sum / numPix;
-                maskValue = 0;
-            } else {
-                imageValue = NAN;
-                maskValue = maskVal;
-            }
-            outImage->data.F32[yOut][xOut] = imageValue;
-	    if (0) {
-	      outMask->data.PS_TYPE_IMAGE_MASK_DATA[yOut][xOut] = maskValue;
+	    if (numPix > 0) {
+		imageValue = sum / numPix;
+		maskValue = 0;
 	    } else {
-	      outMask->data.PS_TYPE_IMAGE_MASK_DATA[yOut][xOut] = 0;
+		imageValue = NAN;
+		maskValue = maskVal;
 	    }
-	    for (int j = 0; j < Nbits; j++) {
-	      if (bitcounter[j] > 0.5 * pxlcount) {
-		outMask->data.PS_TYPE_IMAGE_MASK_DATA[yOut][xOut] |= (int) pow(2,j);
-	      }
+	    outImage->data.F32[yOut][xOut] = imageValue;
+	    if (true) {
+		outMask->data.PS_TYPE_IMAGE_MASK_DATA[yOut][xOut] = maskValue;
+	    } else {
+		outMask->data.PS_TYPE_IMAGE_MASK_DATA[yOut][xOut] = 0;
 	    }
-            xStart = xStop;
-        }
-        yStart = yStop;
+	    // this loop is pointless if pxlcount == 0 (all masked)
+	    if (false) {
+		if (pxlcount) {
+		    for (int j = 0; j < Nbits; j++) {
+			if (bitcounter[j] > 0.5 * pxlcount) {
+			    outMask->data.PS_TYPE_IMAGE_MASK_DATA[yOut][xOut] |= (1 << j);
+			}
+		    }
+		} else {
+		    outMask->data.PS_TYPE_IMAGE_MASK_DATA[yOut][xOut] = maskValue;
+		}
+	    }
+	    xStart = xStop;
+	}
+	yStart = yStop;
     }
 
@@ -120,10 +126,10 @@
     out->data_exists = true;
     if (out->parent) {
-        pmCell *outCell = out->parent;  // Output cell
-        outCell->data_exists = outCell->parent->data_exists = true;
+	pmCell *outCell = out->parent;  // Output cell
+	outCell->data_exists = outCell->parent->data_exists = true;
 
-        // We would copy the concepts from the input cell, except that is done by pmFPACopy,
-        // pmChipCopyStructure, etc.  This function just does the mechanics of binning.
-        // We don't even update the CELL.XBIN, CELL.YBIN because that would apply the correction twice.
+	// We would copy the concepts from the input cell, except that is done by pmFPACopy,
+	// pmChipCopyStructure, etc.  This function just does the mechanics of binning.
+	// We don't even update the CELL.XBIN, CELL.YBIN because that would apply the correction twice.
     }
 
Index: trunk/psModules/src/camera/pmFPAfile.c
===================================================================
--- trunk/psModules/src/camera/pmFPAfile.c	(revision 42356)
+++ trunk/psModules/src/camera/pmFPAfile.c	(revision 42379)
@@ -557,4 +557,7 @@
     if (!strcasecmp(type, "PATTERN.ROW.AMP"))     {
         return PM_FPA_FILE_PATTERN_ROW_AMP;
+    }
+    if (!strcasecmp(type, "PATTERN.DEAD.CELLS"))     {
+        return PM_FPA_FILE_PATTERN_DEAD_CELLS;
     }
     if (!strcasecmp(type, "SUBKERNEL"))     {
@@ -614,4 +617,6 @@
       case PM_FPA_FILE_PATTERN_ROW_AMP:
         return ("PATTERN.ROW.AMP");
+      case PM_FPA_FILE_PATTERN_DEAD_CELLS:
+        return ("PATTERN.DEAD.CELLS");
       case PM_FPA_FILE_SUBKERNEL:
         return ("SUBKERNEL");
Index: trunk/psModules/src/camera/pmFPAfile.h
===================================================================
--- trunk/psModules/src/camera/pmFPAfile.h	(revision 42356)
+++ trunk/psModules/src/camera/pmFPAfile.h	(revision 42379)
@@ -53,4 +53,5 @@
     PM_FPA_FILE_PATTERN,
     PM_FPA_FILE_PATTERN_ROW_AMP,
+    PM_FPA_FILE_PATTERN_DEAD_CELLS,
     PM_FPA_FILE_LINEARITY,
     PM_FPA_FILE_NEWNONLIN,
Index: trunk/psModules/src/camera/pmFPAfileFitsIO.c
===================================================================
--- trunk/psModules/src/camera/pmFPAfileFitsIO.c	(revision 42356)
+++ trunk/psModules/src/camera/pmFPAfileFitsIO.c	(revision 42379)
@@ -153,4 +153,5 @@
       case PM_FPA_FILE_KH_CORRECT:
       case PM_FPA_FILE_PATTERN_ROW_AMP:
+      case PM_FPA_FILE_PATTERN_DEAD_CELLS:
 	{
           pmHDU *hdu = pmFPAviewThisHDU(view, fpa);
Index: trunk/psModules/src/camera/pmFPAfileIO.c
===================================================================
--- trunk/psModules/src/camera/pmFPAfileIO.c	(revision 42356)
+++ trunk/psModules/src/camera/pmFPAfileIO.c	(revision 42379)
@@ -256,4 +256,7 @@
         status = pmPatternRowAmpRead(view, file, config);
         break;
+      case PM_FPA_FILE_PATTERN_DEAD_CELLS:
+        status = pmPatternDeadCellsRead(view, file, config);
+        break;
       case PM_FPA_FILE_SX:
       case PM_FPA_FILE_RAW:
@@ -368,4 +371,5 @@
       case PM_FPA_FILE_KH_CORRECT:
       case PM_FPA_FILE_PATTERN_ROW_AMP:
+      case PM_FPA_FILE_PATTERN_DEAD_CELLS:
       case PM_FPA_FILE_JPEG:
       case PM_FPA_FILE_KAPA:
@@ -460,4 +464,8 @@
       return true;
     }
+    if (file->type == PM_FPA_FILE_PATTERN_DEAD_CELLS) {
+      psTrace("psModules.camera", 6, "skip write for %s, no write function defined", file->name);
+      return true;
+    }
 
     // open the file if not yet opened
@@ -560,4 +568,8 @@
       case PM_FPA_FILE_PATTERN_ROW_AMP:
         psError(PS_ERR_IO, true, "cannot write type PATTERN.ROW.AMP (%s)", file->name);
+        break;
+
+      case PM_FPA_FILE_PATTERN_DEAD_CELLS:
+        psError(PS_ERR_IO, true, "cannot write type PATTERN.DEAD.CELLS (%s)", file->name);
         break;
 
@@ -638,4 +650,5 @@
       case PM_FPA_FILE_KH_CORRECT:
       case PM_FPA_FILE_PATTERN_ROW_AMP:
+      case PM_FPA_FILE_PATTERN_DEAD_CELLS:
       case PM_FPA_FILE_LINEARITY:
       case PM_FPA_FILE_NEWNONLIN:
@@ -718,4 +731,5 @@
       case PM_FPA_FILE_KH_CORRECT:
       case PM_FPA_FILE_PATTERN_ROW_AMP:
+      case PM_FPA_FILE_PATTERN_DEAD_CELLS:
       case PM_FPA_FILE_EXPNUM:
         psTrace ("psModules.camera", 6, "NOT freeing %s (%s) : save for further analysis\n", file->filename, file->name);
@@ -883,4 +897,5 @@
       case PM_FPA_FILE_KH_CORRECT:
       case PM_FPA_FILE_PATTERN_ROW_AMP:
+      case PM_FPA_FILE_PATTERN_DEAD_CELLS:
       case PM_FPA_FILE_LINEARITY:
       case PM_FPA_FILE_NEWNONLIN:
@@ -1089,4 +1104,5 @@
       case PM_FPA_FILE_KH_CORRECT:
       case PM_FPA_FILE_PATTERN_ROW_AMP:
+      case PM_FPA_FILE_PATTERN_DEAD_CELLS:
       case PM_FPA_FILE_SX:
       case PM_FPA_FILE_RAW:
Index: trunk/psModules/src/detrend/pmDark.c
===================================================================
--- trunk/psModules/src/detrend/pmDark.c	(revision 42356)
+++ trunk/psModules/src/detrend/pmDark.c	(revision 42379)
@@ -129,5 +129,5 @@
     } else {
         if (!ordinateParseConcept(value, readout, name)) {
-            psError(PM_ERR_CONFIG, false, "trouble parsing rule %s for DARK.ORDINATE %s", rule, name);
+            psError(PM_ERR_CONFIG, false, "trouble parsing rule %s for DARK.ORDINATE %s", "NULL", name);
             return false;
         }
Index: trunk/psModules/src/detrend/pmDetrendDB.c
===================================================================
--- trunk/psModules/src/detrend/pmDetrendDB.c	(revision 42356)
+++ trunk/psModules/src/detrend/pmDetrendDB.c	(revision 42379)
@@ -113,4 +113,5 @@
 	DETREND_STRING_CASE(KH_CORRECT);
 	DETREND_STRING_CASE(PATTERN_ROW_AMP);
+	DETREND_STRING_CASE(PATTERN_DEAD_CELLS);
     default:
         return NULL;
Index: trunk/psModules/src/detrend/pmDetrendDB.h
===================================================================
--- trunk/psModules/src/detrend/pmDetrendDB.h	(revision 42356)
+++ trunk/psModules/src/detrend/pmDetrendDB.h	(revision 42379)
@@ -43,4 +43,5 @@
     PM_DETREND_TYPE_KH_CORRECT,
     PM_DETREND_TYPE_PATTERN_ROW_AMP,
+    PM_DETREND_TYPE_PATTERN_DEAD_CELLS,
 } pmDetrendType;
 
Index: trunk/psModules/src/detrend/pmOverscan.c
===================================================================
--- trunk/psModules/src/detrend/pmOverscan.c	(revision 42356)
+++ trunk/psModules/src/detrend/pmOverscan.c	(revision 42379)
@@ -38,4 +38,8 @@
     opts->order = order;
     opts->stat = psMemIncrRefCounter(stat);
+
+    opts->minValid = 0.0; // default value if not defined
+    opts->maxValid = (float) 0x10000; // default value if not defined
+    opts->maskVal = 0x0001; // default value if not defined
 
     // Smoothing
@@ -314,4 +318,17 @@
 	psFree(reducedScalar);
 
+	// EAM 2022.03.29 : if the calculated overscan value is below the threshold,
+	// declare the readout dead and mask
+
+	if ((reduced < overscanOpts->minValid) || (reduced > overscanOpts->maxValid)) {
+	    fprintf (stderr, "bad overscan (1) %f, masking readout\n", reduced);
+	    psImage *mask = input->mask;
+	    for (int y = 0; y < mask->numRows; y++) {
+		for (int x = 0; x < mask->numCols; x++) {
+		    mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= overscanOpts->maskVal;
+		}
+	    }
+	}
+
 	psFree(stats);
 	return true;
@@ -371,18 +388,32 @@
 	// generate stats of overscan vector for header
 	{ 
-	  psString comment = NULL;    // Comment to add
-	  psStats *vectorStats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
-	  if (!psVectorStats (vectorStats, reduced, NULL, NULL, 0)) {
-	      psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
-	      return false;
-	  }
-	  psStringAppend(&comment, "Mean Overscan value: %f", vectorStats->sampleMean);
-	  psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
-	  psFree(comment);
-
-	  // write metadata header value
-	  psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE, "Overscan mean", vectorStats->sampleMean);
-	  psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_SIG", PS_META_REPLACE, "Overscan stdev", vectorStats->sampleStdev);
-	  psFree (vectorStats);
+	    psString comment = NULL;    // Comment to add
+	    psStats *vectorStats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+	    if (!psVectorStats (vectorStats, reduced, NULL, NULL, 0)) {
+		psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
+		return false;
+	    }
+	    psStringAppend(&comment, "Mean Overscan value: %f", vectorStats->sampleMean);
+	    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+	    psFree(comment);
+
+	    // write metadata header value
+	    psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE, "Overscan mean", vectorStats->sampleMean);
+	    psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_SIG", PS_META_REPLACE, "Overscan stdev", vectorStats->sampleStdev);
+
+	    // EAM 2022.03.29 : if the calculated overscan value is below the threshold,
+	    // declare the readout dead and mask
+	  
+	    if ((vectorStats->sampleMean < overscanOpts->minValid) || (vectorStats->sampleMean > overscanOpts->maxValid)) {
+		fprintf (stderr, "bad overscan (2) %f, masking readout\n", vectorStats->sampleMean);
+		psImage *mask = input->mask;
+		for (int y = 0; y < mask->numRows; y++) {
+		    for (int x = 0; x < mask->numCols; x++) {
+			mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= overscanOpts->maskVal;
+		    }
+		}
+	    }
+
+	    psFree (vectorStats);
 	}
 
@@ -469,4 +500,17 @@
 	  psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE, "Overscan mean", vectorStats->sampleMean);
 	  psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_SIG", PS_META_REPLACE, "Overscan stdev", vectorStats->sampleStdev);
+
+	  // EAM 2022.03.29 : if the calculated overscan value is below the threshold,
+	  // declare the readout dead and mask
+	  
+	  if ((vectorStats->sampleMean < overscanOpts->minValid) || (vectorStats->sampleMean > overscanOpts->maxValid)) {
+	      fprintf (stderr, "bad overscan (3) %f, masking readout\n", vectorStats->sampleMean);
+	      psImage *mask = input->mask;
+	      for (int y = 0; y < mask->numRows; y++) {
+		  for (int x = 0; x < mask->numCols; x++) {
+		      mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= overscanOpts->maskVal;
+		  }
+	      }
+	  }
 	  psFree (vectorStats);
 	}
Index: trunk/psModules/src/detrend/pmOverscan.h
===================================================================
--- trunk/psModules/src/detrend/pmOverscan.h	(revision 42356)
+++ trunk/psModules/src/detrend/pmOverscan.h	(revision 42379)
@@ -43,4 +43,8 @@
     int boxcar;                         ///< Boxcar smoothing radius
     float gauss;                        ///< Gaussian smoothing sigma
+    float minValid;			///< if overscan is too low, readout is dead : mask
+    float maxValid;			///< if overscan is too high, readout is dead : mask
+    psImageMaskType maskVal;            ///< Mask value to give dead readouts
+
     // Outputs
     psPolynomial1D *poly;               ///< Result of polynomial fit
Index: trunk/psModules/src/detrend/pmPattern.c
===================================================================
--- trunk/psModules/src/detrend/pmPattern.c	(revision 42356)
+++ trunk/psModules/src/detrend/pmPattern.c	(revision 42379)
@@ -1005,4 +1005,132 @@
     psFree(meanMask);
 
+    return true;
+}
+
+// Compare the backs (cellBackgrounds) vector to each of the patterns loaded for this chip.
+// Choose the one that matches best & mask as appropriate
+// can we pass the backgrounds in using an image (8 x 8)
+bool pmPatternDeadCells(pmChip *chip, const psVector *backs, psImageMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_VECTOR_NON_NULL(backs, false);
+    PS_ASSERT_VECTOR_SIZE(backs, chip->cells->n, false);
+    PS_ASSERT_VECTOR_TYPE(backs, PS_TYPE_F32, false);
+
+    // Look for the dead cell pattern cube in the analysis metadata.
+    // NOTE: not all chips have the pattern, skip if not found.
+    bool mdok;
+    psImage *deadCellPattern = (psImage *) psMetadataLookupPtr (&mdok, chip->analysis, "PTN.DEAD.CELL");
+    if (!mdok) {
+        psLogMsg("psModules.detrend", PS_LOG_DETAIL, "No DEAD CELL pattern for chip, skipping\n");
+	return true;
+    }
+
+    int numCells = backs->n;            // Number of cells
+    int numColsD = deadCellPattern->numCols;
+    int numRowsD = deadCellPattern->numRows;
+
+    assert (backs->n == numRowsD);
+
+    // The background values need to be normalized (divide by the median).
+    // First extract the valid data values
+    psVector *valid = psVectorAllocEmpty(backs->n, PS_TYPE_F32); // Mean for each cell
+    for (int i = 0; i < backs->n; i++) {
+	float value = backs->data.F32[i];
+	if (!isfinite(value)) { backs->data.F32[i] = NAN; continue; }
+	if (value > 50000.0)  { backs->data.F32[i] = NAN; continue; } // XXX warning: hard-wired value
+	psVectorAppend (valid, value);
+    }
+    if (valid->n == 0) {
+	psLogMsg("psModules.detrend", PS_LOG_DETAIL, "No valid backgrounds, skipping\n");
+	psFree (valid);
+	return true;
+    }
+
+    // Second, calculate the median
+    psVectorSortInPlace (valid);
+    int midPt = valid->n / 2.0;
+    float median = valid->n % 2 ? valid->data.F32[midPt] : 0.5*(valid->data.F32[midPt] + valid->data.F32[midPt-1]);
+    psFree (valid);
+
+    // Finally, renormalize:
+    for (int i = 0; i < backs->n; i++) {
+	if (!isfinite(backs->data.F32[i])) { continue; }
+	backs->data.F32[i] /= median;
+    }
+
+    // there are 2 columns (value & mask) for each mode, plus the constant mode
+    int nModes = numColsD / 2 + 1;
+    
+    psVector *stdev = psVectorAlloc(nModes, PS_TYPE_F32); // Mean for each cell
+
+    // measure stdev for each comparison
+    for (int i = 0; i < nModes; i++) {
+	float Sum1 = 0.0;
+	float Sum2 = 0.0;
+	int   Npts = 0;
+
+	// choose the column with the background values for this mode
+	int nModeColumn = 2*(i - 1);
+
+	for (int j = 0; j < backs->n; j++) {
+
+	    float valObs = backs->data.F32[j];
+	    float valRef = (i == 0) ? 0.0 : deadCellPattern->data.F32[j][nModeColumn];
+
+	    if (!isfinite(valObs)) continue;
+	    if (!isfinite(valRef)) continue;
+
+	    float dS = valObs - valRef;
+	    Sum1 += dS;
+	    Sum2 += dS*dS;
+	    Npts ++;
+	}
+	if (Npts == 0) {
+	    // is this is an error?
+	    // no valid data, ignore this test
+	    psLogMsg("psModules.detrend", PS_LOG_DETAIL, "No valid backgrounds, skipping\n");
+	    psFree (stdev);
+	    return true;
+	}
+
+	float mean = Sum1 / Npts;
+	float sigma = sqrt(Sum2 / Npts - mean*mean);
+	stdev->data.F32[i] = sigma;
+	fprintf (stderr, "mode: %d, stdev: %f\n", i, sigma);
+    }
+
+    // loop over stdev and choose the lowest one
+    int   minI = 0;
+    float minV = stdev->data.F32[minI];
+
+    for (int i = 1; i < nModes; i++) {
+	if (stdev->data.F32[i] < minV) {
+	    minI = i;
+	    minV = stdev->data.F32[i];
+	}
+    }
+    psFree (stdev);
+
+    if (minI == 0) return true;
+
+    int nModeColumnMask = 2*(minI - 1) + 1;
+
+    // now mask bad cells
+    for (int i = 0; i < numCells; i++) {
+        if (deadCellPattern->data.F32[i][nModeColumnMask] > 0) continue;
+
+        pmCell *cell = chip->cells->data[i]; // Cell of interest
+        pmReadout *ro = cell->readouts->data[0]; // Readout of interest
+
+	psImage *mask = ro->mask; // mask of interest
+	int numCols = mask->numCols, numRows = mask->numRows; // Size of image
+	
+	for (int y = 0; y < numRows; y++) {
+	    for (int x = 0; x < numCols; x++) {
+		mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= maskVal;
+	    }
+	}
+    }
     return true;
 }
Index: trunk/psModules/src/detrend/pmPattern.h
===================================================================
--- trunk/psModules/src/detrend/pmPattern.h	(revision 42356)
+++ trunk/psModules/src/detrend/pmPattern.h	(revision 42379)
@@ -86,4 +86,6 @@
 
 
+bool pmPatternDeadCells(pmChip *chip, const psVector *backs, psImageMaskType maskVal);
+
 /// @}
 #endif
Index: trunk/psModules/src/detrend/pmPatternIO.c
===================================================================
--- trunk/psModules/src/detrend/pmPatternIO.c	(revision 42356)
+++ trunk/psModules/src/detrend/pmPatternIO.c	(revision 42379)
@@ -482,5 +482,8 @@
 }
 
-// read the set of tables, one for each chip
+// Read the set of tables, one for each chip.  The values are saved on the cell->analysis
+// metadata of the pmFPAfile associated with the pattern file.  Later, when this is used (e.g.,
+// ppImageDetrendPatternRowApply), the values are transferred to the cell->analysis metadata of
+// the pmFPAfile for the image being processed.
 bool pmPatternRowAmpReadChips (pmFPAfile *file) {
 
@@ -560,2 +563,90 @@
     return true;
 }
+
+/**************** PatternDeadCells I/O *************************/
+
+bool pmPatternDeadCellsRead (const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+    {
+        // read the full model in one pass: require the level to be FPA
+        if (view->chip != -1) {
+            psError(PS_ERR_IO, false, "Pattern Dead Cells must be read at the FPA level");
+            return false;
+        }
+
+        if (!pmPatternDeadCellsReadFPA (file)) {
+            psError(PS_ERR_IO, false, "Failed to read Pattern Dead Cells for fpa");
+            return false;
+        }
+        return true;
+    }
+
+// read in all chip-level Pattern Dead Cells data for this FPA
+bool pmPatternDeadCellsReadFPA (pmFPAfile *file) {
+
+    if (!pmPatternDeadCellsReadChips (file)) {
+        psError(PS_ERR_IO, false, "Failed to read Pattern Dead Cells for chips");
+        return false;
+    }
+
+    return true;
+}
+
+// Read the set of dead cell image cubes, one for each chip.  The values are saved on the
+// chip->analysis metadata of the pmFPAfile associated with the pattern file.  Later, when this
+// is used (e.g., ppImageDetrendPatternDeadCellsApply), the values are transferred to the
+// chip->analysis metadata of the pmFPAfile for the image being processed.
+bool pmPatternDeadCellsReadChips (pmFPAfile *file) {
+
+    bool haveData, status;
+
+    // loop over the extensions
+    // for each extension, use the extname (eg, XY01.ded) to assign to a chip
+
+    // move to the start of the file
+    haveData = psFitsMoveExtNum (file->fits, 1, false);
+    if (!haveData) {
+        psError(PS_ERR_IO, false, "Failed to read even the first extension?");
+        return false;
+    }
+
+    int nGood = 0;
+    while (haveData) {
+
+	// load the header
+	psMetadata *header = psFitsReadHeader(NULL, file->fits); // The FITS header
+	if (!header) psAbort("cannot read dead cell header");
+
+	// load the full model in one shot
+	psImage *deadCellData = psFitsReadImage(file->fits, psRegionSet(0,0,0,0), 0); // dead cell patterns
+	if (!deadCellData) psAbort("cannot read dead cell pattern");
+	
+	// determine the chip (not all chips have DEAD CELL patterns)
+	char *extname = psMetadataLookupStr (&status, header, "EXTNAME");
+	psLogMsg ("psModules.detrend", 8, "read dead cell pattern for extname %s\n", extname);
+
+	// I expect to find a name of the form: chipName.ded (eg, XY01.ded)
+	// where chipName like 'XY01'
+	psAssert (strlen(extname) == 8, "invalid extension %s", extname);
+	psAssert (extname[5] == 'd', "invalid extension %s", extname);
+	psAssert (extname[6] == 'e', "invalid extension %s", extname);
+	psAssert (extname[7] == 'd', "invalid extension %s", extname);
+
+	char chipName[5];
+	strncpy (chipName, extname, 4);
+	chipName[4] = 0;
+
+	pmChip *chip = pmConceptsChipFromName (file->fpa, chipName);
+	if (!chip) psAbort ("invalid chip?");
+
+	psMetadataAddImage (chip->analysis, PS_LIST_TAIL, "PTN.DEAD.CELL", PS_META_REPLACE, "", deadCellData);
+	psFree (deadCellData);
+	psFree (header);
+
+	// move to the next extension
+	haveData = psFitsMoveExtNum (file->fits, 1, true);
+	nGood ++;
+    }
+    psLogMsg ("psModules.detrend", 4, "read patterns for %d chips from Pattern Dead Cells file\n", nGood);
+
+    return true;
+}
Index: trunk/psModules/src/detrend/pmPatternIO.h
===================================================================
--- trunk/psModules/src/detrend/pmPatternIO.h	(revision 42356)
+++ trunk/psModules/src/detrend/pmPatternIO.h	(revision 42379)
@@ -40,3 +40,7 @@
 bool pmPatternRowAmpReadChips (pmFPAfile *file);
 
+bool pmPatternDeadCellsRead (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmPatternDeadCellsReadFPA (pmFPAfile *file);
+bool pmPatternDeadCellsReadChips (pmFPAfile *file);
+
 #endif
Index: trunk/psModules/src/extras/pmVisualUtils.c
===================================================================
--- trunk/psModules/src/extras/pmVisualUtils.c	(revision 42356)
+++ trunk/psModules/src/extras/pmVisualUtils.c	(revision 42379)
@@ -222,5 +222,5 @@
     }
 
-    strncpy(name, addNodeName, MAX_COMPONENT_LENGTH);
+    ps_strncpy_nowarn(name, addNodeName, MAX_COMPONENT_LENGTH);
     char *pname = name+1;               // Take off the period
     // Iterate through the components of addNodeName.  Strip off the first
Index: trunk/psModules/src/objects/pmModel.c
===================================================================
--- trunk/psModules/src/objects/pmModel.c	(revision 42356)
+++ trunk/psModules/src/objects/pmModel.c	(revision 42379)
@@ -344,5 +344,5 @@
 
                 // 2D residual variations are set for the true source position
-                pixelValue += Io*(Vo + XoSave*Vx + XoSave*Vy);
+                pixelValue += Io*(Vo + XoSave*Vx + YoSave*Vy);
             }
 
