Index: trunk/ppStats/src/.cvsignore
===================================================================
--- trunk/ppStats/src/.cvsignore	(revision 8195)
+++ trunk/ppStats/src/.cvsignore	(revision 8337)
@@ -2,3 +2,6 @@
 Makefile
 Makefile.in
+.libs
+*.lo
+*.la
 ppStats
Index: trunk/ppStats/src/Makefile.am
===================================================================
--- trunk/ppStats/src/Makefile.am	(revision 8195)
+++ trunk/ppStats/src/Makefile.am	(revision 8337)
@@ -1,16 +1,32 @@
+lib_LTLIBRARIES = libppStats.la
+libppStats_la_CPPFLAGS = $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS) $(ppStats_CFLAGS)
+
 bin_PROGRAMS = ppStats
 
 ppStats_CFLAGS += $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
 ppStats_LDFLAGS += $(PSMODULE_LIBS) $(PSLIB_LIBS) -Wl,-Bdynamic
+ppStats_LDADD = libppStats.la
 
-ppStats_SOURCES =		\
+libppStats_la_SOURCES = 	\
 	ppStats.c		\
 	ppStatsData.c		\
 	ppStatsLoop.c		\
-	ppStatsSetup.c
+	ppStatsSetupFromRecipe.c
 
-noinst_HEADERS =		\
+ppStats_SOURCES =		\
+	ppStatsData.c		\
+	ppStatsLoop.c		\
+	ppStatsSetupFromArgs.c	\
+	ppStatsStandAlone.c
+
+include_HEADERS = 		\
+	ppStats.h		\
 	ppStatsData.h		\
-	ppStats.h
+	ppStatsLoop.h		\
+	ppStatsSetupFromRecipe.h
+
+noinst_HEADERS = 		\
+	ppStats.h		\
+	ppStatsSetupFromArgs.h
 
 CLEANFILES = *~
Index: trunk/ppStats/src/ppStats.c
===================================================================
--- trunk/ppStats/src/ppStats.c	(revision 8195)
+++ trunk/ppStats/src/ppStats.c	(revision 8337)
@@ -5,25 +5,22 @@
 #include "ppStats.h"
 
-int main(int argc, char *argv[])
+psMetadata *ppStats(psMetadata *out,    // Output metadata
+                    pmFPA *fpa,         // FPA for which to get statistics
+                    pmConfig *config    // Configuration
+    )
 {
+    // Get the options, open the files
+    ppStatsData *data = ppStatsSetupFromRecipe(NULL, config);
 
-    psLibInit(NULL);
-    psTimerStart(TIMERNAME);
-
-    // Parse the configuration and arguments
-    pmConfig *config = pmConfigRead(&argc, argv);
-
-    // Get the options, open the files
-    ppStatsData *data = ppStatsSetup(config);
+    if (data->fpa) {
+        psFree(data->fpa);
+    }
+    data->fpa = psMemIncrRefCounter(fpa);
 
     // Go through the FPA and do the hard work
-    ppStatsLoop(data, config);
+    out = ppStatsLoop(out, data, config);
 
     psFree(data);
-    psFree(config);
-    pmConceptsDone();
-    pmConfigDone();
-    psLibFinalize();
 
-    return EXIT_SUCCESS;
+    return out;
 }
Index: trunk/ppStats/src/ppStats.h
===================================================================
--- trunk/ppStats/src/ppStats.h	(revision 8195)
+++ trunk/ppStats/src/ppStats.h	(revision 8337)
@@ -3,17 +3,10 @@
 
 #define RECIPENAME "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
-    );
+#include "ppStatsSetupFromRecipe.h"
+#include "ppStatsLoop.h"
 
 #endif
Index: trunk/ppStats/src/ppStatsData.c
===================================================================
--- trunk/ppStats/src/ppStatsData.c	(revision 8195)
+++ trunk/ppStats/src/ppStatsData.c	(revision 8337)
@@ -8,12 +8,8 @@
 {
     // 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->fpa);
+    if (data->fits) {
+        psFitsClose(data->fits);
+        data->fits = NULL;
     }
     psFree(data->headers);
@@ -33,9 +29,6 @@
     psMemSetDeallocator(data, (psFreeFunc)statsDataFree);
 
-    data->inName = NULL;
-    data->inFile = NULL;
-    data->outName = NULL;
-    data->outFile = NULL;
-    data->inFPA = NULL;
+    data->fpa = NULL;
+    data->fits = NULL;
 
     data->headers = psListAlloc(NULL);
Index: trunk/ppStats/src/ppStatsData.h
===================================================================
--- trunk/ppStats/src/ppStatsData.h	(revision 8195)
+++ trunk/ppStats/src/ppStatsData.h	(revision 8337)
@@ -7,9 +7,6 @@
 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
+    psFits *fits;                       // Input file handle
+    pmFPA *fpa;                         // FPA to analyse
     // Stuff to output
     psStats *stats;                     // Statistics to calculate
Index: trunk/ppStats/src/ppStatsLoop.c
===================================================================
--- trunk/ppStats/src/ppStatsLoop.c	(revision 8195)
+++ trunk/ppStats/src/ppStatsLoop.c	(revision 8337)
@@ -5,4 +5,5 @@
 
 #include "ppStats.h"
+#include "ppStatsLoop.h"
 
 
@@ -47,9 +48,19 @@
 
 
-void ppStatsLoop(ppStatsData *data,     // The data
-                 const pmConfig *config // Configuration
+psMetadata *ppStatsLoop(psMetadata *fpaResults, // Metadata to hold the FPA results
+                        ppStatsData *data, // The data
+                        const pmConfig *config // Configuration
     )
 {
-    psMetadata *fpaResults = psMetadataAlloc(); // Metadata to hold the FPA results
+    PS_ASSERT_PTR_NON_NULL(data, NULL);
+    pmFPA *fpa = data->fpa;             // FPA to analyse
+    psFits *fits = data->fits;          // FITS file handle
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    if (!fpaResults) {
+        fpaResults = psMetadataAlloc();
+    }
+
+    bool mdok;                          // Status of MD lookup
 
     // Iterators for the headers and concepts
@@ -58,12 +69,15 @@
 
     // Iterate through the FPA
-    pmFPA *fpa = data->inFPA;           // The FPA of interest
     if (psListLength(data->headers) > 0 && fpa->hdu) {
-        pmFPAReadHeader(fpa, data->inFile);
+        if (fits) {
+            pmFPAReadHeader(fpa, fits);
+        }
         pmHDU *hdu = fpa->hdu;          // HDU for headers
         getMetadata(fpaResults, hdu->header, headersIter);
     }
     if (psListLength(data->concepts) > 0) {
-        pmFPAReadHeader(fpa, data->inFile);
+        if (fits) {
+            pmFPAReadHeader(fpa, fits);
+        }
         pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_ALL, false, config->database);
         getMetadata(fpaResults, fpa->concepts, conceptsIter);
@@ -82,8 +96,11 @@
         }
 
-        psMetadata *chipResults = psMetadataAlloc(); // Metadata to hold the chip-level results
+        psMetadata *chipResults = psMetadataLookupMD(&mdok, fpaResults, chipName); // Chip-level results
+        if (!mdok || !chipResults) {
+            chipResults = psMetadataAlloc();
+        }
 
         if (psListLength(data->headers) > 0 && chip->hdu) {
-            if (!pmChipReadHeader(chip, data->inFile)) {
+            if (fits && !pmChipReadHeader(chip, fits)) {
                 continue;
             }
@@ -92,5 +109,5 @@
         }
         if (psListLength(data->concepts) > 0) {
-            if (!pmChipReadHeader(chip, data->inFile)) {
+            if (fits && !pmChipReadHeader(chip, fits)) {
                 continue;
             }
@@ -112,8 +129,11 @@
             }
 
-            psMetadata *cellResults = psMetadataAlloc(); // Metadata to hold the cell-level results
+            psMetadata *cellResults = psMetadataLookupMD(&mdok, chipResults, cellName); // Cell-level results
+            if (!mdok || !cellResults) {
+                cellResults = psMetadataAlloc();
+            }
 
             if (psListLength(data->headers) > 0 && cell->hdu) {
-                if (!pmCellReadHeader(cell, data->inFile)) {
+                if (fits && !pmCellReadHeader(cell, fits)) {
                     continue;
                 }
@@ -122,5 +142,5 @@
             }
             if (psListLength(data->concepts) > 0) {
-                if (!pmCellReadHeader(cell, data->inFile)) {
+                if (fits && !pmCellReadHeader(cell, fits)) {
                     continue;
                 }
@@ -143,5 +163,5 @@
             }
 
-            if (!pmCellRead(cell, data->inFile, config->database)) {
+            if (fits && !pmCellRead(cell, fits, config->database)) {
                 psLogMsg(__func__, PS_LOG_WARN, "Unable to read chip %s cell %s\n", chipName, cellName);
                 pmCellFreeData(cell);
@@ -229,35 +249,31 @@
 
             // Add the cell results to the chip
-            psMetadataAdd(chipResults, PS_LIST_TAIL, cellName, PS_DATA_METADATA,
-                          "Results for cell", cellResults);
-
-            psFree(cellResults);
-            pmCellFreeData(cell);
-        }
-        pmChipFreeData(chip);
-        if (psListLength(chipResults->list) > 0) {
+            if (psListLength(cellResults->list) > 0 && !psMetadataLookup(chipResults, cellName)) {
+                psMetadataAdd(chipResults, PS_LIST_TAIL, cellName, PS_DATA_METADATA,
+                              "Results for cell", cellResults);
+                psFree(cellResults);
+            }
+
+            if (fits) {
+                pmCellFreeData(cell);
+            }
+        }
+        if (fits) {
+            pmChipFreeData(chip);
+        }
+
+        if (psListLength(chipResults->list) > 0 && !psMetadataLookup(fpaResults, chipName)) {
             psMetadataAdd(fpaResults, PS_LIST_TAIL, chipName, PS_DATA_METADATA,
                           "Results for chip", chipResults);
-        }
-        psFree(chipResults);
-    }
-    pmFPAFreeData(fpa);
+            psFree(chipResults);
+        }
+    }
+    if (fits) {
+        pmFPAFreeData(fpa);
+    }
+
     psFree(headersIter);
     psFree(conceptsIter);
 
-    if (psListLength(fpaResults->list) == 0) {
-        psError(PS_ERR_UNKNOWN, true, "No output.\n");
-        return;
-    }
-
-    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;
+    return fpaResults;
 }
Index: trunk/ppStats/src/ppStatsLoop.h
===================================================================
--- trunk/ppStats/src/ppStatsLoop.h	(revision 8337)
+++ trunk/ppStats/src/ppStatsLoop.h	(revision 8337)
@@ -0,0 +1,13 @@
+#ifndef PP_STATS_LOOP_H
+#define PP_STATS_LOOP_H
+
+#include <psmodules.h>
+#include "ppStatsData.h"
+
+// Loop over the input image and do all the hard work
+psMetadata *ppStatsLoop(psMetadata *fpaResults, // Metadata to hold the FPA results
+                        ppStatsData *data, // The data
+                        const pmConfig *config // Configuration
+    );
+
+#endif
Index: trunk/ppStats/src/ppStatsSetupFromArgs.c
===================================================================
--- trunk/ppStats/src/ppStatsSetupFromArgs.c	(revision 8337)
+++ trunk/ppStats/src/ppStatsSetupFromArgs.c	(revision 8337)
@@ -0,0 +1,197 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <string.h>
+
+#include "ppStats.h"
+#include "ppStatsData.h"
+#include "ppStatsSetupFromArgs.h"
+
+// This file is for setting up the required inputs from the command-line
+
+// Print usage information and die
+static void usageAndDie(pmConfig *config      // Configuration (contains the arguments list)
+    )
+{
+    printf("Return headers, concepts and/or image statistics.\n\n"
+           "Usage:\n"
+           "\t%s INPUT.fits [OUTPUT_NAME]\n"
+           "\n", config->argv[0]);
+    psArgumentHelp(config->arguments);
+    psFree(config);
+    psLibFinalize();
+    pmConceptsDone();
+    pmConfigDone();
+    exit(EXIT_FAILURE);
+}
+
+// Generate a list from the arguments
+static void listFromArguments(psMetadata *arguments, // Arguments to parse
+                              const char *name, // Name of the item, for error message
+                              const char *flag, // The flag for the argument of interest
+                              psList *target // The target list
+    )
+{
+    psString regex = NULL;              // Regular expression for the flag
+    psStringAppend(&regex, "^%s$", flag);
+    psMetadataIterator *iterator = psMetadataIteratorAlloc(arguments, PS_LIST_HEAD, regex); // Iterator
+    psFree(regex);
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iterator))) {
+        if (item->type != PS_DATA_STRING) {
+            psLogMsg(__func__, PS_LOG_WARN, "%s name is not of type STRING (%x) --- ignored.\n",
+                     name, item->type);
+            continue;
+        }
+        if (item->data.V && strlen(item->data.V) > 0) {
+            psListAdd(target, PS_LIST_TAIL, item->data.V);
+        }
+    }
+    psFree(iterator);
+
+    return;
+}
+
+// Set the statistics option; for arguments
+static inline void statsOptionArguments(psMetadata *arguments, // Arguments to parse
+                                        const char *name, // Name for option
+                                        ppStatsData *data, // Configuration data
+                                        psStatsOptions option // Option to check for
+    )
+{
+    if (psMetadataLookupBool(NULL, arguments, name)) {
+        data->stats->options |= option;
+        data->doStats = true;
+    }
+    return;
+}
+
+// Print out what we're going to do, from the list
+static void checkList(psList *list,     // List
+                      const char *name  // Name of list
+    )
+{
+    psString value;
+    psListIterator *iterator = psListIteratorAlloc(list, PS_LIST_HEAD, false);
+    while ((value = psListGetAndIncrement(iterator))) {
+        printf("%s: %s\n", name, value);
+    }
+    psFree(iterator);
+    return;
+}
+
+
+ppStatsData *ppStatsSetupFromArgs(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);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-concept", PS_META_DUPLICATE_OK, "Concept 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", 0.0);
+    psMetadataAddF32(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 && *config->argc != 3)) {
+        printf("Unable to parse command-line arguments.\n\n");
+        usageAndDie(config);
+    }
+
+    // Parse the command-line options
+    ppStatsData *data = ppStatsDataAlloc(); // The data
+    const char *inName = config->argv[1]; // Input file name
+    psArgumentRemove(1, config->argc, config->argv);
+
+    listFromArguments(arguments, "Chip", "-chip", data->chips);
+    listFromArguments(arguments, "Cell", "-cell", data->cells);
+    listFromArguments(arguments, "Header", "-header", data->headers);
+    listFromArguments(arguments, "Concept", "-concept", data->concepts);
+
+    // Set the statistics options
+    statsOptionArguments(arguments, "-mean", data,     PS_STAT_SAMPLE_MEAN);
+    statsOptionArguments(arguments, "-stdev", data,    PS_STAT_SAMPLE_STDEV);
+    statsOptionArguments(arguments, "-median", data,   PS_STAT_SAMPLE_MEDIAN);
+    statsOptionArguments(arguments, "-quartile", data, PS_STAT_SAMPLE_QUARTILE);
+    statsOptionArguments(arguments, "-robust-median", data,   PS_STAT_ROBUST_MEDIAN);
+    statsOptionArguments(arguments, "-robust-stdev", data,    PS_STAT_ROBUST_STDEV);
+    statsOptionArguments(arguments, "-robust-quartile", data, PS_STAT_ROBUST_QUARTILE);
+    statsOptionArguments(arguments, "-fitted-mean", data,   PS_STAT_FITTED_MEAN);
+    statsOptionArguments(arguments, "-fitted-stdev", data,  PS_STAT_FITTED_STDEV);
+    statsOptionArguments(arguments, "-clipped-mean", data,  PS_STAT_CLIPPED_MEAN);
+    statsOptionArguments(arguments, "-clipped-stdev", data, 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->fits = psFitsOpen(inName, "r");
+        if (!data->fits) {
+            psError(PS_ERR_IO, false, "Unable to open input file %s\n", inName);
+            goto die;
+        }
+        psMetadata *header = psFitsReadHeader(NULL, data->fits); // The FITS (primary) header
+        psMetadata *format = pmConfigCameraFormatFromHeader(config, header);
+        if (!format) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine camera format for %s\n", inName);
+            psFree(header);
+            goto die;
+        }
+        data->fpa = pmFPAConstruct(config->camera);
+        if (!data->fpa) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to construct FPA for %s\n", inName);
+            psFree(header);
+            psFree(format);
+            goto die;
+        }
+        pmFPAview *view = pmFPAAddSourceFromHeader(data->fpa, header, format);
+        psFree(header);
+        psFree(format);
+        if (!view) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to add input file %s to FPA.\n", inName);
+            goto die;
+        }
+        psFree(view);
+    }
+
+    // Get the rest from the recipe
+    ppStatsSetupFromRecipe(data, config);
+
+    // Print out what we're going to do
+    if (psTraceGetLevel(__func__) > 9) {
+        checkList(data->chips, "CHIP");
+        checkList(data->cells, "CELL");
+        checkList(data->headers, "HEADER");
+        checkList(data->concepts, "CONCEPT");
+    }
+
+    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/ppStatsSetupFromArgs.h
===================================================================
--- trunk/ppStats/src/ppStatsSetupFromArgs.h	(revision 8337)
+++ trunk/ppStats/src/ppStatsSetupFromArgs.h	(revision 8337)
@@ -0,0 +1,12 @@
+#ifndef PP_STATS_SETUP_FROM_ARGS_H
+#define PP_STATS_SETUP_FROM_ARGS_H
+
+#include <psmodules.h>
+#include "ppStatsData.h"
+
+// Set up the options and input/output files
+ppStatsData *ppStatsSetupFromArgs(pmConfig *config // Configuration
+    );
+
+
+#endif
Index: trunk/ppStats/src/ppStatsSetupFromRecipe.c
===================================================================
--- trunk/ppStats/src/ppStatsSetupFromRecipe.c	(revision 8337)
+++ trunk/ppStats/src/ppStatsSetupFromRecipe.c	(revision 8337)
@@ -0,0 +1,135 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <string.h>
+
+#include "ppStats.h"
+#include "ppStatsData.h"
+#include "ppStatsSetupFromRecipe.h"
+
+// Strings in a recipe may be defined multiply (with MULTI) or listed on a single line
+static void listFromRecipe(psMetadata *recipe, // Recipe to search
+                           const char *name, // Name for item within recipe
+                           psList *target // The target list
+    )
+{
+    // If the list already has entries, don't read anything else
+    if (psListLength(target) > 0) {
+        return;
+    }
+
+    // First check that at least one item of interest exists
+    psMetadataItem *checkItem = psMetadataLookup(recipe, name);
+    if (!checkItem) {
+        // Nothing to see here
+        return;
+    }
+
+    psString regex = NULL;              // Regular expression for the flag
+    psStringAppend(&regex, "^%s$", name);
+    psMetadataIterator *iterator = psMetadataIteratorAlloc(recipe, PS_LIST_HEAD, regex);
+    psFree(regex);
+    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, "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);
+
+    return;
+}
+
+ppStatsData *ppStatsSetupFromRecipe(ppStatsData *data, // Data for running ppStats
+                                    pmConfig *config // Configuration
+    )
+{
+    if (!data) {
+        data = ppStatsDataAlloc();
+    }
+
+    // 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;
+    }
+
+    listFromRecipe(recipe, "CHIP", data->chips);
+    listFromRecipe(recipe, "CELL", data->cells);
+    listFromRecipe(recipe, "HEADER", data->headers);
+    listFromRecipe(recipe, "CONCEPT", data->concepts);
+
+    // Parse the statistics options
+    psList *recipeStats = psListAlloc(NULL); // List of statistics options
+    listFromRecipe(recipe, "STAT", recipeStats);
+    if (psListLength(recipeStats) > 0) {
+        psListIterator *iterator = psListIteratorAlloc(recipeStats, PS_LIST_HEAD, false);
+        psString statString;            // Statistic string, from iteration
+        while ((statString = psListGetAndIncrement(iterator))) {
+            psStatsOptions stat = psStatsOptionFromString(statString);
+            if (stat >= 0) {
+                psLogMsg(__func__, PS_LOG_WARN, "Can't interpret STATS entry in recipe: "
+                         "%s --- ignored.\n", statString);
+                continue;
+            }
+            data->stats->options |= stat;
+            data->doStats = true;
+        }
+    }
+    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");
+        }
+    }
+
+    if (data->maskVal == 0) {
+        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;
+}
Index: trunk/ppStats/src/ppStatsSetupFromRecipe.h
===================================================================
--- trunk/ppStats/src/ppStatsSetupFromRecipe.h	(revision 8337)
+++ trunk/ppStats/src/ppStatsSetupFromRecipe.h	(revision 8337)
@@ -0,0 +1,11 @@
+#ifndef PP_STATS_SETUP_FROM_RECIPE_H
+#define PP_STATS_SETUP_FROM_RECIPE_H
+
+#include <psmodules.h>
+#include "ppStatsData.h"
+
+ppStatsData *ppStatsSetupFromRecipe(ppStatsData *data, // Data for running ppStats
+                                    pmConfig *config // Configuration
+    );
+
+#endif
Index: trunk/ppStats/src/ppStatsStandAlone.c
===================================================================
--- trunk/ppStats/src/ppStatsStandAlone.c	(revision 8337)
+++ trunk/ppStats/src/ppStatsStandAlone.c	(revision 8337)
@@ -0,0 +1,78 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#define TIMERNAME "PPSTATS"
+
+#include "ppStats.h"
+#include "ppStatsSetupFromArgs.h"
+
+int main(int argc, char *argv[])
+{
+    int status = EXIT_SUCCESS;
+
+    psLibInit(NULL);
+    psTimerStart(TIMERNAME);
+
+    // Parse the configuration and arguments
+    pmConfig *config = pmConfigRead(&argc, argv);
+
+    // Get the options, open the files
+    ppStatsData *data = ppStatsSetupFromArgs(config);
+
+    // Output filename is optional
+    const char *outName = NULL;         // Output file name
+    FILE *outFile = stdout;             // Output file
+    if (*config->argc == 2) {
+        outName = config->argv[1];
+        if (outName && strlen(outName) > 0) {
+            outFile = fopen(outName, "w");
+            if (!outFile) {
+                psError(PS_ERR_IO, false, "Unable to open output file %s\n", outName);
+                status = EXIT_FAILURE;
+                goto die;
+            }
+        } else {
+            psError(PS_ERR_IO, false, "Unable to open output file.\n");
+            status = EXIT_FAILURE;
+            goto die;
+        }
+    }
+
+    // Go through the FPA and do the hard work
+    psMetadata *results = ppStatsLoop(NULL, data, config);
+
+    if (psListLength(results->list) == 0) {
+        psError(PS_ERR_UNKNOWN, true, "No output.\n");
+        psFree(results);
+        status = EXIT_FAILURE;
+        goto die;
+    }
+
+    // Format and print the output
+    psString output = psMetadataConfigFormat(results);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate configuration file with result.\n");
+        psFree(results);
+        status = EXIT_FAILURE;
+        goto die;
+    }
+    fprintf(outFile, "%s", output);
+    psFree(output);
+
+    // Clean up
+    psFree(results);
+    if (outName) {
+        fclose(outFile);
+    }
+
+    // Common code for the death.
+die:
+    psFree(data);
+    psFree(config);
+    pmConceptsDone();
+    pmConfigDone();
+    psLibFinalize();
+
+    return status;
+}
