Index: unk/ppStac/src/ppStacOptions.c
===================================================================
--- /trunk/ppStac/src/ppStacOptions.c	(revision 13463)
+++ 	(revision )
@@ -1,128 +1,0 @@
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <string.h>
-
-#include <pslib.h>
-#include <psmodules.h>
-
-#include "ppStac.h"
-
-static void stacOptionsFree(ppStacOptions *options)
-{
-    psFree(options->config);
-    psFree(options->inFiles);
-    if (options->outFile) {
-        psFitsClose(options->outFile);
-    }
-}
-
-
-ppStacOptions *ppStacOptionsAlloc(void)
-{
-    ppStacOptions *options = psAlloc(sizeof(ppStacOptions));
-    psMemSetDeallocator(options, (psFreeFunc)stacOptionsFree);
-
-    options->config = NULL;
-    options->inFiles = NULL;
-    options->outFile = NULL;
-    options->combine = 0;
-    options->iter = 1;
-    options->rej = 3.0;
-    options->maskVal = 0;
-
-    return options;
-}
-
-
-
-// Parse a recipe option according to its type
-#define OPTION_PARSE(OPTION,MD,NAME,TYPE) \
-{ \
-    psMetadataItem *item = psMetadataLookup(MD, NAME); \
-    if (item) { \
-        OPTION = psMetadataItemParse##TYPE(item); \
-    } else { \
-        psWarning("Recipe option %s isn't specified; using default.\n", NAME); \
-    } \
-}
-
-ppStacOptions *ppStacOptionsRead(int *argc, char *argv[])
-{
-    PS_ASSERT_INT_POSITIVE(*argc, NULL);
-    PS_ASSERT_PTR_NON_NULL(argv, NULL);
-
-    if (*argc <= 1 || psArgumentGet(*argc, argv, "-help") || psArgumentGet(*argc, argv, "-h")) {
-        fprintf(stderr,
-                "ppStac: Combine multiple registered images.\n\n"
-                "Usage: %s OUT.fits IN1.fits IN2.fits ... [-recipe NAME FILE]\n\n",
-                argv[0]);
-        exit(PS_EXIT_SUCCESS);
-    }
-
-    ppStacOptions *options = ppStacOptionsAlloc(); // Options, to be returned
-
-    // Parse the configuration and arguments
-    pmConfigReadParamsSet(false);
-    options->config = pmConfigRead(argc, argv, PPSTAC_RECIPE);
-    if (!options->config) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to read configuration.\n");
-        psFree(options);
-        return NULL;
-    }
-
-    bool mdok;                          // Status of MD lookup
-    psMetadata *recipe = psMetadataLookupMetadata(&mdok, options->config->recipes,
-                                                  PPSTAC_RECIPE); // Recipe to use
-    if (!recipe) {
-        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find recipe %s.\n", PPSTAC_RECIPE);
-        psFree(options);
-        return NULL;
-    }
-
-    psString outName = pmConfigConvertFilename(argv[1], options->config, true); // Output filename
-    options->outFile = psFitsOpen(outName, "w");
-    if (!options->outFile) {
-        psError(PS_ERR_IO, false, "Unable to open output file %s.\n", outName);
-        psFree(options);
-        psFree(outName);
-        return NULL;
-    }
-    psFree(outName);
-
-    int numInputs = *argc - 2; // Number of input files
-    if (numInputs <= 1) {
-        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Not enough input images supplied to combine.\n");
-        return NULL;
-    }
-
-    options->inFiles = psArrayAlloc(numInputs);
-    for (int i = 0; i < numInputs; i++) {
-        psString inName = pmConfigConvertFilename(argv[i + 2], options->config, false);
-        psFits *inFile = psFitsOpen(inName, "r");
-        if (!inFile) {
-            psError(PS_ERR_IO, false, "Unable to open input file %s.\n", inName);
-            psFree(options);
-            psFree(inName);
-            return NULL;
-        }
-        psFree(inName);
-        options->inFiles->data[i] = inFile;
-    }
-
-    OPTION_PARSE(options->rej,     recipe, "REJ",     F32);
-    OPTION_PARSE(options->iter,    recipe, "ITER",    S32);
-    OPTION_PARSE(options->maskVal, recipe, "MASKVAL", U8);
-
-    const char *stat = psMetadataLookupStr(&mdok, recipe, "COMBINE");  // The combination statistic
-    if (!mdok || !stat || strlen(stat) == 0) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't recognise statistic COMBINE.\n");
-        psFree(options);
-        return NULL;
-    }
-    options->combine = psStatsOptionFromString(stat);
-
-    return options;
-}
Index: unk/ppStac/src/ppStack.c
===================================================================
--- /trunk/ppStac/src/ppStack.c	(revision 13463)
+++ 	(revision )
@@ -1,56 +1,0 @@
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <pslib.h>
-#include <psmodules.h>
-
-#include "ppStack.h"
-
-#define TIMER_NAME "PPSTACK"            // Name of timer
-
-int main(int argc, char *argv[])
-{
-    psExit exitValue = PS_EXIT_SUCCESS; // Exit value
-    psTimerStart(TIMER_NAME);
-    psLibInit(NULL);
-
-    pmConfig *config = pmConfigRead(&argc, argv, PPSTACK_RECIPE); // Configuration
-    if (!config) {
-        psErrorStackPrint(stderr, "Error reading configuration.");
-        exitValue = PS_EXIT_CONFIG_ERROR;
-        goto die;
-    }
-
-    if (!ppStackArguments(argc, argv, config)) {
-        psErrorStackPrint(stderr, "Error reading arguments.\n");
-        exitValue = PS_EXIT_CONFIG_ERROR;
-        goto die;
-    }
-
-    if (!ppStackCamera(config)) {
-        psErrorStackPrint(stderr, "Error reading configuration.\n");
-        exitValue = PS_EXIT_CONFIG_ERROR;
-        goto die;
-    }
-
-    if (!ppStackLoop(config)) {
-        psErrorStackPrint(stderr, "Error performing combination.\n");
-        exitValue = PS_EXIT_PROG_ERROR;
-        goto die;
-    }
-
-
-     // Common code for the death.
-die:
-    psTrace("ppStack", 1, "Finished at %f sec\n", psTimerMark(TIMER_NAME));
-    psTimerStop();
-
-    psFree(config);
-    pmConfigDone();
-    psLibFinalize();
-
-    exit(exitValue);
-}
-
Index: unk/ppStac/src/ppStack.h
===================================================================
--- /trunk/ppStac/src/ppStack.h	(revision 13463)
+++ 	(revision )
@@ -1,36 +1,0 @@
-#ifndef PP_STACK_H
-#define PP_STACK_H
-
-#define PPSTACK_RECIPE "PPSTACK"
-
-#include <psmodules.h>
-
-// Parse command-line arguments
-bool ppStackArguments(int argc, char *argv[], // Command-line arguments
-                      pmConfig *config  // Configuration
-    );
-
-// Parse cameras
-bool ppStackCamera(pmConfig *config     // Configuration
-    );
-
-// Loop over the inputs, doing the combination
-bool ppStackLoop(pmConfig *config       // Configuration
-    );
-
-// Perform stacking on a readout
-bool ppStackReadout(pmConfig *config,   // Configuration
-                    const pmFPAview *view // View for readout
-    );
-
-// Return software version
-psString ppStackVersion(void);
-
-// Return long description of software version
-psString ppStackVersionLong(void);
-
-// Supplement metadata with software version
-void ppStackVersionMetadata(psMetadata *metadata // Metadata to supplement
-    );
-
-#endif
Index: unk/ppStac/src/ppStackArguments.c
===================================================================
--- /trunk/ppStac/src/ppStackArguments.c	(revision 13463)
+++ 	(revision )
@@ -1,121 +1,0 @@
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <string.h>
-#include <pslib.h>
-#include <psmodules.h>
-
-#include "ppStack.h"
-
-// Print usage information and die
-static void usage(const char *program,  // Name of the program
-                  psMetadata *arguments, // Command-line arguments
-                  pmConfig *config      // Configuration
-    )
-{
-    fprintf(stderr, "\nPan-STARRS Image combination\n\n");
-    fprintf(stderr, "Usage: %s IMAGES.list MASKS.list OUTPUT_ROOT\n",
-            program);
-    fprintf(stderr, "\n");
-    psArgumentHelp(arguments);
-    psFree(arguments);
-    psFree(config);
-    pmConfigDone();
-    psLibFinalize();
-    exit(PS_EXIT_CONFIG_ERROR);
-}
-
-// Get a float-point value from the command-line or recipe, and add it to the arguments
-#define VALUE_ARG_RECIPE_FLOAT(ARGNAME, RECIPENAME, TYPE) { \
-    ps##TYPE value = psMetadataLookup##TYPE(NULL, arguments, ARGNAME); \
-    if (isnan(value)) { \
-        bool mdok; \
-        value = psMetadataLookup##TYPE(&mdok, recipe, RECIPENAME); \
-        if (!mdok) { \
-            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find %s in recipe %s", \
-                RECIPENAME, PPSTACK_RECIPE); \
-            goto ERROR; \
-        } \
-    } \
-    psMetadataAdd##TYPE(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, value); \
-}
-
-// Get an integer value from the command-line or recipe, and add it to the arguments
-#define VALUE_ARG_RECIPE_INT(ARGNAME, RECIPENAME, TYPE, UNSET) { \
-    ps##TYPE value = psMetadataLookup##TYPE(NULL, arguments, ARGNAME); \
-    if (value == UNSET) { \
-        bool mdok; \
-        value = psMetadataLookup##TYPE(&mdok, recipe, RECIPENAME); \
-        if (!mdok) { \
-            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find %s in recipe %s", \
-                RECIPENAME, PPSTACK_RECIPE); \
-            goto ERROR; \
-        } \
-    } \
-    psMetadataAdd##TYPE(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, value); \
-}
-
-// Get a string value from the command-line and add it to the target
-static bool valueArgStr(pmConfig *config,      // Configuration
-                        psMetadata *arguments, // Command-line arguments
-                        const char *argName, // Argument name in the command-line arguments
-                        const char *mdName, // Name for value in the metadata
-                        psMetadata *target // Target metadata to which to add value
-                        )
-{
-    psString value = psMetadataLookupStr(NULL, arguments, argName); // Value of interest
-    if (value && strlen(value) > 0) {
-        return psMetadataAddStr(target, PS_LIST_TAIL, mdName, 0, NULL, value);
-    }
-    return false;
-}
-
-bool ppStackArguments(int argc, char *argv[], pmConfig *config)
-{
-    assert(config);
-
-    psMetadata *arguments = psMetadataAlloc(); // Command-line arguments
-    psMetadataAddStr(arguments, PS_LIST_TAIL, "-stat", 0, "Statistics file", NULL);
-    psMetadataAddS32(arguments, PS_LIST_TAIL, "-iter", 0, "Number of rejection iterations", 0);
-    psMetadataAddF32(arguments, PS_LIST_TAIL, "-rej", 0, "Rejection thresold (sigma)", NAN);
-    psMetadataAddF32(arguments, PS_LIST_TAIL, "-extent", 0, "Extent of convolution (sigma)", NAN);
-    psMetadataAddU8(arguments,  PS_LIST_TAIL, "-mask-bad", 0, "Mask value for bad pixels", 0);
-    psMetadataAddU8(arguments,  PS_LIST_TAIL, "-mask-blank", 0, "Mask value for blank region", 0);
-
-    if (argc == 1 || !psArgumentParse(arguments, &argc, argv) || argc != 4) {
-        usage(argv[0], arguments, config);
-    }
-
-    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0,
-                     "Root name of the output image list", argv[1]);
-    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "IMAGE.LIST", 0,
-                     "Name of the input image list", argv[2]);
-    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "MASKS.LIST", 0,
-                     "Name of the input masks list", argv[3]);
-
-    valueArgStr(config, arguments, "-stat",      "STATS",         config->arguments);
-
-    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, PPSTACK_RECIPE); // Recipe for ppSim
-    if (!recipe) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find recipe %s", PPSTACK_RECIPE);
-        goto ERROR;
-    }
-
-    VALUE_ARG_RECIPE_INT("-iter",       "ITER",       S32, 0);
-    VALUE_ARG_RECIPE_FLOAT("-rej",      "REJ",        F32);
-    VALUE_ARG_RECIPE_FLOAT("-extent",   "EXTENT",     F32);
-    VALUE_ARG_RECIPE_INT("-mask-bad",   "MASK.BAD",   U8, 0);
-    VALUE_ARG_RECIPE_INT("-mask-blank", "MASK.BLANK", U8, 0);
-
-    psTrace("ppStack", 1, "Done reading command-line arguments\n");
-    psFree(arguments);
-    return true;
-
-ERROR:
-    psFree(arguments);
-    return false;
-}
-
-
Index: unk/ppStac/src/ppStackCamera.c
===================================================================
--- /trunk/ppStac/src/ppStackCamera.c	(revision 13463)
+++ 	(revision )
@@ -1,128 +1,0 @@
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <string.h>
-#include <pslib.h>
-#include <psmodules.h>
-#include <psphot.h>
-
-#include "ppStack.h"
-
-bool ppStackCamera(pmConfig *config)
-{
-    psString imageName = psMetadataLookupStr(NULL, config->arguments, "IMAGES.LIST"); // Image list filename
-    assert(imageName);
-    psString maskName = psMetadataLookupStr(NULL, config->arguments, "MASKS.LIST"); // Mask list filename
-    assert(maskName);
-
-    psString imageList = psSlurpFilename(imageName); // Contents of image list
-    if (!imageList) {
-        psError(PS_ERR_IO, false, "Unable to read list of image files %s", imageName);
-        return false;
-    }
-    psArray *images = psStringSplitArray(imageList, "\n", false); // The image filenames
-
-    psString maskList = psSlurpFilename(maskName); // Contents of mask list
-    if (!maskList) {
-        psError(PS_ERR_IO, false, "Unable to read list of mask files %s", maskName);
-        return false;
-    }
-    psArray *masks = psStringSplitArray(maskList, "\n", false); // The mask filenames
-    if (images->n != masks->n) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
-                "Number input images (%ld) does not match number of input masks (%ld).",
-                images->n, masks->n);
-        psFree(images);
-        psFree(masks);
-        return false;
-    }
-
-    for (int i = 0; i < images->n; i++) {
-        psString image = images->data[i]; // Name of image
-        if (!image || strlen(image) == 0) {
-            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Image name %d is blank.", i);
-            psFree(images);
-            psFree(masks);
-            return false;
-        }
-        psString mask = masks->data[i]; // Name of mask
-        if (!mask || strlen(mask) == 0) {
-            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Mask name %d is blank.", i);
-            psFree(images);
-            psFree(masks);
-            return false;
-        }
-
-        psArray *imageFiles = psArrayAlloc(1); // Array of filenames for this FPA
-        imageFiles->data[0] = image;
-        psMetadataAddArray(config->arguments, PS_LIST_TAIL, "IMAGE.FILENAMES", PS_META_REPLACE,
-                           "Filenames of image files", imageFiles);
-        psFree(imageFiles);
-
-        bool found = false;             // Found the file?
-        pmFPAfile *imageFile = pmFPAfileDefineFromArgs(&found, config, "PPSTACK.INPUT",
-                                                       "IMAGE.FILENAMES", true);
-        if (!imageFile || !found) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to define file from image %d (%s)", i, image);
-            psFree(images);
-            psFree(masks);
-            return false;
-        }
-        if (imageFile->type != PM_FPA_FILE_IMAGE) {
-            psError(PS_ERR_IO, true, "PPSTACK.INPUT is not of type IMAGE");
-            psFree(images);
-            psFree(masks);
-            return false;
-        }
-
-        psArray *maskFiles = psArrayAlloc(1); // Array of filenames for this FPA
-        maskFiles->data[0] = mask;
-        psMetadataAddArray(config->arguments, PS_LIST_TAIL, "MASK.FILENAMES", PS_META_REPLACE,
-                           "Filenames of mask files", maskFiles);
-        found = false;
-        pmFPAfile *maskFile = pmFPAfileBindFromArgs(&found, imageFile, config, "PPSTACK.INPUT.MASK",
-                                                    "MASK.FILENAMES", true);
-        if (!maskFile || !found) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to define file from mask %d (%s)", i, mask);
-            psFree(images);
-            psFree(masks);
-            return false;
-        }
-        if (maskFile->type != PM_FPA_FILE_MASK) {
-            psError(PS_ERR_IO, true, "PPSTACK.INPUT.MASK is not of type MASK");
-            psFree(images);
-            psFree(masks);
-            return false;
-        }
-    }
-    psMetadataRemoveKey(config->arguments, "IMAGE.FILENAMES");
-    psMetadataRemoveKey(config->arguments, "MASK.FILENAMES");
-
-    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "INPUTS.NUM", 0, "Number of input files", images->n);
-
-    // Output image
-    pmFPAfile *output = pmFPAfileDefineOutput(config, NULL, "PPSTACK.OUTPUT");
-    if (!output) {
-        psError(PS_ERR_IO, false, _("Unable to generate output file from PPSTACK.OUTPUT"));
-        return false;
-    }
-    if (output->type != PM_FPA_FILE_IMAGE) {
-        psError(PS_ERR_IO, true, "PPSTACK.OUTPUT is not of type IMAGE");
-        return false;
-    }
-
-    // Output mask
-    pmFPAfile *outMask = pmFPAfileDefineOutput(config, output->fpa, "PPSTACK.OUTPUT.MASK");
-    if (!outMask) {
-        psError(PS_ERR_IO, false, _("Unable to generate output file from PPSTACK.OUTPUT.MASK"));
-        return false;
-    }
-    if (outMask->type != PM_FPA_FILE_MASK) {
-        psError(PS_ERR_IO, true, "PPSTACK.OUTPUT.MASK is not of type MASK");
-        return false;
-    }
-
-    return true;
-}
Index: unk/ppStac/src/ppStackLoop.c
===================================================================
--- /trunk/ppStac/src/ppStackLoop.c	(revision 13463)
+++ 	(revision )
@@ -1,121 +1,0 @@
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <string.h>
-#include <pslib.h>
-#include <psmodules.h>
-#include <ppStats.h>
-
-#include "ppStack.h"
-
-bool ppStackLoop(pmConfig *config)
-{
-    bool mdok;                          // Status of MD lookup
-    const char *statsName = psMetadataLookupStr(&mdok, config->arguments, "STATS"); // Filename for statistics
-    psMetadata *stats = NULL;           // Container for statistics
-    FILE *statsFile = NULL;             // File stream for statistics
-    if (statsName && strlen(statsName) > 0) {
-        psString resolved = pmConfigConvertFilename(statsName, config, true); // Resolved filename
-        statsFile = fopen(resolved, "w");
-        if (!statsFile) {
-            psError(PS_ERR_IO, true, "Unable to open statistics file %s for writing.\n", resolved);
-            psFree(resolved);
-            return false;
-        } else {
-            stats = psMetadataAlloc();
-        }
-        psFree(resolved);
-    }
-
-    pmFPAfile *output = psMetadataLookupPtr(NULL, config->files, "PPSTACK.OUTPUT"); // Output file
-    if (!output) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "Can't find output data!\n");
-        return false;
-    }
-
-    pmFPAview *view = pmFPAviewAlloc(0); // Pointer into FPA hierarchy
-    pmHDU *lastHDU = NULL;              // Last HDU that was updated
-
-    // Iterate over the FPA hierarchy
-    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
-        return false;
-    }
-
-    pmChip *chip;                       // Chip of interest
-    while ((chip = pmFPAviewNextChip(view, output->fpa, 1)) != NULL) {
-        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
-            return false;
-        }
-
-        pmCell *cell;                // Cell of interest
-        while ((cell = pmFPAviewNextCell(view, output->fpa, 1)) != NULL) {
-            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
-                return false;
-            }
-
-            // Put version information into the header
-            pmHDU *hdu = pmHDUFromCell(cell);
-            if (hdu && hdu != lastHDU) {
-                if (!hdu->header) {
-                    hdu->header = psMetadataAlloc();
-                }
-                ppStackVersionMetadata(hdu->header);
-                lastHDU = hdu;
-            }
-
-            pmReadout *readout;         // Readout of interest
-            while ((readout = pmFPAviewNextReadout(view, output->fpa, 1))) {
-                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
-                    return false;
-                }
-
-                // Perform the analysis
-                if (!ppStackReadout(config, view)) {
-                    psError(PS_ERR_UNKNOWN, false, "Unable to stack images.\n");
-                    return false;
-                }
-
-                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
-                    return false;
-                }
-            }
-
-            // Perform statistics on the cell
-            if (stats) {
-                ppStats(stats, output->fpa, view, config);
-            }
-
-            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
-                return false;
-            }
-        }
-
-        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
-            return false;
-        }
-    }
-
-    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
-        return false;
-    }
-
-    psFree(view);
-
-    // Write out summary statistics
-    if (stats) {
-        const char *statsMDC = psMetadataConfigFormat(stats);
-        if (!statsMDC || strlen(statsMDC) == 0) {
-            psError(PS_ERR_IO, false, "Unable to get statistics MDC file.\n");
-        } else {
-            fprintf(statsFile, "%s", statsMDC);
-        }
-        psFree((void *)statsMDC);
-        fclose(statsFile);
-
-        psFree(stats);
-    }
-
-    return true;
-}
Index: unk/ppStac/src/ppStackReadout.c
===================================================================
--- /trunk/ppStac/src/ppStackReadout.c	(revision 13463)
+++ 	(revision )
@@ -1,74 +1,0 @@
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <pslib.h>
-#include <psmodules.h>
-
-#include "ppStack.h"
-
-#define ARRAY_BUFFER 16                 // Number to add to array at a time
-
-
-#define SEEING 1.0                      // Seeing FWHM (pixels)
-#define WEIGHT 1.0                      // Weighting
-
-bool ppStackReadout(pmConfig *config, const pmFPAview *view)
-{
-    // XXX Somehow need to add an HDU in to the output so that we can actually write it out!
-
-    // Get the output target
-    pmCell *outCell = pmFPAfileThisCell(config->files, view, "PPSTACK.OUTPUT"); // Output cell
-    pmReadout *outRO = pmReadoutAlloc(outCell); // Output readout
-
-    int num = psMetadataLookupS32(NULL, config->arguments, "INPUTS.NUM"); // Number of inputs
-    assert(num > 0);
-
-    // Get the input sources
-    psArray *stack = psArrayAllocEmpty(ARRAY_BUFFER); // The stack of inputs
-    psMetadataIterator *inputIter = psMetadataIteratorAlloc(config->files, PS_LIST_HEAD,
-                                                            "^PPSTACK.INPUT$"); // Iterator over input files
-    psMetadataItem *item;               // Item from iteration
-    while ((item = psMetadataGetAndIncrement(inputIter))) {
-        assert(item->type == PS_DATA_UNKNOWN);
-        pmFPAfile *inputFile = item->data.V; // An input file
-        pmReadout *ro = pmFPAviewThisReadout(view, inputFile->fpa); // Corresponding readout
-        pmStackData *data = pmStackDataAlloc(ro, SEEING, WEIGHT); // Data to stack
-        psArrayAdd(stack, ARRAY_BUFFER, data);
-    }
-    psFree(inputIter);
-
-    // Get the recipe values
-    int iter = psMetadataLookupS32(NULL, config->arguments, "ITER"); // Rejection iterations
-    float rej = psMetadataLookupF32(NULL, config->arguments, "REJ"); // Rejection threshold
-    float extent = psMetadataLookupF32(NULL, config->arguments, "EXTENT"); // Extent of convolution
-    psMaskType maskBad = psMetadataLookupU8(NULL, config->arguments, "MASK.BAD"); // Value to mask
-    psMaskType maskBlank = psMetadataLookupU8(NULL, config->arguments, "MASK.BLANK"); // Mask for blank reg.
-
-    if (!pmStackCombine(outRO, stack, maskBad, maskBlank, iter, rej)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to combine input readouts with rejection.");
-        psFree(stack);
-        psFree(outRO);
-        return false;
-    }
-
-    if (!pmStackReject(stack, maskBad, extent, rej)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to reject input pixels.");
-        psFree(stack);
-        psFree(outRO);
-        return false;
-    }
-
-    if (!pmStackCombine(outRO, stack, maskBad, maskBlank, 0, 0.0)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to combine input readouts.");
-        psFree(stack);
-        psFree(outRO);
-        return false;
-    }
-
-    psFree(stack);
-    psFree(outRO);                      // Drop reference
-
-    return true;
-}
Index: unk/ppStac/src/ppStackVersion.c
===================================================================
--- /trunk/ppStac/src/ppStackVersion.c	(revision 13463)
+++ 	(revision )
@@ -1,60 +1,0 @@
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <pslib.h>
-#include <psmodules.h>
-#include <ppStats.h>
-
-#include "ppStack.h"
-
-static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
-
-psString ppStackVersion(void)
-{
-    psString version = NULL;            // Version, to return
-    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
-    return version;
-}
-
-psString ppStackVersionLong(void)
-{
-    psString version = ppStackVersion(); // Version, to return
-    psString tag = psStringStripCVS(cvsTag, "Name"); // CVS tag
-    psStringAppend(&version, " (cvs tag %s) %s, %s", tag, __DATE__, __TIME__);
-    psFree(tag);
-    return version;
-}
-
-
-void ppStackVersionMetadata(psMetadata *metadata)
-{
-    PS_ASSERT_METADATA_NON_NULL(metadata,);
-
-    psString pslib = psLibVersionLong();// psLib version
-    psString psmodules = psModulesVersionLong(); // psModules version
-    psString ppStats = ppStatsVersionLong(); // ppStats version
-    psString ppStack = ppStackVersionLong(); // ppStack version
-
-    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
-    psString timeString = psTimeToISO(time); // The time in an ISO string
-    psFree(time);
-    psString head = NULL;               // Head string
-    psStringAppend(&head, "ppStack processing at %s. Component information:", timeString);
-    psFree(timeString);
-
-    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, head, "");
-    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, pslib, "");
-    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, psmodules, "");
-    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, ppStats, "");
-    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, ppStack, "");
-
-    psFree(head);
-    psFree(pslib);
-    psFree(psmodules);
-    psFree(ppStats);
-    psFree(ppStack);
-
-    return;
-}
Index: /trunk/ppStack/.cvsignore
===================================================================
--- /trunk/ppStack/.cvsignore	(revision 13464)
+++ /trunk/ppStack/.cvsignore	(revision 13464)
@@ -0,0 +1,17 @@
+aclocal.m4
+compile
+config.guess
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+libtool
+ltmain.sh
+Makefile
+Makefile.in
+missing
+ppStac.pc
+test
+autom4te.cache
Index: /trunk/ppStack/Makefile.am
===================================================================
--- /trunk/ppStack/Makefile.am	(revision 13464)
+++ /trunk/ppStack/Makefile.am	(revision 13464)
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+CLEANFILES = *~ core core.*
Index: /trunk/ppStack/autogen.sh
===================================================================
--- /trunk/ppStack/autogen.sh	(revision 13464)
+++ /trunk/ppStack/autogen.sh	(revision 13464)
@@ -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=ppStack
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+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/ppStack/configure.ac
===================================================================
--- /trunk/ppStack/configure.ac	(revision 13464)
+++ /trunk/ppStack/configure.ac	(revision 13464)
@@ -0,0 +1,33 @@
+AC_PREREQ(2.59)
+
+AC_INIT([ppStack], [0.1.1], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE 
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+PKG_CHECK_MODULES([PPSTATS], [ppStats >= 1.0.0]) 
+
+IPP_STDOPTS
+CFLAGS="${CFLAGS=} -Wall -Werror"
+
+AC_SUBST([PPSTACK_CFLAGS])
+AC_SUBST([PPSTACK_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+])
+AC_OUTPUT
Index: /trunk/ppStack/src/.cvsignore
===================================================================
--- /trunk/ppStack/src/.cvsignore	(revision 13464)
+++ /trunk/ppStack/src/.cvsignore	(revision 13464)
@@ -0,0 +1,10 @@
+.deps
+Makefile
+Makefile.in
+.libs
+*.lo
+*.la
+ppStack
+config.h
+config.h.in
+stamp-h1
Index: /trunk/ppStack/src/Makefile.am
===================================================================
--- /trunk/ppStack/src/Makefile.am	(revision 13464)
+++ /trunk/ppStack/src/Makefile.am	(revision 13464)
@@ -0,0 +1,23 @@
+bin_PROGRAMS = ppStack
+
+ppStack_CFLAGS 	= $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS) $(PPSTATS_CFLAGS)$(PPSTACK_CFLAGS)
+ppStack_LDFLAGS = $(PSMODULE_LIBS) $(PSLIB_LIBS) $(PPSTATS_LIBS) $(PPSTACK_LIBS)
+
+ppStack_SOURCES =		\
+	ppStack.c		\
+	ppStackArguments.c	\
+	ppStackCamera.c		\
+	ppStackLoop.c		\
+	ppStackReadout.c	\
+	ppStackVersion.c
+
+noinst_HEADERS = 		\
+	ppStack.h
+
+CLEANFILES = *~
+
+clean-local:
+	-rm -f TAGS
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
Index: /trunk/ppStack/src/ppStack.c
===================================================================
--- /trunk/ppStack/src/ppStack.c	(revision 13464)
+++ /trunk/ppStack/src/ppStack.c	(revision 13464)
@@ -0,0 +1,56 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppStack.h"
+
+#define TIMER_NAME "PPSTACK"            // Name of timer
+
+int main(int argc, char *argv[])
+{
+    psExit exitValue = PS_EXIT_SUCCESS; // Exit value
+    psTimerStart(TIMER_NAME);
+    psLibInit(NULL);
+
+    pmConfig *config = pmConfigRead(&argc, argv, PPSTACK_RECIPE); // Configuration
+    if (!config) {
+        psErrorStackPrint(stderr, "Error reading configuration.");
+        exitValue = PS_EXIT_CONFIG_ERROR;
+        goto die;
+    }
+
+    if (!ppStackArguments(argc, argv, config)) {
+        psErrorStackPrint(stderr, "Error reading arguments.\n");
+        exitValue = PS_EXIT_CONFIG_ERROR;
+        goto die;
+    }
+
+    if (!ppStackCamera(config)) {
+        psErrorStackPrint(stderr, "Error reading configuration.\n");
+        exitValue = PS_EXIT_CONFIG_ERROR;
+        goto die;
+    }
+
+    if (!ppStackLoop(config)) {
+        psErrorStackPrint(stderr, "Error performing combination.\n");
+        exitValue = PS_EXIT_PROG_ERROR;
+        goto die;
+    }
+
+
+     // Common code for the death.
+die:
+    psTrace("ppStack", 1, "Finished at %f sec\n", psTimerMark(TIMER_NAME));
+    psTimerStop();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exitValue);
+}
+
Index: /trunk/ppStack/src/ppStack.h
===================================================================
--- /trunk/ppStack/src/ppStack.h	(revision 13464)
+++ /trunk/ppStack/src/ppStack.h	(revision 13464)
@@ -0,0 +1,36 @@
+#ifndef PP_STACK_H
+#define PP_STACK_H
+
+#define PPSTACK_RECIPE "PPSTACK"
+
+#include <psmodules.h>
+
+// Parse command-line arguments
+bool ppStackArguments(int argc, char *argv[], // Command-line arguments
+                      pmConfig *config  // Configuration
+    );
+
+// Parse cameras
+bool ppStackCamera(pmConfig *config     // Configuration
+    );
+
+// Loop over the inputs, doing the combination
+bool ppStackLoop(pmConfig *config       // Configuration
+    );
+
+// Perform stacking on a readout
+bool ppStackReadout(pmConfig *config,   // Configuration
+                    const pmFPAview *view // View for readout
+    );
+
+// Return software version
+psString ppStackVersion(void);
+
+// Return long description of software version
+psString ppStackVersionLong(void);
+
+// Supplement metadata with software version
+void ppStackVersionMetadata(psMetadata *metadata // Metadata to supplement
+    );
+
+#endif
Index: /trunk/ppStack/src/ppStackArguments.c
===================================================================
--- /trunk/ppStack/src/ppStackArguments.c	(revision 13464)
+++ /trunk/ppStack/src/ppStackArguments.c	(revision 13464)
@@ -0,0 +1,121 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppStack.h"
+
+// Print usage information and die
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments, // Command-line arguments
+                  pmConfig *config      // Configuration
+    )
+{
+    fprintf(stderr, "\nPan-STARRS Image combination\n\n");
+    fprintf(stderr, "Usage: %s IMAGES.list MASKS.list OUTPUT_ROOT\n",
+            program);
+    fprintf(stderr, "\n");
+    psArgumentHelp(arguments);
+    psFree(arguments);
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(PS_EXIT_CONFIG_ERROR);
+}
+
+// Get a float-point value from the command-line or recipe, and add it to the arguments
+#define VALUE_ARG_RECIPE_FLOAT(ARGNAME, RECIPENAME, TYPE) { \
+    ps##TYPE value = psMetadataLookup##TYPE(NULL, arguments, ARGNAME); \
+    if (isnan(value)) { \
+        bool mdok; \
+        value = psMetadataLookup##TYPE(&mdok, recipe, RECIPENAME); \
+        if (!mdok) { \
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find %s in recipe %s", \
+                RECIPENAME, PPSTACK_RECIPE); \
+            goto ERROR; \
+        } \
+    } \
+    psMetadataAdd##TYPE(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, value); \
+}
+
+// Get an integer value from the command-line or recipe, and add it to the arguments
+#define VALUE_ARG_RECIPE_INT(ARGNAME, RECIPENAME, TYPE, UNSET) { \
+    ps##TYPE value = psMetadataLookup##TYPE(NULL, arguments, ARGNAME); \
+    if (value == UNSET) { \
+        bool mdok; \
+        value = psMetadataLookup##TYPE(&mdok, recipe, RECIPENAME); \
+        if (!mdok) { \
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find %s in recipe %s", \
+                RECIPENAME, PPSTACK_RECIPE); \
+            goto ERROR; \
+        } \
+    } \
+    psMetadataAdd##TYPE(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, value); \
+}
+
+// Get a string value from the command-line and add it to the target
+static bool valueArgStr(pmConfig *config,      // Configuration
+                        psMetadata *arguments, // Command-line arguments
+                        const char *argName, // Argument name in the command-line arguments
+                        const char *mdName, // Name for value in the metadata
+                        psMetadata *target // Target metadata to which to add value
+                        )
+{
+    psString value = psMetadataLookupStr(NULL, arguments, argName); // Value of interest
+    if (value && strlen(value) > 0) {
+        return psMetadataAddStr(target, PS_LIST_TAIL, mdName, 0, NULL, value);
+    }
+    return false;
+}
+
+bool ppStackArguments(int argc, char *argv[], pmConfig *config)
+{
+    assert(config);
+
+    psMetadata *arguments = psMetadataAlloc(); // Command-line arguments
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-stat", 0, "Statistics file", NULL);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-iter", 0, "Number of rejection iterations", 0);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-rej", 0, "Rejection thresold (sigma)", NAN);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-extent", 0, "Extent of convolution (sigma)", NAN);
+    psMetadataAddU8(arguments,  PS_LIST_TAIL, "-mask-bad", 0, "Mask value for bad pixels", 0);
+    psMetadataAddU8(arguments,  PS_LIST_TAIL, "-mask-blank", 0, "Mask value for blank region", 0);
+
+    if (argc == 1 || !psArgumentParse(arguments, &argc, argv) || argc != 4) {
+        usage(argv[0], arguments, config);
+    }
+
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0,
+                     "Root name of the output image list", argv[1]);
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "IMAGE.LIST", 0,
+                     "Name of the input image list", argv[2]);
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "MASKS.LIST", 0,
+                     "Name of the input masks list", argv[3]);
+
+    valueArgStr(config, arguments, "-stat",      "STATS",         config->arguments);
+
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, PPSTACK_RECIPE); // Recipe for ppSim
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find recipe %s", PPSTACK_RECIPE);
+        goto ERROR;
+    }
+
+    VALUE_ARG_RECIPE_INT("-iter",       "ITER",       S32, 0);
+    VALUE_ARG_RECIPE_FLOAT("-rej",      "REJ",        F32);
+    VALUE_ARG_RECIPE_FLOAT("-extent",   "EXTENT",     F32);
+    VALUE_ARG_RECIPE_INT("-mask-bad",   "MASK.BAD",   U8, 0);
+    VALUE_ARG_RECIPE_INT("-mask-blank", "MASK.BLANK", U8, 0);
+
+    psTrace("ppStack", 1, "Done reading command-line arguments\n");
+    psFree(arguments);
+    return true;
+
+ERROR:
+    psFree(arguments);
+    return false;
+}
+
+
Index: /trunk/ppStack/src/ppStackCamera.c
===================================================================
--- /trunk/ppStack/src/ppStackCamera.c	(revision 13464)
+++ /trunk/ppStack/src/ppStackCamera.c	(revision 13464)
@@ -0,0 +1,128 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <psphot.h>
+
+#include "ppStack.h"
+
+bool ppStackCamera(pmConfig *config)
+{
+    psString imageName = psMetadataLookupStr(NULL, config->arguments, "IMAGES.LIST"); // Image list filename
+    assert(imageName);
+    psString maskName = psMetadataLookupStr(NULL, config->arguments, "MASKS.LIST"); // Mask list filename
+    assert(maskName);
+
+    psString imageList = psSlurpFilename(imageName); // Contents of image list
+    if (!imageList) {
+        psError(PS_ERR_IO, false, "Unable to read list of image files %s", imageName);
+        return false;
+    }
+    psArray *images = psStringSplitArray(imageList, "\n", false); // The image filenames
+
+    psString maskList = psSlurpFilename(maskName); // Contents of mask list
+    if (!maskList) {
+        psError(PS_ERR_IO, false, "Unable to read list of mask files %s", maskName);
+        return false;
+    }
+    psArray *masks = psStringSplitArray(maskList, "\n", false); // The mask filenames
+    if (images->n != masks->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Number input images (%ld) does not match number of input masks (%ld).",
+                images->n, masks->n);
+        psFree(images);
+        psFree(masks);
+        return false;
+    }
+
+    for (int i = 0; i < images->n; i++) {
+        psString image = images->data[i]; // Name of image
+        if (!image || strlen(image) == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Image name %d is blank.", i);
+            psFree(images);
+            psFree(masks);
+            return false;
+        }
+        psString mask = masks->data[i]; // Name of mask
+        if (!mask || strlen(mask) == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Mask name %d is blank.", i);
+            psFree(images);
+            psFree(masks);
+            return false;
+        }
+
+        psArray *imageFiles = psArrayAlloc(1); // Array of filenames for this FPA
+        imageFiles->data[0] = image;
+        psMetadataAddArray(config->arguments, PS_LIST_TAIL, "IMAGE.FILENAMES", PS_META_REPLACE,
+                           "Filenames of image files", imageFiles);
+        psFree(imageFiles);
+
+        bool found = false;             // Found the file?
+        pmFPAfile *imageFile = pmFPAfileDefineFromArgs(&found, config, "PPSTACK.INPUT",
+                                                       "IMAGE.FILENAMES", true);
+        if (!imageFile || !found) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to define file from image %d (%s)", i, image);
+            psFree(images);
+            psFree(masks);
+            return false;
+        }
+        if (imageFile->type != PM_FPA_FILE_IMAGE) {
+            psError(PS_ERR_IO, true, "PPSTACK.INPUT is not of type IMAGE");
+            psFree(images);
+            psFree(masks);
+            return false;
+        }
+
+        psArray *maskFiles = psArrayAlloc(1); // Array of filenames for this FPA
+        maskFiles->data[0] = mask;
+        psMetadataAddArray(config->arguments, PS_LIST_TAIL, "MASK.FILENAMES", PS_META_REPLACE,
+                           "Filenames of mask files", maskFiles);
+        found = false;
+        pmFPAfile *maskFile = pmFPAfileBindFromArgs(&found, imageFile, config, "PPSTACK.INPUT.MASK",
+                                                    "MASK.FILENAMES", true);
+        if (!maskFile || !found) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to define file from mask %d (%s)", i, mask);
+            psFree(images);
+            psFree(masks);
+            return false;
+        }
+        if (maskFile->type != PM_FPA_FILE_MASK) {
+            psError(PS_ERR_IO, true, "PPSTACK.INPUT.MASK is not of type MASK");
+            psFree(images);
+            psFree(masks);
+            return false;
+        }
+    }
+    psMetadataRemoveKey(config->arguments, "IMAGE.FILENAMES");
+    psMetadataRemoveKey(config->arguments, "MASK.FILENAMES");
+
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "INPUTS.NUM", 0, "Number of input files", images->n);
+
+    // Output image
+    pmFPAfile *output = pmFPAfileDefineOutput(config, NULL, "PPSTACK.OUTPUT");
+    if (!output) {
+        psError(PS_ERR_IO, false, _("Unable to generate output file from PPSTACK.OUTPUT"));
+        return false;
+    }
+    if (output->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPSTACK.OUTPUT is not of type IMAGE");
+        return false;
+    }
+
+    // Output mask
+    pmFPAfile *outMask = pmFPAfileDefineOutput(config, output->fpa, "PPSTACK.OUTPUT.MASK");
+    if (!outMask) {
+        psError(PS_ERR_IO, false, _("Unable to generate output file from PPSTACK.OUTPUT.MASK"));
+        return false;
+    }
+    if (outMask->type != PM_FPA_FILE_MASK) {
+        psError(PS_ERR_IO, true, "PPSTACK.OUTPUT.MASK is not of type MASK");
+        return false;
+    }
+
+    return true;
+}
Index: /trunk/ppStack/src/ppStackLoop.c
===================================================================
--- /trunk/ppStack/src/ppStackLoop.c	(revision 13464)
+++ /trunk/ppStack/src/ppStackLoop.c	(revision 13464)
@@ -0,0 +1,121 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <ppStats.h>
+
+#include "ppStack.h"
+
+bool ppStackLoop(pmConfig *config)
+{
+    bool mdok;                          // Status of MD lookup
+    const char *statsName = psMetadataLookupStr(&mdok, config->arguments, "STATS"); // Filename for statistics
+    psMetadata *stats = NULL;           // Container for statistics
+    FILE *statsFile = NULL;             // File stream for statistics
+    if (statsName && strlen(statsName) > 0) {
+        psString resolved = pmConfigConvertFilename(statsName, config, true); // Resolved filename
+        statsFile = fopen(resolved, "w");
+        if (!statsFile) {
+            psError(PS_ERR_IO, true, "Unable to open statistics file %s for writing.\n", resolved);
+            psFree(resolved);
+            return false;
+        } else {
+            stats = psMetadataAlloc();
+        }
+        psFree(resolved);
+    }
+
+    pmFPAfile *output = psMetadataLookupPtr(NULL, config->files, "PPSTACK.OUTPUT"); // Output file
+    if (!output) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Can't find output data!\n");
+        return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc(0); // Pointer into FPA hierarchy
+    pmHDU *lastHDU = NULL;              // Last HDU that was updated
+
+    // Iterate over the FPA hierarchy
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        return false;
+    }
+
+    pmChip *chip;                       // Chip of interest
+    while ((chip = pmFPAviewNextChip(view, output->fpa, 1)) != NULL) {
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            return false;
+        }
+
+        pmCell *cell;                // Cell of interest
+        while ((cell = pmFPAviewNextCell(view, output->fpa, 1)) != NULL) {
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                return false;
+            }
+
+            // Put version information into the header
+            pmHDU *hdu = pmHDUFromCell(cell);
+            if (hdu && hdu != lastHDU) {
+                if (!hdu->header) {
+                    hdu->header = psMetadataAlloc();
+                }
+                ppStackVersionMetadata(hdu->header);
+                lastHDU = hdu;
+            }
+
+            pmReadout *readout;         // Readout of interest
+            while ((readout = pmFPAviewNextReadout(view, output->fpa, 1))) {
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    return false;
+                }
+
+                // Perform the analysis
+                if (!ppStackReadout(config, view)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to stack images.\n");
+                    return false;
+                }
+
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    return false;
+                }
+            }
+
+            // Perform statistics on the cell
+            if (stats) {
+                ppStats(stats, output->fpa, view, config);
+            }
+
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                return false;
+            }
+        }
+
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            return false;
+        }
+    }
+
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        return false;
+    }
+
+    psFree(view);
+
+    // Write out summary statistics
+    if (stats) {
+        const char *statsMDC = psMetadataConfigFormat(stats);
+        if (!statsMDC || strlen(statsMDC) == 0) {
+            psError(PS_ERR_IO, false, "Unable to get statistics MDC file.\n");
+        } else {
+            fprintf(statsFile, "%s", statsMDC);
+        }
+        psFree((void *)statsMDC);
+        fclose(statsFile);
+
+        psFree(stats);
+    }
+
+    return true;
+}
Index: /trunk/ppStack/src/ppStackReadout.c
===================================================================
--- /trunk/ppStack/src/ppStackReadout.c	(revision 13464)
+++ /trunk/ppStack/src/ppStackReadout.c	(revision 13464)
@@ -0,0 +1,74 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppStack.h"
+
+#define ARRAY_BUFFER 16                 // Number to add to array at a time
+
+
+#define SEEING 1.0                      // Seeing FWHM (pixels)
+#define WEIGHT 1.0                      // Weighting
+
+bool ppStackReadout(pmConfig *config, const pmFPAview *view)
+{
+    // XXX Somehow need to add an HDU in to the output so that we can actually write it out!
+
+    // Get the output target
+    pmCell *outCell = pmFPAfileThisCell(config->files, view, "PPSTACK.OUTPUT"); // Output cell
+    pmReadout *outRO = pmReadoutAlloc(outCell); // Output readout
+
+    int num = psMetadataLookupS32(NULL, config->arguments, "INPUTS.NUM"); // Number of inputs
+    assert(num > 0);
+
+    // Get the input sources
+    psArray *stack = psArrayAllocEmpty(ARRAY_BUFFER); // The stack of inputs
+    psMetadataIterator *inputIter = psMetadataIteratorAlloc(config->files, PS_LIST_HEAD,
+                                                            "^PPSTACK.INPUT$"); // Iterator over input files
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(inputIter))) {
+        assert(item->type == PS_DATA_UNKNOWN);
+        pmFPAfile *inputFile = item->data.V; // An input file
+        pmReadout *ro = pmFPAviewThisReadout(view, inputFile->fpa); // Corresponding readout
+        pmStackData *data = pmStackDataAlloc(ro, SEEING, WEIGHT); // Data to stack
+        psArrayAdd(stack, ARRAY_BUFFER, data);
+    }
+    psFree(inputIter);
+
+    // Get the recipe values
+    int iter = psMetadataLookupS32(NULL, config->arguments, "ITER"); // Rejection iterations
+    float rej = psMetadataLookupF32(NULL, config->arguments, "REJ"); // Rejection threshold
+    float extent = psMetadataLookupF32(NULL, config->arguments, "EXTENT"); // Extent of convolution
+    psMaskType maskBad = psMetadataLookupU8(NULL, config->arguments, "MASK.BAD"); // Value to mask
+    psMaskType maskBlank = psMetadataLookupU8(NULL, config->arguments, "MASK.BLANK"); // Mask for blank reg.
+
+    if (!pmStackCombine(outRO, stack, maskBad, maskBlank, iter, rej)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to combine input readouts with rejection.");
+        psFree(stack);
+        psFree(outRO);
+        return false;
+    }
+
+    if (!pmStackReject(stack, maskBad, extent, rej)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to reject input pixels.");
+        psFree(stack);
+        psFree(outRO);
+        return false;
+    }
+
+    if (!pmStackCombine(outRO, stack, maskBad, maskBlank, 0, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to combine input readouts.");
+        psFree(stack);
+        psFree(outRO);
+        return false;
+    }
+
+    psFree(stack);
+    psFree(outRO);                      // Drop reference
+
+    return true;
+}
Index: /trunk/ppStack/src/ppStackVersion.c
===================================================================
--- /trunk/ppStack/src/ppStackVersion.c	(revision 13464)
+++ /trunk/ppStack/src/ppStackVersion.c	(revision 13464)
@@ -0,0 +1,60 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <ppStats.h>
+
+#include "ppStack.h"
+
+static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
+
+psString ppStackVersion(void)
+{
+    psString version = NULL;            // Version, to return
+    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+    return version;
+}
+
+psString ppStackVersionLong(void)
+{
+    psString version = ppStackVersion(); // Version, to return
+    psString tag = psStringStripCVS(cvsTag, "Name"); // CVS tag
+    psStringAppend(&version, " (cvs tag %s) %s, %s", tag, __DATE__, __TIME__);
+    psFree(tag);
+    return version;
+}
+
+
+void ppStackVersionMetadata(psMetadata *metadata)
+{
+    PS_ASSERT_METADATA_NON_NULL(metadata,);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString ppStats = ppStatsVersionLong(); // ppStats version
+    psString ppStack = ppStackVersionLong(); // ppStack version
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psString head = NULL;               // Head string
+    psStringAppend(&head, "ppStack processing at %s. Component information:", timeString);
+    psFree(timeString);
+
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, head, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, pslib, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, psmodules, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, ppStats, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, ppStack, "");
+
+    psFree(head);
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(ppStats);
+    psFree(ppStack);
+
+    return;
+}
