Index: trunk/ppImage/src/Makefile.am
===================================================================
--- trunk/ppImage/src/Makefile.am	(revision 42964)
+++ trunk/ppImage/src/Makefile.am	(revision 43038)
@@ -33,4 +33,5 @@
 	ppImageMosaicBackground.c \
 	ppImageMaskStats.c \
+	ppImageMaskBadOverscan.c \
 	ppImagePhotom.c \
 	ppImageAstrom.c \
Index: trunk/ppImage/src/ppImage.h
===================================================================
--- trunk/ppImage/src/ppImage.h	(revision 42964)
+++ trunk/ppImage/src/ppImage.h	(revision 43038)
@@ -36,4 +36,5 @@
     bool doNonLin;                      // Non-linearity correction
     bool doNewNonLin;                   // Non-linearity correction
+    bool doMaskBadOverscan;             // Mask cells/readouts with out-of-range overscan 
     bool doOverscan;                    // Overscan subtraction
     bool doNoiseMap;                    // Bias subtraction
@@ -180,4 +181,6 @@
 bool ppImageDetrendBias(pmReadout *inputReadout, pmReadout *bias, pmReadout *dark, ppImageOptions *options);
 
+bool ppImageMaskBadOverscan(pmReadout *input, pmConfig  *config);
+
 bool ppImageDetrendNewNonLinear(pmReadout *input, pmFPAview *linearity, pmConfig *config);
 
Index: trunk/ppImage/src/ppImageArguments.c
===================================================================
--- trunk/ppImage/src/ppImageArguments.c	(revision 42964)
+++ trunk/ppImage/src/ppImageArguments.c	(revision 43038)
@@ -26,4 +26,5 @@
     fprintf(stderr, "\t-linearity/-linearlist: linearity correction file.\n");
     fprintf(stderr, "\t-newnonlin/-newnonlinlist: non-linearity correction file (v 2023.01).\n");
+    fprintf(stderr, "\t-valid-overscan/-valid-overscan-list: overscan valid range file.\n");
     fprintf(stderr, "\n");
     exit (2);
@@ -118,4 +119,5 @@
     pmConfigFileSetsMD (config->arguments, &argc, argv, "LINEARITY", "-linearity", "-linearlist");
     pmConfigFileSetsMD (config->arguments, &argc, argv, "NEWNONLIN", "-newnonlin", "-newnonlinlist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "VALID.OVERSCAN", "-valid-overscan", "-valid-overscan-list");
     pmConfigFileSetsMD (config->arguments, &argc, argv, "PATTERN.ROW.AMP", "-pattern-row-amplitude", "-not-defined");
     pmConfigFileSetsMD (config->arguments, &argc, argv, "PATTERN.DEAD.CELLS", "-pattern-dead-cells", "-not-defined");
Index: trunk/ppImage/src/ppImageDetrendFree.c
===================================================================
--- trunk/ppImage/src/ppImageDetrendFree.c	(revision 42964)
+++ trunk/ppImage/src/ppImageDetrendFree.c	(revision 43038)
@@ -15,4 +15,5 @@
     "PPIMAGE.LINEARITY",
     "PPIMAGE.NEWNONLIN",
+    "PPIMAGE.VALID.OVERSCAN",
     NULL
 };
Index: trunk/ppImage/src/ppImageDetrendReadout.c
===================================================================
--- trunk/ppImage/src/ppImageDetrendReadout.c	(revision 42964)
+++ trunk/ppImage/src/ppImageDetrendReadout.c	(revision 43038)
@@ -167,4 +167,13 @@
     }
 
+    // Mask bad cells based on the overscan values
+    if (options->doMaskBadOverscan) {
+      if (!ppImageMaskBadOverscan(input, config)) {
+	psError(PS_ERR_UNKNOWN, false, "Unable to mask readout based on overscan");
+	psFree(detview);
+	return(false);
+      }
+    }
+
     // measure the overscan-subtracted readoutBackground here (or subtract the overscan value?)
     // XXX this is the measurements and should be independent of the pattern masking
Index: trunk/ppImage/src/ppImageDetrendRecord.c
===================================================================
--- trunk/ppImage/src/ppImageDetrendRecord.c	(revision 42964)
+++ trunk/ppImage/src/ppImageDetrendRecord.c	(revision 43038)
@@ -75,6 +75,7 @@
     detrendRecord(options->doFringe,   detrend, config, view, "PPIMAGE.FRINGE",   "DETREND.FRINGE",   "Fringe filename");
 
-    detrendRecord(options->doNonLin,    detrend, config, view, "PPIMAGE.LINEARITY","DETREND.NONLIN",   "Non-linearity table filename");
-    detrendRecord(options->doNewNonLin, detrend, config, view, "PPIMAGE.NEWNONLIN","DETREND.NEWNONLIN","Non-linearity table filename (v2023)");
+    detrendRecord(options->doNonLin,          detrend, config, view, "PPIMAGE.LINEARITY",      "DETREND.NONLIN",         "Non-linearity table filename");
+    detrendRecord(options->doNewNonLin,       detrend, config, view, "PPIMAGE.NEWNONLIN",      "DETREND.NEWNONLIN",      "Non-linearity table filename (v2023)");
+    detrendRecord(options->doMaskBadOverscan, detrend, config, view, "PPIMAGE.VALID.OVERSCAN", "DETREND.VALID.OVERSCAN", "Valid Overscan table filename");
 
     detrendRecord(options->doDark & options->useVideoDark, detrend, config, view, "PPIMAGE.VIDEODARK", "DETREND.VIDEODARK", "VideoDark filename");
Index: trunk/ppImage/src/ppImageMaskBadOverscan.c
===================================================================
--- trunk/ppImage/src/ppImageMaskBadOverscan.c	(revision 43038)
+++ trunk/ppImage/src/ppImageMaskBadOverscan.c	(revision 43038)
@@ -0,0 +1,64 @@
+#include "ppImage.h"
+
+bool ppImageMaskBadOverscan(pmReadout *input, pmConfig  *config) {
+
+    bool status;
+
+    pmFPAfile *overscan_file = psMetadataLookupPtr(&status, config->files, "PPIMAGE.VALID.OVERSCAN");
+    psFits *overscan_fits = overscan_file->fits;
+
+    char *chipname = psMetadataLookupStr(&status, input->parent->parent->concepts, "CHIP.NAME");
+    if (!chipname) {
+	psError(PS_ERR_IO, false, "missing CHIP.NAME in concepts");
+	return false;
+    }
+    char *cellname = psMetadataLookupStr(&status, input->parent->concepts, "CELL.NAME");
+    if (!cellname) {
+	psError(PS_ERR_IO, false, "missing CELL.NAME in concepts");
+	return false;
+    }
+
+    // if pmFPAfile has been loaded (by ppImageDefineFile in ppImageParseCamera), then
+    // the file corresponding to the current chip is found and opened
+    // NOTE: if the chipname is missing, we skip the correction
+    if (!psFitsMoveExtName(overscan_fits, chipname)) {
+        psLogMsg ("ppImageMaskBadOverscan", 4, "Unable to move to valid overscan table %s, skipping", chipname);
+	return true;
+    }
+  
+    psArray *table = psFitsReadTable(overscan_fits);
+    if (!table) {
+	psError(PS_ERR_IO, false, "Unable to read valid overscan table.\n");
+	return false;
+    }
+
+    psImageMaskType badMask = pmConfigMaskGet("DETECTOR", config);
+    if (!badMask) {
+	psError(PS_ERR_IO, false, "Invalid mask name DETECTOR.\n");
+	return false;
+    }
+
+    // find this cell in the table
+    for (int i = 0; i < table->n; i++) {
+	psMetadata *row = table->data[i];
+	
+	char *thisCell = psMetadataLookupStr(&status, row, "CELL.NAME");
+	if (strcmp(thisCell, cellname)) continue;
+
+	// we have the correct cell now
+	float minValid = psMetadataLookupF32(&status, row, "MIN_VALID");
+	float maxValid = psMetadataLookupF32(&status, row, "MAX_VALID");
+
+	if (!pmMaskBadOverscanApply(input, minValid, maxValid, badMask)) {
+	  psError(PS_ERR_UNKNOWN, false, "Unable to apply valid overscan range.\n");
+	  psFree (table);
+	  return false;
+	}	    
+	psFree (table);
+	return true;
+    }
+    psFree (table);
+    return false;
+}
+
+// the valid overscan file contains tables stored by chip, with one row per cell
Index: trunk/ppImage/src/ppImageOptions.c
===================================================================
--- trunk/ppImage/src/ppImageOptions.c	(revision 42964)
+++ trunk/ppImage/src/ppImageOptions.c	(revision 43038)
@@ -29,4 +29,5 @@
     options->doNonLin        = false;   // Non-linearity correction
     options->doNewNonLin     = false;   // New Non-linearity correction (v2023)
+    options->doMaskBadOverscan = false; // Mask cells / readouts if overscan is out of bounds
     options->doOverscan      = false;   // Overscan subtraction
     options->doNoiseMap      = false;   // Apply Read Noise Map
@@ -182,4 +183,10 @@
     }
 
+    // Mask cells/readouts with out-of-range overscan 
+    // nominal overscan ranges are loaded from a file defined in the detrend system or on the command-line
+    if (psMetadataLookupBool(NULL, recipe, "MASK.BAD.OVERSCAN")) {
+        options->doMaskBadOverscan = true;
+    }
+
     // XXX PAP: The overscan stuff needs to be updated following the reworked API
 
Index: trunk/ppImage/src/ppImageParseCamera.c
===================================================================
--- trunk/ppImage/src/ppImageParseCamera.c	(revision 42964)
+++ trunk/ppImage/src/ppImageParseCamera.c	(revision 43038)
@@ -117,4 +117,15 @@
 	if (!ppImageDefineFile(config, input->fpa, "PPIMAGE.NEWNONLIN", "NEWNONLIN",
 			       PM_FPA_FILE_NEWNONLIN, PM_DETREND_TYPE_NEWNONLIN)) {
+	    psError(PS_ERR_IO, false, "Can't find a new non-linearity correction source");
+	    psFree(options);
+	    return NULL;
+	}
+    }
+    if (options->doMaskBadOverscan) {
+	// if the file has been specified on the command-line (-valid-overscan file), then the file
+	// is identified by the VALID.OVERSCAN entry in config->arguments.  otherwise, load from the
+	// detrend system as type VALID.OVERSCAN
+	if (!ppImageDefineFile(config, input->fpa, "PPIMAGE.VALID.OVERSCAN", "VALID.OVERSCAN",
+			       PM_FPA_FILE_VALID_OVERSCAN, PM_DETREND_TYPE_VALID_OVERSCAN)) {
 	    psError(PS_ERR_IO, false, "Can't find a new non-linearity correction source");
 	    psFree(options);
