Index: trunk/ppImage/src/Makefile.am
===================================================================
--- trunk/ppImage/src/Makefile.am	(revision 19603)
+++ trunk/ppImage/src/Makefile.am	(revision 19928)
@@ -23,5 +23,4 @@
 	ppImageAstrom.c \
 	ppImageAddstar.c \
-	ppImageStats.c \
 	ppImageStatsOutput.c \
 	ppImagePixelStats.c \
@@ -53,5 +52,4 @@
 	ppImageAstrom.c \
 	ppImageAddstar.c \
-	ppImageStats.c \
 	ppImageStatsOutput.c \
 	ppImagePixelStats.c \
Index: trunk/ppImage/src/ppFocus.c
===================================================================
--- trunk/ppImage/src/ppFocus.c	(revision 19603)
+++ trunk/ppImage/src/ppFocus.c	(revision 19928)
@@ -9,5 +9,5 @@
     psLibInit(NULL);
 
-    psTimerStart(TIMERNAME);
+    psTimerStart(TIMER_TOTAL);
 
     // Parse the configuration and arguments
@@ -60,5 +60,5 @@
     ppFocusFitFWHM (config, focus, fwhm);
 
-    psLogMsg ("ppFocus", 3, "complete ppFocus run: %f sec\n", psTimerMark (TIMERNAME));
+    psLogMsg ("ppFocus", 3, "complete ppFocus run: %f sec\n", psTimerMark (TIMER_TOTAL));
 
     // Cleaning up
Index: trunk/ppImage/src/ppImage.c
===================================================================
--- trunk/ppImage/src/ppImage.c	(revision 19603)
+++ trunk/ppImage/src/ppImage.c	(revision 19928)
@@ -9,5 +9,5 @@
     psLibInit(NULL);
 
-    psTimerStart(TIMERNAME);
+    psTimerStart(TIMER_TOTAL);
 
     // Parse the configuration and arguments
@@ -38,5 +38,5 @@
     }
 
-    psLogMsg ("ppImage", 3, "Complete ppImage run: %f sec\n", psTimerMark(TIMERNAME));
+    psLogMsg("ppImage", PS_LOG_INFO, "Complete ppImage run: %f sec\n", psTimerMark(TIMER_TOTAL));
 
     // Cleaning up
Index: trunk/ppImage/src/ppImage.h
===================================================================
--- trunk/ppImage/src/ppImage.h	(revision 19603)
+++ trunk/ppImage/src/ppImage.h	(revision 19928)
@@ -17,5 +17,7 @@
 
 #define RECIPE_NAME "PPIMAGE"           // Name of the recipe to use
-#define TIMERNAME "ppImage"             // Name of timer
+#define TIMER_TOTAL   "PPIMAGE.TOTAL"   // Name of timer for total time
+#define TIMER_DETREND "PPIMAGE.DETREND" // Name of timer for detrend time
+#define TIMER_PHOT    "PPIMAGE.PHOT"    // Name of timer for photometry time
 
 // Options for ppImage processing
@@ -151,9 +153,9 @@
 bool ppImageDefineFile (pmConfig *config, pmFPA *input, char *filerule, char *argname, pmFPAfileType fileType, pmDetrendType detrendType);
 
-// calculate stats, including MD5
-bool ppImageStats (pmConfig *config, pmChip *chip, const pmFPAview *inputView, const ppImageOptions *options);
-
 // write stats to output file
-bool ppImageStatsOutput (pmConfig *config, const ppImageOptions *options);
+bool ppImageStatsOutput(pmConfig *config, // Configuration
+                        psMetadata *stats, // Statistics output
+                        const ppImageOptions *options // Options
+    );
 
 
@@ -197,10 +199,17 @@
 
 // calculate stats, including MD5
-bool ppImagePixelStats (pmConfig *config, const ppImageOptions *options, const pmFPAview *inputView);
+bool ppImagePixelStats(pmConfig *config,// Configuration
+                       psMetadata *stats, // Statistics output
+                       const ppImageOptions *options, // Options
+                       const pmFPAview *inputView // View to data
+    );
 
 // calculate stats from headers and concepts
-bool ppImageMetadataStats (pmConfig *config, const ppImageOptions *options);
-
-void ppImageFileCheck (pmConfig *config);
+bool ppImageMetadataStats(pmConfig *config, // Configuration
+                          psMetadata *stats, // Statistics output
+                          const ppImageOptions *options // Options
+    );
+
+void ppImageFileCheck(pmConfig *config);
 
 /// Dump memory summary to text file
Index: trunk/ppImage/src/ppImageCleanup.c
===================================================================
--- trunk/ppImage/src/ppImageCleanup.c	(revision 19603)
+++ trunk/ppImage/src/ppImageCleanup.c	(revision 19928)
@@ -23,5 +23,5 @@
     // fprintf (stderr, "Found %d leaks at %s\n", Nleaks, "ppImage");
 
-    fprintf (stderr, "Found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "ppImage");
+    fprintf(stderr, "Found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "ppImage");
 
     return;
Index: trunk/ppImage/src/ppImageLoop.c
===================================================================
--- trunk/ppImage/src/ppImageLoop.c	(revision 19603)
+++ trunk/ppImage/src/ppImageLoop.c	(revision 19928)
@@ -5,20 +5,17 @@
 #include "ppImage.h"
 
-# define ESCAPE(MESSAGE) { \
+#define ESCAPE(MESSAGE) { \
   psError(PS_ERR_UNKNOWN, false, MESSAGE); \
-  psFree (view); \
+  psFree(view); \
   return false; \
 }
 
-bool ppImageLoop (pmConfig *config, ppImageOptions *options)
+bool ppImageLoop(pmConfig *config, ppImageOptions *options)
 {
-    bool status;
-    pmChip *chip;
-    pmCell *cell;
-    pmReadout *readout;
-
-    // measure the total elapsed time in ppImageLoop.
-    psTimerStart ("ppImageLoop");
-
+    psMetadata *stats = options->doStats ? psMetadataAlloc() : NULL; // Statistics to output
+    float timeDetrend = 0;              // Amount of time spent in detrend
+    float timePhot = 0;                 // Amount of time spent in photometry
+
+    bool status;                        // Status of MD lookup
     pmFPAfile *input = psMetadataLookupPtr(&status, config->files, "PPIMAGE.INPUT");
     if (!status) {
@@ -40,6 +37,9 @@
 
     // files associated with the science image
-    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for FPA");
-
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        ESCAPE("load failure for FPA");
+    }
+
+    pmChip *chip;                       // Chip from FPA
     while ((chip = pmFPAviewNextChip(view, input->fpa, 1)) != NULL) {
         psLogMsg ("ppImageLoop", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
@@ -47,6 +47,10 @@
             continue;
         }
-        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for Chip");
-
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            ESCAPE("load failure for Chip");
+        }
+
+        psTimerStart(TIMER_DETREND);
+        pmCell *cell;                   // Cell from chip
         while ((cell = pmFPAviewNextCell(view, input->fpa, 1)) != NULL) {
             psLogMsg ("ppImageLoop", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
@@ -54,5 +58,7 @@
                 continue;
             }
-            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for Cell");
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                ESCAPE("load failure for Cell");
+            }
 
             // Put version information into the header
@@ -70,6 +76,9 @@
 
             // 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 (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    ESCAPE("load failure for Readout");
+                }
                 if (!readout->data_exists) {
                     continue;
@@ -78,10 +87,12 @@
                 // XXX set the options->*Mask values here (after the mask images have been loaded
                 // and before any of the value are used)
-                if (!ppImageSetMaskBits (config, options))
-                    ESCAPE ("Unable to set bit masks");
+                if (!ppImageSetMaskBits(config, options)) {
+                    ESCAPE("Unable to set bit masks");
+                }
 
                 // perform the detrend analysis
-                if (!ppImageDetrendReadout(config, options, view))
-                    ESCAPE ("Unable to detrend readout");
+                if (!ppImageDetrendReadout(config, options, view)) {
+                    ESCAPE("Unable to detrend readout");
+                }
             }
         }
@@ -89,32 +100,42 @@
         // Apply the fringe correction
         if (options->doFringe) {
-            if (!ppImageDetrendFringeApply (config, chip, view, options))
-                ESCAPE ("Unable to defringe");
+            if (!ppImageDetrendFringeApply(config, chip, view, options)) {
+                ESCAPE("Unable to defringe");
+            }
         }
 
         // measure various statistics for this image
-        if (!ppImagePixelStats (config, options, view))
-            ESCAPE ("Unable to measures stats for image");
-
-        if (!ppImageMosaicChip(config, options, view, "PPIMAGE.CHIP", "PPIMAGE.OUTPUT"))
-            ESCAPE ("Unable to mosaic chip");
+        if (!ppImagePixelStats(config, stats, options, view)) {
+            ESCAPE("Unable to measures stats for image");
+        }
+        if (!ppImageMosaicChip(config, options, view, "PPIMAGE.CHIP", "PPIMAGE.OUTPUT")) {
+            ESCAPE("Unable to mosaic chip");
+        }
+        timeDetrend += psTimerClear(TIMER_DETREND);
+
 
         // we perform photometry on the readouts of this chip in the output
+        psTimerStart(TIMER_PHOT);
         if (options->doPhotom) {
-            if (!ppImagePhotom(config, view)) ESCAPE ("error running photometry.");
-        }
+            if (!ppImagePhotom(config, view)) {
+                ESCAPE("error running photometry.");
+            }
+        }
+        timePhot += psTimerClear(TIMER_PHOT);
 
         // replace the masked pixels with the background level
         if (options->replaceMasked) {
-            if (!ppImageReplaceBackground (config, view, options))
-                ESCAPE ("Unable to replace masked pixels with background level");
+            if (!ppImageReplaceBackground(config, view, options)) {
+                ESCAPE("Unable to replace masked pixels with background level");
+            }
         }
 
         // binning (used for display) must take place after the background is replaced, if desired
-        if (!ppImageRebinChip(config, view, options, "PPIMAGE.BIN1"))
-            ESCAPE ("Unable to bin chip (level 1).");
-
-        if (!ppImageRebinChip(config, view, options, "PPIMAGE.BIN2"))
-            ESCAPE ("Unable to bin chip (level 2).");
+        if (!ppImageRebinChip(config, view, options, "PPIMAGE.BIN1")) {
+            ESCAPE("Unable to bin chip (level 1).");
+        }
+        if (!ppImageRebinChip(config, view, options, "PPIMAGE.BIN2")) {
+            ESCAPE("Unable to bin chip (level 2).");
+        }
 
         // Close cells (XXX shouldn't pmFPAfileClose iterate down as needed?)
@@ -124,35 +145,57 @@
                 continue;
             }
-            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) ESCAPE ("save failure for Cell");
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                ESCAPE("save failure for Cell");
+            }
         }
 
         // Close chip
-        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) ESCAPE ("save failure for Chip");
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            ESCAPE("save failure for Chip");
+        }
     }
 
     // generate the full-scale FPA mosaic
-    if (!ppImageMosaicFPA(config, options, "PPIMAGE.OUTPUT.FPA1", "PPIMAGE.BIN1")) ESCAPE ("failure in FPA Mosaic (level 1)");
-    if (!ppImageMosaicFPA(config, options, "PPIMAGE.OUTPUT.FPA2", "PPIMAGE.BIN2")) ESCAPE ("failure in FPA Mosaic (level 2)");
+    if (!ppImageMosaicFPA(config, options, "PPIMAGE.OUTPUT.FPA1", "PPIMAGE.BIN1")) {
+        ESCAPE("failure in FPA Mosaic (level 1)");
+    }
+    if (!ppImageMosaicFPA(config, options, "PPIMAGE.OUTPUT.FPA2", "PPIMAGE.BIN2")) {
+        ESCAPE("failure in FPA Mosaic (level 2)");
+    }
 
     // we perform astrometry on all chips after sources have been detected
     // this also performs the psastro file IO
     if (options->doAstromChip || options->doAstromMosaic) {
-        if (!ppImageAstrom(config)) ESCAPE ("error running astrometry.");
+        if (!ppImageAstrom(config)) {
+            ESCAPE("error running astrometry.");
+        }
     }
 
     if (psTraceGetLevel("ppImage") >= 3) {
-        ppImageFileCheck (config);
+        ppImageFileCheck(config);
     }
 
     // Write out summary statistics
-    if (!ppImageMetadataStats (config, options)) ESCAPE ("Unable to write statistics file.");
+    if (!ppImageMetadataStats(config, stats, options)) {
+        ESCAPE("Unable to write statistics file.");
+    }
+
+    // Output and Close FPA
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        ESCAPE("save failure for FPA");
+    }
+
+    psFree(view);
 
     // Write out summary statistics
-    if (!ppImageStatsOutput (config, options)) ESCAPE ("Unable to write statistics file.");
-
-    // Output and Close FPA
-    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) ESCAPE ("save failure for FPA");
-
-    psFree(view);
+    if (options->doStats) {
+        psMetadataAddF32(stats, PS_LIST_TAIL, "DT_DET", 0, "Time spent detrending (sec)", timeDetrend);
+        psMetadataAddF32(stats, PS_LIST_TAIL, "DT_PHOT", 0, "Time spent photometering (sec)", timePhot);
+        psMetadataAddF32(stats, PS_LIST_TAIL, "DT_TOTAL", 0, "Total time (sec)", psTimerMark(TIMER_TOTAL));
+        if (!ppImageStatsOutput(config, stats, options)) {
+            ESCAPE("Unable to write statistics file.");
+        }
+    }
+
     return true;
 }
Index: trunk/ppImage/src/ppImageMetadataStats.c
===================================================================
--- trunk/ppImage/src/ppImageMetadataStats.c	(revision 19603)
+++ trunk/ppImage/src/ppImageMetadataStats.c	(revision 19928)
@@ -6,10 +6,10 @@
 
 // calculate stats from headers and concepts
-bool ppImageMetadataStats (pmConfig *config, const ppImageOptions *options) {
+bool ppImageMetadataStats(pmConfig *config, psMetadata *stats, const ppImageOptions *options)
+{
+    bool mdok;              // Status of MD lookup
 
-    bool mdok;              // Status of MD lookup
-    
     if (!options->doStats) {
-	return true;
+        return true;
     }
 
@@ -20,6 +20,7 @@
 
     if (!outImage && !outPhotom && !outAstrom) {
-	psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find any output file (PPIMAGE.OUTPUT, PSPHOT.OUTPUT, PSASTRO.OUTPUT).");
-	return false;
+        psError(PS_ERR_UNEXPECTED_NULL, true,
+                "Unable to find any output file (PPIMAGE.OUTPUT, PSPHOT.OUTPUT, PSASTRO.OUTPUT).");
+        return false;
     }
 
@@ -27,34 +28,19 @@
     pmFPAfile *output = outAstrom;
     if (!output) {
-	output = outPhotom;
+        output = outPhotom;
     }
     if (!output) {
-	output = outImage;
+        output = outImage;
     }
 
-    // select or create the fpa-level analysis stats metadata for PPIMAGE.OUTPUT:
-    psMetadata *stats = psMetadataLookupPtr (&mdok, outImage->fpa->analysis, "PPIMAGE.STATS");
-    psMemIncrRefCounter (stats);
-    if (!stats) {
-	stats = psMetadataAlloc ();
-	if (!psMetadataAdd (outImage->fpa->analysis, PS_LIST_TAIL, "PPIMAGE.STATS", PS_DATA_METADATA, "stats container", stats)) {
-	    psError(PS_ERR_UNKNOWN, false, "Unable to push stats on fpa.analysis.");
-	    psFree (stats);
-	    return false;
-	}
-    }
-
-    // extract stats for the complete fpa 
+    // extract stats for the complete fpa
     pmFPAview *view = pmFPAviewAlloc(0);
-
     if (!ppStatsMetadata(stats, output->fpa, view, options->maskValue, config)) {
-	psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate stats for image.");
-	psFree(view);
-	psFree(stats);
-	return false;
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate stats for image.");
+        psFree(view);
+        return false;
     }
 
     psFree(view);
-    psFree(stats);
     return true;
 }
Index: trunk/ppImage/src/ppImagePixelStats.c
===================================================================
--- trunk/ppImage/src/ppImagePixelStats.c	(revision 19603)
+++ trunk/ppImage/src/ppImagePixelStats.c	(revision 19928)
@@ -6,6 +6,7 @@
 
 // calculate stats, including MD5
-bool ppImagePixelStats (pmConfig *config, const ppImageOptions *options, const pmFPAview *inputView) {
-
+bool ppImagePixelStats(pmConfig *config, psMetadata *stats, const ppImageOptions *options,
+                       const pmFPAview *inputView)
+{
     bool mdok;              // Status of MD lookup
 
@@ -13,89 +14,69 @@
     pmFPAview *view = pmFPAviewAlloc(0); // View for local processing
     *view = *inputView;
-    
+
     // perform the analysis of for this FPA
     pmFPAfile *output = psMetadataLookupPtr(&mdok, config->files, "PPIMAGE.OUTPUT");
     if (!output) {
-	psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find output file (PPIMAGE.OUTPUT).");
-	psFree (view);
-	return false;
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find output file (PPIMAGE.OUTPUT).");
+        psFree(view);
+        return false;
     }
 
     // select the corresponding chip
-    pmChip *chip = pmFPAviewThisChip (view, output->fpa);
+    pmChip *chip = pmFPAviewThisChip(view, output->fpa);
     if (!chip) {
-	psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find requested chip.");
-	psFree (view);
-	return false;
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find requested chip.");
+        psFree(view);
+        return false;
     }
-    
+
     // Perform statistics for this chip
     if (options->doStats) {
+        if (!ppStatsPixels(stats, output->fpa, view, options->maskValue, config)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate stats for image.");
+            psFree(view);
+            return false;
+        }
 
-	// select or create the fpa-level analysis stats metadata (save on PPIMAGE.OUPUT:PPIMAGE.STATS)
-	psMetadata *stats = psMetadataLookupPtr (&mdok, output->fpa->analysis, "PPIMAGE.STATS");
-	psMemIncrRefCounter (stats);
-	if (!stats) {
-	    stats = psMetadataAlloc ();
-	    if (!psMetadataAdd (output->fpa->analysis, PS_LIST_TAIL, "PPIMAGE.STATS", PS_DATA_METADATA, "stats container", stats)) {
-		psError(PS_ERR_UNKNOWN, false, "Unable to push stats on fpa.analysis.");
-		psFree (view);
-		psFree (stats);
-		return false;
-	    }
-	}
+        // extract the fringe amplitude from the analysis
+        if (options->doFringe && !ppStatsFringe(stats, chip, "FRINGE", "FRINGE.SOLUTION")) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to extract fringe solution for image.");
+            psFree(view);
+            return false;
+        }
 
-	if (!ppStatsPixels(stats, output->fpa, view, options->maskValue, config)) {
-	    psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate stats for image.");
-	    psFree (view);
-	    psFree (stats);
-	    return false;
-	}
-
-	// extract the fringe amplitude from the analysis
-	if (options->doFringe && !ppStatsFringe(stats, chip, "FRINGE", "FRINGE.SOLUTION")) {
-	    psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to extract fringe solution for image.");
-	    psFree (view);
-	    psFree (stats);
-	    return false;
-	}
-
-	// extract the fringe residual amplitude from the analysis
-	if (options->doFringe && !ppStatsFringe(stats, chip, "FRINGE_RESID", "FRINGE.RESIDUAL.SOLUTION")) {
-	    psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to extract fringe solution for image.");
-	    psFree (view);
-	    psFree (stats);
-	    return false;
-	}
-	
-	psFree (stats);
+        // extract the fringe residual amplitude from the analysis
+        if (options->doFringe && !ppStatsFringe(stats, chip, "FRINGE_RESID", "FRINGE.RESIDUAL.SOLUTION")) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to extract fringe solution for image.");
+            psFree(view);
+            return false;
+        }
     }
 
-    pmCell *cell = NULL;
-    pmReadout *readout = NULL;
 
     view->cell = view->readout = -1;
+    pmCell *cell = NULL;                // Cell from iteration
     while ((cell = pmFPAviewNextCell(view, output->fpa, 1)) != NULL) {
-	if (!cell->process || !cell->file_exists) {
-	    continue;
-	}
+        if (!cell->process || !cell->file_exists) {
+            continue;
+        }
 
-	// Add MD5 information for cell
-	pmHDU *hdu = pmHDUFromCell(cell); // HDU that owns the cell
-	while ((readout = pmFPAviewNextReadout(view, output->fpa, 1)) != NULL) {
-	    const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
-	    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME");
+        // Add MD5 information for cell
+        pmHDU *hdu = pmHDUFromCell(cell); // HDU that owns the cell
+        pmReadout *readout = NULL;      // Readout from iteration
+        while ((readout = pmFPAviewNextReadout(view, output->fpa, 1)) != NULL) {
+            const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
+            const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME");
 
-	    psString headerName = NULL; // Header name for MD5
-	    psStringAppend(&headerName, "MD5_%s_%s_%d", chipName, cellName, view->readout);
+            psString headerName = NULL; // Header name for MD5
+            psStringAppend(&headerName, "MD5_%s_%s_%d", chipName, cellName, view->readout);
 
-	    psVector *md5 = psImageMD5(readout->image); // md5 hash
-	    psString md5string = psMD5toString(md5); // String
-	    psFree (md5);
-	    psMetadataAddStr(hdu->header, PS_LIST_TAIL, headerName, PS_META_REPLACE,
-			     "Image MD5", md5string);
-	    psFree (md5string);
-	    psFree (headerName);
-	}
+            psVector *md5 = psImageMD5(readout->image); // md5 hash
+            psString md5string = psMD5toString(md5); // String
+            psFree(md5);
+            psMetadataAddStr(hdu->header, PS_LIST_TAIL, headerName, PS_META_REPLACE, "Image MD5", md5string);
+            psFree(md5string);
+            psFree(headerName);
+        }
     }
 
Index: trunk/ppImage/src/ppImageStats.c
===================================================================
--- trunk/ppImage/src/ppImageStats.c	(revision 19603)
+++ 	(revision )
@@ -1,104 +1,0 @@
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include "ppImage.h"
-
-// calculate stats, including MD5
-bool ppImageStats (pmConfig *config, pmChip *chip, const pmFPAview *inputView, const ppImageOptions *options) {
-
-    bool mdok;              // Status of MD lookup
-    pmCell *cell = NULL;
-    pmReadout *readout = NULL;
-
-    pmFPAview *view = pmFPAviewAlloc(0); // View for local processing
-    *view = *inputView;
-    
-    // select or create the fpa-level analysis stats metadata:
-    psMetadata *stats = psMetadataLookupPtr (&mdok, chip->parent->analysis, "PPIMAGE.STATS");
-    psMemIncrRefCounter (stats);
-    if (!stats) {
-	stats = psMetadataAlloc ();
-	if (!psMetadataAdd (chip->parent->analysis, PS_LIST_TAIL, "PPIMAGE.STATS", PS_DATA_METADATA, "stats container", stats)) {
-	    psError(PS_ERR_UNKNOWN, false, "Unable to push stats on fpa.analysis.");
-	    psFree (view);
-	    psFree (stats);
-	    return false;
-	}
-    }
-
-    view->cell = view->readout = -1;
-    while ((cell = pmFPAviewNextCell(view, chip->parent, 1)) != NULL) {
-	if (!cell->process || !cell->file_exists) {
-	    continue;
-	}
-
-	// Perform statistics on the detrended cell
-	if (options->doStats) {
-
-	    pmFPAfile *outImage = psMetadataLookupPtr(&mdok, config->files, "PPIMAGE.OUTPUT");
-	    pmFPAfile *outPhotom = psMetadataLookupPtr(&mdok, config->files, "PSPHOT.OUTPUT");
-	    pmFPAfile *outAstrom = psMetadataLookupPtr(&mdok, config->files, "PSASTRO.OUTPUT");
-
-	    if (!outImage && !outPhotom && !outAstrom) {
-		psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find any output file (PPIMAGE.OUTPUT, PSPHOT.OUTPUT, PSASTRO.OUTPUT).");
-		psFree (view);
-		psFree(stats);
-		return false;
-	    }
-
-	    // get the latest output product available
-	    pmFPAfile *output = outAstrom;
-	    if (!output) {
-		output = outPhotom;
-	    }
-	    if (!output) {
-		output = outImage;
-	    }
-
-	    if (!ppStatsFPA(stats, output->fpa, view, options->maskValue, config)) {
-		psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate stats for image.");
-		psFree(view);
-		psFree(stats);
-		return false;
-	    }
-
-	    // extract the fringe amplitude from the analysis
-	    if (options->doFringe && !ppStatsFringe(stats, chip, "FRINGE", "FRINGE.SOLUTION")) {
-		psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to extract fringe solution for image.");
-		psFree(view);
-		psFree(stats);
-		return false;
-	    }
-
-	    // extract the fringe residual amplitude from the analysis
-	    if (options->doFringe && !ppStatsFringe(stats, chip, "FRINGE_RESID", "FRINGE.RESIDUAL.SOLUTION")) {
-		psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to extract fringe solution for image.");
-		psFree(view);
-		psFree(stats);
-		return false;
-	    }
-	}
-
-	// Add MD5 information for cell
-	pmHDU *hdu = pmHDUFromCell(cell); // HDU that owns the cell
-	while ((readout = pmFPAviewNextReadout(view, chip->parent, 1)) != NULL) {
-	    const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
-	    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME");
-
-	    psString headerName = NULL; // Header name for MD5
-	    psStringAppend(&headerName, "MD5_%s_%s_%d", chipName, cellName, view->readout);
-
-	    psVector *md5 = psImageMD5(readout->image); // md5 hash
-	    psString md5string = psMD5toString(md5); // String
-	    psFree(md5);
-	    psMetadataAddStr(hdu->header, PS_LIST_TAIL, headerName, PS_META_REPLACE,
-			     "Image MD5", md5string);
-	    psFree(md5string);
-	    psFree(headerName);
-	}
-    }
-    psFree(view);
-    psFree(stats);
-    return true;
-}
Index: trunk/ppImage/src/ppImageStatsOutput.c
===================================================================
--- trunk/ppImage/src/ppImageStatsOutput.c	(revision 19603)
+++ trunk/ppImage/src/ppImageStatsOutput.c	(revision 19928)
@@ -6,30 +6,11 @@
 
 // write stats to output file
-bool ppImageStatsOutput (pmConfig *config, const ppImageOptions *options) {
-
+bool ppImageStatsOutput(pmConfig *config, psMetadata *stats, const ppImageOptions *options)
+{
     bool mdok;
 
     // measure statistics, or ignore?
-    if (!options->doStats) return true;
-
-    // PPIMAGE.STATS is stored on PPIMAGE.OUTPUT
-    pmFPAfile *output = psMetadataLookupPtr(&mdok, config->files, "PPIMAGE.OUTPUT");
-    if (!output) {
-        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find PPIMAGE.OUTPUT entry in config file.\n");
-        return false;
-    }
-
-    // select the fpa-level analysis stats metadata:
-    psMetadata *stats = psMetadataLookupPtr (&mdok, output->fpa->analysis, "PPIMAGE.STATS");
-    if (!stats) {
-        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find PPIMAGE.STATS entry in output fpa analysis.\n");
-        return false;
-    }
-
-    // convert the stats MDC to a string
-    char *statsMDC = psMetadataConfigFormat(stats);
-    if (!statsMDC || strlen(statsMDC) == 0) {
-        psError(PS_ERR_IO, false, "Unable to serialize stats metadata.\n");
-        return false;
+    if (!options->doStats) {
+        return true;
     }
 
@@ -37,15 +18,12 @@
     const char *statsName = psMetadataLookupStr(&mdok, config->arguments, "STATS"); // Filename for statistics
     if (!statsName && !strlen(statsName)) {
-        psError (PS_ERR_UNEXPECTED_NULL, false, "missing STATS entry in arguments list.");
-        psFree(statsMDC);
+        psError(PS_ERR_UNEXPECTED_NULL, false, "missing STATS entry in arguments list.");
         return false;
     }
 
-    // convert to a real UNIX filename
+    // Write out
     psString resolved = pmConfigConvertFilename(statsName, config, true, true); // Resolved filename
-    FILE *statsFile = fopen (resolved, "w");
-    if (!statsFile) {
-        psError(PS_ERR_IO, true, "Unable to open statistics file %s for writing.\n", resolved);
-        psFree(statsMDC);
+    if (!psMetadataConfigWrite(stats, resolved)) {
+        psError(PS_ERR_IO, false, "Unable to serialize stats metadata.\n");
         psFree(resolved);
         return false;
@@ -53,10 +31,4 @@
     psFree(resolved);
 
-    // write the stats MDC to a file
-    // XXX why does this not call psMetadataConfigPrint?
-    fprintf(statsFile, "%s", statsMDC);
-
-    psFree(statsMDC);
-    fclose(statsFile);
     return true;
 }
