Index: /trunk/ppStats/Makefile.am
===================================================================
--- /trunk/ppStats/Makefile.am	(revision 7902)
+++ /trunk/ppStats/Makefile.am	(revision 7902)
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+CLEANFILES = *~ core core.*
Index: /trunk/ppStats/autogen.sh
===================================================================
--- /trunk/ppStats/autogen.sh	(revision 7902)
+++ /trunk/ppStats/autogen.sh	(revision 7902)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=ppStats
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL=aclocal
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+#($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+#        DIE=1
+#}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+#($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+#        DIE=1
+#}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+#$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+#$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /trunk/ppStats/configure.ac
===================================================================
--- /trunk/ppStats/configure.ac	(revision 7902)
+++ /trunk/ppStats/configure.ac	(revision 7902)
@@ -0,0 +1,62 @@
+AC_PREREQ(2.59)
+
+AC_INIT([ppStats], [0.0.1], [price@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+dnl AM_CONFIG_HEADER([config.h])
+AM_MAINTAINER_MODE
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC
+AC_PROG_INSTALL
+dnl AC_PROG_LIBTOOL
+
+dnl handle debug building
+AC_ARG_ENABLE(optimize,
+  [AS_HELP_STRING(--enable-optimize,enable compiler optimization)],
+  [AC_MSG_RESULT(compile optimization enabled)
+   CFLAGS="${CFLAGS=} -O2"],
+  [AC_MSG_RESULT([compile optimization disabled])
+   CFLAGS="${CFLAGS=} -O0 -g"]
+)
+
+ppStats_CFLAGS="-Wall -Werror -std=c99"
+ppStats_LDFLAGS=""
+                                                                                
+dnl handle debug building
+AC_ARG_ENABLE(optimize,
+  [AS_HELP_STRING(--enable-optimize,enable compiler optimization)],
+  [AC_MSG_RESULT(compile optimization enabled)
+   CFLAGS="${CFLAGS=} -O2"],
+  [AC_MSG_RESULT([compile optimization disabled])
+   CFLAGS="${CFLAGS=} -O0 -g"]
+)
+ 
+dnl handle path coverage checking
+AC_ARG_ENABLE(coverage,
+  [AS_HELP_STRING(--enable-coverage,enable path coverage checking)],
+  [AC_MSG_RESULT(path coverage enabled)
+   CFLAGS="${CFLAGS=} -fprofile-arcs -ftest-coverage -pg"]
+)
+ 
+PKG_CHECK_MODULES([PSLIB], [pslib >= 0.9.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodule >= 0.0.0])
+
+dnl handle profiler building
+AC_ARG_ENABLE(profile,
+  [AS_HELP_STRING(--enable-profile,enable compiler profiler information inclusion)],
+  [AC_MSG_RESULT(compile optimization enabled)
+  ppStats_CFLAGS="${ppStats_CFLAGS} -g -pg"
+  ppStats_LDFLAGS="${ppStats_LDFLAGS} -pg -Wl,--start-group -Wl,-Bstatic"]
+  )
+   
+AC_SUBST([ppStats_CFLAGS])
+AC_SUBST([ppStats_LDFLAGS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+])
+AC_OUTPUT
Index: /trunk/ppStats/src/Makefile.am
===================================================================
--- /trunk/ppStats/src/Makefile.am	(revision 7902)
+++ /trunk/ppStats/src/Makefile.am	(revision 7902)
@@ -0,0 +1,23 @@
+bin_PROGRAMS = ppStats
+
+ppStats_CFLAGS += $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+ppStats_LDFLAGS += $(PSMODULE_LIBS) $(PSLIB_LIBS) -Wl,-Bdynamic
+
+ppStats_SOURCES =		\
+	ppStats.c		\
+	ppStatsData.c		\
+	ppStatsLoop.c		\
+	ppStatsSetup.c
+
+noinst_HEADERS =		\
+	ppStatsData.h		\
+	ppStats.h		\
+	ppStatsSetup.h
+
+CLEANFILES = *~
+
+clean-local:
+	-rm -f TAGS
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
Index: /trunk/ppStats/src/ppStats.c
===================================================================
--- /trunk/ppStats/src/ppStats.c	(revision 7902)
+++ /trunk/ppStats/src/ppStats.c	(revision 7902)
@@ -0,0 +1,29 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppStats.h"
+
+int main(int argc, char *argv[])
+{
+
+    psLibInit(NULL);
+    psTimerStart(TIMERNAME);
+
+    // Parse the configuration and arguments
+    pmConfig *config = pmConfigRead(&argc, argv);
+
+    // Get the options, open the files
+    ppStatsData *data = ppStatsSetup(config);
+
+    // Go through the FPA and do the hard work
+    ppStatsLoop(data, config);
+
+    psFree(data);
+    psFree(config);
+    pmConceptsDone();
+    pmConfigDone();
+    psLibFinalize();
+
+    return EXIT_SUCCESS;
+}
Index: /trunk/ppStats/src/ppStats.config
===================================================================
--- /trunk/ppStats/src/ppStats.config	(revision 7902)
+++ /trunk/ppStats/src/ppStats.config	(revision 7902)
@@ -0,0 +1,20 @@
+### Example ppStats recipe
+
+MULTI	HEADERS		# May specify multiple HEADERS
+HEADERS		STR	HEADER1,HEADER2			# Read these headers
+HEADERS		STR	HEADER3				# Read these headers too
+MULTI	STATS		# May specify multiple STATS
+STATS		STR	SAMPLE_MEAN,SAMPLE_STDEV	# Calculate these statistics
+STATS		STR	ROBUST_MEDIAN			# Calculate these statistics too
+
+SAMPLE		F32	0.1				# Fraction of cell to sample
+
+MULTI	CHIPS		# May specify multiple CHIPS
+CHIPS		STR	chip00, chip01			# Look at these chips
+CHIPS		STR	chip22				# Look at this chip too
+
+MULTI	CELLS		# May specify multiple CELLS
+CELLS		STR	LeftCell,RightCell		# Look at these cells
+CELLS		STR	UpsideDownCell			# Look at this cell too
+
+MASKVAL		U8	0xff
Index: /trunk/ppStats/src/ppStats.h
===================================================================
--- /trunk/ppStats/src/ppStats.h	(revision 7902)
+++ /trunk/ppStats/src/ppStats.h	(revision 7902)
@@ -0,0 +1,19 @@
+#ifndef PP_FRINGE_H
+#define PP_FRINGE_H
+
+#define RECIPE "PPSTATS"
+#define TIMERNAME "PPSTATS"
+
+#include <psmodules.h>
+#include "ppStatsData.h"
+
+// Set up the options and input/output files
+ppStatsData *ppStatsSetup(pmConfig *config // Configuration
+    );
+
+// Loop over the input image and do all the hard work
+void ppStatsLoop(ppStatsData *data,     // The data
+                 const pmConfig *config // Configuration
+    );
+
+#endif
Index: /trunk/ppStats/src/ppStatsData.c
===================================================================
--- /trunk/ppStats/src/ppStatsData.c	(revision 7902)
+++ /trunk/ppStats/src/ppStatsData.c	(revision 7902)
@@ -0,0 +1,48 @@
+#include <stdio.h>
+#include <pslib.h>
+
+#include "ppStatsData.h"
+
+static void statsDataFree(ppStatsData *data // Data to free
+    )
+{
+    // inName and region are not on the psLib memory system (they are from argv).
+    psFree(data->inFPA);
+    if (data->inFile) {
+        psFitsClose(data->inFile);
+        data->inFile = NULL;
+    }
+    if (data->outFile) {
+        fclose(data->outFile);
+        data->outFile = NULL;
+    }
+    psFree(data->headers);
+    psFree(data->chips);
+    psFree(data->cells);
+    psFree(data->stats);
+
+    return;
+}
+
+
+ppStatsData *ppStatsDataAlloc(void)
+{
+    ppStatsData *data = psAlloc(sizeof(ppStatsData)); // Newly allocated data
+    psMemSetDeallocator(data, (psFreeFunc)statsDataFree);
+
+    data->inName = NULL;
+    data->inFile = NULL;
+    data->outName = NULL;
+    data->outFile = NULL;
+    data->inFPA = NULL;
+    data->sample = 0;
+    data->maskVal = 0;
+    data->headers = psListAlloc(NULL);
+    data->chips = psListAlloc(NULL);
+    data->cells = psListAlloc(NULL);
+    data->stats = psStatsAlloc(0);
+    data->doStats = false;
+
+    return data;
+}
+
Index: /trunk/ppStats/src/ppStatsData.h
===================================================================
--- /trunk/ppStats/src/ppStatsData.h	(revision 7902)
+++ /trunk/ppStats/src/ppStatsData.h	(revision 7902)
@@ -0,0 +1,27 @@
+#ifndef PP_STATS_DATA_H
+#define PP_STATS_DATA_H
+
+#include <pslib.h>
+#include <psmodules.h>
+
+typedef struct {
+    // Inputs
+    const char *inName;                 // Input FITS image file
+    const char *outName;                // Output filename
+    psFits *inFile;                     // Input file handle
+    FILE *outFile;                      // Output file handle
+    pmFPA *inFPA;                       // Input FPA
+    // Options
+    float sample;                       // Fraction of cell to sample for statistics
+    psMaskType maskVal;                 // Mask value for images
+    psList *headers;                    // Headers to read
+    psList *chips;                      // Chips to look at
+    psList *cells;                      // Cells to look at
+    psStats *stats;                     // Statistics to calculate
+    bool doStats;                       // Do statistics?
+} ppStatsData;
+
+// Allocator
+ppStatsData *ppStatsDataAlloc(void);
+
+#endif
Index: /trunk/ppStats/src/ppStatsLoop.c
===================================================================
--- /trunk/ppStats/src/ppStatsLoop.c	(revision 7902)
+++ /trunk/ppStats/src/ppStatsLoop.c	(revision 7902)
@@ -0,0 +1,186 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppStats.h"
+
+
+static void getHeaders(psMetadata *target, // Target for headers
+                       psMetadata *source, // Source header
+                       psListIterator *keywordsIter // Iterator for header keywords
+    )
+{
+    psListIteratorSet(keywordsIter, PS_LIST_HEAD);
+    psString keyword;                    // Header from iteration
+    while ((keyword = psListGetAndIncrement(keywordsIter))) {
+        psMetadataAddItem(target, psMetadataLookup(source, keyword));
+    }
+    return;
+}
+
+
+void ppStatsLoop(ppStatsData *data,     // The data
+                 const pmConfig *config // Configuration
+    )
+{
+    psMetadata *fpaResults = psMetadataAlloc(); // Metadata to hold the FPA results
+
+    psListIterator *headersIter = psListIteratorAlloc(data->headers, PS_LIST_HEAD, false); // Headers iterator
+
+
+    // Iterate through the FPA
+    pmFPA *fpa = data->inFPA;           // The FPA of interest
+    if (psListLength(data->headers) > 0 && fpa->hdu && pmFPAHeader(fpa, data->inFile)) {
+        pmHDU *hdu = fpa->hdu;          // HDU for headers
+        getHeaders(fpaResults, hdu->header, headersIter);
+    }
+
+    psArray *chips = fpa->chips;        // Array of component chips
+    for (long i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The chip of interest
+        if (!chip) {
+            continue;
+        }
+        const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+        psMetadata *chipResults = psMetadataAlloc(); // Metadata to hold the chip-level results
+
+        if (psListLength(data->headers) > 0 && chip->hdu && pmChipHeader(chip, data->inFile)) {
+            pmHDU *hdu = chip->hdu;     // HDU for headers
+            getHeaders(chipResults, hdu->header, headersIter);
+        }
+
+        psArray *cells = chip->cells;   // Array of component cells
+        for (long j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j]; // The cell of interest
+            if (!cell) {
+                continue;
+            }
+            const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+            psMetadata *cellResults = psMetadataAlloc(); // Metadata to hold the cell-level results
+
+            if (psListLength(data->headers) > 0 && cell->hdu && pmCellHeader(cell, data->inFile)) {
+                pmHDU *hdu = cell->hdu;     // HDU for headers
+                getHeaders(cellResults, hdu->header, headersIter);
+                psMetadataAdd(chipResults, PS_LIST_TAIL, cellName, PS_DATA_METADATA,
+                              "Results for cell", cellResults);
+            }
+
+            if (!data->doStats) {
+                // Nothing further to do --- don't want to waste our time reading the data
+                continue;
+            }
+
+            if (!pmCellRead(cell, data->inFile, config->database)) {
+                psLogMsg(__func__, PS_LOG_WARN, "Unable to read chip %s cell %s\n", chipName, cellName);
+                pmCellFreeData(cell);
+                continue;
+            }
+
+            psArray *readouts = cell->readouts; // Array of component readouts
+            if (readouts->n == 0) {
+                pmCellFreeData(cell);
+                continue;
+            }
+            if (readouts->n > 1) {
+                psLogMsg(__func__, PS_LOG_WARN, "Multiple readouts (%ld) present in chip %s cell %s --- "
+                         "using only the first.\n", readouts->n, chipName, cellName);
+            }
+            pmReadout *readout = readouts->data[0]; // The readout of interest
+            if (!readout->image) {
+                psLogMsg(__func__, PS_LOG_WARN, "No image associated with readout in chip %s cell %s --- "
+                         "ignored.\n", chipName, cellName);
+                pmCellFreeData(cell);
+                continue;
+            }
+
+            // Do the statistics
+            if (data->sample <= 0.0) {
+                if (!psImageStats(data->stats, readout->image, readout->mask, data->maskVal)) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Unable to perform statistics on chip %s cell %s --- "
+                             "ignored.\n", chipName, cellName);
+                    pmCellFreeData(cell);
+                    continue;
+                }
+            } else {
+                // Apply sampling
+                psImage *image = readout->image; // The image of interest
+                psImage *mask = readout->mask; // The mask image
+                int numSamples = sample * image->numCols * image->numRows; // Number of samples
+                int sampleSpace = 1.0 / 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);
+                }
+                samples->n = numSamples;
+                for (int i = 0; i < numSamples; i++) {
+                    int j = i * sampleSpace;
+                    int y = j / image->numRows;
+                    int x = j % image->numRows;
+                    sampleValues->data.F32[i] = image->data.F32[y][x];
+                    if (mask) {
+                        sampleMask->data.U8 = mask->data.U8[y][x];
+                    }
+                }
+                if (!psVectorStats(data->stats, sampleValues, NULL, sampleMask, data->maskVal)) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Unable to perform statistics on chip %s cell %s --- "
+                             "ignored.\n", chipName, cellName);
+                    psFree(sampleValues);
+                    psFree(sampleMask);
+                    pmCellFreeData(cell);
+                    continue;
+                }
+                psFree(sampleValues);
+                psFree(sampleMask);
+            }
+
+            #define WRITE_STAT(SYMBOL, NAME, SOURCE) \
+            if (data->stats & 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);
+
+           // If we didn't add it before, add it now
+            if (!psMetadataLookup(&mdok, chipResults, cellName)) {
+                psMetadataAdd(chipResults, PS_LIST_TAIL, cellName, PS_DATA_METADATA,
+                              "Results for cell", cellResults);
+            }
+
+            psFree(cellResults);
+            pmCellFreeData(cell);
+        }
+        pmChipFreeData(chip);
+        if (psListLength(chipResults->list) > 0) {
+            psMetadataAdd(fpaResults, PS_LIST_TAIL, chipName, PS_DATA_METADATA,
+                          "Results for chip", chipResults);
+        }
+        psFree(chipResults);
+    }
+    pmFPAFreeData(fpa);
+    psFree(headersIter);
+
+    psString output = psMetadataConfigFormat(fpaResults);
+    psFree(fpaResults);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate configuration file with result.\n");
+        return;
+    }
+    fprintf(data->outFile, "%s", output);
+    psFree(output);
+
+    return;
+}
Index: /trunk/ppStats/src/ppStatsSetup.c
===================================================================
--- /trunk/ppStats/src/ppStatsSetup.c	(revision 7902)
+++ /trunk/ppStats/src/ppStatsSetup.c	(revision 7902)
@@ -0,0 +1,268 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <string.h>
+
+#include "ppStats.h"
+
+// Print usage information and die
+static void usageAndDie(pmConfig *config      // Configuration (contains the arguments list)
+    )
+{
+    printf("Return statistics .\n\n"
+           "Usage:\n"
+           "\t%s INPUT.fits OUTPUT.fits\n"
+           "\n", config->argv[0]);
+    psArgumentHelp(config->arguments);
+    psFree(config);
+    psLibFinalize();
+    pmConceptsDone();
+    pmConfigDone();
+    exit(EXIT_FAILURE);
+}
+
+ppStatsData *ppStatsSetup(pmConfig *config // Configuration
+    )
+{
+    // Setup and parse command-line arguments
+    psMetadata *arguments = config->arguments; // Arguments
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-chip", PS_META_DUPLICATE_OK, "Chip to inspect", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-cell", PS_META_DUPLICATE_OK, "Cell to inspect", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-header", PS_META_DUPLICATE_OK, "Header to look up", NULL);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-mean", 0, "Calculate sample mean", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-stdev", 0, "Calculate sample standard deviation", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-median", 0, "Calculate sample median", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-quartile", 0, "Calculate sample quartiles", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-robust-median", 0, "Calculate robust median", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-robust-stdev", 0, "Calculate robust standard deviation", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-robust-quartile", 0, "Calculate robust quartile range", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-fitted-mean", 0, "Calculate fitted mean", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-fitted-stdev", 0, "Calculate fitted standard deviation", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-clipped-mean", 0, "Calculate clipped median", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-clipped-stdev", 0, "Calculate clipped standard deviation", false);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-iter", 0, "Clipping iterations", 0);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-rej", 0, "Clipping level", NAN);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-sample", 0, "Sampling fraction", 0.0);
+
+    if (*config->argc == 1) {
+        // No command-line arguments: print the help
+        usageAndDie(config);
+    }
+    if (!psArgumentParse(arguments, config->argc, config->argv) || *config->argc < 2) {
+        printf("Unable to parse command-line arguments.\n\n");
+        usageAndDie(config);
+    }
+
+    // Parse the command-line options
+    ppStatsData *data = ppStatsDataAlloc(); // The data
+    data->inName = config->argv[1];
+    data->outName = config->argv[2];
+
+    // Get the list of chips, cells and headers
+    #define GET_LIST_FROM_ARGUMENTS(NAME, FLAG, TARGET) { \
+        psMetadataIterator *iterator = psMetadataIteratorAlloc(arguments, PS_LIST_HEAD, "^" FLAG "$"); \
+        psMetadataItem *item; \
+        while ((item = psMetadataGetAndIncrement(chipIter))) { \
+            if (item->type != PS_DATA_STRING) { \
+                psLogMsg(__func__, PS_LOG_WARN, NAME " name is not of type STRING (%x) --- ignored.\n", \
+                         item->type); \
+                continue; \
+            } \
+            psListAdd(TARGET, PS_LIST_TAIL, item->data.V); \
+        } \
+        psFree(iterator); \
+    }
+
+    GET_LIST_FROM_ARGUMENTS("Chip", "-chip", data->chips);
+    GET_LIST_FROM_ARGUMENTS("Cell", "-cell", data->cells);
+    GET_LIST_FROM_ARGUMENTS("Header", "-header", data->headers);
+
+    // Set the statistics options
+    #define STATS_OPTION(NAME, SYMBOL) \
+    if (psMetadataLookupBool(NULL, arguments, NAME)) { \
+        data->stats->options |= SYMBOL; \
+        data->doStats = true; \
+    } \
+
+    STATS_OPTION("-mean",     PS_STAT_SAMPLE_MEAN);
+    STATS_OPTION("-stdev",    PS_STAT_SAMPLE_STDDEV);
+    STATS_OPTION("-median",   PS_STAT_SAMPLE_MEDIAN);
+    STATS_OPTION("-quartile", PS_STAT_SAMPLE_QUARTILE);
+    STATS_OPTION("-robust-median",   PS_STAT_ROBUST_MEDIAN);
+    STATS_OPTION("-robust-stdev",    PS_STAT_ROBUST_STDEV);
+    STATS_OPTION("-robust-quartile", PS_STAT_ROBUST_QUARTILE);
+    STATS_OPTION("-fitted-mean",   PS_STAT_FITTED_MEAN);
+    STATS_OPTION("-fitted-stdev",  PS_STAT_FITTED_STDEV);
+    STATS_OPTION("-clipped-mean",  PS_STAT_CLIPPED_MEAN);
+    STATS_OPTION("-clipped-stdev", PS_STAT_CLIPPED_STDEV);
+    data->stats->clipSigma = psMetadataLookupF32(NULL, arguments, "-rej");
+    data->stats->clipIter = psMetadataLookupS32(NULL, arguments, "-iter");
+    data->sample = psMetadataLookupF32(NULL, arguments, "-sample");
+
+
+    // Open the input file, determine the camera
+    data->inFile = psFitsOpen(data->inName, "r");
+    if (!data->inFile) {
+        psError(PS_ERR_IO, false, "Unable to open input file %s\n", data->inName);
+        goto die;
+    }
+    psMetadata *header = psFitsReadHeader(NULL, data->inFile); // The FITS (primary) header
+    psMetadata *format = pmConfigCameraFormatFromHeader(config, header);
+    if (!format) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine camera format for %s\n", data->inName);
+        psFree(header);
+        goto die;
+    }
+    data->inFPA = pmFPAConstruct(config->camera);
+    if (!data->inFPA) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to construct FPA for %s\n", data->inName);
+        psFree(header);
+        psFree(format);
+        goto die;
+    }
+    pmFPAview *view = pmFPAAddSourceFromHeader(data->inFPA, header, format);
+    psFree(header);
+    psFree(format);
+    if (!view) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to add input file %s to FPA.\n", data->inName);
+        goto die;
+    }
+    psFree(view);
+
+    // Open the output file
+    data->outFile = fopen(data->outName, "w");
+    if (!data->outFile) {
+        psError(PS_ERR_IO, false, "Unable to open output file %s\n", data->outName);
+        goto die;
+    }
+
+
+    // Determine recipe parameters
+    bool mdok;                          // Status of MD lookup
+    psMetadata *recipe = psMetadataLookupMD(&mdok, config->recipes, RECIPENAME);
+    if (!mdok || !recipe) {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to find recipe %s.\n", RECIPENAME);
+        return data;
+    }
+
+    // Strings in a recipe may be defined multiply (with MULTI) or listed on a single line
+    #define GET_LIST_FROM_RECIPE(NAME, TARGET) \
+    if (psListLength(TARGET) < 0) { \
+        psMetadataIterator *iterator = psMetadataIteratorAlloc(recipe, PS_LIST_HEAD, "^" NAME "$"); \
+        psMetadataItem *item; \
+        int numItem = 0; /* Occurrence of the item in the recipe; to help the user in case of trouble */ \
+        while ((item = psMetadataGetAndIncrement(iterator))) { \
+            numItem++; \
+            if (item->type != PS_DATA_STRING) { \
+                psLogMsg(__func__, PS_LOG_WARN, NAME "Occurrence %d of %s in the recipe is " \
+                         "not of type STRING (%x) --- ignored.\n", numItem, NAME, item->type); \
+                continue; \
+            } \
+            /* Parse into a list of independent values */ \
+            psList *values = psStringSplit(item->data.V, " ,;", false); \
+            /* Copy into the target */ \
+            psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false); \
+            psString valueString; \
+            while ((valueString = psListGetAndIncrement(valuesIter))) { \
+                psListAdd(TARGET, PS_LIST_TAIL, valueString); \
+            } \
+            psFree(valuesIter); \
+            psFree(values); \
+        } \
+        psFree(iterator);\
+    }
+
+    GET_LIST_FROM_RECIPE("CHIPS", data->chips);
+    GET_LIST_FROM_RECIPE("CELLS", data->chips);
+    GET_LIST_FROM_RECIPE("HEADER", data->chips);
+
+    // Parse the statistics options
+    psList *recipeStats = psListAlloc(NULL);
+    GET_LIST_FROM_RECIPE("STATS", recipeStats);
+    if (psListLength(recipeStats) > 0) {
+        psListIterator *iterator = psListIteratorAlloc(recipeStats, PS_LIST_HEAD, false);
+        psString stat;
+
+        #define CHECK_STAT(NAME, SYMBOL) \
+        if (strcasecmp(stat, NAME) == 0) { \
+            data->stats->options |= SYMBOL; \
+            data->doStats = true; \
+        }
+
+        while ((stat = psListGetAndIncrement(iterator))) {
+            // This might look a little weird if automatically formated,
+            // but it's legal C once the pre-processing has been done, and
+            // it reads a lot better than a whole heap of "if-then-else" statements.
+            CHECK_STAT("MEAN",     PS_STAT_SAMPLE_MEAN) else
+            CHECK_STAT("STDEV",    PS_STAT_SAMPLE_STDEV) else
+            CHECK_STAT("MEDIAN",   PS_STAT_SAMPLE_MEDIAN) else
+            CHECK_STAT("QUARTILE", PS_STAT_SAMPLE_QUARTILE) else
+            CHECK_STAT("SAMPLE_MEAN",     PS_STAT_SAMPLE_MEAN) else
+            CHECK_STAT("SAMPLE_STDEV",    PS_STAT_SAMPLE_STDEV) else
+            CHECK_STAT("SAMPLE_MEDIAN",   PS_STAT_SAMPLE_MEDIAN) else
+            CHECK_STAT("SAMPLE_QUARTILE", PS_STAT_SAMPLE_QUARTILE) else
+            CHECK_STAT("ROBUST",          PS_STAT_ROBUST_MEDIAN) else
+            CHECK_STAT("ROBUST_MEDIAN",   PS_STAT_ROBUST_MEDIAN) else
+            CHECK_STAT("ROBUST_STDEV",    PS_STAT_ROBUST_STDEV) else
+            CHECK_STAT("ROBUST_QUARTILE", PS_STAT_ROBUST_QUARTILE) else
+            CHECK_STAT("FITTED",       PS_STAT_FITTED_MEAN) else
+            CHECK_STAT("FITTED_MEAN",  PS_STAT_FITTED_MEAN) else
+            CHECK_STAT("FITTED_STDEV", PS_STAT_ROBUST_STDEV) else
+            CHECK_STAT("CLIPPED",       PS_STAT_CLIPPED_MEAN) else
+            CHECK_STAT("CLIPPED_MEAN",  PS_STAT_CLIPPED_MEAN) else
+            CHECK_STAT("CLIPPED_STDEV", PS_STAT_CLIPPED_STDEV) else {
+                psLogMsg(__func__, PS_LOG_WARN, "Can't interpret STATS entry in recipe: "
+                         "%s --- ignored.\n", stat);
+            }
+        }
+    }
+    psFree(recipeStats);
+
+   // Clipping options
+    if (data->stats->clipIter == 0 && isnan(data->stats->clipSigma)) {
+        int iter = psMetadataLookupS32(&mdok, recipe, "ITER"); // Number of clipping iterations
+        if (mdok && iter > 0) {
+            data->stats->clipIter = iter;
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "ITER in recipe is not of type S32 and positive --- "
+                     "retaining default.\n");
+        }
+        float rej = psMetadataLookupF32(&mdok, recipe, "REJ"); // Clipping level
+        if (mdok && rej > 0) {
+            data->stats->clipSigma = rej;
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "REJ in recipe is not of type F32 and positive --- "
+                     "retaining default.\n");
+        }
+
+    }
+
+    if (data->sample == 0) {
+        float sample = psMetadataLookupF32(&mdok, recipe, "SAMPLE"); // Sample fraction
+        if (mdok && sample > 0) {
+            data->sample = sample;
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "SAMPLE in recipe is not of type F32 and positive --- "
+                     "retaining default.\n");
+        }
+    }
+
+    psMaskType maskVal = psMetadataLookupU8(&mdok, recipe, "MASKVAL"); // Mask value
+    if (mdok) {
+        data->maskVal = maskVal;
+    } else {
+        psLogMsg(__func__, PS_LOG_WARN, "ITER in recipe is not of type U8 --- retaining default.\n");
+    }
+
+
+    return data;
+
+    // Common path for error conditions: clean up and exit.
+die:
+    psFree(config);
+    psFree(data);
+    pmConceptsDone();
+    pmConfigDone();
+    psLibFinalize();
+    exit(EXIT_FAILURE);
+}
Index: /trunk/ppStats/src/ppStatsSetup.h
===================================================================
--- /trunk/ppStats/src/ppStatsSetup.h	(revision 7902)
+++ /trunk/ppStats/src/ppStatsSetup.h	(revision 7902)
@@ -0,0 +1,5 @@
+#ifndef PP_FRINGE_SETUP_H
+#define PP_FRINGE_SETUP_H
+
+
+#endif
