Index: /trunk/ppstamp/.cvsignore
===================================================================
--- /trunk/ppstamp/.cvsignore	(revision 15280)
+++ /trunk/ppstamp/.cvsignore	(revision 15280)
@@ -0,0 +1,16 @@
+bin
+aclocal.m4
+autom4te.cache
+compile
+config.log
+config.status
+configure
+depcomp
+install-sh
+Makefile.in
+missing
+Makefile
+config.cache
+config.guess
+config.sub
+ltmain.sh
Index: /trunk/ppstamp/Makefile.am
===================================================================
--- /trunk/ppstamp/Makefile.am	(revision 15280)
+++ /trunk/ppstamp/Makefile.am	(revision 15280)
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+CLEANFILES = *~ core core.*
Index: /trunk/ppstamp/autogen.sh
===================================================================
--- /trunk/ppstamp/autogen.sh	(revision 15280)
+++ /trunk/ppstamp/autogen.sh	(revision 15280)
@@ -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=ppstamp
+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/ppstamp/src/.cvsignore
===================================================================
--- /trunk/ppstamp/src/.cvsignore	(revision 15280)
+++ /trunk/ppstamp/src/.cvsignore	(revision 15280)
@@ -0,0 +1,7 @@
+ppstamp
+Makefile
+Makefile.in
+.deps
+config.h
+config.h.in
+stamp-h1
Index: /trunk/ppstamp/src/Makefile.am
===================================================================
--- /trunk/ppstamp/src/Makefile.am	(revision 15280)
+++ /trunk/ppstamp/src/Makefile.am	(revision 15280)
@@ -0,0 +1,40 @@
+bin_PROGRAMS = ppstamp
+
+noinst_HEADERS = \
+	ppstamp.h  \
+        ppstampErrorCodes.h
+
+ppstamp_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) 
+
+# $(PSASTRO_CFLAGS) $(PPSTATS_CFLAGS) $(ppstamp_CFLAGS)
+
+ppstamp_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) 
+#$(PSPHOT_LIBS) $(PPSTATS_LIBS) $(PSASTRO_LIBS) 
+
+ppstamp_SOURCES = \
+	ppstamp.c \
+	ppstampArguments.c \
+	ppstampCleanup.c \
+        ppstampErrorCodes.c \
+	ppstampOptions.c \
+	ppstampParseCamera.c \
+	ppstampMakeStamp.c \
+	ppstampVersion.c
+
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+### Error codes.
+BUILT_SOURCES = ppstampErrorCodes.h ppstampErrorCodes.c
+CLEANFILES = ppstampErrorCodes.h ppstampErrorCodes.c
+
+ppstampErrorCodes.h : ppstampErrorCodes.dat ppstampErrorCodes.h.in
+	$(ERRORCODES) --data=ppstampErrorCodes.dat --outdir=. ppstampErrorCodes.h
+
+ppstampErrorCodes.c : ppstampErrorCodes.dat ppstampErrorCodes.c.in ppstampErrorCodes.h
+	$(ERRORCODES) --data=ppstampErrorCodes.dat --outdir=. ppstampErrorCodes.c
Index: /trunk/ppstamp/src/ppstamp.c
===================================================================
--- /trunk/ppstamp/src/ppstamp.c	(revision 15280)
+++ /trunk/ppstamp/src/ppstamp.c	(revision 15280)
@@ -0,0 +1,47 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppstamp.h"
+
+int main(int argc, char **argv)
+{
+    ppstampOptions *options;
+
+    psTimerStart(TIMERNAME);
+
+    psLibInit(NULL);
+
+    // Parse the configuration and arguments
+    // Open the input image(s)
+    // note usage is called on error or request by ppstampArguments
+    // TODO is the cleanup really necessary since we're exiting anyways?
+    // (It's useful to know about memory leaks)
+
+    pmConfig *config = ppstampArguments(argc, argv, &options);
+    if (config == NULL) {
+        psErrorStackPrint(stderr, "Unable to parse command-line arguments.");
+        ppstampCleanup(config, options);
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    // define recipe options
+    // define the active I/O files
+    if (!ppstampParseCamera(config)) {
+        psErrorStackPrint(stderr, "Unable to parse camera.");
+        ppstampCleanup(config, options);
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    // find the pixels that we need to copy, setup the output image
+    if (!ppstampMakeStamp(config, options)) {
+        exit(PS_EXIT_UNKNOWN_ERROR);
+    }
+
+    psLogMsg ("ppstamp", 3, "Complete ppstamp run: %f sec\n", psTimerMark(TIMERNAME));
+
+    // Cleaning up
+    ppstampCleanup(config, options);
+
+    return PS_EXIT_SUCCESS;
+}
Index: /trunk/ppstamp/src/ppstamp.h
===================================================================
--- /trunk/ppstamp/src/ppstamp.h	(revision 15280)
+++ /trunk/ppstamp/src/ppstamp.h	(revision 15280)
@@ -0,0 +1,101 @@
+#ifndef PP_STAMP_H
+#define PP_STAMP_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>  // for strcasecmp
+#include <unistd.h>   // for unlink
+#include "pslib.h"
+#include "psmodules.h"
+#include "psphot.h"
+#include "psastro.h"
+#include "ppStats.h"
+
+#define RECIPE_NAME "PPSTAMP"           // Name of the recipe to use
+#define TIMERNAME "ppstamp"             // Name of timer
+
+// We need double precision coordinates when transforming so we
+// define this struct to use instead of a psRegion
+typedef struct {
+    psPlane p0;
+    psPlane p1;
+} ppstampROI;
+
+// Options for ppstamp processing
+typedef struct {
+    // input arguments
+    double centerX;
+    double centerY;
+    double dX;
+    double dY;
+    double centerRA;
+    double centerDEC;
+    double dRA;
+    double dDEC;
+    bool celestialCenter;       // true if center is in RA/dec
+    bool celestialRange;        // true if range is in RA/dec
+    psString    chipName;
+    //
+    // Calculated Parameters
+    //
+    int         chip;
+    int         cell;
+    psRegion    roi;            // roi in chip coordinates
+    // psPlane     fpaCenter;      // center of ROI in FPA coordinates
+    // ppstampROI  fpaROI;         // region of interest in FPA coordinates
+} ppstampOptions;
+
+
+ppstampOptions *ppstampOptionsAlloc(void);
+
+// Determine the processing options
+bool *ppstampOptionsParse(pmConfig *config);
+
+// Get the configuration
+pmConfig *ppstampArguments(int argc, char **argv, ppstampOptions **);
+
+// Determine what type of camera, and initialise
+bool ppstampParseCamera(pmConfig *config);
+
+bool ppstampMakeStamp(pmConfig *config, ppstampOptions *);
+
+// Loop over the input
+bool ppstampLoop(pmConfig *config, ppstampOptions *options);
+
+bool ppstampReadout(pmConfig *config, pmFPAview *view);
+
+// free memory, check for leaks
+void ppstampCleanup (pmConfig *config, ppstampOptions *options);
+
+#ifdef notyet
+// calculate stats, including MD5
+bool ppstampStats (pmConfig *config, pmChip *chip, const pmFPAview *inputView);
+
+// write stats to output file 
+bool ppstampStatsOutput (pmConfig *config);
+#endif
+
+/// Return short version information
+psString ppstampVersion(void);
+
+/// Return long version information
+psString ppstampVersionLong(void);
+
+/// Update the metadata with version information for all dependencies
+void ppstampVersionMetadata(psMetadata *metadata ///< Metadata to update with version information
+    );
+
+
+// calculate stats, including MD5
+bool ppstampPixelStats (pmConfig *config, const pmFPAview *inputView);
+
+// calculate stats from headers and concepts
+bool ppstampMetadataStats (pmConfig *config);
+
+void ppstampFileCheck (pmConfig *config);
+
+#endif
Index: /trunk/ppstamp/src/ppstampArguments.c
===================================================================
--- /trunk/ppstamp/src/ppstampArguments.c	(revision 15280)
+++ /trunk/ppstamp/src/ppstampArguments.c	(revision 15280)
@@ -0,0 +1,138 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppstamp.h"
+
+static void usage (void) {
+    fprintf(stderr, "USAGE: ppstamp -pixcenter x y    -pixrange dx dy    -chip chipname [-file INPUT.fits] [-list INPUT.txt] OUTPUT\n");
+
+    fprintf(stderr, "USAGE: ppstamp -pixcenter x y    -celrange dRA dDEC -chip chipname [-file INPUT.fits] [-list INPUT.txt] OUTPUT\n");
+    fprintf(stderr, "       ppstamp -celcenter RA DEC -celrange dRA dDEC [-file INPUT.fits] [-list INPUT.txt] OUTPUT\n");
+    fprintf(stderr, "       ppstamp -celcenter RA DEC -pixrange dx dy    [-file INPUT.fits] [-list INPUT.txt] OUTPUT\n");
+    fprintf(stderr, "\n");
+
+#ifdef notyet
+    fprintf(stderr, "Optional arguments:\n");
+    fprintf(stderr, "\t-stats STATS.mdc: Output statistics into STATS.mdc\n");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "Input options (single file / file list):\n");
+    fprintf(stderr, "\n");
+#endif
+    exit (2);
+}
+
+void get2Doubles(int argnum, int *pArgc, char **argv, double *p1, double *p2) {
+    if (*pArgc < 3) {
+        usage();
+    }
+    psArgumentRemove(argnum, pArgc, argv);
+    *p1 = atof(argv[argnum]);
+    psArgumentRemove(argnum, pArgc, argv);
+    *p2 = atof(argv[argnum]);
+    psArgumentRemove(argnum, pArgc, argv);
+}
+
+pmConfig *ppstampArguments(int argc, char **argv, ppstampOptions **pOptions)
+{
+    int argnum;                         // Argument number of interest
+    bool gotCenter = false;
+    bool gotRange = false;
+    ppstampOptions *options;
+
+    if (argc == 1) {
+        usage();
+    }
+
+    if (psArgumentGet (argc, argv, "-version")) {
+        psString version;
+        version = ppstampVersionLong();   fprintf (stdout, "%s\n", version); psFree (version);
+// don't need these
+//      version = ppStatsVersionLong();   fprintf (stdout, "%s\n", version); psFree (version);
+//      version = psphotVersionLong();    fprintf (stdout, "%s\n", version); psFree (version);
+//      version = psastroVersionLong();   fprintf (stdout, "%s\n", version); psFree (version);
+        version = psModulesVersionLong(); fprintf (stdout, "%s\n", version); psFree (version);
+        version = psLibVersionLong();     fprintf (stdout, "%s\n", version); psFree (version);
+        exit (0);
+    }
+
+    // load the site-wide configuration information
+    pmConfig *config = pmConfigRead(&argc, argv, RECIPE_NAME);
+    if (config == NULL) {
+        psErrorStackPrint(stderr, "Can't find site configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    (void) pmConfigRecipeOptions (config, RECIPE_NAME);
+
+    options = ppstampOptionsAlloc();
+    *pOptions = options;
+
+    if ((argnum = psArgumentGet(argc, argv, "-pixcenter"))) {
+        gotCenter = true;
+        options->celestialCenter = false;
+        get2Doubles(argnum, &argc, argv, &options->centerX, &options->centerY);
+    }
+    if ((argnum = psArgumentGet(argc, argv, "-celcenter"))) {
+        if (gotCenter) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "can't specify both -pixcenter and -celcenter\n");;
+            usage();
+        }
+        get2Doubles(argnum, &argc, argv, &options->centerRA, &options->centerDEC);
+        options->centerRA  = DEG_TO_RAD(options->centerRA);
+        options->centerDEC = DEG_TO_RAD(options->centerDEC);
+        gotCenter = true;
+        options->celestialCenter = true;
+    }
+    if (!gotCenter) {
+        usage();
+    }
+    if ((argnum = psArgumentGet(argc, argv, "-pixrange"))) {
+        gotRange = true;
+        options->celestialRange = false;
+        get2Doubles(argnum, &argc, argv, &options->dX, &options->dY);
+    }
+    if ((argnum = psArgumentGet(argc, argv, "-celrange"))) {
+        fprintf(stderr, "Specifying range in celestial coordinates is not implemented yet\n");
+        exit(1);
+        if (gotRange) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "can't specify both -pixrange and -celrange\n");;
+            usage();
+        }
+        gotRange = true;
+        options->celestialRange = true;
+        get2Doubles(argnum, &argc, argv, &options->dRA, &options->dDEC);
+        options->dRA  = DEG_TO_RAD(options->dRA);
+        options->dDEC = DEG_TO_RAD(options->dDEC);
+    }
+    if (!gotRange) {
+        usage();
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-chip"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        options->chipName = psStringCopy(argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if (!options->celestialCenter && (options->chipName == NULL)) {
+        fprintf(stderr, "must specify chip name when using -pixcenter\n");
+        usage();
+    }
+
+    // the input file is a required argument; if not found, we will exit
+    bool status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (!status) {
+        // TODO: shall we print a specific message?
+        usage ();
+    }
+
+    if (argc != 2) usage ();
+
+    // Add the input and output images (which remain on the command-line) to the arguments list
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "Name of the output image", argv[1]);
+
+    // psMetadataPrint(stdout, config->arguments,0);
+
+    return config;
+}
Index: /trunk/ppstamp/src/ppstampCleanup.c
===================================================================
--- /trunk/ppstamp/src/ppstampCleanup.c	(revision 15280)
+++ /trunk/ppstamp/src/ppstampCleanup.c	(revision 15280)
@@ -0,0 +1,25 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppstamp.h"
+
+void ppstampCleanup (pmConfig *config, ppstampOptions *options)
+{
+    // Free memory used by ppstamp
+    psFree(config);
+    psFree(options);
+
+    // Free memory used by psModules
+    pmConceptsDone();
+    pmConfigDone();
+    pmModelClassCleanup();
+
+    // Free memory used by psLib
+    psLibFinalize();
+
+    // psMemCheckLeaks (0, NULL, stderr, false);
+    // fprintf (stderr, "Found %d leaks in %s\n", psMemCheckLeaks (0, NULL, NULL, false), "ppstamp");
+
+    return;
+}
Index: /trunk/ppstamp/src/ppstampErrorCodes.c.in
===================================================================
--- /trunk/ppstamp/src/ppstampErrorCodes.c.in	(revision 15280)
+++ /trunk/ppstamp/src/ppstampErrorCodes.c.in	(revision 15280)
@@ -0,0 +1,25 @@
+/*
+ * The line
+    { PSWARP_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "ppstampErrorCodes.h"
+
+void ppstampErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PPSTAMP_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PPSTAMP_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PPSTAMP_ERR_NERROR - PPSTAMP_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /trunk/ppstamp/src/ppstampErrorCodes.dat
===================================================================
--- /trunk/ppstamp/src/ppstampErrorCodes.dat	(revision 15280)
+++ /trunk/ppstamp/src/ppstampErrorCodes.dat	(revision 15280)
@@ -0,0 +1,10 @@
+#
+# This file is used to generate ppstampErrorCodes.h
+#
+BASE = 800		First value we use; lower values belong to psLib
+UNKNOWN			Unknown PM error code
+NOT_IMPLEMENTED		Desired feature is not yet implemented
+ARGUMENTS		Incorrect arguments
+CONFIG			Problem in configure files
+IO			Problem in FITS I/O
+DATA                    Problem in data values
Index: /trunk/ppstamp/src/ppstampErrorCodes.h.in
===================================================================
--- /trunk/ppstamp/src/ppstampErrorCodes.h.in	(revision 15280)
+++ /trunk/ppstamp/src/ppstampErrorCodes.h.in	(revision 15280)
@@ -0,0 +1,18 @@
+#if !defined(PPSTAMP_ERROR_CODES_H)
+#define PPSTAMP_ERROR_CODES_H
+/*
+ * The line
+ *  PPSTAMP_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PPSTAMP_ERR_BASE = 600,
+    PPSTAMP_ERR_${ErrorCode},
+    PPSTAMP_ERR_NERROR
+} pswarpErrorCode;
+
+void pswarpErrorRegister(void);
+
+#endif
Index: /trunk/ppstamp/src/ppstampMakeStamp.c
===================================================================
--- /trunk/ppstamp/src/ppstampMakeStamp.c	(revision 15280)
+++ /trunk/ppstamp/src/ppstampMakeStamp.c	(revision 15280)
@@ -0,0 +1,343 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppstamp.h"
+#include "pmFPAAstrometry.h"
+
+#define USE_MOSAIC
+
+typedef enum {
+    PPSTAMP_OFF_CHIP,
+    PPSTAMP_PARTIALLY_ON_CHIP,
+    PPSTAMP_ON_CHIP,
+    PPSTAMP_ERROR
+} ppstampOverlap;
+
+static bool regionContainsPoint(psRegion *region, psPlane *point);
+static bool regionContainsRegion(psRegion *outer, psRegion *inner);
+static bool copyMetadata(pmFPAfile *output, pmFPAfile *input, pmChip *inChip);
+
+static pmFPAfile *buildMosaic(pmConfig *config, pmFPAfile *input, pmFPAview *view, pmFPAview *mosaicView)
+{
+    bool    status;
+
+    pmFPAfile *mosaic =  psMetadataLookupPtr(&status, config->files, "PPSTAMP.CHIP");
+    if (!status)  {
+        psErrorStackPrint(stderr, "can't find mosaic i/o file\n");
+        exit(EXIT_FAILURE);
+    }
+
+    pmChip  *mChip  = pmFPAviewThisChip(mosaicView, mosaic->fpa);
+    pmChip  *inChip = pmFPAviewThisChip(view, input->fpa);
+    if (!mChip->hdu && !mChip->parent->hdu) {
+        const char *name = psMetadataLookupStr(&status, input->fpa->concepts, "FPA.NAME"); // Name of FPA
+        pmFPAAddSourceFromView(mosaic->fpa, name, mosaicView, mosaic->format);
+    }
+
+    psMaskType blankMask = pmConfigMask("BLANK", config);
+
+    status = pmChipMosaic(mChip, inChip, true, blankMask);
+    if (status) {
+        return mosaic;
+    } else {
+        return NULL;
+    }
+}
+
+static bool makeStamp(pmConfig *config, ppstampOptions *options, pmFPAfile *input, 
+                pmChip *chip, pmFPAview *view)
+{
+    int status = false;
+    pmFPAfile *output = psMetadataLookupPtr(NULL, config->files, "PPSTAMP.OUTPUT");
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find output data\n");
+        return false;
+    }
+    char *fpaName = psMetadataLookupStr(NULL, input->fpa->concepts, "FPA.NAME"); // Name of FPA
+    pmFPAview *outview = pmFPAviewAlloc(0);
+    pmFPAAddSourceFromView(output->fpa, fpaName, outview, output->format);
+
+    outview->chip = 0;
+    outview->cell = 0;
+    outview->readout = 0;
+    pmCell *target =  pmFPAviewThisCell(outview, output->fpa); // Target cell
+    psFree(outview);
+    outview = NULL;
+
+    // these default to zero would that be ok?
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.XBIN", PS_META_REPLACE, "Binning in x", 1);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.YBIN", PS_META_REPLACE, "Binning in y", 1);
+
+    // we extract our postage stamp from a mosaic so that
+    // pmChipMosaic can handle all of the complexities of parity, binning, etc.
+    pmFPAview *mosaicView = pmFPAviewAlloc(0);
+    mosaicView->chip = 0;
+    pmFPAfile *mosaic = buildMosaic(config, input, view, mosaicView);
+    if (mosaic == NULL) {
+        psFree(mosaicView);
+        return false;
+    }
+
+    mosaicView->cell = 0;
+    pmReadout *readout;
+    while ((readout = pmFPAviewNextReadout (mosaicView, mosaic->fpa, 1)) != NULL) {
+        if (!readout->data_exists) {
+            // HMM, this probably means something is wrong
+            continue;
+        }
+        pmReadout *outReadout = pmReadoutAlloc(target);
+        if (!outReadout) {
+            psError(PS_ERR_UNKNOWN, false, "failed to allocate output readout\n");
+            status = false;
+            break;
+        }
+
+        // define a subset of the mosaic's readout
+        psImage *subsetImage = psImageSubset(readout->image, options->roi);
+        if (subsetImage) {
+            // make a copy since we're going to change the image's internals
+            outReadout->image = psImageCopy(NULL, subsetImage, subsetImage->type.type);
+            psFree(subsetImage);
+            if (outReadout->image) {
+                // TODO: put this into a function in psImage
+                // (What I really needed was a function to "create a copy of this portion of an image)
+                P_PSIMAGE_SET_COL0(outReadout->image, 0);
+                P_PSIMAGE_SET_ROW0(outReadout->image, 0);
+
+                outReadout->data_exists = true;
+                outReadout->parent->data_exists = true;         // cell
+                outReadout->parent->parent->data_exists = true; // chip
+                status = true;
+            } else {
+                psError(PS_ERR_UNKNOWN, false, "Copying of readout image failed.\n");
+                status = false;
+            }
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Image subset failed.\n");
+            status = false;
+        }
+        psFree(outReadout); // drop reference
+    }
+
+    psFree(mosaicView);
+
+    if (status) {
+        status = copyMetadata(output, input, chip);
+    }
+    return status;
+}
+
+static bool copyMetadata(pmFPAfile *output, pmFPAfile *input, pmChip *inChip)
+{
+    pmChip    *outChip;
+    pmFPAview *view = pmFPAviewAlloc(0);
+
+    // our output file has a single chip
+    view->chip = 0;
+    outChip = pmFPAviewThisChip(view, output->fpa);
+    psFree(view);
+
+    outChip->parent->concepts = psMetadataCopy(outChip->parent->concepts, inChip->parent->concepts);
+
+    pmHDU *inHDU = pmHDUGetHighest(input->fpa, inChip, NULL);
+    pmHDU *outHDU = pmHDUGetHighest(output->fpa, outChip, NULL);
+
+    if (inHDU->header) {
+        outHDU->header = psMetadataCopy(outHDU->header, inHDU->header);
+    } else if (!outHDU->header) {
+        outHDU->header = psMetadataAlloc();
+    }
+    // TODO set up the astrometry related information
+    return true;
+}
+
+
+ppstampOverlap findROI(ppstampOptions *options, pmFPAfile *input, pmChip *chip,
+                pmAstromObj *center, psRegion *roi)
+{
+    psRegion    *chipExtent = pmChipPixels(chip);
+    bool        onChip = false;
+    ppstampOverlap   returnval = PPSTAMP_OFF_CHIP;
+    psString chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
+
+    if (options->celestialCenter || options->celestialRange) {
+        // set up the astrometry
+        pmHDU *hdu = pmHDUFromChip(chip);
+        PS_ASSERT_PTR_NON_NULL(hdu, 1)
+        PS_ASSERT_PTR_NON_NULL(hdu->header, 1)
+
+        if (!pmAstromReadWCS(input->fpa, chip, hdu->header, 1.0)) {
+            psError(PS_ERR_UNKNOWN, false, "Failed to Read WCS\n");
+            psFree(chipExtent);
+            psFree(center);
+            return PPSTAMP_ERROR;
+        }
+    }
+
+    if (options->celestialCenter) {
+
+        // convert from sky to FP
+        center->sky->r = options->centerRA;
+        center->sky->d = options->centerDEC;
+        center->sky->rErr = 0;  // TODO: is this right?
+        center->sky->dErr = 0;
+
+        psProject(center->TP, center->sky, input->fpa->toSky);
+        psPlaneTransformApply(center->FP, input->fpa->fromTPA, center->TP);
+
+        // convert from FP to chip
+        psPlaneTransformApply(center->chip, chip->fromFPA, center->FP);
+
+        if (regionContainsPoint(chipExtent, center->chip)) {
+            psLogMsg("ppstampMakeStamp", 3, "Found center on chip: %s\n", chipName);
+            onChip = true;
+        }
+    } else {
+        // center specified in pixels, user needs to have specified the chip
+        if (chipName == NULL) {
+            psError(PS_ERR_UNKNOWN, true, "Failed to find CHIP.NAME\n");
+            returnval = PPSTAMP_ERROR;
+        }
+        // ppstampArguments insures that options->chipName is not null
+        if (!strcmp(chipName, options->chipName)) {
+            psLogMsg("ppstampMakeStamp", 3, "Center on chip: %s\n", chipName);
+            onChip = true;
+            onChip = true;
+            center->chip->x = options->centerX;
+            center->chip->y = options->centerY;
+        }
+    }
+
+    if (onChip) {
+        if (options->celestialRange) {
+            // Protect against unimplemented feature
+            psError(PS_ERR_PROGRAMMING, true, "not ready for Range in celestial coordinates yet.\n");
+            exit(PS_EXIT_PROG_ERROR);
+            // find the bounding box in pixel space that encloses the ROI
+        } else {
+            int width  = options->dX;
+            int height = options->dY;
+
+            // calculate the ROI in chip coordinates
+            roi->x0 = center->chip->x - width / 2;
+            roi->x1 = roi->x0 +  width;
+            roi->y0 = center->chip->y - height / 2;
+            roi->y1 = roi->y0 + height;
+
+            if (regionContainsRegion(chipExtent, roi)) {
+                returnval = PPSTAMP_ON_CHIP;
+            } else {
+                returnval = PPSTAMP_PARTIALLY_ON_CHIP;
+            }
+        }
+    }
+    psFree(chipExtent);
+
+    return returnval;
+}
+
+bool ppstampMakeStamp (pmConfig *config, ppstampOptions *options)
+{
+    bool        status = false;
+    bool        returnval = false;;
+    pmAstromObj *center = pmAstromObjAlloc();
+
+    pmFPAfile *input = psMetadataLookupPtr(&status, config->files, "PPSTAMP.INPUT");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "Can't find input data!\n");
+        psFree(center);
+        return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc(0);// View for level of interest
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        // TODO log error
+        psError(PS_ERR_UNKNOWN, false, "Failed to load input.");
+        psFree(center);
+        psFree (view);
+        return false;
+    }
+
+    // Loop over the chips and find the one that contains the center
+    pmChip *chip;
+    while ((chip = pmFPAviewNextChip(view, input->fpa, 1)) != NULL) {
+	bool allDone = false;
+
+        if (!chip->process || !chip->file_exists) {
+            continue;
+        }
+
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            // TODO log error
+            psError(PS_ERR_UNKNOWN, false, "failed to load chip");
+            status = false;
+            break;
+        }
+
+        ppstampOverlap overlap = findROI(options, input, chip, center, &options->roi);
+
+        if (overlap == PPSTAMP_ON_CHIP) {
+
+            returnval = makeStamp(config, options, input, chip, view);
+
+            allDone = true;
+	} else if (overlap == PPSTAMP_PARTIALLY_ON_CHIP) {
+            psError(PS_ERR_UNKNOWN, false, "Region of interest must be confined to a single chip\n");
+            // XXX: perhaps print a helpful message about what coordinates would be valid
+            returnval = false;
+            allDone = true;
+        } else if (overlap == PPSTAMP_ERROR) {
+            returnval = false;
+            allDone = true;
+        }
+
+	pmFPAfileIOChecks(config, view, PM_FPA_AFTER);
+
+        if (allDone) {
+            view->chip = -1;
+            view->cell = -1;
+            break;
+        }
+    } 
+    pmFPAfileIOChecks(config, view, PM_FPA_AFTER);
+
+    psFree(center);
+    psFree(view);
+
+    return returnval;
+}
+
+
+static bool regionContainsPoint(psRegion *r, psPlane *pt)
+{
+    // TODO: should this comparison be done with integers since we
+    // are dealing with pixels?
+    // Maybe we should round and truncate before we get here.
+    if (pt->x < r->x0)
+        return false;
+    if (pt->x >= r->x1)
+        return false;
+    if (pt->y < r->y0)
+        return false;
+    if (pt->y >= r->y1)
+        return false;
+
+    return true;
+}
+
+// true if the inner region is equal to or completely contained in
+// the outer region
+static bool regionContainsRegion(psRegion *outer, psRegion *inner)
+{
+    if ((outer->x0 <= inner->x0) &&
+        (outer->y0 <= inner->y0) &&
+        (outer->x1 >= inner->x1) &&
+        (outer->y1 >= inner->y1)) {
+        return true;
+    } else {
+        return false;
+    }
+}
Index: /trunk/ppstamp/src/ppstampOptions.c
===================================================================
--- /trunk/ppstamp/src/ppstampOptions.c	(revision 15280)
+++ /trunk/ppstamp/src/ppstampOptions.c	(revision 15280)
@@ -0,0 +1,31 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppstamp.h"
+
+
+static void imageOptionsFree(ppstampOptions *options)
+{
+    psFree(options->chipName);
+}
+
+ppstampOptions *ppstampOptionsAlloc(void)
+{
+    ppstampOptions *options = psAlloc(sizeof(ppstampOptions));
+    psMemSetDeallocator(options, (psFreeFunc)imageOptionsFree);
+
+    options->celestialCenter = false;
+    options->centerX         = NAN;
+    options->centerY         = NAN;
+    options->centerRA        = NAN;
+    options->centerDEC       = NAN;
+    options->celestialRange  = false;
+    options->dX   = 0;
+    options->dY   = 0;
+    options->dRA  = 0;
+    options->dDEC = 0;
+    options->chipName = NULL;
+
+    return options;
+}
Index: /trunk/ppstamp/src/ppstampParseCamera.c
===================================================================
--- /trunk/ppstamp/src/ppstampParseCamera.c	(revision 15280)
+++ /trunk/ppstamp/src/ppstampParseCamera.c	(revision 15280)
@@ -0,0 +1,74 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppstamp.h"
+
+// Set up the ppstamp output Image file
+bool setupOutput(pmConfig *config, pmFPAfile *input)
+{
+    pmFPAfile *output = pmFPAfileDefineSkycell(config, NULL, "PPSTAMP.OUTPUT");
+    output->save = true;
+
+    return true;
+}
+
+// This function seems mis-named. What are we doing with regards to the Camera?
+// Well we are building the output image based on the camera. Is there
+// something else that I'm missing?
+
+bool ppstampParseCamera(pmConfig *config)
+{
+    bool status = false;
+
+    // the input image defines the camera, and all recipes and options the follow
+    pmFPAfile *input = pmFPAfileDefineFromArgs (&status, config, "PPSTAMP.INPUT", "INPUT");
+    if (!status || !input) {
+        psError(PS_ERR_IO, false, "Failed to build FPA from PPSTAMP.INPUT");
+        return false;
+    }
+    if (input->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPSTAMP.INPUT is not of type IMAGE");
+        return false;
+    }
+
+#ifdef notyet
+    // add recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, RECIPE_NAME);
+#endif
+
+    // Set up the output target
+
+    if (!setupOutput(config, input)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to setup output.");
+        return false;
+    }
+    pmFPAfile *chipImage = pmFPAfileDefineChipMosaic(config, input->fpa, "PPSTAMP.CHIP");
+    if (!chipImage) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPSTAMP.CHIP"));
+        return NULL;
+    }
+    if (chipImage->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPSTAMP.CHIP is not of type IMAGE");
+        return NULL;
+    }
+ 
+
+#ifdef notyet
+    if (psTraceGetLevel("ppstamp.config") > 0) {
+        // Get a look inside all the files.
+        psMetadataIterator *filesIter = psMetadataIteratorAlloc(config->files, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *item;               // Item from iteration
+        fprintf(stderr, "Files:\n");
+        while ((item = psMetadataGetAndIncrement(filesIter))) {
+            pmFPAfile *file = item->data.V; // File of interest
+            fprintf(stderr, "%s: %p %p %p (%p) %p\n", file->name,
+                    file->src, file->fpa,
+                    file->camera, file->fpa->camera, file->format);
+        }
+        psFree(filesIter);
+    }
+#endif
+
+    return true;
+}
Index: /trunk/ppstamp/src/ppstampVersion.c
===================================================================
--- /trunk/ppstamp/src/ppstampVersion.c	(revision 15280)
+++ /trunk/ppstamp/src/ppstampVersion.c	(revision 15280)
@@ -0,0 +1,67 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppstamp.h"
+
+static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
+
+psString ppstampVersion(void)
+{
+    psString version = NULL;            // Version, to return
+    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+    return version;
+}
+
+psString ppstampVersionLong(void)
+{
+    psString version = ppstampVersion(); // 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 ppstampVersionMetadata(psMetadata *metadata)
+{
+    PS_ASSERT_METADATA_NON_NULL(metadata,);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+#ifdef notdef
+    psString psphot = psphotVersionLong(); // psphot version
+    psString psastro = psastroVersionLong(); // psastro version
+    psString ppStats = ppStatsVersionLong(); // ppStats version
+#endif
+    psString ppstamp = ppstampVersionLong(); // ppstamp 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, "ppstamp 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, "");
+#ifdef notdef
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, psphot, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, psastro, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, ppStats, "");
+#endif
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, ppstamp, "");
+
+    psFree(head);
+    psFree(pslib);
+    psFree(psmodules);
+#ifdef notdef
+    psFree(psphot);
+    psFree(psastro);
+    psFree(ppStats);
+#endif
+    psFree(ppstamp);
+
+    return;
+}
