Index: /trunk/ppArith/.cvsignore
===================================================================
--- /trunk/ppArith/.cvsignore	(revision 15571)
+++ /trunk/ppArith/.cvsignore	(revision 15571)
@@ -0,0 +1,17 @@
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
+config.guess
+libtool
+ltmain.sh
+stamp-h1
+config.sub
+test
Index: /trunk/ppArith/Makefile.am
===================================================================
--- /trunk/ppArith/Makefile.am	(revision 15571)
+++ /trunk/ppArith/Makefile.am	(revision 15571)
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
Index: /trunk/ppArith/autogen.sh
===================================================================
--- /trunk/ppArith/autogen.sh	(revision 15571)
+++ /trunk/ppArith/autogen.sh	(revision 15571)
@@ -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=ppArith
+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/ppArith/configure.ac
===================================================================
--- /trunk/ppArith/configure.ac	(revision 15571)
+++ /trunk/ppArith/configure.ac	(revision 15571)
@@ -0,0 +1,36 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.61)
+
+AC_INIT([ppArith], [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]) 
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+CFLAGS="${CFLAGS} -Wall -Werror"
+
+AC_SUBST([PPARITH_CFLAGS])
+AC_SUBST([PPARITH_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+])
+
+AC_OUTPUT
Index: /trunk/ppArith/src/.cvsignore
===================================================================
--- /trunk/ppArith/src/.cvsignore	(revision 15571)
+++ /trunk/ppArith/src/.cvsignore	(revision 15571)
@@ -0,0 +1,8 @@
+config.h
+config.h.in
+Makefile
+Makefile.in
+stamp-h1
+.deps
+.libs
+ppArith
Index: /trunk/ppArith/src/Makefile.am
===================================================================
--- /trunk/ppArith/src/Makefile.am	(revision 15571)
+++ /trunk/ppArith/src/Makefile.am	(revision 15571)
@@ -0,0 +1,21 @@
+bin_PROGRAMS = ppArith
+ppArith_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PPSTATS_CFLAGS) $(PSPHOT_CFLAGS) $(PPARITH_CFLAGS)
+ppArith_LDFLAGS  = $(PSLIB_LIBS)   $(PSMODULE_LIBS)   $(PPSTATS_LIBS)   $(PSPHOT_LIBS)   $(PPARITH_LIBS)
+
+ppArith_SOURCES =		\
+	ppArith.c		\
+	ppArithArguments.c	\
+	ppArithLoop.c		\
+	ppArithReadout.c	\
+	ppArithVersion.c            
+
+noinst_HEADERS = \
+	ppArith.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
Index: /trunk/ppArith/src/ppArith.c
===================================================================
--- /trunk/ppArith/src/ppArith.c	(revision 15571)
+++ /trunk/ppArith/src/ppArith.c	(revision 15571)
@@ -0,0 +1,45 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppArith.h"
+
+int main(int argc, char *argv[])
+{
+    psExit exitValue = PS_EXIT_SUCCESS; // Exit value
+    psTimerStart("ppArith");
+    psLibInit(NULL);
+
+    pmConfig *config = pmConfigRead(&argc, argv, PPARITH_RECIPE); // Configuration
+    if (!config) {
+        psErrorStackPrint(stderr, "Error reading configuration.");
+        exitValue = PS_EXIT_CONFIG_ERROR;
+        goto die;
+    }
+
+    if (!ppArithArguments(argc, argv, config)) {
+        psErrorStackPrint(stderr, "Error reading arguments.\n");
+        exitValue = PS_EXIT_CONFIG_ERROR;
+        goto die;
+    }
+
+    if (!ppArithLoop(config)) {
+        psErrorStackPrint(stderr, "Error performing arithmetic.\n");
+        exitValue = PS_EXIT_PROG_ERROR;
+        goto die;
+    }
+
+ die:
+    psTrace("ppArith", 1, "Finished at %f sec\n", psTimerMark("ppArith"));
+    psTimerStop();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exitValue);
+}
Index: /trunk/ppArith/src/ppArith.h
===================================================================
--- /trunk/ppArith/src/ppArith.h	(revision 15571)
+++ /trunk/ppArith/src/ppArith.h	(revision 15571)
@@ -0,0 +1,32 @@
+#ifndef PP_ARITH_H
+#define PP_ARITH_H
+
+#define PPARITH_RECIPE "PPARITH"            /// Name of the recipe to use
+
+/// Parse the arguments
+bool ppArithArguments(int argc, char *argv[], ///< Command-line arguments
+                      pmConfig *config    ///< Configuration
+    );
+
+/// Parse the camera input
+bool ppArithCamera(pmConfig *config       ///< Configuration
+    );
+
+/// Loop over the FPA hierarchy
+bool ppArithLoop(pmConfig *config         ///< Configuration
+    );
+
+/// Perform arithmetic on the readout
+bool ppArithReadout(pmReadout *output,  ///< Output readout
+                    const pmReadout *input1, ///< Input readout
+                    const pmReadout *input2, ///< Input readout
+                    const pmConfig *config, ///< Configuration
+                    const pmFPAview *view ///< View of readout on which to operate
+    );
+
+/// Put the program version information into a metadata
+void ppArithVersionMetadata(psMetadata *metadata ///< Metadata to populate
+    );
+
+
+#endif
Index: /trunk/ppArith/src/ppArithArguments.c
===================================================================
--- /trunk/ppArith/src/ppArithArguments.c	(revision 15571)
+++ /trunk/ppArith/src/ppArithArguments.c	(revision 15571)
@@ -0,0 +1,144 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppArith.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 arithmetic\n\n");
+    fprintf(stderr, "Usage: %s -file1 INPUT1.fits -op OP -file2 INPUT2.fits OUTPUT_ROOT\n\n",
+            program);
+    fprintf(stderr, "\n");
+    psArgumentHelp(arguments);
+    psFree(arguments);
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(PS_EXIT_CONFIG_ERROR);
+}
+
+// Get a string value from the command-line and add it to the target
+static bool valueArgStr(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;
+}
+
+// Add a single filename to the arguments as an array, so that it can be used with pmFPAfileBindFromArgs, etc
+static void fileList(const char *file, // The symbolic name for the file
+                     const char *name, // The name of the file
+                     const char *comment, // Description of the file
+                     pmConfig *config // Configuration
+    )
+{
+    psArray *files = psArrayAlloc(1); // Array with file names
+    files->data[0] = psStringCopy(name);
+    psMetadataAddArray(config->arguments, PS_LIST_TAIL, file, 0, comment, files);
+    psFree(files);
+    return;
+}
+
+bool ppArithArguments(int argc, char *argv[], pmConfig *config)
+{
+    assert(config);
+
+    psMetadata *arguments = psMetadataAlloc(); // Command-line arguments
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-file1", 0, "First image", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-op", 0, "Operation to perform", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-file2", 0, "Second image", NULL);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-mask", 0, "Treat images as masks", false);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-stats", 0, "Statistics file", NULL);
+
+    if (argc == 1 || !psArgumentParse(arguments, &argc, argv) || argc != 2) {
+        usage(argv[0], arguments, config);
+    }
+
+    bool isMask = psMetadataLookupBool(NULL, arguments, "-mask"); // Are we dealing with masks?
+    const char *inFilerule = isMask ? "PPARITH.INPUT.MASK" : "PPARITH.INPUT.IMAGE"; // Input file rule
+    const char *outFilerule = isMask ? "PPARITH.OUTPUT.MASK" : "PPARITH.OUTPUT.IMAGE"; // Output file rule
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "FILERULE.INPUT", 0,
+                     "File rule for input", inFilerule);
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "FILERULE.OUTPUT", 0,
+                     "File rule for output", outFilerule);
+
+    bool status = false;                // Status for file definition
+
+    // First file
+    const char *name1 = psMetadataLookupStr(NULL, arguments, "-file1"); // Name of first image
+    if (!name1 || strlen(name1) == 0) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "No input image specified.");
+        goto ERROR;
+    }
+    fileList("INPUT1", name1, "Name of the first input image", config);
+    pmFPAfile *file1 = pmFPAfileDefineFromArgs(&status, config, inFilerule, "INPUT1");
+    if (!status || !file1) {
+        psError(PS_ERR_IO, false, "Failed to build FPA from %s", inFilerule);
+        goto ERROR;
+    }
+    if ((isMask && file1->type != PM_FPA_FILE_MASK) || file1->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "File rule %s is not of the expected type", inFilerule);
+        goto ERROR;
+    }
+
+    // Second file is optional (won't be one for unary operations)
+    const char *name2 = psMetadataLookupStr(NULL, arguments, "-file2"); // Name of second image
+    if (name2 && strlen(name2) > 0) {
+        fileList("INPUT2", name2, "Name of the second input image", config);
+        pmFPAfile *file2 = pmFPAfileDefineFromArgs(&status, config, inFilerule, "INPUT2");
+        if (!status || !file2) {
+            psError(PS_ERR_IO, false, "Failed to build FPA from %s", inFilerule);
+            goto ERROR;
+        }
+        if ((isMask && file2->type != PM_FPA_FILE_MASK) || file2->type != PM_FPA_FILE_IMAGE) {
+            psError(PS_ERR_IO, true, "File rule %s is not of the expected type", inFilerule);
+            goto ERROR;
+        }
+        if (file2->fpa->camera != file1->fpa->camera) {
+            psError(PS_ERR_IO, true, "Cameras for inputs differ.");
+            goto ERROR;
+        }
+    }
+
+    // Output image
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "Name of the output image", argv[1]);
+    pmFPAfile *output = pmFPAfileDefineOutput(config, file1->fpa, outFilerule);
+    if (!output) {
+        psError(PS_ERR_IO, false, "Unable to generate output file from %s", outFilerule);
+        goto ERROR;
+    }
+    if ((isMask && output->type != PM_FPA_FILE_MASK) || output->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "%s is not of the expected type", outFilerule);
+        goto ERROR;
+    }
+    output->save = true;
+
+    valueArgStr(arguments, "-op",    "OPERATION", config->arguments);
+    valueArgStr(arguments, "-stats", "STATS",     config->arguments);
+
+    psTrace("ppArith", 1, "Done reading command-line arguments\n");
+    psFree(arguments);
+    return true;
+
+ERROR:
+    psFree(arguments);
+    return false;
+}
+
+
Index: /trunk/ppArith/src/ppArithLoop.c
===================================================================
--- /trunk/ppArith/src/ppArithLoop.c	(revision 15571)
+++ /trunk/ppArith/src/ppArithLoop.c	(revision 15571)
@@ -0,0 +1,174 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <ppStats.h>
+#include <psphot.h>
+
+#include "ppArith.h"
+
+bool ppArithLoop(pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    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);
+    }
+
+    const char *outName = psMetadataLookupStr(NULL, config->arguments, "FILERULE.OUTPUT"); // Output filerule
+    pmFPAfile *output = psMetadataLookupPtr(NULL, config->files, outName); // Output file
+    assert(output);                     // We added it earlier
+
+    const char *inName = psMetadataLookupStr(NULL, config->arguments, "FILERULE.INPUT"); // Input filerule
+    pmFPAfile *input1 = NULL, *input2 = NULL; // Input files
+    psString fileRegex = NULL;          // Regular expression to find input files
+    psStringAppend(&fileRegex, "^%s$", inName);
+    psMetadataIterator *iter = psMetadataIteratorAlloc(config->files, PS_LIST_HEAD, fileRegex); // Iterator
+    psMetadataItem *item = psMetadataGetAndIncrement(iter); // Item from iteration
+    input1 = item->data.V;
+    assert(input1);                     // It should be there!
+    if ((item = psMetadataGetAndIncrement(iter))) {
+        input2 = item->data.V;
+        assert(input2);
+    }
+    psFree(iter);
+
+    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 *outChip;                    // Output chip of interest
+    while ((outChip = pmFPAviewNextChip(view, output->fpa, 1)) != NULL) {
+        pmChip *inChip1 = pmFPAviewThisChip(view, input1->fpa); // Input chip of interest
+        pmChip *inChip2 = input2 ? pmFPAviewThisChip(view, input2->fpa) : NULL; // Input chip of interest
+        if (inChip2 && ((!inChip1->file_exists && inChip2->file_exists) ||
+                        (inChip1->file_exists && !inChip2->file_exists))) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA format discrepency between inputs");
+            psFree(view);
+            return false;
+        }
+
+        if (!inChip1->file_exists) {
+            continue;
+        }
+
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            return false;
+        }
+
+        pmCell *outCell;                // Cell of interest
+        while ((outCell = pmFPAviewNextCell(view, output->fpa, 1)) != NULL) {
+            pmCell *inCell1 = pmFPAviewThisCell(view, input1->fpa); // Input cell of interest
+            pmCell *inCell2 = input2 ? pmFPAviewThisCell(view, input2->fpa) : NULL; // Input cell of interest
+            if (inCell2 && ((!inCell1->file_exists && inCell2->file_exists) ||
+                            (inCell1->file_exists && !inCell2->file_exists))) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA format discrepency between inputs");
+                psFree(view);
+                return false;
+            }
+            if (!inCell1->file_exists) {
+                continue;
+            }
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                return false;
+            }
+
+            // Put version information into the header
+            pmHDU *hdu = pmHDUFromCell(outCell);
+            if (hdu && hdu != lastHDU) {
+                if (!hdu->header) {
+                    hdu->header = psMetadataAlloc();
+                }
+                ppArithVersionMetadata(hdu->header);
+                lastHDU = hdu;
+            }
+
+            pmReadout *outRO;           // Readout of interest
+            while ((outRO = pmFPAviewNextReadout(view, output->fpa, 1))) {
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    return false;
+                }
+                pmReadout *inRO1 = pmFPAviewThisReadout(view, input1->fpa);// Input readout of interest
+                pmReadout *inRO2 = input2 ? pmFPAviewThisReadout(view, input2->fpa) :
+                    NULL;// Input readout of interest
+
+                if (inRO2 && ((!inRO1->data_exists && inRO2->data_exists) ||
+                              (inRO1->data_exists && !inRO2->data_exists))) {
+                    psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                            "FPA format discrepency between inputs");
+                    psFree(view);
+                    return false;
+                }
+                if (!inRO1->data_exists) {
+                    continue;
+                }
+
+                // Perform the analysis
+                if (!ppArithReadout(outRO, inRO1, inRO2, config, view)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to perform arithmetic.\n");
+                    return false;
+                }
+
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    return false;
+                }
+            }
+
+            // Perform statistics on the cell
+            if (stats) {
+                ppStatsFPA(stats, output->fpa, view, 0, 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) {
+            psWarning("Unable to get statistics MDC file.\n");
+        } else {
+            fprintf(statsFile, "%s", statsMDC);
+        }
+        psFree((void *)statsMDC);
+        fclose(statsFile);
+
+        psFree(stats);
+    }
+
+    return true;
+}
Index: /trunk/ppArith/src/ppArithReadout.c
===================================================================
--- /trunk/ppArith/src/ppArithReadout.c	(revision 15571)
+++ /trunk/ppArith/src/ppArithReadout.c	(revision 15571)
@@ -0,0 +1,63 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppArith.h"
+
+bool ppArithReadout(pmReadout *output, const pmReadout *input1, const pmReadout *input2,
+                    const pmConfig *config, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(output, false);
+    PS_ASSERT_PTR_NON_NULL(input1, false);
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    psImage *inImage1 = input1->image;  // Input image 1
+    psImage *outImage = output->image;  // Output image
+    if (!inImage1) {
+        inImage1 = input1->mask;
+        outImage = output->mask;
+        if (!outImage) {
+            output->image = outImage = psImageAlloc(inImage1->numCols, inImage1->numRows, PS_TYPE_MASK);
+        }
+    } else if (!outImage) {
+        output->image = outImage = psImageAlloc(inImage1->numCols, inImage1->numRows, PS_TYPE_F32);
+    }
+    PS_ASSERT_IMAGE_NON_NULL(inImage1, false);
+
+    psImage *inImage2 = NULL;           // Input image 2
+    if (input2) {
+        inImage2 = input2->image;
+        if (!inImage2) {
+            inImage2 = input2->mask;
+        }
+        PS_ASSERT_IMAGE_NON_NULL(inImage2, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(inImage2, inImage1, false);
+        PS_ASSERT_IMAGE_TYPE(inImage2, inImage1->type.type, false);
+    }
+
+#if 0
+    pmCell *outCell = output->parent;   // Output cell
+    pmChip *outChip = outCell->parent;  // Output chip
+    pmFPA *outFPA = outChip->parent;    // Output FPA
+    pmHDU *outHDU = pmHDUGetLowest(outFPA, outChip, outCell); // Output HDU
+    if (!outHDU->header) {
+        outHDU->header = psMetadataAlloc();
+    }
+#endif
+
+    // Look up appropriate values
+    const char *op = psMetadataLookupStr(NULL, config->arguments, "OPERATION"); // Operation to perform
+
+    if (input2) {
+        psBinaryOp(outImage, inImage1, op, inImage2);
+    } else {
+        psUnaryOp(outImage, inImage1, op);
+    }
+
+    return true;
+}
Index: /trunk/ppArith/src/ppArithVersion.c
===================================================================
--- /trunk/ppArith/src/ppArithVersion.c	(revision 15571)
+++ /trunk/ppArith/src/ppArithVersion.c	(revision 15571)
@@ -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 "ppArith.h"
+
+static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
+
+psString ppArithVersion(void)
+{
+    psString version = NULL;            // Version, to return
+    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+    return version;
+}
+
+psString ppArithVersionLong(void)
+{
+    psString version = ppArithVersion(); // 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 ppArithVersionMetadata(psMetadata *metadata)
+{
+    PS_ASSERT_METADATA_NON_NULL(metadata,);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString ppStats = ppStatsVersionLong(); // ppStats version
+    psString ppArith = ppArithVersionLong(); // ppArith 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, "ppArith 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, ppArith, "");
+
+    psFree(head);
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(ppStats);
+    psFree(ppArith);
+
+    return;
+}
