Index: /trunk/ppStats/src/Makefile.am
===================================================================
--- /trunk/ppStats/src/Makefile.am	(revision 14002)
+++ /trunk/ppStats/src/Makefile.am	(revision 14003)
@@ -17,4 +17,8 @@
 	ppStatsFringe.c			\
 	ppStatsLoop.c			\
+	ppStatsChip.c			\
+	ppStatsCell.c			\
+	ppStatsReadout.c		\
+	ppStatsUtils.c  		\
 	ppStatsPixels.c			\
 	ppStatsMetadata.c		\
Index: /trunk/ppStats/src/ppStats.h
===================================================================
--- /trunk/ppStats/src/ppStats.h	(revision 14002)
+++ /trunk/ppStats/src/ppStats.h	(revision 14003)
@@ -47,4 +47,5 @@
 		   pmCell *cell,     // Cell for which to get statistics
 		   psFits *fits,     // FITS file handle
+		   pmFPAview *view,  // View for analysis
 		   ppStatsData *data,// The data
 		   const pmConfig *config // Configuration
@@ -97,3 +98,31 @@
 psString ppStatsVersionLong(void);
 
+void p_ppStatsGetMetadata(psMetadata *target, // Target for metadata
+			  psMetadata *source, // Source for metadata
+			  psList *list    // List containing keywords
+    );
+
+void p_ppStatsGetAnalysis(psMetadata *target, // Output Target for metadata
+			  psList *headers,    // List containing desired keywords
+			  psMetadata *source, // Input Source for metadata
+			  psList *list        // List containing analysis blocks
+    );
+
+bool p_ppStatsDoThis(psList *toDoList,    // List of things to do
+		     const char *this     // The name of "this"
+    );
+
+void p_ppStatsAddToHierarchy(psMetadata *source, // Source to add
+			     psMetadata *target, // Target to which to add
+			     const char *name, // Name of source
+			     const char *comment // Comment for source
+    );
+
+psExit ppStatsReadout(psMetadata *cellResults, // Metadata holding the chip results
+		      pmReadout *readout,	// Cell for which to get statistics
+		      int nReadout,	// readout number
+		      ppStatsData *data,	// The data
+		      const pmConfig *config // Configuration
+    );
+
 #endif
Index: /trunk/ppStats/src/ppStatsCell.c
===================================================================
--- /trunk/ppStats/src/ppStatsCell.c	(revision 14003)
+++ /trunk/ppStats/src/ppStatsCell.c	(revision 14003)
@@ -0,0 +1,120 @@
+# include "ppStatsInternal.h"
+
+psExit ppStatsCell(psMetadata *chipResults, // Metadata holding the chip results
+		   pmCell *cell,	// Cell for which to get statistics
+		   psFits *fits,	// FITS file handle
+		   pmFPAview *view,  // View for analysis
+		   ppStatsData *data,	// The data
+		   const pmConfig *config // Configuration
+    )
+{
+    assert(chipResults);
+    assert(cell);
+    assert(data);
+    assert(config);
+
+    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+
+    // Check to see if this is a cell of interest
+    if (!p_ppStatsDoThis(data->cells, cellName)) {
+        return PS_EXIT_SUCCESS;
+    }
+
+    // select the header unit for this cell
+    // XXX why is this needed for Cell but not Chip?
+    pmHDU *hdu = pmHDUFromCell(cell); // HDU for cell
+    if (!hdu || hdu->blankPHU) {
+        if (fits) {
+            // No HDU means there's no data in this cell
+            return PS_EXIT_SUCCESS;
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Can't find HDU for cell\n");
+            return PS_EXIT_CONFIG_ERROR;
+        }
+    }
+
+    // Extract Header and Concept values from the Cell and Readout->analysis level
+
+    // find or generate a metadata to hold the results
+    bool mdok;                          // Status of MD lookup
+    psMetadata *cellResults = psMemIncrRefCounter(psMetadataLookupMetadata(&mdok, chipResults, cellName));
+    if (!mdok || !cellResults) {
+        cellResults = psMetadataAlloc();
+    }
+
+    // Extract Header values
+    if (psListLength(data->headers)) {
+	// extract from existing headers
+	if (cell->hdu) {
+	    if (fits && !pmCellReadHeader(cell, fits)) {
+		psError (PS_ERR_IO, false, "trouble reading cell header\n");
+		psFree(cellResults);
+		return PS_EXIT_DATA_ERROR;
+	    }
+	    pmHDU *hdu = cell->hdu;     // HDU for headers
+	    p_ppStatsGetMetadata(cellResults, hdu->header, data->headers);
+	}
+	// extract from data->analysis output MD entries
+	if (psListLength(data->analysis)) {
+	    p_ppStatsGetAnalysis (cellResults, data->headers, cell->analysis, data->analysis);
+	}
+    }
+
+    // Extract Concept values
+    if (psListLength(data->concepts) > 0) {
+        if (fits && cell->hdu && !pmCellReadHeader(cell, fits)) {
+            psError (PS_ERR_IO, false, "trouble reading cell header\n");
+            psFree(cellResults);
+            return PS_EXIT_DATA_ERROR;
+        }
+        pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_ALL, false, config->database);
+        p_ppStatsGetMetadata(cellResults, cell->concepts, data->concepts);
+    }
+
+    // If we want to measure pixel statistics, we must read (or have) the pixel data
+    if (data->doStats || psListLength(data->summary)) {
+	// Read the image pixel data
+	if (fits && !pmCellRead(cell, fits, config->database)) {
+	    psError (PS_ERR_IO, false, "trouble reading cell data\n");
+	    psFree(cellResults);
+	    return PS_EXIT_DATA_ERROR;
+	}
+    }
+
+    // check if we can legitimately iterate to the readout level
+    psArray *readouts = cell->readouts; // Array of component readouts
+    if (readouts->n == 0) {
+        psLogMsg(__func__, PS_LOG_WARN, "No readouts present in cell %s --- skipping\n", cellName);
+    }
+    if (view->readout >= readouts->n) {
+	psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Desired readout view (%d) doesn't match "
+		"number of readouts (%ld)\n", view->readout, readouts->n);
+	return PS_EXIT_CONFIG_ERROR;
+    }
+    if ((view->readout == -1) && (readouts->n > 1)) {
+	psLogMsg(__func__, PS_LOG_WARN, "Multiple readouts (%ld) present in cell %s\n", readouts->n, cellName);
+    }
+
+    // Iterate over readouts
+    for (int i = 0; i < readouts->n; i++) {
+	if ((view->readout >= 0) && (i != view->readout)) continue; 
+        pmReadout *readout = readouts->data[i];  // Cell of interest
+        psExit result = ppStatsReadout(cellResults, readout, i, data, config);
+        if (result != PS_EXIT_SUCCESS) {
+            psError(PS_ERR_UNKNOWN, false, "trouble with readout stats for %d\n", i);
+            if (fits) {
+                pmCellFreeData(cell);
+            }
+            psFree(cellResults);
+            return result;
+        }
+    }
+
+    // Add the cell results to the chip
+    p_ppStatsAddToHierarchy(cellResults, chipResults, cellName, "Results for cell");
+    psFree (cellResults);
+    if (fits) {
+        pmCellFreeData(cell);
+    }
+    return PS_EXIT_SUCCESS;
+}
Index: /trunk/ppStats/src/ppStatsChip.c
===================================================================
--- /trunk/ppStats/src/ppStatsChip.c	(revision 14003)
+++ /trunk/ppStats/src/ppStatsChip.c	(revision 14003)
@@ -0,0 +1,93 @@
+# include "ppStatsInternal.h"
+
+psExit ppStatsChip(psMetadata *fpaResults, // Metadata holding the fpa results
+		   pmChip *chip,     // Chip for which to get statistics
+		   psFits *fits,     // FITS file handle
+		   pmFPAview *view,  // View for analysis
+		   ppStatsData *data,// The data
+		   const pmConfig *config // Configuration
+    )
+{
+    assert(fpaResults);
+    assert(chip);
+    assert(view);
+    assert(data);
+    assert(config);
+
+    const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+
+    // Check to see if this is a chip of interest
+    if (!p_ppStatsDoThis(data->chips, chipName)) {
+        return PS_EXIT_SUCCESS;
+    }
+
+    // Extract Header and Concept values from the Chip level
+
+    // find or generate a metadata to hold the results
+    bool mdok;                          // Status of MD lookup
+    psMetadata *chipResults = psMemIncrRefCounter(psMetadataLookupMetadata(&mdok, fpaResults, chipName));
+    if (!mdok || !chipResults) {
+        chipResults = psMetadataAlloc();
+    }
+
+    // Extract Header values
+    if (psListLength(data->headers)) {
+	// extract from existing headers
+	if (chip->hdu) {
+	    if (fits && !pmChipReadHeader(chip, fits)) {
+		psError (PS_ERR_IO, false, "trouble reading chip header\n");
+		psFree(chipResults);
+		return PS_EXIT_DATA_ERROR;
+	    }
+	    pmHDU *hdu = chip->hdu;     // HDU for headers
+	    p_ppStatsGetMetadata(chipResults, hdu->header, data->headers);
+	}
+	// extract from data->analysis output MD entries
+	if (psListLength(data->analysis)) {
+	    p_ppStatsGetAnalysis (chipResults, data->headers, chip->analysis, data->analysis);
+	}
+    }
+
+    // Extract Concept values
+    if (psListLength(data->concepts) > 0) {
+        if (fits && chip->hdu && !pmChipReadHeader(chip, fits)) {
+            psError (PS_ERR_IO, false, "trouble reading chip header\n");
+            psFree(chipResults);
+            return PS_EXIT_DATA_ERROR;
+        }
+        pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_ALL, false, false, config->database);
+        p_ppStatsGetMetadata(chipResults, chip->concepts, data->concepts);
+    }
+
+    // check if we can legitimately iterate to the cell level
+    psArray *cells = chip->cells;       // Array of cells
+    if (view->cell >= cells->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Desired cell view (%d) doesn't match "
+                "number of cells (%ld)\n", view->cell, cells->n);
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    // Iterate over cells (if view->cell is set, skip all others)
+    for (int i = 0; i < cells->n; i++) {
+	if ((view->cell >= 0) && (i != view->cell)) continue; 
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psExit result = ppStatsCell(chipResults, cell, fits, view, data, config);
+        if (result != PS_EXIT_SUCCESS) {
+            psError(PS_ERR_UNKNOWN, false, "trouble with cell stats for %d\n", i);
+            if (fits) {
+                pmChipFreeData(chip);
+            }
+            psFree(chipResults);
+            return result;
+        }
+    }
+
+    if (fits) {
+        pmChipFreeData(chip);
+    }
+
+    p_ppStatsAddToHierarchy(chipResults, fpaResults, chipName, "Results for chip");
+
+    psFree (chipResults);
+    return PS_EXIT_SUCCESS;
+}
Index: /trunk/ppStats/src/ppStatsLoop.c
===================================================================
--- /trunk/ppStats/src/ppStatsLoop.c	(revision 14002)
+++ /trunk/ppStats/src/ppStatsLoop.c	(revision 14003)
@@ -1,375 +1,3 @@
 # include "ppStatsInternal.h"
-
-static void getMetadata(psMetadata *target, // Target for metadata
-                        psMetadata *source, // Source for metadata
-                        psList *list    // List containing keywords
-    )
-{
-    psListIterator *iterator = psListIteratorAlloc(list, PS_LIST_HEAD, false); // Iterator
-    psString name;                      // Name from iteration
-    while ((name = psListGetAndIncrement(iterator))) {
-        psMetadataItem *item = psMetadataLookup(source, name); // Item of interest, or NULL
-        if (item) {
-            psMetadataAddItem(target, item, PS_LIST_TAIL, 0);
-        }
-    }
-    psFree(iterator);
-    return;
-}
-
-static void getAnalysis(psMetadata *target, // Output Target for metadata
-                        psList *headers,    // List containing desired keywords
-                        psMetadata *source, // Input Source for metadata
-                        psList *list        // List containing analysis blocks
-    )
-{
-    bool status;
-
-    psListIterator *iterator = psListIteratorAlloc(list, PS_LIST_HEAD, false); // Iterator
-    psString name;                      // Name from iteration
-    while ((name = psListGetAndIncrement(iterator))) {
-        psMetadata *folder = psMetadataLookupMetadata(&status, source, name); // Item of interest, or NULL
-        if (folder) {
-            getMetadata (target, folder, headers); 
-        }
-    }
-    psFree(iterator);
-    return;
-}
-
-static bool doThis(psList *toDoList,    // List of things to do
-                   const char *this     // The name of "this"
-    )
-{
-    if (psListLength(toDoList) == 0) {
-        // No list --- do everything
-        return true;
-    }
-
-    psListIterator *iterator = psListIteratorAlloc(toDoList, PS_LIST_HEAD, false); // Iterator
-    psString test;                      // Test string, from iteration
-    while ((test = psListGetAndIncrement(iterator))) {
-        if (strcmp(this, test) == 0) {
-            // It's in the list --- do it
-            psFree(iterator);
-            return true;
-        }
-    }
-    psFree(iterator);
-    // Couldn't find it --- don't do it
-    return false;
-}
-
-static void addToHierarchy(psMetadata *source, // Source to add
-                           psMetadata *target, // Target to which to add
-                           const char *name, // Name of source
-                           const char *comment // Comment for source
-    )
-{
-    if (psListLength(source->list) > 0 && !psMetadataLookup(target, name)) {
-        psMetadataAdd(target, PS_LIST_TAIL, name, PS_DATA_METADATA,
-                      comment, source);
-    }
-    return;
-}
-
-
-psExit ppStatsCell(psMetadata *chipResults, // Metadata holding the chip results
-		   pmCell *cell,	// Cell for which to get statistics
-		   psFits *fits,	// FITS file handle
-		   ppStatsData *data,	// The data
-		   const pmConfig *config // Configuration
-    )
-{
-    assert(chipResults);
-    assert(cell);
-    assert(data);
-    assert(config);
-
-    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
-
-    // Check to see if this is a cell of interest
-    if (!doThis(data->cells, cellName)) {
-        return PS_EXIT_SUCCESS;
-    }
-
-    // select the header unit for this cell
-    pmHDU *hdu = pmHDUFromCell(cell); // HDU for cell
-    if (!hdu || hdu->blankPHU) {
-        if (fits) {
-            // No HDU means there's no data in this cell
-            return PS_EXIT_SUCCESS;
-        } else {
-            psError(PS_ERR_UNKNOWN, false, "Can't find HDU for cell\n");
-            return PS_EXIT_CONFIG_ERROR;
-        }
-    }
-
-    /*** psphot and psastro put their results on the readout->analysis metadata (PSPHOT.HEADER,
-	 PSASTRO.HEADER).  we need to pull quantities of interest from those locations. to do
-	 this, we need to select the appropriate readout.  ***/
-
-    // Select the readout
-    psArray *readouts = cell->readouts; // Array of component readouts
-    if (readouts->n == 0) {
-        psLogMsg(__func__, PS_LOG_WARN, "No readouts present in cell %s --- skipping\n", cellName);
-        goto cellDone;
-    }
-    if (readouts->n > 1) {
-        psLogMsg(__func__, PS_LOG_WARN, "Multiple readouts (%ld) present in cell %s --- "
-                 "using only the first.\n", readouts->n, cellName);
-    }
-    pmReadout *readout = readouts->data[0]; // The readout of interest
-
-    // Extract Header and Concept values from the Cell and Readout->analysis level
-    bool mdok;                          // Status of MD lookup
-    psMetadata *cellResults = psMemIncrRefCounter(psMetadataLookupMetadata(&mdok, chipResults, cellName));
-    if (!mdok || !cellResults) {
-        cellResults = psMetadataAlloc();
-    }
-
-    // Extract Header values
-    if (psListLength(data->headers) > 0 && cell->hdu) {
-        if (fits && !pmCellReadHeader(cell, fits)) {
-            psError (PS_ERR_IO, false, "trouble reading cell header\n");
-            psFree(cellResults);
-            return PS_EXIT_DATA_ERROR;
-        }
-        pmHDU *hdu = cell->hdu;     // HDU for headers
-        getMetadata(cellResults, hdu->header, data->headers);
-
-	// search in the readout->analysis metadata blocks listed in data->analysis
-	if (psListLength(data->analysis) > 0) {
-	    getAnalysis (cellResults, data->headers, readout->analysis, data->analysis);
-	}
-    }
-
-    // Extract Concept values
-    if (psListLength(data->concepts) > 0) {
-        if (fits && cell->hdu && !pmCellReadHeader(cell, fits)) {
-            psError (PS_ERR_IO, false, "trouble reading cell header\n");
-            psFree(cellResults);
-            return PS_EXIT_DATA_ERROR;
-        }
-        pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_ALL, false, config->database);
-        getMetadata(cellResults, cell->concepts, data->concepts);
-    }
-
-    // Do we want to measure pixel statistics?
-    if (!data->doStats && psListLength(data->summary)) {
-        // Nothing further to do --- don't want to waste our time reading the data
-        goto cellDone;
-    }
-
-    // Read the image pixel data
-    if (fits && !pmCellRead(cell, fits, config->database)) {
-        psError (PS_ERR_IO, false, "trouble reading cell data\n");
-        psFree(cellResults);
-        return PS_EXIT_DATA_ERROR;
-    }
-
-    if (!readout->image) {
-        psLogMsg(__func__, PS_LOG_WARN, "No image associated with readout in cell %s --- ignored.\n", cellName);
-        goto cellDone;
-    }
-
-    // Measure basic image statistics (means, stdevs, etc).
-    if (data->sample <= 0.0) {
-        if (!psImageStats(data->stats, readout->image, readout->mask, data->maskVal)) {
-            psWarning("Unable to perform statistics on cell %s --- ignored.\n", cellName);
-            goto statsDone;
-        }
-    } else {
-        // Apply sampling
-        psImage *image = readout->image; // The image of interest
-        psImage *mask = readout->mask; // The mask image
-        int numSamples = data->sample * image->numCols * image->numRows; // Number of samples
-        int sampleSpace = 1.0 / data->sample; // Space between samples
-        psVector *sampleValues = psVectorAlloc(numSamples, PS_TYPE_F32); // Vector of samples
-        psVector *sampleMask = NULL;  // Corresponding mask
-        if (mask) {
-            sampleMask = psVectorAlloc(numSamples, PS_TYPE_U8);
-        }
-        for (int i = 0; i < numSamples; i++) {
-            int j = i * sampleSpace;
-            int y = j / image->numCols;
-            int x = j % image->numCols;
-            sampleValues->data.F32[i] = image->data.F32[y][x];
-            if (mask) {
-                sampleMask->data.U8[i] = mask->data.U8[y][x];
-            }
-        }
-        if (!psVectorStats(data->stats, sampleValues, NULL, sampleMask, data->maskVal)) {
-            psLogMsg(__func__, PS_LOG_WARN, "Unable to perform statistics on cell %s --- "
-                     "ignored.\n", cellName);
-            psFree(sampleValues);
-            psFree(sampleMask);
-            goto statsDone;
-        }
-        psFree(sampleValues);
-        psFree(sampleMask);
-    }
-
-#define WRITE_STAT(SYMBOL, NAME, SOURCE) \
-    if (data->stats->options & SYMBOL) { \
-        psMetadataAddF32(cellResults, PS_LIST_TAIL, NAME, 0, NULL, data->stats->SOURCE); \
-    }
-
-    WRITE_STAT(PS_STAT_SAMPLE_MEAN,     "SAMPLE_MEAN",   sampleMean);
-    WRITE_STAT(PS_STAT_SAMPLE_MEDIAN,   "SAMPLE_MEDIAN", sampleMedian);
-    WRITE_STAT(PS_STAT_SAMPLE_STDEV,    "SAMPLE_STDEV",  sampleStdev);
-    WRITE_STAT(PS_STAT_SAMPLE_QUARTILE, "SAMPLE_LQ",     sampleLQ);
-    WRITE_STAT(PS_STAT_SAMPLE_QUARTILE, "SAMPLE_UQ",     sampleUQ);
-    WRITE_STAT(PS_STAT_ROBUST_MEDIAN,   "ROBUST_MEDIAN", robustMedian);
-    WRITE_STAT(PS_STAT_ROBUST_STDEV,    "ROBUST_STDEV",  robustStdev);
-    WRITE_STAT(PS_STAT_ROBUST_QUARTILE, "ROBUST_LQ",     robustLQ);
-    WRITE_STAT(PS_STAT_ROBUST_QUARTILE, "ROBUST_UQ",     robustUQ);
-    WRITE_STAT(PS_STAT_ROBUST_QUARTILE, "ROBUST_N50",    robustN50);
-    WRITE_STAT(PS_STAT_FITTED_MEAN,     "FITTED_MEAN",   fittedMean);
-    WRITE_STAT(PS_STAT_FITTED_STDEV,    "FITTED_STDEV",  fittedStdev);
-    WRITE_STAT(PS_STAT_CLIPPED_MEAN,    "CLIPPED_MEAN",  clippedMean);
-    WRITE_STAT(PS_STAT_CLIPPED_STDEV,   "CLIPPED_STDEV", clippedStdev);
-
-    // measure other types of statistics tests
-
-statsDone:
-    // count saturated pixels
-    if (psListLength(data->summary) > 0) {
-        bool get_nSatPixels = false;
-        bool get_fSatPixels = false;
-
-        psListIterator *iterator = psListIteratorAlloc(data->summary, PS_LIST_HEAD, false);
-        psString choice;
-        while ((choice = psListGetAndIncrement(iterator))) {
-            if (!strcasecmp (choice, "SAT_PIXEL_NUM"))  get_nSatPixels = true;
-            if (!strcasecmp (choice, "SAT_PIXEL_FRAC")) get_fSatPixels = true;
-        }
-
-        if (!get_nSatPixels && !get_fSatPixels) {
-            goto cellDone;
-        }
-
-        // Get the "concepts" of interest
-        float saturation = psMetadataLookupF32(&mdok, cell->concepts, "CELL.SATURATION"); // Saturation level
-        if (!mdok || isnan(saturation)) {
-            psLogMsg(__func__, PS_LOG_WARN, "CELL.SATURATION is not set --- unable to measure N_SAT_PIXELS.\n");
-            if (get_nSatPixels) psMetadataAddS32(cellResults, PS_LIST_TAIL, "SAT_PIXEL_NUM", 0, NULL, 0);
-            if (get_fSatPixels) psMetadataAddF32(cellResults, PS_LIST_TAIL, "SAT_PIXEL_FRAC", 0, NULL, NAN);
-            goto cellDone;
-        }
-
-        int nSatPixels = 0;
-        for (int j = 0; j < readout->image->numRows; j++) {
-            for (int i = 0; i < readout->image->numCols; i++) {
-                if (readout->image->data.F32[j][i] >= saturation) {
-                    nSatPixels ++;
-                }
-            }
-        }
-        if (get_nSatPixels) psMetadataAddS32(cellResults, PS_LIST_TAIL, "SAT_PIXEL_NUM", 0, NULL, nSatPixels);
-        if (get_fSatPixels) psMetadataAddF32(cellResults, PS_LIST_TAIL, "SAT_PIXEL_FRAC", 0, NULL, nSatPixels / (double)(readout->image->numRows * readout->image->numCols));
-    }
-
-cellDone:
-    // Add the cell results to the chip
-    addToHierarchy(cellResults, chipResults, cellName, "Results for cell");
-    psFree (cellResults);
-    if (fits) {
-        pmCellFreeData(cell);
-    }
-    return PS_EXIT_SUCCESS;
-}
-
-psExit ppStatsChip(psMetadata *fpaResults, // Metadata holding the fpa results
-		   pmChip *chip,     // Chip for which to get statistics
-		   psFits *fits,     // FITS file handle
-		   pmFPAview *view,  // View for analysis
-		   ppStatsData *data,// The data
-		   const pmConfig *config // Configuration
-    )
-{
-    assert(fpaResults);
-    assert(chip);
-    assert(view);
-    assert(data);
-    assert(config);
-
-    psExit result = PS_EXIT_SUCCESS;
-
-    const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
-
-    // Check to see if this is a chip of interest
-    if (!doThis(data->chips, chipName)) {
-        return PS_EXIT_SUCCESS;
-    }
-
-    psArray *cells = chip->cells;       // Array of cells
-    if (view->cell >= cells->n) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Desired cell view (%d) doesn't match "
-                "number of cells (%ld)\n", view->cell, cells->n);
-        return PS_EXIT_CONFIG_ERROR;
-    }
-
-    // Chip-level results
-    bool mdok;                          // Status of MD lookup
-    psMetadata *chipResults = psMemIncrRefCounter(psMetadataLookupMetadata(&mdok, fpaResults, chipName));
-    if (!mdok || !chipResults) {
-        chipResults = psMetadataAlloc();
-    }
-
-    if (psListLength(data->headers) > 0 && chip->hdu) {
-        if (fits && !pmChipReadHeader(chip, fits)) {
-            psError (PS_ERR_IO, false, "trouble reading chip header\n");
-            psFree(chipResults);
-            return PS_EXIT_DATA_ERROR;
-        }
-        pmHDU *hdu = chip->hdu;     // HDU for headers
-        getMetadata(chipResults, hdu->header, data->headers);
-    }
-    if (psListLength(data->concepts) > 0) {
-        if (fits && chip->hdu && !pmChipReadHeader(chip, fits)) {
-            psError (PS_ERR_IO, false, "trouble reading chip header\n");
-            psFree(chipResults);
-            return PS_EXIT_DATA_ERROR;
-        }
-        pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_ALL, false, false, config->database);
-        getMetadata(chipResults, chip->concepts, data->concepts);
-    }
-
-    if (view->cell >= 0) {
-        pmCell *cell = cells->data[view->cell]; // Cell of interest
-        result = ppStatsCell(chipResults, cell, fits, data, config);
-        if (result != PS_EXIT_SUCCESS) {
-            psError(PS_ERR_UNKNOWN, false, "trouble with cell stats for %d\n", view->cell);
-        }
-        addToHierarchy(chipResults, fpaResults, chipName, "Results for chip");
-        psFree (chipResults);
-        if (fits) {
-            pmChipFreeData(chip);
-        }
-        return result;
-    }
-
-    // Iterate over cells
-    for (int i = 0; i < cells->n; i++) {
-        pmCell *cell = cells->data[i];  // Cell of interest
-        result = ppStatsCell(chipResults, cell, fits, data, config);
-        if (result != PS_EXIT_SUCCESS) {
-            psError(PS_ERR_UNKNOWN, false, "trouble with cell stats for %d\n", i);
-            if (fits) {
-                pmChipFreeData(chip);
-            }
-            psFree(chipResults);
-            return result;
-        }
-    }
-
-    addToHierarchy(chipResults, fpaResults, chipName, "Results for chip");
-    psFree (chipResults);
-    if (fits) {
-        pmChipFreeData(chip);
-    }
-    return PS_EXIT_SUCCESS;
-}
 
 psMetadata *ppStatsLoop(psExit *result,
@@ -401,5 +29,5 @@
         }
         pmHDU *hdu = fpa->hdu;          // HDU for headers
-        getMetadata(newResults, hdu->header, data->headers);
+        p_ppStatsGetMetadata(newResults, hdu->header, data->headers);
     }
     if (psListLength(data->concepts) > 0) {
@@ -412,26 +40,18 @@
         }
         pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_ALL, false, config->database);
-        getMetadata(newResults, fpa->concepts, data->concepts);
+        p_ppStatsGetMetadata(newResults, fpa->concepts, data->concepts);
     }
 
+    // check if we can legitimately iterate to the chip level
     psArray *chips = fpa->chips;        // Array of chips
-    if (view->chip >= 0) {
-        pmChip *chip = chips->data[view->chip]; // Chip of interest
-        *result = ppStatsChip(newResults, chip, fits, view, data, config);
-        if (*result != PS_EXIT_SUCCESS) {
-            psError(PS_ERR_UNKNOWN, false, "trouble with stats for cell %d\n", view->cell);
-            psFree (view);
-            psFree (newResults);
-            return NULL;
-        }
-        if (fits) {
-            pmFPAFreeData(fpa);
-        }
-        psFree(view);
-        return newResults;
+    if (view->chip >= chips->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Desired chip view (%d) doesn't match "
+                "number of chips (%ld)\n", view->chip, chips->n);
+        return NULL;
     }
 
-    // Iterate over chips
+    // Iterate over chips (if view->chip is set, skip all others)
     for (int i = 0; i < chips->n; i++) {
+	if ((view->chip >= 0) && (i != view->chip)) continue; 
         pmChip *chip = chips->data[i];  // Chip of interest
         *result = ppStatsChip(newResults, chip, fits, view, data, config);
@@ -447,6 +67,6 @@
         pmFPAFreeData(fpa);
     }
+
     psFree(view);
-
     return newResults;
 }
Index: /trunk/ppStats/src/ppStatsReadout.c
===================================================================
--- /trunk/ppStats/src/ppStatsReadout.c	(revision 14003)
+++ /trunk/ppStats/src/ppStatsReadout.c	(revision 14003)
@@ -0,0 +1,152 @@
+# include "ppStatsInternal.h"
+
+psExit ppStatsReadout(psMetadata *cellResults, // Metadata holding the chip results
+		      pmReadout *readout,	// Cell for which to get statistics
+		      int nReadout,	// readout number
+		      ppStatsData *data,	// The data
+		      const pmConfig *config // Configuration
+    )
+{
+    assert(cellResults);
+    assert(readout);
+    assert(data);
+    assert(config);
+
+    /*** psphot and psastro put their results on the readout->analysis metadata (PSPHOT.HEADER,
+	 PSASTRO.HEADER).  we need to pull quantities of interest from those locations. to do
+	 this, we need to select the appropriate readout.  ***/
+
+    // Extract Header and Concept values from the Cell and Readout->analysis level
+
+    psString readoutName = NULL;
+    psStringAppend (&readoutName, "READOUT.%02d", nReadout);
+
+    // find or generate a metadata to hold the results
+    bool mdok;                          // Status of MD lookup
+    psMetadata *readoutResults = psMemIncrRefCounter(psMetadataLookupMetadata(&mdok, cellResults, readoutName));
+    if (!mdok || !readoutResults) {
+        readoutResults = psMetadataAlloc();
+    }
+
+    // Extract Header values
+    if (psListLength(data->headers)) {
+	// extract from data->analysis output MD entries
+	if (psListLength(data->analysis)) {
+	    p_ppStatsGetAnalysis (readoutResults, data->headers, readout->analysis, data->analysis);
+	}
+    }
+
+    // Do we want to measure pixel statistics?
+    if (!data->doStats && !psListLength(data->summary)) {
+        // Nothing further to do --- don't want to waste our time reading the data
+        goto readoutDone;
+    }
+
+    if (!readout->image) {
+        psLogMsg(__func__, PS_LOG_WARN, "No image associated with readout --- ignored.\n");
+        goto readoutDone;
+    }
+
+    // Measure basic image statistics (means, stdevs, etc).
+    if (data->sample <= 0.0) {
+        if (!psImageStats(data->stats, readout->image, readout->mask, data->maskVal)) {
+            psWarning("Unable to perform statistics on readout %s --- ignored.\n", readoutName);
+            goto statsDone;
+        }
+    } else {
+        // Apply sampling
+        psImage *image = readout->image; // The image of interest
+        psImage *mask = readout->mask; // The mask image
+        int numSamples = data->sample * image->numCols * image->numRows; // Number of samples
+        int sampleSpace = 1.0 / data->sample; // Space between samples
+        psVector *sampleValues = psVectorAlloc(numSamples, PS_TYPE_F32); // Vector of samples
+        psVector *sampleMask = NULL;  // Corresponding mask
+        if (mask) {
+            sampleMask = psVectorAlloc(numSamples, PS_TYPE_U8);
+        }
+        for (int i = 0; i < numSamples; i++) {
+            int j = i * sampleSpace;
+            int y = j / image->numCols;
+            int x = j % image->numCols;
+            sampleValues->data.F32[i] = image->data.F32[y][x];
+            if (mask) {
+                sampleMask->data.U8[i] = mask->data.U8[y][x];
+            }
+        }
+        if (!psVectorStats(data->stats, sampleValues, NULL, sampleMask, data->maskVal)) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to perform statistics on readout %s --- "
+                     "ignored.\n", readoutName);
+            psFree(sampleValues);
+            psFree(sampleMask);
+            goto statsDone;
+        }
+        psFree(sampleValues);
+        psFree(sampleMask);
+    }
+
+#define WRITE_STAT(SYMBOL, NAME, SOURCE) \
+    if (data->stats->options & SYMBOL) { \
+        psMetadataAddF32(readoutResults, PS_LIST_TAIL, NAME, 0, NULL, data->stats->SOURCE); \
+    }
+
+    WRITE_STAT(PS_STAT_SAMPLE_MEAN,     "SAMPLE_MEAN",   sampleMean);
+    WRITE_STAT(PS_STAT_SAMPLE_MEDIAN,   "SAMPLE_MEDIAN", sampleMedian);
+    WRITE_STAT(PS_STAT_SAMPLE_STDEV,    "SAMPLE_STDEV",  sampleStdev);
+    WRITE_STAT(PS_STAT_SAMPLE_QUARTILE, "SAMPLE_LQ",     sampleLQ);
+    WRITE_STAT(PS_STAT_SAMPLE_QUARTILE, "SAMPLE_UQ",     sampleUQ);
+    WRITE_STAT(PS_STAT_ROBUST_MEDIAN,   "ROBUST_MEDIAN", robustMedian);
+    WRITE_STAT(PS_STAT_ROBUST_STDEV,    "ROBUST_STDEV",  robustStdev);
+    WRITE_STAT(PS_STAT_ROBUST_QUARTILE, "ROBUST_LQ",     robustLQ);
+    WRITE_STAT(PS_STAT_ROBUST_QUARTILE, "ROBUST_UQ",     robustUQ);
+    WRITE_STAT(PS_STAT_ROBUST_QUARTILE, "ROBUST_N50",    robustN50);
+    WRITE_STAT(PS_STAT_FITTED_MEAN,     "FITTED_MEAN",   fittedMean);
+    WRITE_STAT(PS_STAT_FITTED_STDEV,    "FITTED_STDEV",  fittedStdev);
+    WRITE_STAT(PS_STAT_CLIPPED_MEAN,    "CLIPPED_MEAN",  clippedMean);
+    WRITE_STAT(PS_STAT_CLIPPED_STDEV,   "CLIPPED_STDEV", clippedStdev);
+
+    // measure other types of statistics tests
+
+statsDone:
+    // count saturated pixels
+    if (psListLength(data->summary) > 0) {
+        bool get_nSatPixels = false;
+        bool get_fSatPixels = false;
+
+        psListIterator *iterator = psListIteratorAlloc(data->summary, PS_LIST_HEAD, false);
+        psString choice;
+        while ((choice = psListGetAndIncrement(iterator))) {
+            if (!strcasecmp (choice, "SAT_PIXEL_NUM"))  get_nSatPixels = true;
+            if (!strcasecmp (choice, "SAT_PIXEL_FRAC")) get_fSatPixels = true;
+        }
+
+        if (!get_nSatPixels && !get_fSatPixels) {
+            goto readoutDone;
+        }
+
+        // Get the "concepts" of interest
+        float saturation = psMetadataLookupF32(&mdok, readout->parent->concepts, "CELL.SATURATION"); // Saturation level
+        if (!mdok || isnan(saturation)) {
+            psLogMsg(__func__, PS_LOG_WARN, "CELL.SATURATION is not set --- unable to measure N_SAT_PIXELS.\n");
+            if (get_nSatPixels) psMetadataAddS32(readoutResults, PS_LIST_TAIL, "SAT_PIXEL_NUM", 0, NULL, 0);
+            if (get_fSatPixels) psMetadataAddF32(readoutResults, PS_LIST_TAIL, "SAT_PIXEL_FRAC", 0, NULL, NAN);
+            goto readoutDone;
+        }
+
+        int nSatPixels = 0;
+        for (int j = 0; j < readout->image->numRows; j++) {
+            for (int i = 0; i < readout->image->numCols; i++) {
+                if (readout->image->data.F32[j][i] >= saturation) {
+                    nSatPixels ++;
+                }
+            }
+        }
+        if (get_nSatPixels) psMetadataAddS32(readoutResults, PS_LIST_TAIL, "SAT_PIXEL_NUM", 0, NULL, nSatPixels);
+        if (get_fSatPixels) psMetadataAddF32(readoutResults, PS_LIST_TAIL, "SAT_PIXEL_FRAC", 0, NULL, nSatPixels / (double)(readout->image->numRows * readout->image->numCols));
+    }
+
+readoutDone:
+    // Add the readout results to the cell
+    p_ppStatsAddToHierarchy(readoutResults, cellResults, readoutName, "Results for readout");
+    psFree (readoutResults);
+    return PS_EXIT_SUCCESS;
+}
Index: /trunk/ppStats/src/ppStatsUtils.c
===================================================================
--- /trunk/ppStats/src/ppStatsUtils.c	(revision 14003)
+++ /trunk/ppStats/src/ppStatsUtils.c	(revision 14003)
@@ -0,0 +1,76 @@
+# include "ppStatsInternal.h"
+
+void p_ppStatsGetMetadata(psMetadata *target, // Target for metadata
+			  psMetadata *source, // Source for metadata
+			  psList *list    // List containing keywords
+    )
+{
+    psListIterator *iterator = psListIteratorAlloc(list, PS_LIST_HEAD, false); // Iterator
+    psString name;                      // Name from iteration
+    while ((name = psListGetAndIncrement(iterator))) {
+        psMetadataItem *item = psMetadataLookup(source, name); // Item of interest, or NULL
+        if (item) {
+            psMetadataAddItem(target, item, PS_LIST_TAIL, 0);
+        }
+    }
+    psFree(iterator);
+    return;
+}
+
+void p_ppStatsGetAnalysis(psMetadata *target, // Output Target for metadata
+			  psList *headers,    // List containing desired keywords
+			  psMetadata *source, // Input Source for metadata
+			  psList *list        // List containing analysis blocks
+    )
+{
+    bool status;
+
+    psListIterator *iterator = psListIteratorAlloc(list, PS_LIST_HEAD, false); // Iterator
+    psString name;                      // Name from iteration
+    while ((name = psListGetAndIncrement(iterator))) {
+        psMetadata *folder = psMetadataLookupMetadata(&status, source, name); // Item of interest, or NULL
+        if (folder) {
+            p_ppStatsGetMetadata (target, folder, headers); 
+        }
+    }
+    psFree(iterator);
+    return;
+}
+
+bool p_ppStatsDoThis(psList *toDoList,    // List of things to do
+		     const char *this     // The name of "this"
+    )
+{
+    if (psListLength(toDoList) == 0) {
+        // No list --- do everything
+        return true;
+    }
+
+    psListIterator *iterator = psListIteratorAlloc(toDoList, PS_LIST_HEAD, false); // Iterator
+    psString test;                      // Test string, from iteration
+    while ((test = psListGetAndIncrement(iterator))) {
+        if (strcmp(this, test) == 0) {
+            // It's in the list --- do it
+            psFree(iterator);
+            return true;
+        }
+    }
+    psFree(iterator);
+    // Couldn't find it --- don't do it
+    return false;
+}
+
+void p_ppStatsAddToHierarchy(psMetadata *source, // Source to add
+			     psMetadata *target, // Target to which to add
+			     const char *name, // Name of source
+			     const char *comment // Comment for source
+    )
+{
+    if (psListLength(source->list) > 0 && !psMetadataLookup(target, name)) {
+        psMetadataAdd(target, PS_LIST_TAIL, name, PS_DATA_METADATA,
+                      comment, source);
+    }
+    return;
+}
+
+
